#pragma once

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

enum lcLightSection : quint32
{
	LC_LIGHT_SECTION_INVALID = LC_OBJECT_SECTION_INVALID,
	LC_LIGHT_SECTION_POSITION = 0,
	LC_LIGHT_SECTION_TARGET
};

enum class lcLightType
{
	Point,
	Spot,
	Directional,
	Area,
	Count
};

enum class lcLightAreaShape
{
	Rectangle,
	Square,
	Disk,
	Ellipse,
	Count
};

struct lcLightHistoryState
{
	lcObjectId Id;
	bool Hidden;
	QString Name;
	lcLightType LightType;
	bool CastShadow;
	lcObjectProperty<lcVector3> Position;
	lcObjectProperty<lcMatrix33> Rotation;
	lcObjectProperty<lcVector3> Color;
	lcObjectProperty<float> BlenderPower;
	lcObjectProperty<float> BlenderRadius;
	lcObjectProperty<float> BlenderAngle;
	lcObjectProperty<float> POVRayPower;
	lcObjectProperty<float> POVRayFadeDistance;
	lcObjectProperty<float> POVRayFadePower;
	lcObjectProperty<float> SpotConeAngle;
	lcObjectProperty<float> SpotPenumbraAngle;
	lcObjectProperty<float> POVRaySpotTightness;
	lcLightAreaShape AreaShape;
	lcObjectProperty<float> AreaSizeX;
	lcObjectProperty<float> AreaSizeY;
	lcObjectProperty<int> POVRayAreaGridX;
	lcObjectProperty<int> POVRayAreaGridY;

	bool operator==(const lcLightHistoryState& Other) const
	{
		return Id == Other.Id && Hidden == Other.Hidden && Name == Other.Name && LightType == Other.LightType &&
            CastShadow == Other.CastShadow && Position == Other.Position && Rotation == Other.Rotation && Color == Other.Color &&
            BlenderPower == Other.BlenderPower && BlenderRadius == Other.BlenderRadius && BlenderAngle == Other.BlenderAngle &&
            POVRayPower == Other.POVRayPower && POVRayFadeDistance == Other.POVRayFadeDistance && POVRayFadePower == Other.POVRayFadePower &&
            SpotConeAngle == Other.SpotConeAngle && SpotPenumbraAngle == Other.SpotPenumbraAngle &&
            POVRaySpotTightness == Other.POVRaySpotTightness && AreaShape == Other.AreaShape && AreaSizeX == Other.AreaSizeX &&
            AreaSizeY == Other.AreaSizeY && POVRayAreaGridX == Other.POVRayAreaGridX && POVRayAreaGridY == Other.POVRayAreaGridY;
	}
};

class lcLight : public lcObject
{
public:
	lcLight();
	lcLight(const lcVector3& Position, lcLightType LightType);
	virtual ~lcLight() = default;

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

	static QString GetLightTypeString(lcLightType LightType);
	static QStringList GetLightTypeStrings();
	static QString GetAreaShapeString(lcLightAreaShape LightAreaShape);
	static QStringList GetAreaShapeStrings();

	bool IsPointLight() const
	{
		return mLightType == lcLightType::Point;
	}

	bool IsSpotLight() const
	{
		return mLightType == lcLightType::Spot;
	}

	bool IsDirectionalLight() const
	{
		return mLightType == lcLightType::Directional;
	}

	bool IsAreaLight() const
	{
		return mLightType == lcLightType::Area;
	}

	lcLightType GetLightType() const
	{
		return mLightType;
	}

	bool SetLightType(lcLightType LightType);

	quint32 GetAllowedTransforms() const override
	{
		if (IsPointLight())
			return LC_OBJECT_TRANSFORM_MOVE_XYZ;

		const quint32 Section = GetFocusSection();

		if (Section == LC_LIGHT_SECTION_POSITION || Section == LC_LIGHT_SECTION_INVALID)
			return LC_OBJECT_TRANSFORM_MOVE_XYZ | LC_OBJECT_TRANSFORM_ROTATE_XYZ;

		if (Section == LC_LIGHT_SECTION_TARGET)
			return LC_OBJECT_TRANSFORM_MOVE_XYZ;

		return 0;
	}

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

		if (Section == LC_LIGHT_SECTION_POSITION)
			return lcMatrix33(mWorldMatrix);
		else
			return lcMatrix33Identity();
	}

	lcVector3 GetSectionPosition(quint32 Section) const override
	{
		if (Section == LC_LIGHT_SECTION_POSITION)
			return mWorldMatrix.GetTranslation();

		if (Section == LC_LIGHT_SECTION_TARGET)
			return lcMul31(lcVector3(0.0f, 0.0f, -mTargetDistance), mWorldMatrix);

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

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

	void SetRotation(const lcMatrix33& Rotation, lcStep Step, bool AddKey)
	{
		mRotation.ChangeKey(Rotation, Step, AddKey);
	}

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

		if (Section == LC_LIGHT_SECTION_POSITION || Section == LC_LIGHT_SECTION_INVALID)
		{
			return mWorldMatrix.GetTranslation();
		}
		else
		{
			return lcMul31(lcVector3(0.0f, 0.0f, -mTargetDistance), mWorldMatrix);
		}
	}

	lcVector3 GetPosition() const
	{
		return mWorldMatrix.GetTranslation();
	}

	lcVector3 GetDirection() const
	{
		return -lcVector3(mWorldMatrix[2]);
	}

	const lcMatrix44& GetWorldMatrix() const
	{
		return mWorldMatrix;
	}

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

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;
	lcLightHistoryState GetHistoryState(const lcModel* Model) const;
	void SetHistoryState(const lcLightHistoryState& State, const lcModel* Model);

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

	bool IsVisible() const
	{
		return !mHidden;
	}

	bool SetColor(const lcVector3& Color, lcStep Step, bool AddKey);

	lcVector3 GetColor() const
	{
		return mColor;
	}

	bool SetPOVRayFadeDistance(float Distance, lcStep Step, bool AddKey);

	float GetPOVRayFadeDistance() const
	{
		return mPOVRayFadeDistance;
	}

	bool SetPOVRayFadePower(float Power, lcStep Step, bool AddKey);

	float GetPOVRayFadePower() const
	{
		return mPOVRayFadePower;
	}

	bool SetSpotConeAngle(float Angle, lcStep Step, bool AddKey);

	float GetSpotConeAngle() const
	{
		return mSpotConeAngle;
	}

	bool SetSpotPenumbraAngle(float Angle, lcStep Step, bool AddKey);

	float GetSpotPenumbraAngle() const
	{
		return mSpotPenumbraAngle;
	}

	bool SetSpotPOVRayTightness(float Angle, lcStep Step, bool AddKey);

	float GetSpotPOVRayTightness() const
	{
		return mPOVRaySpotTightness;
	}

	bool SetAreaShape(lcLightAreaShape LightAreaShape);

	lcLightAreaShape GetAreaShape() const
	{
		return mAreaShape;
	}

	bool SetAreaPOVRayGridX(int AreaGrid, lcStep Step, bool AddKey);

	int GetAreaPOVRayGridX() const
	{
		return mPOVRayAreaGridX;
	}

	bool SetAreaPOVRayGridY(int AreaGrid, lcStep Step, bool AddKey);

	int GetAreaPOVRayGridY() const
	{
		return mPOVRayAreaGridY;
	}

	bool SetBlenderRadius(float Radius, lcStep Step, bool AddKey);

	float GetBlenderRadius() const
	{
		return mBlenderRadius;
	}

	bool SetBlenderAngle(float Angle, lcStep Step, bool AddKey);

	float GetBlenderAngle() const
	{
		return mBlenderAngle * LC_RTOD;
	}

	bool SetAreaSizeX(float Size, lcStep Step, bool AddKey);

	float GetAreaSizeX() const
	{
		return mAreaSizeX;
	}

	bool SetAreaSizeY(float Size, lcStep Step, bool AddKey);

	float GetAreaSizeY() const
	{
		return mAreaSizeY;
	}

	bool SetBlenderPower(float Power, lcStep Step, bool AddKey);

	float GetBlenderPower() const
	{
		return mBlenderPower;
	}

	bool SetPOVRayPower(float Power, lcStep Step, bool AddKey);

	float GetPOVRayPower() const
	{
		return mPOVRayPower;
	}

	bool SetCastShadow(bool CastShadow);

	bool GetCastShadow() const
	{
		return mCastShadow;
	}

	bool SetName(const QString& Name);

	QString GetName() const override
	{
		return mName;
	}

	void CompareBoundingBox(lcVector3& Min, lcVector3& Max);
	void UpdatePosition(lcStep Step) override;
	void MoveSelected(lcStep Step, bool AddKey, const lcVector3& Distance, bool FirstMove);
	void Rotate(lcStep Step, bool AddKey, const lcMatrix33& RotationMatrix, const lcVector3& Center, const lcMatrix33& RotationFrame);
	void CreateName(const std::vector<std::unique_ptr<lcLight>>& Lights);

protected:
	void DrawPointLight(lcContext* Context) const;
	void DrawSpotLight(lcContext* Context) const;
	void DrawDirectionalLight(lcContext* Context) const;
	void DrawAreaLight(lcContext* Context) const;

	void SetupLightMatrix(lcContext* Context) const;
	void DrawSphere(lcContext* Context, const lcVector3& Center, float Radius) const;
	void DrawCylinder(lcContext* Context, float Radius, float Height) const;
	void DrawTarget(lcContext* Context) const;
	void DrawCone(lcContext* Context, float TargetDistance) const;

	QString mName;
	lcLightType mLightType = lcLightType::Point;
	bool mCastShadow = true;
	lcObjectProperty<lcVector3> mPosition = lcObjectProperty<lcVector3>(lcVector3(0.0f, 0.0f, 0.0f));
	lcObjectProperty<lcMatrix33> mRotation = lcObjectProperty<lcMatrix33>(lcMatrix33Identity());
	lcObjectProperty<lcVector3> mColor = lcObjectProperty<lcVector3>(lcVector3(1.0f, 1.0f, 1.0f));
	lcObjectProperty<float> mBlenderPower = lcObjectProperty<float>(10.0f);
	lcObjectProperty<float> mBlenderRadius = lcObjectProperty<float>(0.0f);
	lcObjectProperty<float> mBlenderAngle = lcObjectProperty<float>(0.526f * LC_DTOR);
	lcObjectProperty<float> mPOVRayPower = lcObjectProperty<float>(1.0f);
	lcObjectProperty<float> mPOVRayFadeDistance = lcObjectProperty<float>(0.0f);
	lcObjectProperty<float> mPOVRayFadePower = lcObjectProperty<float>(0.0f);
	lcObjectProperty<float> mSpotConeAngle = lcObjectProperty<float>(80.0f);
	lcObjectProperty<float> mSpotPenumbraAngle = lcObjectProperty<float>(0.0f);
	lcObjectProperty<float> mPOVRaySpotTightness = lcObjectProperty<float>(0.0f);
	lcLightAreaShape mAreaShape = lcLightAreaShape::Rectangle;
	lcObjectProperty<float> mAreaSizeX = lcObjectProperty<float>(250.0f);
	lcObjectProperty<float> mAreaSizeY = lcObjectProperty<float>(250.0f);
	lcObjectProperty<int> mPOVRayAreaGridX = lcObjectProperty<int>(2);
	lcObjectProperty<int> mPOVRayAreaGridY = lcObjectProperty<int>(2);

	lcVector3 mTargetMovePosition = lcVector3(0.0f, 0.0f, 0.0f);
	lcMatrix44 mWorldMatrix;

	static constexpr float mTargetDistance = 50.0f;
};
