#pragma once

#include "object.h"
#include "lc_math.h"

enum class lcViewpoint
{
	Front,
	Back,
	Top,
	Bottom,
	Left,
	Right,
	Home,
	Count
};

enum class lcCameraProjection
{
	Perspective,
	Orthographic,
	Count
};

enum lcCameraSection : quint32
{
	LC_CAMERA_SECTION_INVALID = LC_OBJECT_SECTION_INVALID,
	LC_CAMERA_SECTION_POSITION = 0,
	LC_CAMERA_SECTION_TARGET,
	LC_CAMERA_SECTION_UPVECTOR
};

struct lcCameraHistoryState
{
	lcObjectId Id;
	bool Hidden;
	bool Simple;
	float Fovy;
	float NearPlane;
	float FarPlane;
	lcCameraProjection Projection;
	lcObjectProperty<lcVector3> Position;
	lcObjectProperty<lcVector3> TargetPosition;
	lcObjectProperty<lcVector3> UpVector;
	QString Name;

	bool operator==(const lcCameraHistoryState& Other) const
	{
		return Id == Other.Id && Hidden == Other.Hidden && Simple == Other.Simple && Fovy == Other.Fovy &&
            NearPlane == Other.NearPlane && FarPlane == Other.FarPlane && Projection == Other.Projection &&
            Position == Other.Position && TargetPosition == Other.TargetPosition && UpVector == Other.UpVector && Name == Other.Name;
	}
};

class lcCamera : public lcObject
{
public:
	lcCamera();
	lcCamera(bool Simple);
	lcCamera(bool Simple, const lcVector3& Position, const lcVector3& TargetPosition);
	virtual ~lcCamera();

	lcCamera(const lcCamera&) = delete;
	lcCamera(lcCamera&&) = delete;
	lcCamera& operator=(const lcCamera&) = delete;
	lcCamera& operator=(lcCamera&&) = delete;

	static QString GetCameraProjectionString(lcCameraProjection CameraProjection);
	static QStringList GetCameraProjectionStrings();
	static lcViewpoint GetViewpoint(const QString& ViewpointName);

	QString GetName() const override
	{
		return mName;
	}

	bool SetName(const QString& Name);
	void CreateName(const std::vector<std::unique_ptr<lcCamera>>& Cameras);

	bool IsSimple() const
	{
		return mSimple;
	}

	lcCameraProjection GetProjection() const
	{
		return mProjection;
	}

	bool SetProjection(lcCameraProjection CameraProjection);
	bool SetFOV(float Fovy);
	bool SetNearPlane(float NearPlane);
	bool SetFarPlane(float FarPlane);

	quint32 GetAllowedTransforms() const override
	{
		return LC_OBJECT_TRANSFORM_MOVE_XYZ;
	}

	lcVector3 GetSectionPosition(quint32 Section) const override
	{
		switch (Section)
		{
		case LC_CAMERA_SECTION_POSITION:
			return mPosition;

		case LC_CAMERA_SECTION_TARGET:
			return mTargetPosition;

		case LC_CAMERA_SECTION_UPVECTOR:
			return lcMul31(lcVector3(0, 25, 0), lcMatrix44AffineInverse(mWorldView));
		}

		return lcVector3(0.0f, 0.0f, 0.0f);
	}

	lcMatrix33 GetRelativeRotation() const
	{
		const quint32 Section = GetFocusSection();

		if (Section == LC_CAMERA_SECTION_POSITION)
			return lcMatrix33(mWorldView);
		else
			return lcMatrix33Identity();
	}

	lcVector3 GetRotationCenter() const
	{
		const quint32 Section = GetFocusSection();

		if (Section != LC_CAMERA_SECTION_TARGET)
		{
			return mPosition;
		}
		else
		{
			return mTargetPosition;
		}
	}

	void SaveLDraw(QTextStream& Stream) const;
	bool ParseLDrawLine(QTextStream& Stream);

public:
	bool IsVisible() const
	{
		return !mHidden;
	}

	bool IsHidden() const
	{
		return mHidden;
	}

	void SetHidden(bool Hidden)
	{
		mHidden = Hidden;
	}

	void SetPosition(const lcVector3& Position, lcStep Step, bool AddKey)
	{
		mPosition.ChangeKey(Position, Step, AddKey);
	}

	void SetTargetPosition(const lcVector3& TargetPosition, lcStep Step, bool AddKey)
	{
		mTargetPosition.ChangeKey(TargetPosition, Step, AddKey);
	}

	void SetUpVector(const lcVector3& UpVector, lcStep Step, bool AddKey)
	{
		mUpVector.ChangeKey(UpVector, Step, AddKey);
	}

	float GetOrthoHeight() const
	{
		// Compute the FOV/plane intersection radius.
		//                d               d
		//   a = 2 atan(------) => ~ a = --- => d = af
		//                2f              f
		const float f = (mPosition - mTargetPosition).Length();
		return (m_fovy * f) * (LC_PI / 180.0f);
	}

public:
	void RayTest(lcObjectRayTest& ObjectRayTest) const override;
	void BoxTest(lcObjectBoxTest& ObjectBoxTest) const override;
	void DrawInterface(lcContext* Context, const lcScene& Scene) const override;
	QVariant GetPropertyValue(lcObjectPropertyId PropertyId) const override;
	bool SetPropertyValue(lcObjectPropertyId PropertyId, lcStep Step, bool AddKey, QVariant Value) override;
	bool HasKeyFrame(lcObjectPropertyId PropertyId, lcStep Time) const override;
	bool SetKeyFrame(lcObjectPropertyId PropertyId, lcStep Time, bool KeyFrame) override;
	void RemoveKeyFrames() override;
	lcCameraHistoryState GetHistoryState(const lcModel* Model) const;
	void SetHistoryState(const lcCameraHistoryState& State, const lcModel* Model);

	void InsertTime(lcStep Start, lcStep Time);
	void RemoveTime(lcStep Start, lcStep Time);

	static bool FileLoad(lcFile& file);

	void CompareBoundingBox(lcVector3& Min, lcVector3& Max);
	void UpdatePosition(lcStep Step) override;
	void CopyPosition(const lcCamera* Camera);
	void CopySettings(const lcCamera* Camera);

	void ZoomExtents(float AspectRatio, const lcVector3& Center, const std::vector<lcVector3>& Points, lcStep Step, bool AddKey);
	void ZoomRegion(float AspectRatio, const lcVector3& Position, const lcVector3& TargetPosition, const lcVector3* Corners, lcStep Step, bool AddKey);
	void Zoom(float Distance, lcStep Step, bool AddKey);
	void Pan(const lcVector3& Distance, lcStep Step, bool AddKey);
	void Orbit(float DistanceX, float DistanceY, const lcVector3& CenterPosition, lcStep Step, bool AddKey);
	void Roll(float Distance, lcStep Step, bool AddKey);
	void Center(const lcVector3& NewCenter, lcStep Step, bool AddKey);
	void MoveSelected(lcStep Step, bool AddKey, const lcVector3& Distance);
	void MoveRelative(const lcVector3& Distance, lcStep Step, bool AddKey);
	void Rotate(lcStep Step, bool AddKey, const lcMatrix33& RotationMatrix, const lcVector3& Center, const lcMatrix33& RotationFrame);
	void SetViewpoint(lcViewpoint Viewpoint);
	void SetViewpoint(const lcVector3& Position);
	void SetViewpoint(const lcVector3& Position, const lcVector3& Target, const lcVector3& Up);
	void GetAngles(float& Latitude, float& Longitude, float& Distance) const;
	void SetAngles(float Latitude, float Longitude, float Distance);

	float m_fovy = 30.0f;
	float m_zNear = 25.0f;
	float m_zFar = 50000;

	lcMatrix44 mWorldView;
	lcObjectProperty<lcVector3> mPosition = lcObjectProperty<lcVector3>(lcVector3(0.0f, 0.0f, 0.0f));
	lcObjectProperty<lcVector3> mTargetPosition = lcObjectProperty<lcVector3>(lcVector3(0.0f, 0.0f, 0.0f));
	lcObjectProperty<lcVector3> mUpVector = lcObjectProperty<lcVector3>(lcVector3(0.0f, 0.0f, 0.0f));

protected:
	QString mName;
	bool mSimple = true;
	lcCameraProjection mProjection = lcCameraProjection::Perspective;
};
