#include <SST/SST_Math.h>

//Perspective Camera Class, Used to compute camera transforms
class ZPerspectiveCamera
{
protected:
	bool transformDirty;	//Dirty flag for computing camera transform

	SST_Mat44f CameraTransform;

	SST_Vec3f EyePoint;
	SST_Vec3f ForwardVec;
	SST_Vec3f UpVec;
	SST_Vec3f RightVec;

	float NearClip;
	float FarClip;
	float XFov;
	float AspectRatio;

	void ComputeCameraTransform()
	{
		SST_Mat44f perspectiveMatrix;
		SST_Mat44f viewMatrix;
		SST_Vec3f target = {ForwardVec.x + EyePoint.x,
							ForwardVec.y + EyePoint.y,
							ForwardVec.z + EyePoint.z};

		SST_Math_Mat44fCreatePerspective(&perspectiveMatrix, this->XFov, this->AspectRatio, this->NearClip, this->FarClip);
		SST_Math_Mat44fCreateLookAt(&viewMatrix, &this->EyePoint, &target, &this->UpVec);
		SST_Math_Mat44fMultiplyMatrix(&perspectiveMatrix, &viewMatrix, &this->CameraTransform);

		this->transformDirty = false;
	}

	//Flags the transform as 'dirty'
	void FlagTransforms()
	{
		this->transformDirty = true;
	}

public:
	ZPerspectiveCamera()
	{
		SST_Vec3f eye = { 0, 0, 0 };
		SST_Vec3f forward = { 0, 1, 0 };
		SST_Vec3f up = { 0, 0, 1 };
		SST_Vec3f right = { 1, 0, 1 };

		SetEyePoint(&eye);
		SetForwardVec(&forward);
		SetUpVec(&up);
		SetRightVec(&right);

		NearClip = 1.0f;
		FarClip = 10000.0f;
		XFov = 90.0f;
		AspectRatio = 1.0f;

		this->transformDirty = true;
	}

	//Getters for camera data
	const SST_Vec3f* GetEyePoint()		{ return &EyePoint; }
	const SST_Vec3f* GetForwardVec()	{ return &ForwardVec; }
	const SST_Vec3f* GetUpVec()			{ return &UpVec; }
	const SST_Vec3f* GetRightVec()		{ return &RightVec; }
	float  GetNearClip()				{ return NearClip; }
	float  GetFarClip()					{ return FarClip; }
	float  GetXFov()					{ return XFov; }
	float  GetAspectRatio()				{ return AspectRatio; }

	//Setters for camera data.  Will flag the transform as 'dirty'
	void SetEyePoint(const SST_Vec3f* _eye)			{ EyePoint = *_eye;			FlagTransforms(); }
	void SetForwardVec(const SST_Vec3f* _forward)	{ ForwardVec = *_forward;	FlagTransforms(); }
	void SetUpVec(const SST_Vec3f* _up)				{ UpVec = *_up;				FlagTransforms(); }
	void SetRightVec(const SST_Vec3f* _right)		{ RightVec = *_right;		FlagTransforms(); }

	void SetNearClip(float _nearClip)	{ NearClip = _nearClip;			FlagTransforms(); }
	void SetFarClip(float _farClip)		{ FarClip = _farClip;			FlagTransforms(); }
	void SetXFov(float _xFov)			{ XFov = _xFov;					FlagTransforms(); }
	void SetAspectRatio(float _ratio)	{ AspectRatio = _ratio;			FlagTransforms(); }

	//Movement methods, which move the camera in the direction if it's basis vectors by a factor
	void MoveForward(float t)	
	{ 
		SST_Vec3f tmp; 
		
		SST_Math_Vec3fScale(&ForwardVec, t, &tmp); 
		SST_Math_Vec3fAddLocal(&EyePoint, &tmp);	
		
		FlagTransforms(); 
	}

	void MoveUp(float t)		
	{ 
		SST_Vec3f tmp; 
		
		SST_Math_Vec3fScale(&UpVec, t, &tmp); 
		SST_Math_Vec3fAddLocal(&EyePoint, &tmp);	

		FlagTransforms(); 
	}

	void MoveRight(float t)		
	{ 
		SST_Vec3f tmp; 
		
		SST_Math_Vec3fScale(&RightVec, t, &tmp); 
		SST_Math_Vec3fAddLocal(&EyePoint, &tmp);		
		FlagTransforms(); 
	}

	//Rotation Methods, which rotate the camera about it's basis vectors
	void RotateUp(float t)
	{		
		SST_Math_Vec3fRotateAboutLocal(&this->ForwardVec, &this->RightVec, t);
		SST_Math_Vec3fRotateAboutLocal(&this->UpVec, &this->RightVec, t);
		FlagTransforms(); 
	}

	void RotateRight(float t)		
	{
		SST_Math_Vec3fRotateAboutLocal(&this->ForwardVec, &this->UpVec, t);
		SST_Math_Vec3fRotateAboutLocal(&this->RightVec, &this->UpVec, t);
		FlagTransforms();
	}

	void RotateClockwise(float t)	
	{
		SST_Math_Vec3fRotateAboutLocal(&this->UpVec, &this->ForwardVec, t);
		SST_Math_Vec3fRotateAboutLocal(&this->RightVec, &this->ForwardVec, t);
		FlagTransforms();
	}

	//Camera transform getters, which will trigger a recompute of the transform matrix if need be
	const SST_Mat44f* GetCameraTransform() 
	{ 
		if (this->transformDirty) 
		{
			this->ComputeCameraTransform();
		}
		return &this->CameraTransform;
	}
};