/*  ZZ Open GL graphics plugin
 *  Copyright on Zerofrog's ZeroGS KOSMOS (c)2005-2008
 *
 *  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
 */

#define _CRT_SECURE_NO_DEPRECATE

// Builds all possible shader files from ps2hw.fx and stores them in
// a preprocessed database
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <assert.h>

#ifdef _WIN32
#	include <zlib/zlib.h>
#else
#	include <zlib.h>
#endif

#include "zpipe.h"

#include <Cg/cg.h>
#include <Cg/cgGL.h>

#define SAFE_RELEASE(x) { if( (x) != NULL ) { (x)->Release(); x = NULL; } }

#include <map>
#include <vector>

using namespace std;

#include "zerogsshaders.h"

char* srcfilename = "ps2hw.fx";
char* dstfilename = "ps2hw.dat";

#ifndef ArraySize
#define ArraySize(x) (sizeof(x) / sizeof((x)[0]))
#endif

struct SHADERINFO
{
	int type;
	vector<char> buf;
};

map<int, SHADERINFO> mapShaders;
CGcontext g_cgcontext;

void LoadShader(int index, const char* pshader, CGprofile prof, vector<const char*>& vargs, int context)
{
	vector<const char*> realargs;
	realargs.reserve(16);
	realargs.resize(vargs.size());
	if( vargs.size() > 0 )
		memcpy(&realargs[0], &vargs[0], realargs.size() * sizeof(realargs[0]));
	realargs.push_back(context ? "-Ictx1" : "-Ictx0");
	realargs.push_back(NULL);

	CGprogram prog = cgCreateProgramFromFile(g_cgcontext, CG_SOURCE, srcfilename, prof, pshader, &realargs[0]);
	if( !cgIsProgram(prog) ) {
		printf("Failed to load shader %s: \n%s\n", pshader, cgGetLastListing(g_cgcontext));
		return;
	}

	if( mapShaders.find(index) != mapShaders.end() ) {
		printf("error: two shaders share the same index %d\n", index);
		exit(0);
	}

	if( !cgIsProgramCompiled(prog) )
		cgCompileProgram(prog);

	const char* pstr = cgGetProgramString(prog, CG_COMPILED_PROGRAM);

	const char* pprog = strstr(pstr, "#program");
	if( pprog == NULL ) {
		printf("program field not found!\n");
		return;
	}
	pprog += 9;
	const char* progend = strchr(pprog, '\r');
	if( progend == NULL ) progend = strchr(pprog, '\n');

	if( progend == NULL ) {
		printf("prog end not found!\n");
		return;
	}

	const char* defname = "main";

	SHADERINFO info;
	info.type = 0;
	info.buf.resize(strlen(pstr)+1);

	// change the program name to main
	memset(&info.buf[0], 0, info.buf.size());
	memcpy(&info.buf[0], pstr, pprog-pstr);
	memcpy(&info.buf[pprog-pstr], defname, 4);
	memcpy(&info.buf[pprog-pstr+4], progend, strlen(pstr)-(progend-pstr));

	if( mapShaders.find(index) != mapShaders.end() )
		printf("same shader\n");
	assert( mapShaders.find(index) == mapShaders.end() );
	mapShaders[index] = info;

	cgDestroyProgram(prog);
}

int main(int argc, char** argv)
{
	printf("usage: [src] [dst] [opts]\n");

	if( argc >= 2 ) srcfilename = argv[1];
	if( argc >= 3 ) dstfilename = argv[2];

	FILE* fsrc = fopen(srcfilename, "r");
	if( fsrc == NULL ) {
		printf("cannot open %s\n", srcfilename);
		return 0;
	}
	fclose(fsrc);

	g_cgcontext = cgCreateContext();
	if( !cgIsContext(g_cgcontext) ) {
		printf("failed to create cg context\n");
		return -1;
	}

	CGprofile cgvProf = CG_PROFILE_ARBVP1;
	CGprofile cgfProf = CG_PROFILE_ARBFP1;
	if( !cgGLIsProfileSupported(cgvProf) != CG_TRUE ) {
		printf("arbvp1 not supported\n");
		return 0;
	}
	if( !cgGLIsProfileSupported(cgfProf) != CG_TRUE ) {
		printf("arbfp1 not supported\n");
		return 0;
	}

	cgGLEnableProfile(cgvProf);
	cgGLEnableProfile(cgfProf);
	cgGLSetOptimalOptions(cgvProf);
	cgGLSetOptimalOptions(cgfProf);

	vector<const char*> vmacros;

	LoadShader(SH_BITBLTVS, "BitBltVS", cgvProf, vmacros, 0);
	LoadShader(SH_BITBLTPS, "BitBltPS", cgfProf, vmacros, 0);
	LoadShader(SH_BITBLTDEPTHPS, "BitBltDepthPS", cgfProf, vmacros, 0);
	LoadShader(SH_BITBLTDEPTHMRTPS, "BitBltDepthMRTPS", cgfProf, vmacros, 0);
	LoadShader(SH_CRTCTARGPS, "CRTCTargPS", cgfProf, vmacros, 0);
	LoadShader(SH_CRTCPS, "CRTCPS", cgfProf, vmacros, 0);
	LoadShader(SH_CRTC_NEARESTPS, "CRTCPS_Nearest", cgfProf, vmacros, 0);
	LoadShader(SH_CRTC24PS, "CRTC24PS", cgfProf, vmacros, 0);
	LoadShader(SH_ZEROPS, "ZeroPS", cgfProf, vmacros, 0);
	LoadShader(SH_BASETEXTUREPS, "BaseTexturePS", cgfProf, vmacros, 0);
	LoadShader(SH_BITBLTAAPS, "BitBltPS", cgfProf, vmacros, 0);
	LoadShader(SH_CRTCTARGINTERPS, "CRTCTargInterPS", cgfProf, vmacros, 0);
	LoadShader(SH_CRTCINTERPS, "CRTCInterPS", cgfProf, vmacros, 0);
	LoadShader(SH_CRTCINTER_NEARESTPS, "CRTCInterPS_Nearest", cgfProf, vmacros, 0);
	LoadShader(SH_CRTC24INTERPS, "CRTC24InterPS", cgfProf, vmacros, 0);
	LoadShader(SH_CONVERT16TO32PS, "Convert16to32PS", cgfProf, vmacros, 0);
	LoadShader(SH_CONVERT32TO16PS, "Convert32to16PS", cgfProf, vmacros, 0);

	const int vsshaders[4] = { SH_REGULARVS, SH_TEXTUREVS, SH_REGULARFOGVS, SH_TEXTUREFOGVS };
	const char* pvsshaders[4] = { "RegularVS", "TextureVS", "RegularFogVS", "TextureFogVS" };

	// load the texture shaders
	char str[255], strdir[255];

	strcpy(strdir, srcfilename);
	int i = (int)strlen(strdir);
	while(i > 0) {
		if( strdir[i-1] == '/' || strdir[i-1] == '\\' )
			break;
		--i;
	}

	strdir[i] = 0;

	for(i = 0; i < ArraySize(vsshaders); ++i) {
		for(int writedepth = 0; writedepth < 2; ++writedepth ) {

			if( writedepth ) vmacros.push_back("-DWRITE_DEPTH");
			LoadShader(vsshaders[i]|(writedepth?SH_WRITEDEPTH:0), pvsshaders[i], cgvProf, vmacros, 0);
			LoadShader(vsshaders[i]|(writedepth?SH_WRITEDEPTH:0)|SH_CONTEXT1, pvsshaders[i], cgvProf, vmacros, 1);
			if( writedepth ) vmacros.pop_back();
		}
	}

	const int psshaders[2] = { SH_REGULARPS, SH_REGULARFOGPS };
	const char* ppsshaders[2] = { "RegularPS", "RegularFogPS" };

	for(i = 0; i < ArraySize(psshaders); ++i) {
		for(int writedepth = 0; writedepth < 2; ++writedepth ) {
			if( writedepth ) vmacros.push_back("-DWRITE_DEPTH");
			LoadShader(psshaders[i]|(writedepth?SH_WRITEDEPTH:0), ppsshaders[i], cgfProf, vmacros, 0);
			if( writedepth ) vmacros.pop_back();
		}
	}

	printf("creating shaders, note that ctx0/ps2hw_ctx.fx, and ctx1/ps2hw_ctx.fx are required\n");
	vmacros.resize(0);

	for(int texwrap = 0; texwrap < NUM_TEXWRAPS; ++texwrap ) {

		if( g_pPsTexWrap[texwrap] != NULL )
			vmacros.push_back(g_pPsTexWrap[texwrap]);

		for(int context = 0; context < 2; ++context) {

			for(int texfilter = 0; texfilter < NUM_FILTERS; ++texfilter) {
				for(int fog = 0; fog < 2; ++fog ) {
					for(int writedepth = 0; writedepth < 2; ++writedepth ) {

						if( writedepth )
							vmacros.push_back("-DWRITE_DEPTH");

						for(int testaem = 0; testaem < 2; ++testaem ) {

							if( testaem )
								vmacros.push_back("-DTEST_AEM");

							for(int exactcolor = 0; exactcolor < 2; ++exactcolor ) {

								if( exactcolor )
									vmacros.push_back("-DEXACT_COLOR");

								// 32
								sprintf(str, "Texture%s%d_32PS", fog?"Fog":"", texfilter);

								vmacros.push_back("-DACCURATE_DECOMPRESSION");
								LoadShader(GET_SHADER_INDEX(0, texfilter, texwrap, fog, writedepth, testaem, exactcolor, context, SHADER_ACCURATE), str, cgfProf, vmacros, context);
								vmacros.pop_back();

								LoadShader(GET_SHADER_INDEX(0, texfilter, texwrap, fog, writedepth, testaem, exactcolor, context, 0), str, cgfProf, vmacros, context);

								if( texfilter == 0 ) {
									// tex32
									sprintf(str, "Texture%s%d_tex32PS", fog?"Fog":"", texfilter);

//									vmacros.push_back("-DACCURATE_DECOMPRESSION");
//									LoadShader(GET_SHADER_INDEX(1, texfilter, texwrap, fog, writedepth, testaem, exactcolor, context, SHADER_ACCURATE), str, cgfProf, vmacros, context);
//									vmacros.pop_back();

									LoadShader(GET_SHADER_INDEX(1, texfilter, texwrap, fog, writedepth, testaem, exactcolor, context, 0), str, cgfProf, vmacros, context);

									// clut32
									sprintf(str, "Texture%s%d_clut32PS", fog?"Fog":"", texfilter);

//									vmacros.push_back("-DACCURATE_DECOMPRESSION");
//									LoadShader(GET_SHADER_INDEX(2, texfilter, texwrap, fog, writedepth, testaem, exactcolor, context, SHADER_ACCURATE), str, cgfProf, vmacros, context);
//									vmacros.pop_back();

									LoadShader(GET_SHADER_INDEX(2, texfilter, texwrap, fog, writedepth, testaem, exactcolor, context, 0), str, cgfProf, vmacros, context);

									// tex32to16
									sprintf(str, "Texture%s%d_tex32to16PS", fog?"Fog":"", texfilter);

//									vmacros.push_back("-DACCURATE_DECOMPRESSION");
//									LoadShader(GET_SHADER_INDEX(3, texfilter, texwrap, fog, writedepth, testaem, exactcolor, context, SHADER_ACCURATE), str, cgfProf, vmacros, context);
//									vmacros.pop_back();

									LoadShader(GET_SHADER_INDEX(3, texfilter, texwrap, fog, writedepth, testaem, exactcolor, context, 0), str, cgfProf, vmacros, context);

									// tex16to8h
									sprintf(str, "Texture%s%d_tex16to8hPS", fog?"Fog":"", texfilter);

//									vmacros.push_back("-DACCURATE_DECOMPRESSION");
//									LoadShader(GET_SHADER_INDEX(4, texfilter, texwrap, fog, writedepth, testaem, exactcolor, context, SHADER_ACCURATE), str, cgfProf, vmacros, context);
//									vmacros.pop_back();

									LoadShader(GET_SHADER_INDEX(4, texfilter, texwrap, fog, writedepth, testaem, exactcolor, context, 0), str, cgfProf, vmacros, context);
								}

								if( exactcolor )
									vmacros.pop_back();
							}

							if( testaem )
								vmacros.pop_back();
						}

						if( writedepth )
							vmacros.pop_back();
					}
				}
			}
		}

		if( g_pPsTexWrap[texwrap] != NULL )
			vmacros.pop_back();
	}

	if( vmacros.size() != 0 )
		printf("error with macros!\n");

	// create the database

	int num = (int)mapShaders.size();

	// first compress
	vector<char> buffer;
	buffer.reserve(10000000); // 10mb
	buffer.resize(sizeof(SHADERHEADER)*num);

	i = 0;
	for(map<int, SHADERINFO>::iterator it = mapShaders.begin(); it != mapShaders.end(); ++it, ++i) {
		SHADERHEADER h;
		h.index = it->first | it->second.type;
		h.offset = (int)buffer.size();
		h.size = (int)it->second.buf.size();

		memcpy(&buffer[0] + i*sizeof(SHADERHEADER), &h, sizeof(SHADERHEADER));

		size_t cur = buffer.size();
		buffer.resize(cur + it->second.buf.size());
		memcpy(&buffer[cur], &it->second.buf[0], it->second.buf.size());
	}

	int compressed_size;
	int real_size = (int)buffer.size();
	vector<char> dst;
	dst.resize(buffer.size());
	def(&buffer[0], &dst[0], (int)buffer.size(), &compressed_size);

	// write to file
	// fmt: num shaders, size of compressed, compressed data
	FILE* fdst = fopen(dstfilename, "wb");
	if( fdst == NULL ) {
		printf("failed to open %s\n", dstfilename);
		return 0;
	}

	fwrite(&num, 4, 1, fdst);
	fwrite(&compressed_size, 4, 1, fdst);
	fwrite(&real_size, 4, 1, fdst);
	fwrite(&dst[0], compressed_size, 1, fdst);

	fclose(fdst);

	printf("wrote %s\n", dstfilename);

	cgDestroyContext(g_cgcontext);

	return 0;
}
