/*  ZZ Open GL graphics plugin
 *  Copyright (c)2009 zeydlitz@gmail.com
 *  Based on Zerofrog's ZeroGS KOSMOS (c)2005-2006
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

#if defined(GLSL_API) && !defined(GLSL4_API) 		// This code is only for GLSL API
// ZZogl Shader manipulation functions.

/*
 * used cg calls:
 * cgGLIsProfileSupported		-- don't needed
 * cgGetErrorString 			-- later	
 * cgGetLastListing			-- later
 * cgSetErrorHandler			-- later	
 * cgCreateContext			-- think that don't need
 * cgGLEnableProfile			-- don't need
 * cgGLSetOptimalOptions		-- don't need?
 * cgGLSetManageTextureParameters	-- what's this?	
 * cgCreateParameter			-- don't need
 * cgGLLoadProgram			void LinkProgram(uint program)
 * cgGetError				-- later
 * cgGLDisableProfile			-- don't need
 * cgGLSetParameter4fv
 * cgGetNamedParameter
 * cgGLEnableTextureParameter
 * cgIsParameterUsed
 * cgGLBindProgram			void UseProgram(uint program)
 * cgConnectParameter
 * cgIsProgram				bool IsProgram(uint program)
 * cgCreateProgramFromFile
 */

//------------------- Includes
#include "Util.h"
#include "ZZoglShaders.h"
#include "zpipe.h"
#include <math.h>
#include <map>
#include  <fcntl.h>			// this for open(). Maybe linux-specific
#include <sys/mman.h>			// and this for mmap

// ----------------- Defines

#define TEXWRAP_REPEAT 0
#define TEXWRAP_CLAMP 1
#define TEXWRAP_REGION_REPEAT 2
#define TEXWRAP_REPEAT_CLAMP 3

#ifdef DEVBUILD
#	define UNIFORM_ERROR_LOG 	ZZLog::Error_Log
#else
#	define UNIFORM_ERROR_LOG
#endif

// Set it to 0 to diable context usage, 1 -- to enable. FFX-1 have a strange issue with ClampExt.
#define NOCONTEXT		0
#define NUMBER_OF_SAMPLERS 	11
#define MAX_SHADER_NAME_SIZE	25
#define MAX_UNIFORM_NAME_SIZE	20
#define DEFINE_STRING_SIZE	256
//------------------ Constants

// Used in a logarithmic Z-test, as (1-o(1))/log(MAX_U32).
const float g_filog32 = 0.999f / (32.0f * logf(2.0f));

const static char* g_pTexTypes[] = { "32", "tex32", "clut32", "tex32to16", "tex16to8h" };
const static char* g_pShaders[4] = { "full", "reduced", "accurate", "accurate-reduced" };

// ----------------- Global Variables 

ZZshContext	g_cgcontext;
ZZshProfile 	cgvProf, cgfProf;
int 		g_nPixelShaderVer = 0; 		// default
u8* 		s_lpShaderResources = NULL;
ZZshShaderLink 	pvs[16] = {sZero}, g_vsprog = sZero, g_psprog = sZero;							// 2 -- ZZ
ZZshParameter 	g_vparamPosXY[2] = {pZero}, g_fparamFogColor = pZero;

ZZshProgram	ZZshMainProgram;
char*		ZZshSource;			// Shader's source data.	
off_t		ZZshSourceSize;

extern char EFFECT_NAME[256];				// All this variables used for testing and set manually
extern char EFFECT_DIR[256];

bool g_bCRTCBilinear = true;

float4 g_vdepth, vlogz;
FRAGMENTSHADER ppsBitBlt[2], ppsBitBltDepth, ppsOne;
FRAGMENTSHADER ppsBaseTexture, ppsConvert16to32, ppsConvert32to16;
FRAGMENTSHADER ppsRegular[4], ppsTexture[NUM_SHADERS];
FRAGMENTSHADER ppsCRTC[2], /*ppsCRTC24[2],*/ ppsCRTCTarg[2];
VERTEXSHADER pvsStore[16];
VERTEXSHADER pvsBitBlt;

inline bool LoadEffects();
extern bool s_bWriteDepth;

struct SHADERHEADER
{
	unsigned int index, offset, size; // if highest bit of index is set, pixel shader
};
map<int, SHADERHEADER*> mapShaderResources;

// Debug variable, store name of the function that call the shader.
const char* ShaderCallerName = "";
const char* ShaderHandleName = "";

int NumActiveUniforms, NumGlobalUniforms;
ZZshParamInfo UniformsIndex[MAX_ACTIVE_UNIFORMS] = {qZero};
const char* ShaderNames[MAX_ACTIVE_SHADERS] = {""};
ZZshShaderType ShaderTypes[MAX_ACTIVE_SHADERS] = {ZZ_SH_NONE};

ZZshProgram CompiledPrograms[MAX_ACTIVE_SHADERS][MAX_ACTIVE_SHADERS] = {{0}};
const char* TextureUnits[NUMBER_OF_SAMPLERS] = 
	{"g_sMemory[0]", 	"g_sMemory[1]", 	"g_sSrcFinal", 		"g_sBitwiseANDX", 	"g_sBitwiseANDY",  "g_sInterlace", \
		"g_sCLUT", 		"g_sBlocks", 		"g_sBilinearBlocks", 		"g_sConv16to32", 	"g_sConv32to16"};
ZZshPARAMTYPE TextureTypes[NUMBER_OF_SAMPLERS] = 
	{ZZ_TEXTURE_RECT, 	ZZ_TEXTURE_RECT, 	ZZ_TEXTURE_RECT, 	ZZ_TEXTURE_RECT, 	ZZ_TEXTURE_RECT, 	ZZ_TEXTURE_RECT, \
		ZZ_TEXTURE_2D, 		ZZ_TEXTURE_2D, 		ZZ_TEXTURE_2D,			ZZ_TEXTURE_2D,		 ZZ_TEXTURE_3D} ;

//------------------ Code

inline int GET_SHADER_INDEX(int type, int texfilter, int texwrap, int fog, int writedepth, int testaem, int exactcolor, int context, int ps) {
	return type + texfilter*NUM_TYPES + NUM_FILTERS*NUM_TYPES*texwrap + NUM_TEXWRAPS*NUM_FILTERS*NUM_TYPES*(fog+2*writedepth+4*testaem+8*exactcolor+16*context+32*ps) ;
}

// Nothing need to be done.
bool ZZshCheckProfilesSupport() {	
	return true;
}

// Error handler. Setup in ZZogl_Create once.
void HandleCgError(ZZshContext ctx, ZZshError err, void* appdata)
{/*
	ZZLog::Error_Log("%s->%s: %s", ShaderCallerName, ShaderHandleName, cgGetErrorString(err));
	const char* listing = cgGetLastListing(g_cgcontext);
	if (listing != NULL) 
		ZZLog::Debug_Log("	last listing: %s", listing);
*/
}

float ZeroFloat4[4] = {0};

inline void SettleFloat(float* f, const float* v) {
	f[0] = v[0];
	f[1] = v[1];
	f[2] = v[2];
	f[3] = v[3];
}

inline ZZshParamInfo ParamInfo(const char* ShName, ZZshPARAMTYPE type, const float fvalue[], GLuint sampler, GLint texid, bool Constant, bool Settled) {
	ZZshParamInfo x;
	x.ShName = new char[MAX_UNIFORM_NAME_SIZE];
	x.ShName = ShName;
	x.type = type;
	SettleFloat(x.fvalue, fvalue);
	x.sampler = sampler;
	x.texid = texid;
	x.Constant = Constant;
	x.Settled = Settled;
	return x;
}

inline void SetGlobalUniform(ZZshParameter* param, const char* name) {
	*param = NumActiveUniforms;
	UniformsIndex[NumActiveUniforms] = ParamInfo(name, ZZ_FLOAT4, ZeroFloat4, -1, 0, false, false);
	NumActiveUniforms++;  
}

bool ZZshStartUsingShaders() {
	
	ZZLog::Error_Log("Creating effects.");
	B_G(LoadEffects(), return false);
	if (!glCreateShader) 
	{
		ZZLog::Error_Log("GLSL shaders is not supported, stop.");
		return false;
	}

	// create a sample shader
	clampInfo temp;
	memset(&temp, 0, sizeof(temp));
	temp.wms = 3; temp.wmt = 3;

	g_nPixelShaderVer = 0;//SHADER_ACCURATE;
	// test
	bool bFailed;
	FRAGMENTSHADER* pfrag = ZZshLoadShadeEffect(0, 1, 1, 1, 1, temp, 0, &bFailed);
	if( bFailed || pfrag == NULL ) {
		g_nPixelShaderVer = SHADER_ACCURATE|SHADER_REDUCED;

		pfrag = ZZshLoadShadeEffect(0, 0, 1, 1, 0, temp, 0, &bFailed);
		if( pfrag != NULL )
			glLinkProgram(pfrag->Shader);
		if( bFailed || pfrag == NULL || glGetError() != GL_NO_ERROR) {
			g_nPixelShaderVer = SHADER_REDUCED;
			ZZLog::Error_Log("Basic shader test failed.");
		}
	}
	ZZshMainProgram = glCreateProgram();
	NumActiveUniforms = 0;
	SetGlobalUniform(&g_fparamFogColor, "g_fFogColor"); 
	SetGlobalUniform(&g_vparamPosXY[0], "g_fPosXY[0]");
	SetGlobalUniform(&g_vparamPosXY[1], NOCONTEXT?"g_fPosXY[1]":"g_fPosXY[0]");
	NumGlobalUniforms = NumActiveUniforms;

	if (g_nPixelShaderVer & SHADER_REDUCED)
		conf.bilinear = 0;

	ZZLog::Error_Log("Creating extra effects.");
	B_G(ZZshLoadExtraEffects(), return false);

	ZZLog::Error_Log("Using %s shaders.", g_pShaders[g_nPixelShaderVer]);	

	return true;
}

// open shader file according to build target 
bool ZZshCreateOpenShadersFile() {
	std::string ShaderFileName("plugins/ps2hw.glsl");
	int ShaderFD = open(ShaderFileName.c_str(), O_RDONLY);
	struct stat sb;
	if ((ShaderFD == -1) || (fstat(ShaderFD, &sb) == -1)) {	
		// Each linux distributions have his rules for path so we give them the possibility to
		// change it with compilation flags. -- Gregory
#ifdef GLSL_SHADER_DIR_COMPILATION
#define xGLSL_SHADER_DIR_str(s) GLSL_SHADER_DIR_str(s)
#define GLSL_SHADER_DIR_str(s) #s
		ShaderFileName = string(xGLSL_SHADER_DIR_str(GLSL_SHADER_DIR_COMPILATION)) + "/ps2hw.glsl";
		ShaderFD = open(ShaderFileName.c_str(), O_RDONLY);
#endif
		if ((ShaderFD == -1) || (fstat(ShaderFD, &sb) == -1)) {	
			ZZLog::Error_Log("No source for %s: \n", ShaderFileName.c_str()); 
			return false;
		}
	}

	ZZshSourceSize = sb.st_size;
	ZZshSource = (char*)mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, ShaderFD, 0);		// This function directly maped file into memory. 
	ZZshSource[ ZZshSourceSize - 1] = 0;										// Made source null-terminated.

	close(ShaderFD);
	return true;
}

void ZZshExitCleaning() {
	munmap(ZZshSource, ZZshSourceSize);
}

// Disable CG
void ZZshGLDisableProfile() {			// This stop all other shader programs from running;
	glUseProgram(0);
}
//Enable CG
void ZZshGLEnableProfile() {
}
//-------------------------------------------------------------------------------------

// The same function for texture, also to cgGLEnable
void ZZshGLSetTextureParameter(ZZshParameter param, GLuint texobj, const char* name) {
	if (param > -1) {
//		ZZLog::Error_Log("Set texture parameter %s %d... Ok", name, texobj);
		UniformsIndex[param].texid = texobj;
		UniformsIndex[param].Settled = true;
	}
}

void ZZshGLSetTextureParameter(ZZshShaderLink prog, ZZshParameter param, GLuint texobj, const char* name) {
	if (param > -1) {
//		ZZLog::Error_Log("Set texture parameter %s %d... Ok", name, texobj);
		UniformsIndex[param].texid = texobj;
		UniformsIndex[param].Settled = true;
	}
}

// This is helper of cgGLSetParameter4fv, made for debug purpose.
// Name could be any string. We must use it on compilation time, because erroneus handler does not
// return name
void ZZshSetParameter4fv(ZZshShaderLink& prog, ZZshParameter param, const float* v, const char* name) {
	if (param > -1) {
//		ZZLog::Error_Log("Set float parameter %s %f, %f, %f, %f... Ok", name, v[0], v[1], v[2], v[3]);
		SettleFloat(UniformsIndex[param].fvalue, v);
		UniformsIndex[param].Settled = true;
	}
}
 
void ZZshSetParameter4fv(ZZshParameter param, const float* v, const char* name) {	
	if (param > -1) {
//		ZZLog::Error_Log("Set float parameter %s %f, %f, %f, %f... Ok", name, v[0], v[1], v[2], v[3]);
		SettleFloat(UniformsIndex[param].fvalue, v);	
		UniformsIndex[param].Settled = true;
	}
}

// The same stuff, but also with retry of param, name should be USED name of param for prog.
void ZZshSetParameter4fvWithRetry(ZZshParameter* param, ZZshShaderLink& prog, const float* v, const char* name) {
	if (param != NULL)
		ZZshSetParameter4fv(prog, *param, v, name);
}

// Used sometimes for color 1.
void ZZshDefaultOneColor( FRAGMENTSHADER& ptr ) {
//	return;	
	ShaderHandleName = "Set Default One colot";
	float4 v = float4 ( 1, 1, 1, 1 );
	ZZshSetParameter4fv(ptr.prog, ptr.sOneColor, v, "DegaultOne");
}
//-------------------------------------------------------------------------------------

const GLchar * EmptyVertex = "void main(void) {gl_Position = ftransform();}"; 
const GLchar * EmptyFragment = "void main(void) {gl_FragColor = gl_Color;}"; 

inline ZZshProgram UseEmptyProgram(const char* name, GLenum shaderType) {
	GLuint shader = glCreateShader(shaderType);
	if (shaderType == GL_VERTEX_SHADER)
		glShaderSource(shader, 1, &EmptyVertex, NULL); 
	else
		glShaderSource(shader, 1, &EmptyFragment, NULL); 

	glCompileShader(shader); 
	ZZshProgram prog = glCreateProgram(); 
	glAttachShader(prog, shader); 
	glLinkProgram(prog);
	if( !glIsProgram(prog) || glGetError() != GL_NO_ERROR ) { 
		ZZLog::Error_Log("Failed to load empty shader for %s:", name); 
		return -1; 
	} 
	ZZLog::Error_Log("Used Empty program for %s... Ok.",name);
	return prog;
}

ZZshShaderType ZZshGetShaderType(const char* name) {
	if (strncmp(name, "TextureFog", 10) == 0) return ZZ_SH_TEXTURE_FOG;
	if (strncmp(name, "Texture", 7) == 0) return ZZ_SH_TEXTURE;
	if (strncmp(name, "RegularFog", 10) == 0) return ZZ_SH_REGULAR_FOG;
	if (strncmp(name, "Regular", 7) == 0) return ZZ_SH_REGULAR;
	if (strncmp(name, "Zero", 4) == 0) return ZZ_SH_ZERO;
	return ZZ_SH_CRTC;
}

inline ZZshShader UseEmptyShader(const char* name, GLenum shaderType) {
	GLuint shader = glCreateShader(shaderType);
	if (shaderType == GL_VERTEX_SHADER)
		glShaderSource(shader, 1, &EmptyVertex, NULL); 
	else
		glShaderSource(shader, 1, &EmptyFragment, NULL); 

	glCompileShader(shader); 

	ShaderNames[shader] = name;
	ShaderTypes[shader] = ZZshGetShaderType(name);

	ZZLog::Error_Log("Used Empty shader for %s... Ok.",name);
	return shader;
}

inline bool GetCompilationLog(GLuint shader) {
	GLint CompileStatus;
	glGetShaderiv(shader, GL_COMPILE_STATUS, &CompileStatus);
	if (CompileStatus == GL_TRUE)
		return true;

	int* lenght, infologlength;
	glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infologlength);
	char* InfoLog = new char[infologlength];
	glGetShaderInfoLog(shader, infologlength, lenght, InfoLog);
	ZZLog::Error_Log("Compiling... %d:\t %s", shader, InfoLog);

	return false;
}

inline bool CompileShader(ZZshProgram& shader, const char* DefineString, const char* name, GLenum shaderType) {
	const GLchar* ShaderSource[2];
	ShaderSource[0] = (const GLchar*)DefineString;
	ShaderSource[1] = (const GLchar*)ZZshSource;

	shader = glCreateShader(shaderType);
	glShaderSource(shader, 2, &ShaderSource[0], NULL); 
	glCompileShader(shader); 
	ZZLog::Debug_Log("Creating shader %d for %s", shader, name);

	if (!GetCompilationLog(shader)) {
		ZZLog::Error_Log("Failed to compile shader for %s:", name); 
		return false;
	}

	ShaderTypes[shader] = ZZshGetShaderType(name);
	ShaderNames[shader] = name;

	GL_REPORT_ERRORD();
	return true;
}

inline bool LoadShaderFromFile(ZZshShader& shader, const char* DefineString, const char* name, GLenum ShaderType) {			// Linux specific, as I presume
	if (!CompileShader(shader, DefineString, name, ShaderType)) {
		ZZLog::Error_Log("Failed to compile shader for %s: ", name); 
	       	return false; 
	}

	ZZLog::Error_Log("Used shader for %s... Ok",name);
	return true;
}

inline bool GetLinkLog(ZZshProgram prog) {
	GLint LinkStatus;
	glGetProgramiv(prog, GL_LINK_STATUS, &LinkStatus);

	int unif, atrib;
	glGetProgramiv(prog, GL_ACTIVE_UNIFORMS, &unif);
	glGetProgramiv(prog, GL_ACTIVE_ATTRIBUTES, &atrib);
	UNIFORM_ERROR_LOG("Uniforms %d, attributes %d", unif, atrib);

	if (LinkStatus == GL_TRUE && glIsProgram(prog)) return true;

#if	defined(DEVBUILD) || defined(_DEBUG)
	int* lenght, infologlength;
	glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &infologlength);
	char* InfoLog = new char[infologlength];
	glGetProgramInfoLog(prog, infologlength, lenght, InfoLog);
	if (!infologlength == 0)
		ZZLog::Error_Log("Linking %d... %d:\t %s", prog, infologlength, InfoLog);
#endif

	return false;	

}

//-------------------------------------------------------------------------------------
inline ZZshProgram madeProgram(ZZshShader shader, ZZshShader shader2, char* name) {
	ZZshProgram prog = glCreateProgram();
	glAttachShader(prog, shader); 
	if (shader2 != 0)
		glAttachShader(prog, shader2); 
	glLinkProgram(prog);
	if (!GetLinkLog(prog)) { 
		ZZLog::Error_Log("Failed to link shader for %s: ", name); 
		prog = UseEmptyProgram(name, GL_FRAGMENT_SHADER);
	}
	glDetachShader(prog, shader); 

	ZZLog::Error_Log("Made shader program for %s... Ok",name);
	return prog;
}

static void PutParametersInProgam(int start, int finish) {
	for (int i = start; i < finish; i++) {
		ZZshParamInfo param = UniformsIndex[i];
		GLint location = glGetUniformLocation(ZZshMainProgram, param.ShName);

		if (location != -1 && param.type != ZZ_UNDEFINED) {
			UNIFORM_ERROR_LOG("\tTry uniform %d %d %d %s...\t\t", i, location, param.type, param.ShName);

			if (!param.Settled && !param.Constant) {
				UNIFORM_ERROR_LOG("\tUnsettled, non-constant uniform, could be bug: %d %s", param.type, param.ShName);
				continue;
			}

			if (param.type == ZZ_FLOAT4) {
				glUniform4fv(location, 1, param.fvalue);
			}
			else
			{
				// assert(param.texid != 0);
				// ZZLog::Error_Log("Set texture (%s) : %d with sampler %d\n", param.ShName, param.texid, param.sampler);
				// assert(param.sampler >= 0);
				glActiveTexture(GL_TEXTURE0 + param.sampler);
				if (param.type == ZZ_TEXTURE_2D)
					glBindTexture(GL_TEXTURE_2D, param.texid);
				else if (param.type == ZZ_TEXTURE_3D)
					glBindTexture(GL_TEXTURE_3D, param.texid);
				else
					glBindTexture(GL_TEXTURE_RECTANGLE, param.texid);
				GL_REPORT_ERRORD();
			}

			if (glGetError() == GL_NO_ERROR)
				UNIFORM_ERROR_LOG("Ok. Param name %s, location %d, type %d", param.ShName, location, param.type);
			else
				ZZLog::Error_Log("error in PutParametersInProgam param name %s, location %d, type %d", param.ShName, location, param.type);

			if (!param.Constant)								// Unset used parameters 
				UniformsIndex[i].Settled == false;
		}
		else if (start != 0 && location == -1 && param.Settled)					// No global variable
			ZZLog::Error_Log("Warning! Unused, but set uniform %d, %s", location, param.ShName);
	}
	GL_REPORT_ERRORD();
}

void PutSInProgam(int start, int finish) {
	for (int i = start; i < finish; i++) {
		ZZshParamInfo param = UniformsIndex[i];
		GLint location = glGetUniformLocation(ZZshMainProgram, param.ShName);

		if (location != -1 && param.type != ZZ_UNDEFINED) {
			if (param.type != ZZ_FLOAT4) {
				UNIFORM_ERROR_LOG("\tTry sampler %d %d %d %s %d...\t\t", i, location, param.type, param.ShName, param.sampler);
				if (glGetError() == GL_NO_ERROR)
					UNIFORM_ERROR_LOG("Ok");
				else
					UNIFORM_ERROR_LOG("error!");
				glUniform1i(location, param.sampler);
			}
		}
	}
	GL_REPORT_ERRORD();
}

static bool ValidateProgram(ZZshProgram Prog) {
	GLint isValid;
	glGetProgramiv(Prog, GL_VALIDATE_STATUS, &isValid);

	if (!isValid) {
		glValidateProgram(Prog);
		int* lenght, infologlength;
		glGetProgramiv(Prog, GL_INFO_LOG_LENGTH, &infologlength);
		char* InfoLog = new char[infologlength];
		glGetProgramInfoLog(Prog, infologlength, lenght, InfoLog);
		ZZLog::Error_Log("Validation %d... %d:\t %s", Prog, infologlength, InfoLog);
	}
	return (isValid != 0); 
}

static void PutParametersAndRun(VERTEXSHADER* vs, FRAGMENTSHADER* ps) {
	UNIFORM_ERROR_LOG("Run program %s(%d) \t+\t%s(%d)", ShaderNames[vs->Shader], vs->Shader, ShaderNames[ps->Shader], ps->Shader);

	glUseProgram(ZZshMainProgram);	
	if (glGetError() != GL_NO_ERROR) {
		ZZLog::Error_Log("Something weird happened on Linking stage.");

		glUseProgram(0);
		return;
	}

	PutSInProgam(vs->ParametersStart, vs->ParametersFinish);
	PutSInProgam(ps->ParametersStart, ps->ParametersFinish);

	PutParametersInProgam(0, NumGlobalUniforms);
	PutParametersInProgam(vs->ParametersStart, vs->ParametersFinish);
	PutParametersInProgam(ps->ParametersStart, ps->ParametersFinish);	

	ValidateProgram(ZZshMainProgram);
	GL_REPORT_ERRORD();
}

static void CreateNewProgram(VERTEXSHADER* vs, FRAGMENTSHADER* ps) {
	ZZLog::Error_Log("\n--->  New shader program %d, %s(%d) \t+\t%s(%d).", ZZshMainProgram, ShaderNames[vs->Shader], vs->Shader, ShaderNames[ps->Shader], ps->Shader);

	if (vs->Shader != 0)
		glAttachShader(ZZshMainProgram, vs->Shader);
	if (ps->Shader != 0)
		glAttachShader(ZZshMainProgram, ps->Shader);

	glLinkProgram(ZZshMainProgram);
	if (!GetLinkLog(ZZshMainProgram)) {
		ZZLog::Error_Log("Main program linkage error, don't use any shader for this stage.");
		return;
	}

	GL_REPORT_ERRORD();
}

inline bool ZZshCheckShaderCompatibility(VERTEXSHADER* vs, FRAGMENTSHADER* ps) { 
	if (vs == NULL) return false;
	if (vs->ShaderType == ZZ_SH_ZERO) return true;			// ZeroPS is compatible with everything
	if (ps == NULL) return false;

	return (vs->ShaderType == ps->ShaderType);
}

static void ZZshSetShader(VERTEXSHADER* vs, FRAGMENTSHADER* ps) {
	if (!ZZshCheckShaderCompatibility(vs, ps)) 				// We don't need to link uncompatible shaders
		return;	

	int vss = (vs!=NULL)?vs->Shader:0;
	int pss = (ps!=NULL)?ps->Shader:0;

	if (vss !=0 && pss != 0) {
		if (CompiledPrograms[vss][pss] != 0 && glIsProgram(CompiledPrograms[vss][pss])) {
			ZZshMainProgram = CompiledPrograms[vs->Shader][ps->Shader];
		}
		else {
			ZZshProgram NewProgram = glCreateProgram();
			ZZshMainProgram = NewProgram;
			CompiledPrograms[vss][pss] = NewProgram;
			CreateNewProgram(vs, ps) ;
		}

		PutParametersAndRun(vs, ps);	
		GL_REPORT_ERRORD();
	}
}

void ZZshSetVertexShader(ZZshShaderLink prog) {
	g_vsprog = prog;
	ZZshSetShader((VERTEXSHADER*)(g_vsprog.link), (FRAGMENTSHADER*)(g_psprog.link)) ;
}

void ZZshSetPixelShader(ZZshShaderLink prog) {
	g_psprog = prog;
	ZZshSetShader((VERTEXSHADER*)(g_vsprog.link), (FRAGMENTSHADER*)(g_psprog.link)) ;
}

//------------------------------------------------------------------------------------------------------------------

// For several reason texobj could not be put in sampler directly, only though GL_TEXTUREi interface. So we need to check correct sampler for each one.
inline void SettleTextureUnit(ZZshParamInfo* param, const char* name) {
	for (int i = 0; i < NUMBER_OF_SAMPLERS; i++) {
		if (strcmp(TextureUnits[i], name) == 0) {
			param->sampler = i;
			param->type = TextureTypes[i];
			return;
		}
	}
}

inline int SetUniformParam(ZZshProgram prog, ZZshParameter* param, const char* name) {
	GLint p = glGetUniformLocation(prog, name); 
	if (p > -1) { 
		*param = NumActiveUniforms;
		UniformsIndex[NumActiveUniforms] = ParamInfo(name, ZZ_FLOAT4, ZeroFloat4, -1, 0, false, false);		// By define Uniform is FLOAT4

		SettleTextureUnit(&(UniformsIndex[NumActiveUniforms]), name);
		UNIFORM_ERROR_LOG("uniform %s \t\t%d %d", name, p, UniformsIndex[NumActiveUniforms].type); 

		NumActiveUniforms++;  		
	}
	else *param = -1; 
	return p;
}

#define SET_UNIFORMPARAM(var, name) { \
	p = SetUniformParam(prog, &(pf->var), name); \
} 

#define INIT_SAMPLERPARAM(tex, name) { \
	ZZshParameter x; \
	p = SetUniformParam(prog, &x, name); \
	(UniformsIndex[x]).Constant = true; \
	ZZshGLSetTextureParameter(pf->prog, x, tex, name); \
}

#define INIT_UNIFORMPARAM(var, name) { \
	ZZshParameter x; \
	p = SetUniformParam(prog, &x, name); \
	(UniformsIndex[x]).Constant = true; \
	ZZshSetParameter4fv(pf->prog, x, var, name); \
} 

char* AddContextToName(const char* name, int context) {
	char* newname = new char[MAX_UNIFORM_NAME_SIZE];
	sprintf(newname, "%s[%d]", name, context * NOCONTEXT);
	return newname;
}

static void SetupFragmentProgramParameters(FRAGMENTSHADER* pf, int context, int type)
{
	// uniform parameters
	GLint p;
	pf->prog.link = (void*)pf;			// Setting autolink
	pf->prog.isFragment = true;			// Setting autolink
	pf->ShaderType = ShaderTypes[pf->Shader];

	pf->ParametersStart = NumActiveUniforms;
	ZZshProgram prog = madeProgram(pf->Shader, 0, "");
	glUseProgram(prog);
	GL_REPORT_ERRORD();

	SET_UNIFORMPARAM(sOneColor, "g_fOneColor");
	SET_UNIFORMPARAM(sBitBltZ, "g_fBitBltZ");
	SET_UNIFORMPARAM(sInvTexDims, "g_fInvTexDims");
	SET_UNIFORMPARAM(fTexAlpha2, AddContextToName("fTexAlpha2", context));
	SET_UNIFORMPARAM(fTexOffset, AddContextToName("g_fTexOffset", context));
	SET_UNIFORMPARAM(fTexDims, AddContextToName("g_fTexDims", context));
	SET_UNIFORMPARAM(fTexBlock, AddContextToName("g_fTexBlock", context));
	SET_UNIFORMPARAM(fClampExts, AddContextToName("g_fClampExts",  context)); 		// FIXME: There is a bug, that lead FFX-1 to incorrect CLAMP if this uniform have context.
	SET_UNIFORMPARAM(fTexWrapMode, AddContextToName("TexWrapMode", context));
	SET_UNIFORMPARAM(fRealTexDims, AddContextToName("g_fRealTexDims", context));
	SET_UNIFORMPARAM(fTestBlack, AddContextToName("g_fTestBlack", context));
	SET_UNIFORMPARAM(fPageOffset, AddContextToName("g_fPageOffset", context));
	SET_UNIFORMPARAM(fTexAlpha, AddContextToName("fTexAlpha", context));
	GL_REPORT_ERRORD();

	// textures
	INIT_SAMPLERPARAM(ptexBlocks, "g_sBlocks");
	if (type == 3) 
		{INIT_SAMPLERPARAM(ptexConv16to32, "g_sConv16to32");}
	else if (type == 4) 
		{INIT_SAMPLERPARAM(ptexConv32to16, "g_sConv32to16");}
	else 
		{INIT_SAMPLERPARAM(ptexBilinearBlocks, "g_sBilinearBlocks");}
	GL_REPORT_ERRORD();

	SET_UNIFORMPARAM(sMemory, AddContextToName("g_sMemory", context));
	SET_UNIFORMPARAM(sFinal, "g_sSrcFinal");
	SET_UNIFORMPARAM(sBitwiseANDX, "g_sBitwiseANDX");
	SET_UNIFORMPARAM(sBitwiseANDY, "g_sBitwiseANDY");
	SET_UNIFORMPARAM(sCLUT, "g_sCLUT");
	SET_UNIFORMPARAM(sInterlace, "g_sInterlace");
	GL_REPORT_ERRORD();

	// set global shader constants
	INIT_UNIFORMPARAM(float4(0.5f, (conf.settings().exact_color)?0.9f/256.0f:0.5f/256.0f, 0,1/255.0f), "g_fExactColor");
	INIT_UNIFORMPARAM(float4(-0.2f, -0.65f, 0.9f, 1.0f / 32767.0f ), "g_fBilinear");
	INIT_UNIFORMPARAM(float4(1.0f/256.0f, 1.0004f, 1, 0.5f), "g_fZBias");
	INIT_UNIFORMPARAM(float4(0,1, 0.001f, 0.5f), "g_fc0");
	INIT_UNIFORMPARAM(float4(1/1024.0f, 0.2f/1024.0f, 1/128.0f, 1/512.0f), "g_fMult");
	pf->ParametersFinish = NumActiveUniforms;
	if (NumActiveUniforms > MAX_ACTIVE_UNIFORMS)
		ZZLog::Error_Log("Too many shader variables. You may increase the limit in source %d.", NumActiveUniforms);
		
	glUseProgram(0);
	GL_REPORT_ERRORD();
}

void SetupVertexProgramParameters(VERTEXSHADER* pf, int context)
{
	GLint p;
	pf->prog.link = (void*)pf;			// Setting autolink
	pf->prog.isFragment = false;			// Setting autolink
	pf->ShaderType = ShaderTypes[pf->Shader];

	pf->ParametersStart = NumActiveUniforms;

	ZZshProgram prog = madeProgram(pf->Shader, 0, "");
	glUseProgram(prog);

	GL_REPORT_ERRORD();

	// Set Z-test, log or no log;
	if (conf.settings().no_logz) {
       		g_vdepth = float4( 255.0 /256.0f,  255.0/65536.0f, 255.0f/(65535.0f*256.0f), 1.0f/(65536.0f*65536.0f));
		vlogz = float4( 1.0f, 0.0f, 0.0f, 0.0f);
	}
	else {
		g_vdepth = float4( 256.0f*65536.0f, 65536.0f, 256.0f, 65536.0f*65536.0f);	
		vlogz = float4( 0.0f, 1.0f, 0.0f, 0.0f);
	}

	INIT_UNIFORMPARAM(g_vdepth, "g_fZ");
	if (p > -1) {
		INIT_UNIFORMPARAM(vlogz, "g_fZMin");
		if (p == -1)  ZZLog::Error_Log ("Shader file version is outdated! Only log-Z is possible.");
	}
	GL_REPORT_ERRORD();

	float4 vnorm = float4(g_filog32, 0, 0,0);
	INIT_UNIFORMPARAM(vnorm, "g_fZNorm");
	INIT_UNIFORMPARAM(float4(-0.2f, -0.65f, 0.9f, 1.0f / 32767.0f ),  "g_fBilinear");
	INIT_UNIFORMPARAM(float4(1.0f/256.0f, 1.0004f, 1, 0.5f),  "g_fZBias") ;
	INIT_UNIFORMPARAM(float4(0,1, 0.001f, 0.5f), "g_fc0");

	SET_UNIFORMPARAM(sBitBltPos, "g_fBitBltPos");
	SET_UNIFORMPARAM(sBitBltTex, "g_fBitBltTex");
	SET_UNIFORMPARAM(fBitBltTrans, "g_fBitBltTrans");
	pf->ParametersFinish = NumActiveUniforms;
	if (NumActiveUniforms > MAX_ACTIVE_UNIFORMS)
		ZZLog::Error_Log("Too many shader variables. You may increase the limit in the source.");

	glUseProgram(0);
	GL_REPORT_ERRORD();
}

const int GLSL_VERSION = 130;  			// Sampler2DRect appear in 1.3

// We use strictly compilation from source for GSLS
static __forceinline void GlslHeaderString(char* header_string, const char* name, const char* depth)
{
	sprintf(header_string, "#version %d\n#define %s main\n%s\n", GLSL_VERSION, name, depth);
}

static __forceinline bool LOAD_VS(char* DefineString, const char* name, VERTEXSHADER& vertex, int shaderver, ZZshProfile context, const char* depth)
{
	bool flag;
	char temp[200];
	GlslHeaderString(temp, name, depth);
	sprintf(DefineString, "%s#define VERTEX_SHADER 1\n#define CTX %d\n", temp, context * NOCONTEXT);
	//ZZLog::WriteLn("Define for VS == '%s'", DefineString);
	flag = LoadShaderFromFile(vertex.Shader, DefineString, name, GL_VERTEX_SHADER);
	SetupVertexProgramParameters(&vertex, context);
	return flag;
}

static __forceinline bool LOAD_PS(char* DefineString, const char* name, FRAGMENTSHADER& fragment, int shaderver, ZZshProfile context, const char* depth)
{
	bool flag;
	char temp[200];
	GlslHeaderString(temp, name, depth);
	sprintf(DefineString, "%s#define FRAGMENT_SHADER 1\n#define CTX %d\n", temp, context * NOCONTEXT);
	//ZZLog::WriteLn("Define for PS == '%s'", DefineString);
	flag = LoadShaderFromFile(fragment.Shader, DefineString, name, GL_FRAGMENT_SHADER);
	SetupFragmentProgramParameters(&fragment, context, 0); 
	return flag;
}

inline bool LoadEffects()
{
	// clear the textures
	for(u32 i = 0; i < ArraySize(ppsTexture); ++i) {
		SAFE_RELEASE_PROG(ppsTexture[i].prog);
	}

#ifndef _DEBUG
	memset(ppsTexture, 0, sizeof(ppsTexture));
#endif

	return true;
}

bool ZZshLoadExtraEffects() {
	bool bLoadSuccess = true;
	char DefineString[DEFINE_STRING_SIZE] = "";
	const char* writedepth = "#define WRITE_DEPTH 1\n";	// should we write depth field


	const char* pvsshaders[4] = { "RegularVS", "TextureVS", "RegularFogVS", "TextureFogVS" };

	for (int i = 0; i < 4; ++i) {
		if (!LOAD_VS(DefineString, pvsshaders[i], pvsStore[2 * i], cgvProf, 0, "")) bLoadSuccess = false;
		if (!LOAD_VS(DefineString, pvsshaders[i], pvsStore[2 *i + 1 ], cgvProf, 1, "")) bLoadSuccess = false;
		if (!LOAD_VS(DefineString, pvsshaders[i], pvsStore[2 *i + 8 ], cgvProf, 0, writedepth)) bLoadSuccess = false;
		if (!LOAD_VS(DefineString, pvsshaders[i], pvsStore[2 *i + 8 + 1], cgvProf, 1, writedepth)) bLoadSuccess = false;
	}
	for (int i = 0; i < 16; ++i) 
		pvs[i] = pvsStore[i].prog;	

	if (!LOAD_VS(DefineString, "BitBltVS", pvsBitBlt, cgvProf, 0, "")) bLoadSuccess = false;
	GLint p;
	GL_REPORT_ERRORD();

	if (!LOAD_PS(DefineString, "RegularPS", ppsRegular[0], cgfProf, 0, "")) bLoadSuccess = false;
	if (!LOAD_PS(DefineString, "RegularFogPS", ppsRegular[1], cgfProf, 0, "")) bLoadSuccess = false;

	if( conf.mrtdepth ) {
		if (!LOAD_PS(DefineString, "RegularPS", ppsRegular[2], cgfProf, 0, writedepth)) bLoadSuccess = false;
		if (!bLoadSuccess) conf.mrtdepth = 0;
		
		if (!LOAD_PS(DefineString, "RegularFogPS", ppsRegular[3], cgfProf, 0, writedepth)) bLoadSuccess = false;
		if (!bLoadSuccess) conf.mrtdepth = 0;
	}

	if (!LOAD_PS(DefineString, "BitBltPS", ppsBitBlt[0], cgfProf, 0, "")) bLoadSuccess = false;
	if (!LOAD_PS(DefineString, "BitBltAAPS", ppsBitBlt[1], cgfProf, 0, "")) bLoadSuccess = false;
	if (!bLoadSuccess) {
		ZZLog::Error_Log("Failed to load BitBltAAPS, using BitBltPS.");
		if (!LOAD_PS(DefineString, "BitBltPS", ppsBitBlt[1], cgfProf, 0, "")) bLoadSuccess = false;
	}

	if (!LOAD_PS(DefineString, "BitBltDepthPS", ppsBitBltDepth, cgfProf, 0, "")) bLoadSuccess = false;
	if (!LOAD_PS(DefineString, "CRTCTargPS", ppsCRTCTarg[0], cgfProf, 0, "")) bLoadSuccess = false;
	if (!LOAD_PS(DefineString, "CRTCTargInterPS", ppsCRTCTarg[1], cgfProf, 0, "")) bLoadSuccess = false;
	
	g_bCRTCBilinear = true;
	if (!LOAD_PS(DefineString, "CRTCPS", ppsCRTC[0], cgfProf, 0, "")) bLoadSuccess = false;
	if( !bLoadSuccess ) {
		// switch to simpler
		g_bCRTCBilinear = false;
		if (!LOAD_PS(DefineString, "CRTCPS_Nearest", ppsCRTC[0], cgfProf, 0, "")) bLoadSuccess = false;
		if (!LOAD_PS(DefineString, "CRTCInterPS_Nearest", ppsCRTC[0], cgfProf, 0, "")) bLoadSuccess = false;
	}
	else {
		if (!LOAD_PS(DefineString, "CRTCInterPS", ppsCRTC[1], cgfProf, 0, "")) bLoadSuccess = false;
	}

	if( !bLoadSuccess )
		ZZLog::Error_Log("Failed to create CRTC shaders.");
	
	// if (!LOAD_PS(DefineString, "CRTC24PS", ppsCRTC24[0], cgfProf, 0, "")) bLoadSuccess = false;
	// if (!LOAD_PS(DefineString, "CRTC24InterPS", ppsCRTC24[1], cgfProf, 0, "")) bLoadSuccess = false;
	if (!LOAD_PS(DefineString, "ZeroPS", ppsOne, cgfProf, 0, "")) bLoadSuccess = false;
	if (!LOAD_PS(DefineString, "BaseTexturePS", ppsBaseTexture, cgfProf, 0, "")) bLoadSuccess = false;
	if (!LOAD_PS(DefineString, "Convert16to32PS", ppsConvert16to32, cgfProf, 0, "")) bLoadSuccess = false;
	if (!LOAD_PS(DefineString, "Convert32to16PS", ppsConvert32to16, cgfProf, 0, "")) bLoadSuccess = false;

	GL_REPORT_ERRORD();
	return true;
}

const static char* g_pPsTexWrap[] = { "#define REPEAT 1\n", "#define CLAMP 1\n", "#define REGION_REPEAT 1\n", "" };

static ZZshShader LoadShaderFromType(const char* srcdir, const char* srcfile, int type, int texfilter, int texwrap, int fog, int writedepth, int testaem, int exactcolor, int ps, int context) {

	assert( texwrap < NUM_TEXWRAPS);
	assert( type < NUM_TYPES );
	//ZZLog::Error_Log("\n");

	ZZshProgram prog;

	char* name = new char[MAX_SHADER_NAME_SIZE];
	sprintf(name, "Texture%s%d_%sPS", fog?"Fog":"", texfilter, g_pTexTypes[type]);

	ZZLog::Debug_Log("Starting shader for %s", name);
	
	const char* AddWrap 	= g_pPsTexWrap[texwrap];
	const char* AddDepth 	= writedepth?"#define WRITE_DEPTH 1\n":"";
	const char* AddAEM	= testaem?"#define TEST_AEM 1\n":"";
	const char* AddExcolor	= exactcolor?"#define EXACT_COLOR 1\n":"";
	const char* AddAccurate  = (ps & SHADER_ACCURATE)?"#define ACCURATE_DECOMPRESSION 1\n":"";
	char DefineString[DEFINE_STRING_SIZE] = "";
	char temp[200];
	GlslHeaderString(temp, name, AddWrap);
	sprintf(DefineString, "%s#define FRAGMENT_SHADER 1\n%s%s%s%s\n#define CTX %d\n", temp, AddDepth, AddAEM, AddExcolor, AddAccurate, context * NOCONTEXT);

	ZZshShader shader;
	if (!CompileShader(shader, DefineString, name, GL_FRAGMENT_SHADER)) 
		return UseEmptyShader(name, GL_FRAGMENT_SHADER);

	ZZLog::Debug_Log("Used shader for type:%d filter:%d wrap:%d for:%d depth:%d aem:%d color:%d decompression:%d ctx:%d... Ok \n", type, texfilter, texwrap, fog, writedepth, testaem, exactcolor, ps, context);

	GL_REPORT_ERRORD();
	return shader;
}

FRAGMENTSHADER* ZZshLoadShadeEffect(int type, int texfilter, int fog, int testaem, int exactcolor, const clampInfo& clamp, int context, bool* pbFailed)
{
	int texwrap;
	
	assert( texfilter < NUM_FILTERS );
	//assert( g_nPixelShaderVer == SHADER_30 );
	if( clamp.wms == clamp.wmt ) {
		switch( clamp.wms ) {
			case 0: texwrap = TEXWRAP_REPEAT; break;
			case 1: texwrap = TEXWRAP_CLAMP; break;
			case 2: texwrap = TEXWRAP_CLAMP; break;
			default:
				texwrap = TEXWRAP_REGION_REPEAT; break;
		}
	}
	else if( clamp.wms==3||clamp.wmt==3)
		texwrap = TEXWRAP_REGION_REPEAT;
	else
		texwrap = TEXWRAP_REPEAT_CLAMP;

	int index = GET_SHADER_INDEX(type, texfilter, texwrap, fog, s_bWriteDepth, testaem, exactcolor, context, 0);

	if( pbFailed != NULL ) *pbFailed = false;

	FRAGMENTSHADER* pf = ppsTexture+index;

	if (ZZshExistProgram(pf)) 
	{
		return pf;
	}
	pf->Shader = LoadShaderFromType(EFFECT_DIR, EFFECT_NAME, type, texfilter, texwrap, fog, s_bWriteDepth, testaem, exactcolor, g_nPixelShaderVer, context);
	
	if (ZZshExistProgram(pf)) {
		SetupFragmentProgramParameters(pf, context, type);
		GL_REPORT_ERRORD();

		if( glGetError() != GL_NO_ERROR ) {
				ZZLog::Error_Log("Failed to load shader %d,%d,%d,%d.", type, fog, texfilter, 4*clamp.wms+clamp.wmt);
				if (pbFailed != NULL ) *pbFailed = true;
				return pf;
		}
		
		return pf;
	}

	ZZLog::Error_Log("Failed to create shader %d,%d,%d,%d.", type, fog, texfilter, 4*clamp.wms+clamp.wmt);
	if( pbFailed != NULL ) *pbFailed = true;
	
	GL_REPORT_ERRORD();
	return NULL;
}

#endif
