#ifndef _MGGLSLPROGRAM_H
#define _MGGLSLPROGRAM_H

class mgStaticGLAttrib;
class MGColor;
class mgGLSLProgram;

#include "mg/MGCL.h"
#include <string>
#include <glm/glm.hpp>
#include <iostream>
/** @file */

/** @addtogroup DisplayHandling
 *  @{
 */

namespace mgGLSL{

// function ID
/// 描画タイプ(FuncTypeがstandardのときのみ有効)
/// Primitive:通常のプリミティブ
/// Texture:テクスチャ描画
/// gridTexture:点のスプライト描画
typedef enum {
	Primitive = 0,
	Texture = 11,
	gridTexture=12,
} DrawType;

/// 描画タイプ
/// standard:通常描画
/// Select:Select処理用描画
/// Analysis:解析表示用描画
typedef enum {
	standard=0,
	Select = 1,
	Analysis = 5,
} FuncType;

/// 座標変換タイプ(Vertexの座標系)
/// World:ワールド座標系
/// NdcScreen:NDC空間座標系
/// AnchorPoint:アンカーポイントからの相対距離(アンカーポイントはワールド座標系で指定)
/// AnchorPointScreen:アンカーポイントからの相対距離(アンカーポイントはNDC座標系で指定)
typedef enum {
	World = 0,
	NdcScreen=2,
	AnchorPoint = 3,
	AnchorPointScreen = 4,
}CoordinateType;

/// 照光処理
/// NoShading:照光なし
/// Shading:照光あり
typedef enum {
	NoShading=0,
	Shading=1,
} ShadeMode;

/// ゼブラ処理タイプ
/// ZebraVert:垂直方向縞
/// ZebraHorizon:水平方向縞
typedef enum {
	ZebraVert = 0,
	ZebraHorizon = 1,
}ZebraType;


///Set static color as selection name.
MG_DLL_DECLR void setColorAsSelectionName(unsigned name);

///change error code to string and print it.
MG_DLL_DECLR void printOpenGLError(int errorCode);

/// Returns 1 if an OpenGL error occurred, 0 otherwise.
MG_DLL_DECLR int checkForOpenGLError(const char*, int);
MG_DLL_DECLR void dumpGLInfo(bool dumpExtensions = false);

// get LightEnable (Shade On/Off)
MG_DLL_DECLR bool LightEnabled();

MG_DLL_DECLR void CALLBACK debugCallback(GLenum source,
                                           GLenum type,
                                           GLuint id,
                                           GLenum severity,
                                           GLsizei length,
                                           const GLchar* message,
                                           void* userParam);

MG_DLL_DECLR void initializeStaticGLAttribStack();
MG_DLL_DECLR std::stack<mgStaticGLAttrib>&  getStaticGLAttribStack();
MG_DLL_DECLR mgStaticGLAttrib& getCurrentStaticGLAttrib();
MG_DLL_DECLR void execStaticGLAttrib(const mgStaticGLAttrib& attrib);
MG_DLL_DECLR void execStaticColorAttrib(const MGColor& color);
MG_DLL_DECLR void execStaticColorAttrib(const float color[4]);
MG_DLL_DECLR void execStaticLineWidth(float lineWidth);
MG_DLL_DECLR void execStaticLineStipple(short int factor, GLuint pattern);
MG_DLL_DECLR void execLightMode(// set Light(Shading On/Off)
	int mode/// <0: undefined, =0:Light is disabled, >0:Light is enabled.
);

MG_DLL_DECLR void pushStaticGLAttrib();
MG_DLL_DECLR void popStaticGLAttrib();

};


///Utility class to invoke glsl's setFuncType.

///mgFuncTypeSwitcher saves the current function type and invoke input function type.
///When mgFuncTypeSwitcher is destructed, the saved original type is restored.
class MG_DLL_DECLR mgFuncTypeSwitcher{
public:
	mgFuncTypeSwitcher(mgGLSL::FuncType type);

	~mgFuncTypeSwitcher();

private:
	mgGLSLProgram* m_pGLSL;//The current glsl program is save.
	int m_orgFuncType;//The original function type is save.
};


///mgGLSLProgramはOpenGL Shader Programをcompile , linkしてそのuniform変数の管理を行います.

///利用方法：
///１、mgGLSLProgramオブジェクトの作成
///２、compileShaderFromFile、またはcompileShaderFromStringで必要なshaderを
///    コンパイル
///３、必要であればbindAttribLocation(), bindFragDataLocation()を呼んだ後、link()
///３、use()によりshaderプログラムを選択し、setUniform()によりuniform変数に値をセット
///
///***** MGCLではshaderに対して次のlocationと変数名を予約している：
///locationに対してこれらの値をは使用してはならない。
/// また、これらの変数はすべてのvertex shaderに下記のように宣言されなければならない。
///uniform int functionID;//Function ID of the shaders:
///   0: standard(no shading functions);
///  11: Texture that uses texture1 sampler2D.(MGPlaneImage Texture uses this one.)
///	
/// uniform mat4 modelViewProjMatrix;//=projMatrix*modelViewMatrix.
/// uniform mat3 normalMatrix;
/// uniform mat4 modelViewMatrix;
/// uniform mat4 projMatrix;
/// uniform sampler2D texture1;
///
/// layout(location = 0) in vec3 vPosition;
/// layout(location = 1) in vec4 vColor;
/// layout(location = 2) in vec3 vNormal;
/// layout(location = 3) in vec2 vTextureCoord;
class MG_DLL_DECLR mgGLSLProgram{
public:

	/// CreateShaderのタイプ列挙子
     typedef enum{
        VERTEX, FRAGMENT, GEOMETRY,
        TESS_CONTROL, TESS_EVALUATION
    } GLSLShaderType;

	//Vertex attrib変数定義用
	typedef enum {
		vPosition=0,
		vColor,
		vNormal,
		vTextureCoord
	} VertexAttribId;

	union SELECT_NAME{
		unsigned uiName;
		unsigned short usName[2];
		unsigned char ucName[4];//
	};

	mgGLSLProgram();
	 ~mgGLSLProgram();

	///compileがうまく行われたときはtrueを、失敗したときはfalseを返します
    bool compileShaderFromFile(const char* fileName, GLSLShaderType type);

    ///compileがうまく行われたときはtrueを、失敗したときはfalseを返します
	bool compileShaderFromString(const std::string& source, GLSLShaderType type);

	///Delete this program and initialize this.
	void freeProgram();

    ///linkがうまく行われたときはtrueを、失敗したときはfalseを返します
	bool link();

	///このmgGLSLProgramの利用を開始します。
    void use();

	///エラーが起こった時のログ内容を文字で求めます
    std::string& log();

	///Programハンドルを返します
    int getHandle();

	///Link済か否かを尋ねる
    bool isLinked();

	///nameに割り当てるlocationを指定する。locationはこの後のlink()で有効となる。
    void bindAttribLocation( GLuint location, const char * name);

	///Current mgGLSLProgramを求める。
	static void setCurrentGLSLProgram(mgGLSLProgram* glsl);

	///Current mgGLSLProgramを求める。
	static mgGLSLProgram* getCurrentGLSLProgram();

	///nameに割り当てるlocationを指定する。locationはこの後のlink()で有効となる。
	void bindFragDataLocation( GLuint location, const char * name );

	///規定のvetex attrib のlocationを求める。
	int getvPositionLocation()const;///vPosition
	int getvColorLocation()const;///vColor
	int getvNormalLocation()const;///vNormal
	int getvTextureCoordLocation()const;///vTexture

	// Uniform変数まわり
	GLint getUniformLocation(const char* name)const;
	GLint getUniformLocation(const std::string& name)const{
		return getUniformLocation(name.c_str());
	};

	// Uniformに値をセットする関数群。locが負の値のときはAssertを吐きます。
	// locのチェックを行ったうえでこれらの関数を呼び出してください。
    void setUniform( GLint loc, float x, float y, float z);
    void setUniform( GLint loc, const glm::vec3& v);
    void setUniform( GLint loc, const glm::vec4& v);
    void setUniform( GLint loc, const glm::mat4& m);
    void setUniform( GLint loc, const glm::mat3& m);
    void setUniform( GLint loc, float val);
    void setUniform( GLint loc, int val);
    void setUniform( GLint loc, bool val);

public:
    void printActiveUniforms();
    void printActiveAttribs();


	void EnableLights(bool bEnabled = true);
	bool LightEnabled();


	static const size_t MAX_ATTRIB_LOC = 20;
	//Last name of UNIFORM_ATTRIB_ID must be less than MAX_ATTRIB_LOC.
	typedef enum{
		modelViewProjMatrix=0,	// mat4
		modelViewMatrix,		// mat4
		projMatrix,				// mat4
		normalMatrix,			// mat3 

		ndcMarix,				// mat4
		ndcScaleMatrix,			// mat3
		dpiFactor,				// float

		FuncType,               // draw function(Draw/Select/Zebra Analysis) FuncTypeでEnum定義
		DrawType,				// func typeがDrawのときのみ有効 DrawTypeでEnum定義
		ShaderMode,				// int 0:NoShading 1:Shading

		CoordinateType,			// CoordinateType (World/NDC/AnchorPoint/AnchorPointScreen) CoordinateTypeでEnum定義

		texture2D,				// texture.
		pointSize,				// Vertexの表示サイズ。カーソルなどのPOINT_SPLITE表示に使う

		anchorPoint,			// AnchorPoint。Billboard表示(拡大。縮小してもサイズが変わらない描画)の基準点

		LightTwoSides,			// boolean True:TwoSide, False:片面
		ForceLight,				// falseのときは、有効なLightが無い場合は、NO_SHADINGモードで描画。

		ZebraAxis,				// ゼブラ方向 ZebraTypeで指定
		ZebraSize,				// ゼブラの幅。 ゼブラのステップ刻みは step(0.5f, fract(1.0f / ZebraSize * atan(r.y, r.x)));
	}UniformName;

    void setUniform( UniformName name, float x, float y, float z);
    void setUniform( UniformName name, const glm::vec3& v);
    void setUniform( UniformName name, const glm::vec4& v);
    void setUniform( UniformName name, const glm::mat4& m);
    void setUniform( UniformName name, const glm::mat3& m);
    void setUniform( UniformName name, float val);
    void setUniform( UniformName name, int val);
    void setUniform( UniformName name, bool val);

	// Lightまわり
	static const GLint LIGHT_NUM = 10;
	static const GLint PROP_NUM = 11;

	typedef enum {
		isEnabled=0,	// bool
		ambientColor,	// vec4 ambient Color
		diffuseColor,	// vec4 diffulse color
		specularColor,	// vec4 specular color
		position,		// vec4 position
		spotDirection,	// vec3
		spotExponent,	// float
		spotCutoff,		// float
		constantAttenuation,	// float
		linearAttenuation,		// float
		quadraticAttenuation	// float
	}LightProps;

    void setUniformLights( GLint lightNo, LightProps name, float x, float y, float z);
    void setUniformLights( GLint lightNo, LightProps name, const glm::vec3& v);
    void setUniformLights( GLint lightNo, LightProps name, const glm::vec4& v);
    void setUniformLights( GLint lightNo, LightProps name, const glm::mat4& m);
    void setUniformLights( GLint lightNo, LightProps name, const glm::mat3& m);
    void setUniformLights( GLint lightNo, LightProps name, float val);
    void setUniformLights( GLint lightNo, LightProps name, int val);
    void setUniformLights( GLint lightNo, LightProps name, bool val);

	void setOpenGLVersion();///Get version by glGetInteger(), and set the info
		///in this program.

	void getOpenGLVerion(GLint& major, GLint& minor)const;

	void printPrjMatrix();

	void printLightProps();

	void printLightProps(GLint lightNo);
	
	void setFuncType(mgGLSL::FuncType type);
	int getFuncType();

private:
    int  m_handle;///<Shader program handler.
	GLint m_VersionMajor, m_VersionMinor;
    bool m_linked;
    std::string m_logString;
	static mgGLSLProgram* m_CurrrentGLSL;
	
	GLint m_uniform_locations[MAX_ATTRIB_LOC];

	GLint m_LightPropsIds[LIGHT_NUM][PROP_NUM];

	//m_uniform_locations[]に値をセットする(link()内の処理）
	void build_attribLocations();

	//m_uniform_locationsLights[]に値をセットする(ling()内の処理）
	void build_attribLocationsLight();

    int  getAttribLocation(const char* name)const;
	bool fileExists(const std::string& fileName)const;
};

/** @} */ // end of DisplayHandling group

#endif // _MGGLSLPROGRAM_H
