/*
 * $Id: variant.h 967 2009-11-20 12:26:38Z misek $
 *
 * Description  : One line description of file.
 * Author       : Anton�n M�ek <mail: antonin.misek@skoda-auto.cz>
 *
 * Purpose      :
 *     Long description of what the file is for.
 */


#ifndef __VARIANT__H__
#define __VARIANT__H__

//#include "moddefs.h"
//#include <vector>
#include <algorithm>
#include "countable.h"
#include "3dmath.h"

namespace VRUT
{
	struct ShortCut
	{
	public:
		int modifiers;
		int keyCode;
		ShortCut() : modifiers(0), keyCode(-1) {}
		ShortCut(int scModifiers, int scKeyCode) : modifiers(scModifiers), keyCode(scKeyCode) {}
		bool operator!=(const ShortCut &s) const
		{
			return ((modifiers != s.modifiers) || (keyCode != s.keyCode));
		}
	};

	///Base class for variant representation - abstract?
	class Variant : public Countable
	{
	private:
		///VID
		VARIANT_ID vid;

	public:
		///name
		wxString name;
		///Type of variant
		enum VARIANT_TYPE
		{
			VARIANT_UNKNOWN,    ///<Unknown variant type
			VARIANT_GEOMETRY,   ///<geometry variant
			VARIANT_MATERIAL,   ///<material variant
			VARIANT_SET,        ///<variant set
			VARIANT_CAMERA,     ///<camera views
		} type;
		///list of states
		std::vector<wxString> statesList;
		///active state
		size_t activeState;//-1=hide all, -2=show all

		///Class constructor
		Variant(const VARIANT_ID & _vid, const wxString &_name, VARIANT_TYPE _type=VARIANT_UNKNOWN) : vid(_vid), name(_name), type(_type), activeState((size_t)-2)  {}
		///Copy constructor
		Variant(const Variant & v) : vid(v.vid), name(v.name), type(v.type), activeState(v.activeState)
		{
			statesList.assign(v.statesList.begin(), v.statesList.end());
		}
		///Class destructor
		virtual ~Variant() {}

		///Get ID
		VARIANT_ID GetID() const
		{
			return vid;
		}

		wxString GetName() const
		{
			return name;
		}

		virtual void AddState(const wxString &stateName, size_t stateID=(size_t)-1)=0;

		virtual bool RemoveState(size_t stateID)=0;
		virtual bool RemoveState(const wxString &stateName)
		{
			std::vector<wxString>::iterator pos=find(statesList.begin(), statesList.end(), stateName);
			if (pos!=statesList.end())
				return RemoveState(pos-statesList.begin());
			return false;
		}

		size_t FindFirstStateIDByName(const wxString &stateName) const
		{
			std::vector<wxString>::const_iterator pos=find(statesList.begin(), statesList.end(), stateName);
			if (pos!=statesList.end())
				return (pos-statesList.begin());
			if (stateName==wxT("Show all"))
				return (size_t)-2;
			return (size_t)-1;
		}

		virtual bool IsSameAs(const Variant *v, int fieldsMask) const
		{
			//name
			if (fieldsMask & 1)
				if (name != v->name)
					return false;

			//type
			if (fieldsMask & 2)
				if (type != v->type)
					return false;

			//statesList
			if (fieldsMask & 4)
			{
				if (statesList.size() != v->statesList.size())
					return false;
				for (size_t i = 0; i < statesList.size(); ++i)
					if (statesList[i] != v->statesList[i])
						return false;
			}

			//activeState
			if (fieldsMask & 8)
				if (activeState != v->activeState)
					return false;

			return true;
		}

		///Get string with variant properties
		virtual wxString ToString() const
		{
			return wxString::Format(wxT("Variant type: %i\n"),	unsigned(type));
		}

		///Get copy of instance
		virtual Variant * Clone() const = 0;

		friend class Scene;
	};

	///Base class for geometric variant representation
	class VariantGeometry : public Variant
	{
	public:
		///list of nodes
		std::vector<NODE_ID> nodesList;
		///table of visibility state x node
		std::vector<std::vector<bool> > stateNodesTable;

		///Class constructor
		VariantGeometry(const VARIANT_ID & _vid, const wxString &_name) : Variant(_vid, _name, VARIANT_GEOMETRY) {}
		///Copy constructor
		VariantGeometry(const VariantGeometry & v) : Variant(v)
		{
			nodesList.assign(v.nodesList.begin(), v.nodesList.end());
			stateNodesTable.resize(v.stateNodesTable.size(), std::vector<bool>());
			for (size_t i=0; i<stateNodesTable.size(); i++)
				stateNodesTable.at(i).assign(v.stateNodesTable.at(i).begin(), v.stateNodesTable.at(i).end());
		}
		///Class destructor
		virtual ~VariantGeometry() {}

		virtual void AddState(const wxString &stateName, size_t stateID=(size_t)-1)
		{
			if (stateID==(size_t)-1)
				stateID=statesList.size();
			if (stateID>=statesList.size())
			{
				statesList.resize(stateID+1, wxT(""));
				//resize and update stateNodesTable
				size_t i=stateNodesTable.size();
				stateNodesTable.resize(stateID+1, std::vector<bool>());
				for (; i<stateNodesTable.size(); i++)
					stateNodesTable.at(i).resize(nodesList.size(), false);
			}
			statesList.at(stateID)=stateName;
		}

		virtual bool RemoveState(size_t stateID)
		{
			if (stateID>=statesList.size())
				return false;
			statesList.erase(statesList.begin()+stateID);
			if (activeState>=stateID)
				activeState--;
			stateNodesTable.erase(stateNodesTable.begin()+stateID);
			return true;
		}

		void AddNode(NODE_ID nodeID)
		{
			if (find(nodesList.begin(), nodesList.end(), nodeID)==nodesList.end())
			{
				nodesList.push_back(nodeID);
				for (auto &stateNodes : stateNodesTable)
					stateNodes.push_back(false);
			}
		}

		bool RemoveNode(NODE_ID nodeID)
		{
			size_t index = FindNodeIDPos(nodeID);
			if (index != (size_t)-1)
			{
				nodesList.erase(nodesList.begin() + index);
				for (size_t i = 0; i < stateNodesTable.size(); i++)
					stateNodesTable.at(i).erase(stateNodesTable.at(i).begin() + index);
				return true;
			}
			return false;
		}

		bool ReplaceNode(NODE_ID oldID, NODE_ID newID)
		{
			std::vector<NODE_ID>::iterator posO=find(nodesList.begin(), nodesList.end(), oldID);
			if (posO!=nodesList.end())
			{
				size_t posOindex=posO-nodesList.begin();
				std::vector<NODE_ID>::iterator posN=find(nodesList.begin(), nodesList.end(), newID);
				if (posN!=nodesList.end())//TODO: decide what to do (both nodes are in variant)
				{
					if (posN<posO)
						posOindex--;
					RemoveNode(newID);
				}
				nodesList[posOindex]=newID;
				return true;
			}
			return false;
		}

		size_t FindNodeIDPos(NODE_ID nodeID)
		{
			for (size_t i=0; i<nodesList.size(); i++)
				if (nodesList.at(i)==nodeID)
					return i;
			return (size_t)-1;
		}
/*
		bool GetStateNodeStatus(size_t stateID, NODE_ID nodeID)
		{
			if (stateID>=stateNodesTable.size())
				return false;
			size_t nodePos=FindNodeIDPos(nodeID);
			if (nodePos>=nodesList.size())
				return false;
			return stateNodesTable.at(stateID).at(nodePos);
		}

		bool GetStateNodeStatus(const wxString &stateName, NODE_ID nodeID)
		{
			return GetStateNodeStatus(FindFirstStateIDByName(stateName), nodeID);
		}*/

		void SetStateNodeStatus(size_t stateID, NODE_ID nodeID, bool status)
		{
			if (stateID>=stateNodesTable.size())
				return;
			size_t nodePos=FindNodeIDPos(nodeID);
			if (nodePos>=nodesList.size())
				return;
			stateNodesTable.at(stateID).at(nodePos)=status;
		}

		void SetStateNodeStatus(const wxString &stateName, NODE_ID nodeID, bool status)
		{
			SetStateNodeStatus(FindFirstStateIDByName(stateName), nodeID, status);
		}

		virtual bool IsSameAs(const Variant *v, int fieldsMask) const
		{
			if (!Variant::IsSameAs(v, fieldsMask))
				return false;

			//nodesList
			if (fieldsMask & 16)
			{
				if (nodesList.size() != ((VariantGeometry*)v)->nodesList.size())
					return false;
				for (size_t i = 0; i < nodesList.size(); ++i)
					if (nodesList[i] != ((VariantGeometry*)v)->nodesList[i])
						return false;
			}

			//stateNodesTable
			if (fieldsMask & 32)
			{
				if (stateNodesTable.size() != ((VariantGeometry*)v)->stateNodesTable.size())
					return false;
				for (size_t i = 0; i < stateNodesTable.size(); ++i)
				{
					if (stateNodesTable[i].size() != ((VariantGeometry*)v)->stateNodesTable[i].size())
						return false;
					for (size_t j = 0; j < stateNodesTable[i].size(); ++j)
						if (stateNodesTable[i][j] != ((VariantGeometry*)v)->stateNodesTable[i][j])
							return false;
				}
			}

			return true;
		}

		///Get string with variant properties
		virtual wxString ToString() const
		{
			return wxString::Format(wxT("Variant type: %i\n"),	unsigned(type));
		}

		///Get copy of instance
		virtual Variant * Clone() const
		{
			return new VariantGeometry(*this);
		}

		friend class Scene;
	};

	///Base class for material variant representation
	class VariantMaterial : public Variant
	{
	public:
		///Class constructor
		VariantMaterial(const VARIANT_ID & _vid, const wxString &_name) : Variant(_vid, _name, VARIANT_MATERIAL) {}
		///Copy constructor
		VariantMaterial(const VariantMaterial & v) : Variant(v)
		{
		}
		///Class destructor
		virtual ~VariantMaterial() {}

		virtual void AddState(const wxString &stateName, size_t stateID=(size_t)-1)
		{
			std::vector<wxString>::iterator pos=find(statesList.begin(), statesList.end(), stateName);
			if (pos==statesList.end())
			{
				if (stateID==(size_t)-1)
					stateID=statesList.size();
				if (stateID>=statesList.size())
					statesList.resize(stateID+1, wxT(""));
				statesList.at(stateID)=stateName;
			}
		}

		virtual bool RemoveState(size_t stateID)
		{
			if (stateID>=statesList.size())
				return false;
			statesList.erase(statesList.begin()+stateID);
			if (activeState>=stateID)
				activeState--;
			return true;
		}

		///Get string with variant properties
		virtual wxString ToString() const
		{
			return wxString::Format(wxT("Variant type: %i\n"),	unsigned(type));
		}

		///Get copy of instance
		virtual Variant * Clone() const
		{
			return new VariantMaterial(*this);
		}

		friend class Scene;
	};

	///Base class for variant set representation
	class VariantSet : public Variant
	{
	public:
		enum VariantSetDetailType {
			VARIANT_SET_DETAIL_VARIANT = 0,
			VARIANT_SET_DETAIL_SWITCH,
			VARIANT_SET_DETAIL_SCRIPT
		};

		///list of details
		std::vector<size_t> detailsList;
		///list of details types
		std::vector<VariantSetDetailType> detailsTypes;//false(0)=variant, true(1)=switch, 2=script
		//list of shortcuts
		std::vector<ShortCut> shortCuts;
		///table of visibility state x node
		std::vector<std::vector<wxString> > stateDetailsTable;

		///Class constructor
		VariantSet(const VARIANT_ID & _vid, const wxString &_name) : Variant(_vid, _name, VARIANT_SET) {}
		///Copy constructor
		VariantSet(const VariantSet & v) : Variant(v)
		{
			detailsList.assign(v.detailsList.begin(), v.detailsList.end());
			detailsTypes.assign(v.detailsTypes.begin(), v.detailsTypes.end());
			shortCuts.assign(v.shortCuts.begin(), v.shortCuts.end());
			stateDetailsTable.resize(v.stateDetailsTable.size(), std::vector<wxString>());
			for (size_t i=0; i<stateDetailsTable.size(); i++)
				stateDetailsTable.at(i).assign(v.stateDetailsTable.at(i).begin(), v.stateDetailsTable.at(i).end());
		}
		///Class destructor
		virtual ~VariantSet() {}

		virtual void AddState(const wxString &stateName, size_t stateID=(size_t)-1)
		{
			if (stateID==(size_t)-1)
				stateID=statesList.size();
			if (stateID>=statesList.size())
			{
				statesList.resize(stateID+1, wxT(""));
				shortCuts.resize(stateID+1);
				//resize and update stateNodesTable
				size_t i=stateDetailsTable.size();
				stateDetailsTable.resize(stateID+1, std::vector<wxString>());
				for (; i<stateDetailsTable.size(); i++)
					stateDetailsTable.at(i).resize(detailsList.size(), wxT("Hide all"));
			}
			statesList.at(stateID)=stateName;
		}

		virtual bool RemoveState(size_t stateID)
		{
			if (stateID>=statesList.size())
				return false;
			statesList.erase(statesList.begin()+stateID);
			shortCuts.erase(shortCuts.begin()+stateID);
			if (activeState>=stateID)
				activeState--;
			stateDetailsTable.erase(stateDetailsTable.begin()+stateID);
			return true;
		}

		void AddVariant(VARIANT_ID vid)
		{
			if (FindVariantIDPos(vid)==(size_t)-1)
			{
				detailsList.push_back((size_t)vid);
				detailsTypes.push_back(VARIANT_SET_DETAIL_VARIANT);
				for (auto &stateDetails : stateDetailsTable)
					stateDetails.push_back(wxT("Hide all"));
			}
		}

		void AddSwitch(NODE_ID switchID)
		{
			if (FindSwitchIDPos(switchID)==(size_t)-1)
			{
				detailsList.push_back((size_t)switchID);
				detailsTypes.push_back(VARIANT_SET_DETAIL_SWITCH);
				for (auto &stateDetails : stateDetailsTable)
					stateDetails.push_back(wxT("Hide all"));
			}
		}

		void AddScript(size_t scriptID)
		{
			if (FindScriptIDPos(scriptID)==(size_t)-1)
			{
				detailsList.push_back(scriptID);
				detailsTypes.push_back(VARIANT_SET_DETAIL_SCRIPT);
				for (auto &stateDetails : stateDetailsTable)
					stateDetails.push_back(wxT(""));
			}
		}

		bool RemoveVariant(VARIANT_ID vid)
		{
			size_t index = FindVariantIDPos(vid);
			if (index != (size_t)-1)
			{
				detailsList.erase(detailsList.begin() + index);
				detailsTypes.erase(detailsTypes.begin() + index);
				for (size_t i = 0; i < stateDetailsTable.size(); i++)
					stateDetailsTable.at(i).erase(stateDetailsTable.at(i).begin() + index);
				return true;
			}
			return false;
		}

		bool RemoveSwitch(NODE_ID switchID)
		{
			size_t index = FindSwitchIDPos(switchID);
			if (index != (size_t)-1)
			{
				detailsList.erase(detailsList.begin() + index);
				detailsTypes.erase(detailsTypes.begin() + index);
				for (size_t i = 0; i < stateDetailsTable.size(); i++)
					stateDetailsTable.at(i).erase(stateDetailsTable.at(i).begin() + index);
				return true;
			}
			return false;
		}

		void RemoveScript(size_t scriptID)
		{
			size_t index = FindScriptIDPos(scriptID);
			if (index != (size_t)-1)
			{
				detailsList.erase(detailsList.begin() + index);
				detailsTypes.erase(detailsTypes.begin() + index);
				for (size_t i = 0; i < stateDetailsTable.size(); i++)
					stateDetailsTable.at(i).erase(stateDetailsTable.at(i).begin() + index);
			}
		}

/*		void ReplaceVariant(VARIANT_ID oldID, VARIANT_ID newID)
		{
			std::vector<NODE_ID>::iterator posO=find(detailsList.begin(), detailsList.end(), oldID);
			if (posO!=detailsList.end())
			{
				if (find(detailsList.begin(), detailsList.end(), newID)!=detailsList.end())//TODO: decide what to do (both nodes are in variant)
					RemoveVariant(newID);
				*posO=newID;
			}
		}*/

		size_t FindVariantIDPos(VARIANT_ID vid)
		{
			for (size_t i=0; i<detailsList.size(); i++)
				if ((detailsTypes.at(i) == VARIANT_SET_DETAIL_VARIANT) && (detailsList.at(i)==(size_t)vid))
					return i;
			return (size_t)-1;
		}

		size_t FindSwitchIDPos(NODE_ID switchID)
		{
			for (size_t i=0; i<detailsList.size(); i++)
				if ((detailsTypes.at(i) == VARIANT_SET_DETAIL_SWITCH) && (detailsList.at(i)==(size_t)switchID))
					return i;
			return (size_t)-1;
		}

		size_t FindScriptIDPos(size_t scriptID)
		{
			for (size_t i=0; i<detailsList.size(); i++)
				if ((detailsTypes.at(i) == VARIANT_SET_DETAIL_SCRIPT) && (detailsList.at(i)==scriptID))
					return i;
			return (size_t)-1;
		}

/*		wxString GetStateDetailStatus(size_t stateID, VARIANT_ID variantID)
		{
			if (stateID>=stateDetailsTable.size())
				return false;
			size_t variantPos=FindVariantIDPos(variantID);
			if (variantPos>=detailsList.size())
				return false;
			return stateDetailsTable.at(stateID).at(variantPos);
		}

		bool GetStateDetailStatus(const wxString &stateName, VARIANT_ID variantID)
		{
			return GetStateDetailStatus(FindFirstStateIDByName(stateName), variantID);
		}*/

		void SetStateDetailStatus(size_t stateID, size_t detailID, VariantSetDetailType detailType, const wxString &status)
		{
			if (stateID>=stateDetailsTable.size())
				return;
			size_t detailPos = (size_t)-1;
			switch (detailType)
			{
				case VARIANT_SET_DETAIL_VARIANT:
					detailPos=FindVariantIDPos((VARIANT_ID)detailID);
					break;
				case VARIANT_SET_DETAIL_SWITCH:
					detailPos=FindSwitchIDPos((NODE_ID)detailID);
					break;
				case VARIANT_SET_DETAIL_SCRIPT:
					detailPos=FindScriptIDPos(detailID);
					break;
			}
			if (detailPos>=detailsList.size())
				return;
			stateDetailsTable.at(stateID).at(detailPos)=status;
		}

		void SetStateDetailStatus(const wxString &stateName, size_t detailID, VariantSetDetailType detailType, const wxString &status)
		{
			SetStateDetailStatus(FindFirstStateIDByName(stateName), detailID, detailType, status);
		}

		void SetStateShortCut(size_t stateID, const ShortCut &shortCut)
		{
			if (stateID>=shortCuts.size())
				return;
			shortCuts[stateID]=shortCut;
		}

		virtual bool IsSameAs(const Variant *v, int fieldsMask) const
		{
			if (!Variant::IsSameAs(v, fieldsMask))
				return false;

			//detailsList && detailsTypes
			if (fieldsMask & 64)
			{
				if (detailsList.size() != ((VariantSet*)v)->detailsList.size())
					return false;
				if (detailsTypes.size() != ((VariantSet*)v)->detailsTypes.size())
					return false;
				if (detailsList.size() != detailsTypes.size())
					return false;
				for (size_t i = 0; i < detailsList.size(); ++i)
				{
					if (detailsList[i] != ((VariantSet*)v)->detailsList[i])
						return false;
					if (detailsTypes[i] != ((VariantSet*)v)->detailsTypes[i])
						return false;
				}
			}

			//stateDetailsTable
			if (fieldsMask & 128)
			{
				if (stateDetailsTable.size() != ((VariantSet*)v)->stateDetailsTable.size())
					return false;
				for (size_t i = 0; i < stateDetailsTable.size(); ++i)
				{
					if (stateDetailsTable[i].size() != ((VariantSet*)v)->stateDetailsTable[i].size())
						return false;
					for (size_t j = 0; j < stateDetailsTable[i].size(); ++j)
						if (stateDetailsTable[i][j] != ((VariantSet*)v)->stateDetailsTable[i][j])
							return false;
				}
			}

			//shortCuts
			if (fieldsMask & 256)
			{
				if (shortCuts.size() != ((VariantSet*)v)->shortCuts.size())
					return false;
				for (size_t i = 0; i < shortCuts.size(); ++i)
					if (shortCuts[i] != ((VariantSet*)v)->shortCuts[i])
						return false;
			}

			return true;
		}

		///Get string with variant properties
		virtual wxString ToString() const
		{
			return wxString::Format(wxT("Variant type: %i\n"),	unsigned(type));
		}

		///Get copy of instance
		virtual Variant * Clone() const
		{
			return new VariantSet(*this);
		}

		friend class Scene;
	};

	///Base class for camera views representation
	class VariantCamera : public Variant
	{
	public:
		///list of transformations
		std::vector<Matrix> transformations;
		///list of FOVs
		std::vector<float> FOVs;
		///scene cameraNode
		NODE_ID camID;

		///Class constructor
		VariantCamera(const VARIANT_ID & _vid, const wxString &_name) : Variant(_vid, _name, VARIANT_CAMERA), camID(NODE_ID_NONE) {}
		///Copy constructor
		VariantCamera(const VariantCamera & v) : Variant(v), camID(v.camID)
		{
			transformations.assign(v.transformations.begin(), v.transformations.end());
			FOVs.assign(v.FOVs.begin(), v.FOVs.end());
		}
		///Class destructor
		virtual ~VariantCamera() {}

		virtual void AddState(const wxString &stateName, size_t stateID=(size_t)-1)
		{
			if (stateID==(size_t)-1)
				stateID=statesList.size();
			if (stateID>=statesList.size())
			{
				statesList.resize(stateID+1, wxT(""));
				//resize and update transformations and FOVs
				transformations.resize(stateID+1, Matrix());
				FOVs.resize(stateID+1, 45);
			}
			statesList.at(stateID)=stateName;
		}

		virtual bool RemoveState(size_t stateID)
		{
			if (stateID>=statesList.size())
				return false;
			statesList.erase(statesList.begin()+stateID);
			if (activeState>=stateID)
				activeState--;
			transformations.erase(transformations.begin()+stateID);
			FOVs.erase(FOVs.begin()+stateID);
			return true;
		}

		void SetStateCamera(size_t stateID, const Matrix &transformation, const float FOV)
		{
			if (stateID>=statesList.size())
				return;
			transformations.at(stateID)=transformation;
			FOVs.at(stateID)=FOV;
		}

		void SetStateCamera(const wxString &stateName, const Matrix &transformation, const float FOV)
		{
			SetStateCamera(FindFirstStateIDByName(stateName), transformation, FOV);
		}

		virtual bool IsSameAs(const Variant *v, int fieldsMask) const
		{
			if (!Variant::IsSameAs(v, fieldsMask))
				return false;

			//camID
			if (fieldsMask & 512)
				if (camID != ((VariantCamera*)v)->camID)
					return false;

			//FOVs
			if (fieldsMask & 1024)
			{
				if (FOVs.size() != ((VariantCamera*)v)->FOVs.size())
					return false;
				for (size_t i = 0; i < FOVs.size(); ++i)
					if (FOVs[i] != ((VariantCamera*)v)->FOVs[i])
						return false;
			}

			//transformations
			if (fieldsMask & 2048)
			{
				if (transformations.size() != ((VariantCamera*)v)->transformations.size())
					return false;
				for (size_t i = 0; i < transformations.size(); ++i)
					if (transformations[i] != ((VariantCamera*)v)->transformations[i])
						return false;
			}

			return true;
		}

		///Get string with variant properties
		virtual wxString ToString() const
		{
			return wxString::Format(wxT("Variant type: %i\n"),	unsigned(type));
		}

		///Get copy of instance
		virtual Variant * Clone() const
		{
			return new VariantCamera(*this);
		}

		friend class Scene;
	};
}

#endif
