#import "GPUImageTransformFilter.h"

NSString *const kGPUImageTransformVertexShaderString = SHADER_STRING
(
 attribute vec4 position;
 attribute vec4 inputTextureCoordinate;
 
 uniform mat4 transformMatrix;
 uniform mat4 orthographicMatrix;
 
 varying vec2 textureCoordinate;
 
 void main()
 {
     gl_Position = transformMatrix * vec4(position.xyz, 1.0) * orthographicMatrix;
     textureCoordinate = inputTextureCoordinate.xy;
 }
);

@implementation GPUImageTransformFilter

@synthesize affineTransform;
@synthesize transform3D = _transform3D;
@synthesize ignoreAspectRatio = _ignoreAspectRatio;
@synthesize anchorTopLeft = _anchorTopLeft;

#pragma mark -
#pragma mark Initialization and teardown

- (id)init;
{
    if (!(self = [super initWithVertexShaderFromString:kGPUImageTransformVertexShaderString fragmentShaderFromString:kGPUImagePassthroughFragmentShaderString]))
    {
        return nil;
    }
    
    transformMatrixUniform = [filterProgram uniformIndex:@"transformMatrix"];
    orthographicMatrixUniform = [filterProgram uniformIndex:@"orthographicMatrix"];
    
    self.transform3D = CATransform3DIdentity;
    
    return self;
}

#pragma mark -
#pragma mark Conversion from matrix formats

- (void)loadOrthoMatrix:(GLfloat *)matrix left:(GLfloat)left right:(GLfloat)right bottom:(GLfloat)bottom top:(GLfloat)top near:(GLfloat)near far:(GLfloat)far;
{
    GLfloat r_l = right - left;
    GLfloat t_b = top - bottom;
    GLfloat f_n = far - near;
    GLfloat tx = - (right + left) / (right - left);
    GLfloat ty = - (top + bottom) / (top - bottom);
    GLfloat tz = - (far + near) / (far - near);
    
	float scale = 2.0f;
	if (_anchorTopLeft)
	{
		scale = 4.0f;
		tx=-1.0f;
		ty=-1.0f;
	}
	
    matrix[0] = scale / r_l;
    matrix[1] = 0.0f;
    matrix[2] = 0.0f;
    matrix[3] = tx;
    
    matrix[4] = 0.0f;
    matrix[5] = scale / t_b;
    matrix[6] = 0.0f;
    matrix[7] = ty;
    
    matrix[8] = 0.0f;
    matrix[9] = 0.0f;
    matrix[10] = scale / f_n;
    matrix[11] = tz;
    
    matrix[12] = 0.0f;
    matrix[13] = 0.0f;
    matrix[14] = 0.0f;
    matrix[15] = 1.0f;
}

//- (void)convert3DTransform:(CATransform3D *)transform3D toMatrix:(GLfloat *)matrix;
//{
//	//	struct CATransform3D
//	//	{
//	//		CGFloat m11, m12, m13, m14;
//	//		CGFloat m21, m22, m23, m24;
//	//		CGFloat m31, m32, m33, m34;
//	//		CGFloat m41, m42, m43, m44;
//	//	};
//	
//	matrix[0] = (GLfloat)transform3D->m11;
//	matrix[1] = (GLfloat)transform3D->m12;
//	matrix[2] = (GLfloat)transform3D->m13;
//	matrix[3] = (GLfloat)transform3D->m14;
//	matrix[4] = (GLfloat)transform3D->m21;
//	matrix[5] = (GLfloat)transform3D->m22;
//	matrix[6] = (GLfloat)transform3D->m23;
//	matrix[7] = (GLfloat)transform3D->m24;
//	matrix[8] = (GLfloat)transform3D->m31;
//	matrix[9] = (GLfloat)transform3D->m32;
//	matrix[10] = (GLfloat)transform3D->m33;
//	matrix[11] = (GLfloat)transform3D->m34;
//	matrix[12] = (GLfloat)transform3D->m41;
//	matrix[13] = (GLfloat)transform3D->m42;
//	matrix[14] = (GLfloat)transform3D->m43;
//	matrix[15] = (GLfloat)transform3D->m44;
//}

- (void)convert3DTransform:(CATransform3D *)transform3D toMatrix:(GPUMatrix4x4 *)matrix;
{
	//	struct CATransform3D
	//	{
	//		CGFloat m11, m12, m13, m14;
	//		CGFloat m21, m22, m23, m24;
	//		CGFloat m31, m32, m33, m34;
	//		CGFloat m41, m42, m43, m44;
	//	};
    
    GLfloat *mappedMatrix = (GLfloat *)matrix;
	
	mappedMatrix[0] = (GLfloat)transform3D->m11;
	mappedMatrix[1] = (GLfloat)transform3D->m12;
	mappedMatrix[2] = (GLfloat)transform3D->m13;
	mappedMatrix[3] = (GLfloat)transform3D->m14;
	mappedMatrix[4] = (GLfloat)transform3D->m21;
	mappedMatrix[5] = (GLfloat)transform3D->m22;
	mappedMatrix[6] = (GLfloat)transform3D->m23;
	mappedMatrix[7] = (GLfloat)transform3D->m24;
	mappedMatrix[8] = (GLfloat)transform3D->m31;
	mappedMatrix[9] = (GLfloat)transform3D->m32;
	mappedMatrix[10] = (GLfloat)transform3D->m33;
	mappedMatrix[11] = (GLfloat)transform3D->m34;
	mappedMatrix[12] = (GLfloat)transform3D->m41;
	mappedMatrix[13] = (GLfloat)transform3D->m42;
	mappedMatrix[14] = (GLfloat)transform3D->m43;
	mappedMatrix[15] = (GLfloat)transform3D->m44;
}

#pragma mark -
#pragma mark GPUImageInput

- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex;
{
    CGSize currentFBOSize = [self sizeOfFBO];
    CGFloat normalizedHeight = currentFBOSize.height / currentFBOSize.width;
    
    GLfloat adjustedVertices[] = {
        -1.0f, -normalizedHeight,
        1.0f, -normalizedHeight,
        -1.0f,  normalizedHeight,
        1.0f,  normalizedHeight,
    };
    static const GLfloat squareVertices[] = {
        -1.0f, -1.0f,
        1.0f, -1.0f,
        -1.0f,  1.0f,
        1.0f,  1.0f,
    };

	GLfloat adjustedVerticesAnchorTL[] = {
        0.0f, 0.0f,
        1.0f, 0.0f,
        0.0f,  normalizedHeight,
        1.0f,  normalizedHeight,
    };

    static const GLfloat squareVerticesAnchorTL[] = {
        0.0f, 0.0f,
        1.0f, 0.0f,
        0.0f,  1.0f,
        1.0f,  1.0f,
    };

    if (_ignoreAspectRatio)
    {
		if (_anchorTopLeft)
		{
			[self renderToTextureWithVertices:squareVerticesAnchorTL textureCoordinates:[[self class] textureCoordinatesForRotation:inputRotation]];
		}
		else
		{
			[self renderToTextureWithVertices:squareVertices textureCoordinates:[[self class] textureCoordinatesForRotation:inputRotation]];
		}
    }
    else
    {
		if (_anchorTopLeft)
		{
			[self renderToTextureWithVertices:adjustedVerticesAnchorTL textureCoordinates:[[self class] textureCoordinatesForRotation:inputRotation]];
		}
		else
		{
			[self renderToTextureWithVertices:adjustedVertices textureCoordinates:[[self class] textureCoordinatesForRotation:inputRotation]];
		}
    }
    
    [self informTargetsAboutNewFrameAtTime:frameTime];
}

- (void)setupFilterForSize:(CGSize)filterFrameSize;
{
    if (!_ignoreAspectRatio)
    {
        [self loadOrthoMatrix:(GLfloat *)&orthographicMatrix left:-1.0 right:1.0 bottom:(-1.0 * filterFrameSize.height / filterFrameSize.width) top:(1.0 * filterFrameSize.height / filterFrameSize.width) near:-1.0 far:1.0];
        //     [self loadOrthoMatrix:orthographicMatrix left:-1.0 right:1.0 bottom:(-1.0 * (GLfloat)backingHeight / (GLfloat)backingWidth) top:(1.0 * (GLfloat)backingHeight / (GLfloat)backingWidth) near:-2.0 far:2.0];

        [self setMatrix4f:orthographicMatrix forUniform:orthographicMatrixUniform program:filterProgram];
    }
}

#pragma mark -
#pragma mark Accessors

- (void)setAffineTransform:(CGAffineTransform)newValue;
{
    self.transform3D = CATransform3DMakeAffineTransform(newValue);
}

- (CGAffineTransform)affineTransform;
{
    return CATransform3DGetAffineTransform(self.transform3D);
}

- (void)setTransform3D:(CATransform3D)newValue;
{
    _transform3D = newValue;
        
    GPUMatrix4x4 temporaryMatrix;
    
    [self convert3DTransform:&_transform3D toMatrix:&temporaryMatrix];
    [self setMatrix4f:temporaryMatrix forUniform:transformMatrixUniform program:filterProgram];
}

- (void)setIgnoreAspectRatio:(BOOL)newValue;
{
    _ignoreAspectRatio = newValue;
    
    if (_ignoreAspectRatio)
    {
        [self loadOrthoMatrix:(GLfloat *)&orthographicMatrix left:-1.0 right:1.0 bottom:-1.0 top:1.0 near:-1.0 far:1.0];
        [self setMatrix4f:orthographicMatrix forUniform:orthographicMatrixUniform program:filterProgram];
    }
    else
    {
        [self setupFilterForSize:[self sizeOfFBO]];
    }
}

- (void)setAnchorTopLeft:(BOOL)newValue
{
	_anchorTopLeft = newValue;
	[self setIgnoreAspectRatio:_ignoreAspectRatio];
}

@end
