/*
 *  psychlops_g_shader_Win32GL.cpp
 *  Psychlops Standard Library (Universal)
 *
 *  Last Modified 2009/12/14 by Kenchi HOSOKAWA
 *  (C) 2009 Kenchi HOSOKAWA, Kazushi MARUYA and Takao SATO
 */

#define PSYCHLOPS_SHADER_PLATFORM
#include "../../../platform/psychlops_platform_selector.h"

#include "psychlops_g_shader_gl.h"



namespace Psychlops {

	static char *shader_const_source[16];
	static int shader_const_source_n;

	const char* ShaderAPI::shader_core_pix =
	"void pix(in float r, in float g, in float b, in float a) { gl_FragColor = vec4(floor(255.0*r+0.5)/255.0,floor(255.0*g+0.5)/255.0,floor(255.0*b+0.5)/255.0,a); }"
	;
	const char* ShaderAPI::shader_core_pix_bitsmono =
//	"vec4 toBitsMono(in float l) { int w = int(l*65535.0); return vec4( floor(l*255.996094)/255.0, fract(l*255.996094), 0.0, 1.0  ); }"
	"vec4 toBitsMono(in float l) { return vec4( floor(l*255.0)/255.0, fract(l*255.0), 0.0, 1.0  ); }"
	"void pix(in float l, in float g, in float b, in float a) { gl_FragColor = toBitsMono(l); }"
	;
	const char* ShaderAPI::shader_core_pix_bitsmono_bitshift =
//	"vec4 toBitsMono(in float l) { int w = int(l*65535.0); return vec4( w && 65280 >> 8 , w && 255, 0.0, 1.0  ); }"
	"void pix(in float l) { gl_FragColor = toBitsMono(l); }"
	;
	const char* ShaderAPI::shader_core_pix_bitscolor =
	"float xp() { return  gl_FragCoord[0]-gl_TexCoord[3][0]; }"
	"float yp() { return -gl_FragCoord[1]-gl_TexCoord[3][1]; }"
	"float toBitsH(in float v) { return floor(v*255.0)/255.0; }"
	"float toBitsL(in float v) { return fract(v*255.0); }"
	"void pix(in float r, in float g, in float b, in float a) { if(mod(gl_FragCoord[0],2.0)<1.0){ gl_FragColor = vec4(toBitsH(r), toBitsH(g), toBitsH(b), 1.0);} else { gl_FragColor = vec4(toBitsL(r), toBitsL(g), toBitsL(b), 1.0); }"
	;

	const char* ShaderAPI::shader_core_getpix =
	"vec4 getPix() { return texture2D(texture0, vec2(gl_TexCoord[0][0], gl_TexCoord[0][1]) ); }"
	//"vec4 getPix(in int x, in int y) { return texelFetch( texture0, ivec2( x, y ), 0 ); }"
	"vec4 getPix(in int x, in int y) { return texture2D( texture0, vec2( float(x)/float(SIZE_LOG.x), float(y)/float(SIZE_LOG.y)  ) ); }"
	"vec4 getPixOffset(in int x, in int y) { return texture2D( texture0, gl_TexCoord[0].xy + vec2( float(x)/float(SIZE_LOG.x), float(y)/float(SIZE_LOG.y)  ) ); }"
	;
	const char* ShaderAPI::shader_core_getpix_bitsmono =
	"vec4 getPix() { return texture2D(texture0, vec2(gl_TexCoord[0][0], gl_TexCoord[0][1]) ); }"
	//"vec4 getPix(in int x, in int y) { return texelFetch( texture0, ivec2( x, y ), 0 ); }"
	"vec4 getPix(in int x, in int y) { return texture2D( texture0, vec2( float(x)/float(SIZE_LOG.x), float(y)/float(SIZE_LOG.y)  ) ); }"
	"vec4 getPixOffset(in int x, in int y) { return texture2D( texture0, gl_TexCoord[0].xy + vec2( float(x)/float(SIZE_LOG.x), float(y)/float(SIZE_LOG.y)  ) ); }"
	//"vec4 toBitsMono(in float l) { int w = int(l*65535.0); return vec4( floor(l*255.996094)/255.0, fract(l*255.996094), 0.0, 1.0  ); }"
	//"void pix(in float l) { gl_FragColor = toBitsMono(l); }"
	;
	const char* ShaderAPI::shader_core_getpix_bitsmono_bitshift =
	"vec4 getPix() { vec4 c = texture2D(texture0, vec2(gl_TexCoord[0][0], gl_TexCoord[0][1]) ); double l = ((c.r*255.0)<<8) + (c.b*255.0); return vec4( l, l, l, 1.0 ): }"
	//"vec4 getPix(in int x, in int y) { return texelFetch( texture0, ivec2( x, y ), 0 ); }"
	"vec4 getPix(in int x, in int y) { return texture2D( texture0, vec2( float(x)/float(SIZE_LOG.x), float(y)/float(SIZE_LOG.y)  ) ); }"
	"vec4 getPixOffset(in int x, in int y) { return texture2D( texture0, gl_TexCoord[0].xy + vec2( float(x)/float(SIZE_LOG.x), float(y)/float(SIZE_LOG.y)  ) ); }"
	//"vec4 toBitsMono(in float l) { int w = int(l*65535.0); return vec4( w && 65280 >> 8 , w && 255, 0.0, 1.0  ); }"
	//"void pix(in float l) { gl_FragColor = toBitsMono(l); }"
	;

	const char* ShaderAPI::shader_base_funcs =
	"const float PI = 3.14159265358979323846264338327950288;"
	"const float E  = 2.718281828459045235360287471352;"
	"const int PSYCHLOPS_ARG_BASE = 4;"
	"float NormalDistibution(in float x, in float mu, in float sigma) { float xm = x-mu, sig22 = 2.0*sigma*sigma; return exp( -( xm*xm / sig22 ) ) / sqrt(PI*sig22); }"
	"float GaussianM0(in float x, in float sigma) { return exp( -(x*x) / (2.0*sigma*sigma) ); }"
//	"float argv(in int i) { return gl_TexCoord[i/4+PSYCHLOPS_ARG_BASE][i%4]; }"
//	"float argv(in int high, in int low) { return gl_TexCoord[high+PSYCHLOPS_ARG_BASE][low]; }"
	"const vec4 PSYCHLOPS_COLOR_ADJ = vec4(0.001953125,0.001953125,0.001953125,0.0);"
//	"void pix(in float r, in float g, in float b, in float a) { gl_FragColor = vec4(floor(255.0*r+0.5)/255.0,floor(255.0*g+0.5)/255.0,floor(255.0*b+0.5)/255.0,a); }"
//	"void pix(in float r, in float g, in float b, in float a) { gl_FragColor = vec4(round(255.0*r)/255.0,round(255.0*g)/255.0,round(255.0*b)/255.0,a); }"
//	"void pix(in float r, in float g, in float b, in float a) { gl_FragColor = round(255.0*vec4(r,g,b,a))/255.0; }"
//	"void pix(in float r, in float g, in float b, in float a) { gl_FragColor = vec4(r,g,b,a)+PSYCHLOPS_COLOR_ADJ; }"
	"void pix(in float r, in float g, in float b) { pix(r,g,b,1.0); }"
	"void pix(in float l, in float a) { pix(l,l,l,a); }"
	"void pix(in float l) { pix(l,l,l,1.0); }"
	"void pix(in vec4 c) { pix(c.r, c.g, c.b, c.a); }"
	"float width() { return gl_TexCoord[3][2]; }"
	"float height() { return gl_TexCoord[3][3]; }"
	;

	const char* ShaderAPI::shader_field_funcs_platform =
	"const vec4 field_origin = vec4(0.5,0.5,0.0,1.0);"
	"float xp() { return  gl_FragCoord[0]-gl_TexCoord[3][0]; }"
	"float yp() { return -gl_FragCoord[1]-gl_TexCoord[3][1]; }"
	"float rp() { return length(vec2(xp(),yp())); }"
	"float thetaf() { return atan(yp(), xp()); }"
	;

	const char* ShaderAPI::shader_texture_funcs_platform =
	"uniform sampler2D texture0;"
	"uniform ivec2 SIZE_REAL, SIZE_LOG;"
	"const vec4 field_origin = vec4(0.5,0.5,0.0,1.0);"
	"float xp() { return  gl_FragCoord[0]-gl_TexCoord[3][0]; }"
	"float yp() { return -gl_FragCoord[1]+gl_TexCoord[3][1]; }"
	"float rp() { return length(vec2(xp(),yp())); }"
	"float thetaf() { return atan(yp(), xp()); }"
	;

	const char* ShaderAPI::shader_texture_pix[2] = {
		""
		,
		""
	};

	void ShaderAPI::cache(const std::string &final_source, const std::vector<std::string> &vars, Drawable &target)
	{
		initialize();
		initField();

		const char *source = final_source.c_str();
		GLint length = final_source.length();
		char *logbuf;
		std::string errormsg;
		GLint logSize;

		GLint compiled, linked;
		fragShader = glCreateShader(GL_FRAGMENT_SHADER);
		glShaderSource(fragShader, 1, &source, &length);
		glCompileShader(fragShader);
		glGetShaderiv(fragShader, GL_COMPILE_STATUS, &compiled);
		if(compiled == GL_FALSE) {
			glGetShaderiv(fragShader, GL_INFO_LOG_LENGTH , &logSize);
			char *logbuf = new char[logSize+3];
			logbuf[logSize+2] = 0;
			if(logSize>1) {
				glGetShaderInfoLog(fragShader, logSize, &logSize, logbuf);
			}
			errormsg = logbuf;
			delete [] logbuf;
			throw new Exception(typeid(*this), "Shader compile failed", errormsg);
		}

		gl2Program = glCreateProgram();
		glAttachShader(gl2Program, fragShader);

		glLinkProgram(gl2Program);
		glGetProgramiv(gl2Program, GL_LINK_STATUS, &linked);
		if(linked == GL_FALSE) throw new Exception("Shader link failed");
	}
	void ShaderAPI::cacheField(const std::string &orig_source, const std::vector<std::string> &vars, Drawable &target)
	{
		std::string final_source = "";

		int mode = 0;
		if(Color::getCalibrationMode() == Color::BITS_MONO) mode = 3;
		switch(mode)
		{
		case 3:
			final_source.append(shader_core_pix_bitsmono);
//			final_source.append(shader_core_getpix_bitsmono);
			//final_source.append(shader_core_getpix_bitsmono_bitshift);
			//final_source.append(shader_core_pix_bitsmono_bitshift;
			break;
		default:
			final_source.append(shader_core_pix);
//			final_source.append(shader_core_getpix);
		}

		final_source.append(shader_base_funcs);
		final_source.append(shader_field_funcs_platform);
		final_source.append(orig_source);
		cache(final_source, vars, target);
	}
	void ShaderAPI::cacheTex(const std::string &orig_source, const std::vector<std::string> &vars, Drawable &target)
	{
		std::string final_source = "";
		int mode = 0;
		if(Color::getCalibrationMode() == Color::BITS_MONO) mode = 3;
		switch(mode)
		{
		case 3:
			final_source.append(shader_core_pix_bitsmono);
			final_source.append(shader_core_getpix_bitsmono);
			//final_source.append(shader_core_getpix_bitsmono_bitshift);
			//final_source.append(shader_core_pix_bitsmono_bitshift;
			break;
		default:
			final_source.append(shader_core_pix);
			final_source.append(shader_core_getpix);
		}

		final_source.append(shader_base_funcs);
		final_source.append(shader_texture_funcs_platform);
		final_source.append(orig_source);
		cache(final_source, vars, target);
		texture[0] =  glGetUniformLocation(gl2Program, "texture0");
		size     = glGetUniformLocation(gl2Program, "SIZE_REAL");
		size_log = glGetUniformLocation(gl2Program, "SIZE_LOG");
	}


	bool ShaderAPI::field_initialized = false;
	GLuint ShaderAPI::null_texture;
	const unsigned char ShaderAPI::NULL_TEX[16] = {0,0,0,0 ,0,0,0,0 ,0,0,0,0 ,0,0,0,0};
	void ShaderAPI::initField()
	{
		if(!field_initialized) {
			glEnable(GL_TEXTURE_2D);
			glGenTextures(1, &null_texture);
			glBindTexture(GL_TEXTURE_2D, null_texture);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
			glTexImage2D(GL_TEXTURE_2D, 0, 1, 4, 4, 0, GL_ALPHA, GL_UNSIGNED_BYTE, &NULL_TEX);
			glDisable(GL_TEXTURE_2D);
			field_initialized = true;
		}
	}
	void ShaderAPI::drawField(const Rectangle &rect, const double *arga, const int argn)
	{
		const double WID = rect.getWidth(), HEI = rect.getHeight();
		const double ADJPX = -0.125, ADJPY = -0.1255;
		const double PX = rect.getLeft()+WID/2+.5, PY = rect.getTop()+HEI/2-Display::getHeight()+.5;
		glUseProgram(gl2Program);
		int i=0;
		glEnable(GL_TEXTURE_2D);
		/*for(i=0; i<argn+1; i++) {
			glActiveTexture(GL_TEXTURE0+i);
			glBindTexture(GL_TEXTURE_2D, null_texture);
		}*/
		glBegin(GL_QUADS);
			glMultiTexCoord4d(GL_TEXTURE3, PX, PY, WID, HEI);
			//glMultiTexCoord4d(GL_TEXTURE0, -WID/2+ADJPX, -HEI/2+ADJPY, WID, HEI);
			for(i=0; i<argn; i++) glMultiTexCoord4dv(GL_TEXTURE4+i, arga+i*4);
			//for(i=0; i<argn; i++) glVertexAttrib1d(argv[i], *(arga+i));
				glVertex2f(rect.getLeft(),  rect.getTop());
			glMultiTexCoord4d(GL_TEXTURE3, PX, PY, WID, HEI);
			//glMultiTexCoord4d(GL_TEXTURE0,  WID/2+ADJPX, -HEI/2+ADJPY, WID, HEI);
			for(i=0; i<argn; i++) glMultiTexCoord4dv(GL_TEXTURE4+i, arga+i*4);
			//for(i=0; i<argn; i++) glVertexAttrib1d(argv[i], *(arga+i));
				glVertex2f(rect.getRight()+1, rect.getTop());
			glMultiTexCoord4d(GL_TEXTURE3, PX, PY, WID, HEI);
			//glMultiTexCoord4d(GL_TEXTURE0,  WID/2+ADJPX,  HEI/2+ADJPY, WID, HEI);
			for(i=0; i<argn; i++) glMultiTexCoord4dv(GL_TEXTURE4+i, arga+i*4);
			//for(i=0; i<argn; i++) glVertexAttrib1d(argv[i], *(arga+i));
				glVertex2f(rect.getRight()+1, rect.getBottom()+1);
			glMultiTexCoord4d(GL_TEXTURE3, PX, PY, WID, HEI);
			//glMultiTexCoord4d(GL_TEXTURE0, -WID/2+ADJPX,  HEI/2+ADJPY, WID, HEI);
			for(i=0; i<argn; i++) glMultiTexCoord4dv(GL_TEXTURE4+i, arga+i*4);
			//for(i=0; i<argn; i++) glVertexAttrib1d(argv[i], *(arga+i));
				glVertex2f(rect.getLeft(),  rect.getBottom()+1);
		glEnd();
		glActiveTexture(GL_TEXTURE0);
		glDisable(GL_TEXTURE_2D);
		glUseProgram(0);
	}
	void ShaderAPI::fieldToImage(Image &target, const Rectangle &rect, const double *argv, const int argn, Canvas &media)
	{
		//Rectangle rect2(rect.getWidth(), rect.getHeight());
		glDrawBuffer(GL_AUX0);
		glReadBuffer(GL_AUX0);
		media.rect(rect, Color::black);
		drawField(rect, argv, argn);
		media.to(target, rect);
		glDrawBuffer(GL_BACK);
		glReadBuffer(GL_BACK);
	}


	int ceil2n(int x) {
		 return pow(2, ceil(log((double)x)/log(2.0)));
	}
	void ShaderAPI::drawImage(Image &img, const double *arga, const int argn, Canvas &target)
	{
		if(img.caches.count(&target)!=0) {
			APIImageCache *api_ = img.caches[&target].id;
			double tex_top = 1.0;
			double tex_bottom = ((double)api_->tex_height-img.height_)/api_->tex_height;
			const double WID = img.targetarea_.getWidth(), HEI = img.targetarea_.getHeight();
			const double left = img.targetarea_.getLeft(), top = img.targetarea_.getTop(),
			             right = left+WID, bottom = top+HEI;
			//const double PX = left+WID/2+.5, PY =top+HEI/2-HEI+.5;
			const double PX = left+.5, PY = target.getHeight()-top+.5;
			int i;

			glUseProgram(gl2Program);
			glUniform1i(texture[0], 0);
			glUniform2i(size,     WID, HEI);
			glUniform2i(size_log, api_->tex_width, api_->tex_height);
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, api_->getTexIndex());
			glBegin(GL_QUADS);
				glMultiTexCoord2d(GL_TEXTURE0, 0, tex_top);
				glMultiTexCoord4d(GL_TEXTURE3, PX, PY, WID, HEI);
				for(i=0; i<argn; i++) glMultiTexCoord4dv(GL_TEXTURE4+i, arga+i*4);
					glVertex2f(left, top);
				glMultiTexCoord2d(GL_TEXTURE0, (double)img.width_/api_->tex_width , tex_top);
				glMultiTexCoord4d(GL_TEXTURE3, PX, PY, WID, HEI);
				for(i=0; i<argn; i++) glMultiTexCoord4dv(GL_TEXTURE4+i, arga+i*4);
					glVertex2f(right, top);
				glMultiTexCoord2d(GL_TEXTURE0, (double)img.width_/api_->tex_width , tex_bottom);
				glMultiTexCoord4d(GL_TEXTURE3, PX, PY, WID, HEI);
				for(i=0; i<argn; i++) glMultiTexCoord4dv(GL_TEXTURE4+i, arga+i*4);
					glVertex2f(right, bottom);
				glMultiTexCoord2d(GL_TEXTURE0, 0 , tex_bottom);
				glMultiTexCoord4d(GL_TEXTURE3, PX, PY, WID, HEI);
				for(i=0; i<argn; i++) glMultiTexCoord4dv(GL_TEXTURE4+i, arga+i*4);
					glVertex2f(left, bottom);
			glEnd();
			glActiveTexture(GL_TEXTURE0);
			glDisable(GL_TEXTURE_2D);
			glUseProgram(0);
		} else {
		}
	}
	void ShaderAPI::imageToImage(Image &target, Image &img, const double *argv, const int argn, Canvas &media)
	{
		glDrawBuffer(GL_AUX0);
		glReadBuffer(GL_AUX0);
		drawImage(img, argv, argn, media);
		media.to(target, Rectangle(img.getLeft(), img.getTop(), img.getRight(), img.getBottom()));
		glDrawBuffer(GL_BACK);
		glReadBuffer(GL_BACK);
	}


}	/*	<- namespace Psycholops 	*/
