/*
 * $Id: shaderprogram.h 4815 2018-01-08 10:19:58Z antonin.misek-skoda-auto.cz $
 *
 * Description  : One line description of file.
 * Author       : Vaclav Kyba <mail/Jabber: vaseo1@gmail.com>  <ICQ: 98576293>
 * Edit			: Kry�tof Valenta valenkry@fel.cvut.cz - Geometry shader support added (19.5.2011)
 * 
 * Purpose      :
 *     Long description of what the file is for.
 */


#ifndef __SHADER_PROGRAM__H__
#define __SHADER_PROGRAM__H__
#define MAXMATRIXCOUNT 8
#include "common.h"
#include <vector>

#include "flexilog.h"
#include "textparser.h"

namespace VRUT
{
	///Shader parameter
	struct ShaderParam
	{
		///Name
		wxString name;
		///Parameter type
		enum PARAM_TYPE
		{
			BOOL,
			INT,
			FLOAT1,
			FLOAT2,
			FLOAT3,
			FLOAT4,
			SAMPLER,
			SAMPLERCUBE,
			SAMPLER3D
		} type;
		///Parameter float values (\c FLOAT types only)
		float fVals[4];
		///Image name (\c SAMPLER and SAMPLERCUBE types only)
		wxString imageName;
		///Parameter int value (\c also BOOL type)
		int iVal;
		///Constructor
		ShaderParam() : type(INT)
		{
			memset(fVals, 0, sizeof(float)*4);
			iVal=0;
		}
		ShaderParam(const wxString &name, PARAM_TYPE type=INT) : name(name), type(type)
		{
			memset(fVals, 0, sizeof(float)*4);
			iVal=0;
		}

		ShaderParam(const wxString &name, PARAM_TYPE type, const int iValue) : name(name), type(type)
		{
			memset(fVals, 0, sizeof(float) * 4);
			iVal = iValue;
		}

		ShaderParam(const wxString &name, PARAM_TYPE type, const float fValue) : name(name), type(type)
		{
			memset(fVals, 0, sizeof(float) * 4);
			fVals[0] = fValue;
			iVal = 0;
		}

		ShaderParam(const wxString &name, PARAM_TYPE type, const wxString &imageName) : name(name), type(type), imageName(imageName)
		{
			memset(fVals, 0, sizeof(float) * 4);
			iVal = 0;
		}

		///Assign operator
		ShaderParam & operator=(const ShaderParam & sp)
		{
			name = CloneWxString(sp.name);
			type = sp.type;
			fVals[0] = sp.fVals[0];
			fVals[1] = sp.fVals[1];
			fVals[2] = sp.fVals[2];
			fVals[3] = sp.fVals[3];
			imageName = CloneWxString(sp.imageName);
			iVal = sp.iVal;
			return *this;
		}
		///Compare shader parameters
		bool IsSameAs(const ShaderParam & sp, int fieldsMask, bool withoutPaths)
		{
			//type and name
			if (fieldsMask & 1)
				if ((type != sp.type) || (name != sp.name))
					return false;
			//values
			if (fieldsMask & 2)
			{
				switch (type)
				{
				case BOOL:
				case INT:
					if (iVal != sp.iVal)
						return false;
					break;
				case FLOAT4:
					if (fVals[3] != sp.fVals[3])
						return false;
				case FLOAT3:
					if (fVals[2] != sp.fVals[2])
						return false;
				case FLOAT2:
					if (fVals[1] != sp.fVals[1])
						return false;
				case FLOAT1:
					if (fVals[0] != sp.fVals[0])
						return false;
					break;
				case SAMPLERCUBE:
				case SAMPLER:
				case SAMPLER3D:
					if (withoutPaths)
					{
						if (!TextParser::ComparePaths(imageName, sp.imageName, 3))
							return false;
					}
					else if (imageName != sp.imageName)
						return false;
					break;
				default:
					LOGERROR(wxString::Format(wxT("ShaderParam type %i not supported."), type));
					break;
				}
			}
			return true;
		}
		ShaderParam(const ShaderParam & sp) : name(sp.name), type(sp.type), imageName(CloneWxString(sp.imageName))
		{
			iVal = sp.iVal;
			for(size_t i = 0; i < 4; i++)
				fVals[i] = sp.fVals[i];
		}
		///Get copy of instance
		ShaderParam * Clone() const
		{
			return new ShaderParam(*this);
		}
		wxString ToString() const
		{
			wxString res(wxT("name: ")+name+wxString::Format(wxT(", type: %lu, value: "), (unsigned long)type));
			switch (type)
			{
			case BOOL:
				res+=((iVal!=0)?wxT("1"):wxT("0"));
				break;
			case INT:
				res+=wxString::Format(wxT("%i"), iVal);
				break;
			case FLOAT1:
			case FLOAT2:
			case FLOAT3:
			case FLOAT4:
				res+=TextWriter::WriteToWxString(fVals, type-1, wxT(", "));
				break;
			case SAMPLERCUBE:
			case SAMPLER:
			case SAMPLER3D:
				res+=imageName;
				break;
			default:
				res+=wxT("unknown type");
				break;
			}
			return res;
		}
	};

	///Shader program data
	struct ShaderProgram
	{
	public:
		enum DEFAULT_PARAMS
		{
			LIGHT_COUNT
		};
	private:
		void addDefaultParams()
		{
			ShaderParam lightCountParam;
			lightCountParam.name = wxT("enabledLightCount");
			lightCountParam.type = ShaderParam::INT;
			paramList.push_back(lightCountParam);
		}
	public:
		///Shader language type
		enum SHADER_LANGUAGE
		{
			LANG_GLSL,    ///<GLSL language
			LANG_OPENRT,  ///<OPENRT shader
			LANG_CGFX,    ///<GLSL language
			LANG_NONE     ///<Invalid shader language
		} type;
		///Vertex shader source
		wxString vsSource;
		///Fragment shader source
		wxString fsSource;
		///Geometry shader source
		wxString gsSource;
		///Vertex shader file
		wxString vsFileName;
		///Fragment shader file
		wxString fsFileName;
		///Geometry shader file
		wxString gsFileName;
		///Shader parameter list
		std::vector<ShaderParam> paramList;

		///Constructor
		ShaderProgram() : type(LANG_NONE)   { addDefaultParams(); }
		///Disallow implicit conversions from bool (and integers, pointers etc.) to ShaderProgram
		explicit ShaderProgram(bool bAddDefaultParams) : type(LANG_NONE)   { if (bAddDefaultParams) { addDefaultParams(); } }
		///Copy constructor
		ShaderProgram(const ShaderProgram & sp)
			: type(sp.type), vsSource(CloneWxString(sp.vsSource)), fsSource(CloneWxString(sp.fsSource)), gsSource(CloneWxString(sp.gsSource)), vsFileName(CloneWxString(sp.vsFileName)), fsFileName(CloneWxString(sp.fsFileName)), gsFileName(CloneWxString(sp.gsFileName))
		{
			for(auto &param : sp.paramList)
				paramList.push_back(param);
			//paramList.assign(sp.paramList.begin(), sp.paramList.end());
		}
		///Get copy of instance
		ShaderProgram * Clone() const
		{
			return new ShaderProgram(*this);
		}
		bool IsDefaultParam(const ShaderParam &param)
		{
			return (param.name == wxT("enabledLightCount"));
		}
		///Compare shaders
		bool IsSameAs(const ShaderProgram & sp, int fieldsMask, bool withoutPaths)
		{
			//type and source
			if (fieldsMask & 1)
				if ((type != sp.type) || (vsSource != sp.vsSource) || (fsSource != sp.fsSource) || (gsSource != sp.gsSource))
					return false;
			//parameters and values
			if (fieldsMask & 2)
			{
				if (paramList.size() != sp.paramList.size())
					return false;
				//TODO: parameters can be in different order...
				for (size_t i=0; i<paramList.size(); i++)
				{
					if (!paramList[i].IsSameAs(sp.paramList[i], 3, withoutPaths))
						return false;
				}
			}
			return true;
		}
		///Get string with shader's properties
		wxString ToString() const
		{
			wxString params;
			for (auto &param : paramList)
				params+=wxT("\n")+param.ToString();
			wxString stype;
			switch (type)
			{
			case LANG_GLSL:
				stype = wxT("LANG_GLSL");		break;
			case LANG_OPENRT:
				stype = wxT("LANG_OPENRT");		break;
			case LANG_CGFX:
				stype = wxT("LANG_CGFX");		break;
			case LANG_NONE:
			default:
				stype = wxT("LANG_NONE");		break;
			};
			return wxT("type: ") + stype +
				wxT("\nfsSource: ") + vsSource +
				wxT("\nfsSource: ") + fsSource +
				wxT("\ngsSource: ") + gsSource +
				wxT("\nvsFileName: ") + vsFileName +
				wxT(", fsFileName: ") + fsFileName +
				wxT(", gsFileName: ") + gsFileName +
				wxT(", paramList:") + params;
		}
	};

#ifndef VRUT_HPC
	///Shader param IDs
	struct ShaderParamID
	{
		GLuint paramLocation;
		GLuint textureID;
		ShaderParamID(GLint pLoc = -1, GLuint tex = GL_ID_NONE)
		{
			paramLocation = pLoc;
			textureID = tex;
		}
	};

	///Shader handlers
	struct ShaderHandler
	{
		GLhandleARB program;
		GLhandleARB fragmentShader;
		GLhandleARB vertexShader;
		GLhandleARB geometryShader;
		std::vector<ShaderParamID> paramList;
		ShaderHandler() : program(GL_ID_NONE), fragmentShader(GL_ID_NONE), vertexShader(GL_ID_NONE), geometryShader(GL_ID_NONE)  {}
		ShaderHandler & operator=(const ShaderHandler &handler)
		{
			program        = handler.program,
			fragmentShader = handler.fragmentShader;
			vertexShader   = handler.vertexShader;
			geometryShader = handler.geometryShader;
			paramList	   = handler.paramList;
			return *this;
		}
	};
#endif //VRUT_HPC
}

#endif
