/*
Copyright (C) 2002-2003 Victor Luchits

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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "r_local.h"

#ifdef MYSTICARB

#define ARB_PROGRAMS_HASHSIZE	128

mempool_t			*r_arb_program_pmempool;

#define ARB_Programs_Malloc(size) Mem_Alloc(r_arb_program_pmempool,size)
#define Programs_Free(data) Mem_Free(data)


static ARB_program_t	*r_ARB_programsHash[ARB_PROGRAMS_HASHSIZE];
static ARB_program_t	*r_ARB_programs[MAX_ARB_PROGRAMS];
static int			r_num_ARB_Programs;

ARB_program_t			*r_ARB_defaultVertexProgram;
ARB_program_t			*r_ARB_defaultFragmentProgram;
extern					int numColors;


/*
 =================
 R_ARB_ProgramList_f
 =================
*/
void R_ARB_ProgramList_f (void){

	ARB_program_t	*program;
	int			i;

	Com_Printf("\n");
	Com_Printf("-----------------------------------\n");

	for (i = 0; i < r_num_ARB_Programs; i++){
		program = r_ARB_programs[i];

		Com_Printf("%3i: ", i);

		switch (program->uploadTarget){
		case GL_VERTEX_PROGRAM_ARB:
			Com_Printf("VP ");
			break;
		case GL_FRAGMENT_PROGRAM_ARB:
			Com_Printf("FP ");
			break;
		default:
			Com_Printf("?? ");
			break;
		}

		Com_Printf("%s\n", program->name);
	}

	Com_Printf("-----------------------------------\n");
	Com_Printf("%i total programs\n", r_num_ARB_Programs);
	Com_Printf("\n");
}

/*
 =================
 R_ARB_UploadProgram
 =================
*/
static qboolean R_ARB_UploadProgram (const char *name, unsigned target, const char *string, int length, unsigned *progNum){

	const char	*errString;
	int			errPosition;


	qglEnable(target);
	qglGenProgramsARB(1, progNum);
	qglBindProgramARB(target, *progNum);
	qglProgramStringARB(target, GL_PROGRAM_FORMAT_ASCII_ARB, length, string);

	errString = qglGetString(GL_PROGRAM_ERROR_STRING_ARB);
	qglGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &errPosition);

	if (errPosition == -1) {
		//qglBindProgramARB(target, 0);
		qglDisable(target);
			switch (target){
				case GL_VERTEX_PROGRAM_ARB:
					Com_Printf(S_COLOR_WHITE "Vertex program '%s' loaded\n", name);
				break;
				case GL_FRAGMENT_PROGRAM_ARB:
					Com_Printf(S_COLOR_WHITE "Fragment program '%s' loaded\n", name);
				break;
			}
		return qtrue;
	}

	switch (target){
	case GL_VERTEX_PROGRAM_ARB:
		Com_Printf(S_COLOR_RED "Error in vertex program '%s' at char %i: %s\n", name, errPosition, errString);
		break;
	case GL_FRAGMENT_PROGRAM_ARB:
		Com_Printf(S_COLOR_RED "Error in fragment program '%s' at char %i: %s\n", name, errPosition, errString);
		break;
	}

	qglDeleteProgramsARB(1, progNum);
	qglDisable(target);

	return qfalse;
}

/*
 =================
 R_ARB_LoadProgram
 =================
*/
ARB_program_t *R_ARB_LoadProgram (const char *name, unsigned target, const char *string, int length){

	ARB_program_t	*program;
	unsigned	progNum;
	unsigned	hashKey;

	if (!R_ARB_UploadProgram(name, target, string, length, &progNum)){
		switch (target){
		case GL_VERTEX_PROGRAM_ARB:
			return r_ARB_defaultVertexProgram;
		case GL_FRAGMENT_PROGRAM_ARB:
			return r_ARB_defaultFragmentProgram;
		}
	}

	if (r_num_ARB_Programs == MAX_ARB_PROGRAMS)
		Com_Error(ERR_DROP, "R_LoadProgram: MAX_ARB_PROGRAMS hit");

	r_ARB_programs[r_num_ARB_Programs++] = program = (ARB_program_t*) ARB_Programs_Malloc(sizeof(ARB_program_t));
	

	// Fill it in
	Q_strncpyz(program->name, name, sizeof(program->name));
	program->uploadTarget = target;
	program->progNum = progNum;

	// Add to hash table
	hashKey = Com_HashKey((char *)name, ARB_PROGRAMS_HASHSIZE);

	program->nextHash = r_ARB_programsHash[hashKey];
	r_ARB_programsHash[hashKey] = program;

	return program;
}

/*
 =================
 R_ARB_FindProgram
 =================
*/
ARB_program_t *R_ARB_FindProgram (const char *name, unsigned target){

	ARB_program_t	*program;
	char		*string;
	int			length;
	unsigned	hashKey;
	qbyte	stack[0x4000];

	if (!name || !name[0])
		Com_Error(ERR_DROP, "R_ARB_FindProgram: NULL name");

	if (strlen(name) >= MAX_QPATH)
		Com_Error(ERR_DROP, "R_ARB_FindProgram: program name exceeds MAX_QPATH");

	// See if already loaded
	hashKey = Com_HashKey((char *)name, ARB_PROGRAMS_HASHSIZE);

	for (program = r_ARB_programsHash[hashKey]; program; program = program->nextHash){
		if (!Q_stricmp(program->name, name)){
			if (program->uploadTarget == target)
				return program;
		}
	}

	// Load it from disk
	length = FS_LoadFile( name, (void **)&string, stack, sizeof( stack ) );
	if (string){
		program = R_ARB_LoadProgram(name, target, string, length);
		if( (qbyte *)string != stack )
			FS_FreeFile( string );
		return program;
	} else {
		switch (target){
		case GL_VERTEX_PROGRAM_ARB:
			return r_ARB_defaultVertexProgram;
		case GL_FRAGMENT_PROGRAM_ARB:
			return r_ARB_defaultFragmentProgram;
		}
	}

	// Not found
	return NULL;
}

/*
 =================
 R_ARB_InitPrograms
 =================
*/
void R_ARB_InitPrograms ( void ){

	Com_Printf( "Initializing ARB Programs...\n" );

	r_num_ARB_Programs = 0;
	r_ARB_defaultVertexProgram = NULL;
	r_ARB_defaultFragmentProgram = NULL;
	r_arb_program_pmempool = Mem_AllocPool( NULL, "ARB_Programs" );
	
	if (glConfig.arb_program) {
		// PRE-CACHE

		// RENDER_TYPE_DIFFUSE_VF_DEFAULT
		r_ARB_defaultVertexProgram = R_ARB_FindProgram("arb_shaders/shader_DEF_vp.s", GL_VERTEX_PROGRAM_ARB);
		r_ARB_defaultFragmentProgram = R_ARB_FindProgram("arb_shaders/shader_DEF_fp.s", GL_FRAGMENT_PROGRAM_ARB);
		
		// RENDER_TYPE_DIFFUSE_VF_BUMPMAP
		R_ARB_FindProgram("arb_shaders/shader_DB_vp.s", GL_VERTEX_PROGRAM_ARB);
		R_ARB_FindProgram("arb_shaders/shader_DB_fp.s", GL_FRAGMENT_PROGRAM_ARB);
		
		//RENDER_TYPE_DIFFUSE_VF_BUMPMAP_SPECULAR	
		R_ARB_FindProgram("arb_shaders/shader_DBS_vp.s", GL_VERTEX_PROGRAM_ARB);
		R_ARB_FindProgram("arb_shaders/shader_DBS_fp.s", GL_FRAGMENT_PROGRAM_ARB);
		
		//RENDER_TYPE_DIFFUSE_VF_BUMPMAP_GLOSS_SPECULAR
		R_ARB_FindProgram("arb_shaders/shader_DBGS_vp.s", GL_VERTEX_PROGRAM_ARB);
		R_ARB_FindProgram("arb_shaders/shader_DBGS_fp.s", GL_FRAGMENT_PROGRAM_ARB);
	}
}

/*
 =================
 R_ARB_ShutdownPrograms
 =================
*/
void R_ARB_ShutdownPrograms (void){

	ARB_program_t	*program;
	int			i;

	Com_Printf( "Shutdown Programs\n" );
	if(r_num_ARB_Programs) {
		for (i = 0; i < r_num_ARB_Programs; i++){
			program = r_ARB_programs[i];
			qglDeleteProgramsARB(1, &program->progNum);
			//Programs_Free((void*)program);
		}
	}

	Mem_FreePool( &r_arb_program_pmempool );

	memset(r_ARB_programsHash, 0, sizeof(r_ARB_programsHash));
	memset(r_ARB_programs, 0, sizeof(r_ARB_programs));

	r_num_ARB_Programs = 0;
}


/*=========================================================
					management
=========================================================*/
shader_formatdescriptor_t shader_fdescriptor[] =
{
	// VERTEX/FRAGMENT PROGRAMS
	{ RENDER_TYPE_VF_DEFAULT, "$CMD_RT_VF_DEFAULT", (VATTRIB_VERTEX | VATTRIB_TEX0) },
	{ RENDER_TYPE_VF_GENERIC, "$CMD_RT_VF_GENERIC", (VATTRIB_VERTEX | VATTRIB_TEX0 | VATTRIB_TANGENT | VATTRIB_BINORMAL | VATTRIB_NORMAL) },
	{ RENDER_TYPE_VF_DIFFUSE_BUMPMAP, "$CMD_RT_VF_DB", (VATTRIB_VERTEX | VATTRIB_TEX0 | VATTRIB_TANGENT | VATTRIB_BINORMAL | VATTRIB_NORMAL) },
	{ RENDER_TYPE_VF_DIFFUSE_BUMPMAP_SPECULAR, "$CMD_RT_VF_DBS", (VATTRIB_VERTEX | VATTRIB_TEX0 | VATTRIB_TANGENT | VATTRIB_BINORMAL | VATTRIB_NORMAL) },
	{ RENDER_TYPE_VF_DIFFUSE_BUMPMAP_GLOSS_SPECULAR, "$CMD_RT_VF_DBGS", (VATTRIB_VERTEX | VATTRIB_TEX0 | VATTRIB_TANGENT | VATTRIB_BINORMAL | VATTRIB_NORMAL) },
	// VERTEX PROGRAM
	{ RENDER_TYPE_V_DEFAULT, "$CMD_RT_V_DEFAULT", (VATTRIB_VERTEX | VATTRIB_TEX0) },
	{ RENDER_TYPE_V_GENERIC, "$CMD_RT_V_GENERIC", (VATTRIB_VERTEX | VATTRIB_TEX0) },
	// FRAGMENT PROGRAM
	{ RENDER_TYPE_F_DEFAULT, "$CMD_RT_F_DEFAULT", (VATTRIB_VERTEX | VATTRIB_TEX0) },
	{ RENDER_TYPE_F_GENERIC, "$CMD_RT_F_GENERIC", (VATTRIB_VERTEX | VATTRIB_TEX0) },

	// IT HAS TO EXIST :P
	{ RENDER_TYPE_INVALID, "", VATTRIB_INVALID, }
};
static int shadernum_fdescriptor = sizeof(shader_fdescriptor) / sizeof(shader_fdescriptor[0]) - 1;


/*
===============
R_EnableVertexAttribsArrays
===============
*/
inline void R_EnableVertexAttribsArrays(enum_flag_attribute flags)
{
	//TODO - Already activated by qfusion
#if 0
	if(flags & VATTRIB_VERTEX)
		qglEnableClientState(GL_VERTEX_ARRAY);
#endif
		
	if(flags & VATTRIB_TEX0)
		qglEnableVertexAttribArrayARB(ATTR_TEXCOORD0_POS);
		
	if(flags & VATTRIB_TEX1)
		qglEnableVertexAttribArrayARB(ATTR_TEXCOORD1_POS);
		
	if(flags & VATTRIB_TANGENT)
		qglEnableVertexAttribArrayARB(ATTR_TANGENT_POS);
	
	if(flags & VATTRIB_BINORMAL)
		qglEnableVertexAttribArrayARB(ATTR_BINORMAL_POS);
		
	if(flags & VATTRIB_NORMAL)
		//qglEnableVertexAttribArrayARB(ATTR_NORMAL_POS);
		qglEnableClientState(GL_NORMAL_ARRAY);

	//TODO
#if 0
	if(flags & VATTRIB_LIGHT)
		qglEnableVertexAttribArrayARB(ATTR_LIGHT_POS);
#endif

	//TODO - Already activated by qfusion
#if 0
	if((flags & VATTRIB_COLOR ) && numColors > 1)
		qglEnableVertexAttribArrayARB(ATTR_COLOR_POS);
		//qglEnableClientState(GL_COLOR_ARRAY);
#endif
}

/*
===============
R_DisableVertexAttribsArrays
===============
*/
inline void R_DisableVertexAttribsArrays(enum_flag_attribute flags)
{
	//TODO - Already activated by qfusion
#if 0
	if(flags & VATTRIB_VERTEX)
		qglDisableClientState(GL_VERTEX_ARRAY);
#endif
		
	if(flags & VATTRIB_TEX0)
		qglDisableVertexAttribArrayARB(ATTR_TEXCOORD0_POS);
		
	if(flags & VATTRIB_TEX1)
		qglDisableVertexAttribArrayARB(ATTR_TEXCOORD1_POS);
		
	if(flags & VATTRIB_TANGENT)
		qglDisableVertexAttribArrayARB(ATTR_TANGENT_POS);
	
	if(flags & VATTRIB_BINORMAL)
		qglDisableVertexAttribArrayARB(ATTR_BINORMAL_POS);
		
	if(flags & VATTRIB_NORMAL)
		//qglDisableVertexAttribArrayARB(ATTR_NORMAL_POS);
		qglDisableClientState(GL_NORMAL_ARRAY);
		
	//TODO
#if 0
	if(flags & VATTRIB_LIGHT)
		qglDisableVertexAttribArrayARB(ATTR_LIGHT_POS);
#endif
		
	//TODO - Already activated by qfusion
#if 0
	if((flags & VATTRIB_COLOR ) && numColors > 1)
		qglDisableVertexAttribArrayARB(ATTR_COLOR_POS);
		//qglDisableClientState( GL_COLOR_ARRAY );
#endif
}

/*
===============
R_GetRenderTypesFromCmd
===============
*/
enum_render_type R_GetRenderTypesFromCmd(const char *cmd)
{
	int i;

	for( i = 0; i < shadernum_fdescriptor; i++)
		if ( !stricmp( cmd, shader_fdescriptor[i].cmd) )
			return shader_fdescriptor[i].render_type;

	return RENDER_TYPE_INVALID;
}
/*
===============
R_GetCommandRenderTypes
===============
*/
const char *R_GetCommandRenderTypes(enum_render_type et)
{
	int i;

	for( i = 0; i < shadernum_fdescriptor; i++)
		if(et == shader_fdescriptor[i].render_type) return shader_fdescriptor[i].cmd;
	return NULL;
}
/*
===============
R_GetAttribRenderTypes
===============
*/
enum_flag_attribute R_GetAttribRenderTypes(enum_render_type et)
{
	int i;
	for( i = 0; i < shadernum_fdescriptor; i++)
		if(et == shader_fdescriptor[i].render_type) return shader_fdescriptor[i].flag_attrib;

	return VATTRIB_INVALID;
}


/*
===============
R_SetVertexAttribPointers
===============
*/
inline void R_SetVertexAttribPointers(enum_flag_attribute flags, const void *VertexArray, const void *CoordsArray0,
			   const void *CoordsArray1, const void *TangentsArray, const void *BinormalsArray,
			   const void *NormalsArray, const void *LightsArray, const void *ColorsArray)
{
	//TODO - Already activated by qfusion
#if 0
	if((flags & VATTRIB_VERTEX) && VertexArray)
		qglVertexAttribPointerARB(ATTR_VERTEX_POS, 3, GL_FLOAT, GL_FALSE, 0, VertexArray);
#endif

	if((flags & VATTRIB_TEX0) && CoordsArray0)
		qglVertexAttribPointerARB(ATTR_TEXCOORD0_POS, 2, GL_FLOAT, GL_FALSE, 0, CoordsArray0);
		
	if((flags & VATTRIB_TEX1) && CoordsArray1)
		qglVertexAttribPointerARB(ATTR_TEXCOORD1_POS, 2, GL_FLOAT, GL_FALSE, 0, CoordsArray1);
		
	if((flags & VATTRIB_TANGENT) && TangentsArray)
		qglVertexAttribPointerARB(ATTR_TANGENT_POS, 3, GL_FLOAT, GL_FALSE, 0, TangentsArray);
	
	if((flags & VATTRIB_BINORMAL) && BinormalsArray)
		qglVertexAttribPointerARB(ATTR_BINORMAL_POS, 3, GL_FLOAT, GL_FALSE, 0, BinormalsArray);
		
	if((flags & VATTRIB_NORMAL) && NormalsArray)
		//qglVertexAttribPointerARB(ATTR_NORMAL_POS, 3, GL_FLOAT, GL_FALSE, 0, NormalsArray);
		qglNormalPointer( GL_FLOAT, 0, NormalsArray );

	//TODO
#if 0
	if((flags & VATTRIB_LIGHT) && LightsArray)
		qglVertexAttribPointerARB(ATTR_LIGHT_POS, 3, GL_FLOAT, GL_FALSE, 0, LightsArray);
#endif
		
	//TODO - Already activated by qfusion
#if 0
	if((flags & VATTRIB_COLOR ) && ColorsArray && numColors > 1)
		//qglVertexAttribPointerARB(ATTR_COLOR_POS, 3, GL_FLOAT, GL_FALSE, 0, ColorsArray);
		qglVertexAttribPointerARB(ATTR_COLOR_POS, 4, GL_UNSIGNED_BYTE, GL_FALSE, 0, ColorsArray);
		//qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, ColorArray );
#endif
}

#endif // MYSTICARB

