/*
gl_rmisc.c - renderer misceallaneous
Copyright (C) 2010 Uncle Mike

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 3 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.
*/

#include "common.h"
#include "client.h"
#include "gl_local.h"
#include "mod_local.h"
#include "shake.h"

typedef struct
{
	const char	*texname;
	const char	*detail;
	const char	material;
	int		lMin;
	int		lMax;
} dmaterial_t;

typedef struct
{
	char		texname[64];		// shortname
	imgfilter_t	filter;
} dfilter_t;

dfilter_t		*tex_filters[MAX_TEXTURES];
int		num_texfilters;

// default rules for apply detail textures.
// maybe move this to external script?
static const dmaterial_t detail_table[] =
{
{ "crt",		"dt_conc",	'C', 0, 0 },	// concrete
{ "rock",		"dt_rock1",	'C', 0, 0 },
{ "conc", 	"dt_conc",	'C', 0, 0 },
{ "brick", 	"dt_brick",	'C', 0, 0 },
{ "wall", 	"dt_brick",	'C', 0, 0 },
{ "city", 	"dt_conc",	'C', 0, 0 },
{ "crete",	"dt_conc",	'C', 0, 0 },
{ "generic",	"dt_brick",	'C', 0, 0 },
{ "floor", 	"dt_conc",	'C', 0, 0 },
{ "metal",	"dt_metal%i",	'M', 1, 2 },	// metal
{ "mtl",		"dt_metal%i",	'M', 1, 2 },
{ "pipe",		"dt_metal%i",	'M', 1, 2 },
{ "elev",		"dt_metal%i",	'M', 1, 2 },
{ "sign",		"dt_metal%i",	'M', 1, 2 },
{ "barrel",	"dt_metal%i",	'M', 1, 2 },
{ "bath",		"dt_ssteel1",	'M', 1, 2 },
{ "tech",		"dt_ssteel1",	'M', 1, 2 },
{ "refbridge",	"dt_metal%i",	'M', 1, 2 },
{ "panel",	"dt_ssteel1",	'M', 0, 0 },
{ "brass",	"dt_ssteel1",	'M', 0, 0 },
{ "rune",		"dt_metal%i",	'M', 1, 2 },
{ "car",		"dt_metal%i",	'M', 1, 2 },
{ "circuit",	"dt_metal%i",	'M', 1, 2 },
{ "steel",	"dt_ssteel1",	'M', 0, 0 },
{ "dirt",		"dt_ground%i",	'D', 1, 5 },	// dirt
{ "drt",		"dt_ground%i",	'D', 1, 5 },
{ "out",		"dt_ground%i",	'D', 1, 5 },
{ "grass",	"dt_grass1",	'D', 0, 0 },
{ "mud",		"dt_carpet1",	'D', 0, 0 },
{ "vent",		"dt_ssteel1",	'V', 1, 4 },	// vent
{ "duct",		"dt_ssteel1",	'V', 1, 4 },
{ "tile",		"dt_smooth%i",	'T', 1, 2 },
{ "labflr",	"dt_smooth%i",	'T', 1, 2 },
{ "bath",		"dt_smooth%i",	'T', 1, 2 },
{ "grate",	"dt_stone%i",	'G', 1, 4 },	// vent
{ "stone",	"dt_stone%i",	'G', 1, 4 },
{ "grt",		"dt_stone%i",	'G', 1, 4 },
{ "wiz",		"dt_wood%i",	'W', 1, 3 },
{ "wood",		"dt_wood%i",	'W', 1, 3 },
{ "wizwood",	"dt_wood%i",	'W', 1, 3 },
{ "wd",		"dt_wood%i",	'W', 1, 3 },
{ "table",	"dt_wood%i",	'W', 1, 3 },
{ "board",	"dt_wood%i",	'W', 1, 3 },
{ "chair",	"dt_wood%i",	'W', 1, 3 },
{ "brd",		"dt_wood%i",	'W', 1, 3 },
{ "carp",		"dt_carpet1",	'W', 1, 3 },
{ "book",		"dt_wood%i",	'W', 1, 3 },
{ "box",		"dt_wood%i",	'W', 1, 3 },
{ "cab",		"dt_wood%i",	'W', 1, 3 },
{ "couch",	"dt_wood%i",	'W', 1, 3 },
{ "crate",	"dt_wood%i",	'W', 1, 3 },
{ "poster",	"dt_plaster%i",	'W', 1, 2 },
{ "sheet",	"dt_plaster%i",	'W', 1, 2 },
{ "stucco",	"dt_plaster%i",	'W', 1, 2 },
{ "comp",		"dt_smooth1",	'P', 0, 0 },
{ "cmp",		"dt_smooth1",	'P', 0, 0 },
{ "elec",		"dt_smooth1",	'P', 0, 0 },
{ "vend",		"dt_smooth1",	'P', 0, 0 },
{ "monitor",	"dt_smooth1",	'P', 0, 0 },
{ "phone",	"dt_smooth1",	'P', 0, 0 },
{ "glass",	"dt_ssteel1",	'Y', 0, 0 },
{ "window",	"dt_ssteel1",	'Y', 0, 0 },
{ "flesh",	"dt_rough1",	'F', 0, 0 },
{ "meat",		"dt_rough1",	'F', 0, 0 },
{ "fls",		"dt_rough1",	'F', 0, 0 },
{ "ground",	"dt_ground%i",	'D', 1, 5 },
{ "gnd",		"dt_ground%i",	'D', 1, 5 },
{ "snow",		"dt_snow%i",	'O', 1, 2 },	// snow
{ "wswamp",	"dt_smooth1",	'W', 0, 0 },
{ NULL, NULL, 0, 0, 0 }
};

static const char *R_DetailTextureForName( const char *name )
{
	const dmaterial_t	*table;

	if( !name || !*name ) return NULL;
	if( !Q_strnicmp( name, "sky", 3 ))
		return NULL; // never details for sky

	// never apply details for liquids
	if( !Q_strnicmp( name + 1, "!lava", 5 ))
		return NULL;
	if( !Q_strnicmp( name + 1, "!slime", 6 ))
		return NULL;
	if( !Q_strnicmp( name, "!cur_90", 7 ))
		return NULL;
	if( !Q_strnicmp( name, "!cur_0", 6 ))
		return NULL;
	if( !Q_strnicmp( name, "!cur_270", 8 ))
		return NULL;
	if( !Q_strnicmp( name, "!cur_180", 8 ))
		return NULL;
	if( !Q_strnicmp( name, "!cur_up", 7 ))
		return NULL;
	if( !Q_strnicmp( name, "!cur_dwn", 8 ))
		return NULL;
	if( name[0] == '!' )
		return NULL;

	// never apply details to the special textures
	if( !Q_strnicmp( name, "origin", 6 ))
		return NULL;
	if( !Q_strnicmp( name, "clip", 4 ))
		return NULL;
	if( !Q_strnicmp( name, "hint", 4 ))
		return NULL;
	if( !Q_strnicmp( name, "skip", 4 ))
		return NULL;
	if( !Q_strnicmp( name, "translucent", 11 ))
		return NULL;
	if( !Q_strnicmp( name, "3dsky", 5 ))	// xash-mod support :-)
		return NULL;
	if( !Q_strnicmp( name, "scroll", 6 ))
		return NULL;
	if( name[0] == '@' )
		return NULL;

	// last check ...
	if( !Q_strnicmp( name, "null", 4 ))
		return NULL;

	for( table = detail_table; table && table->texname; table++ )
	{
		if( Q_stristr( name, table->texname ))
		{
			if(( table->lMin + table->lMax ) > 0 )
				return va( table->detail, COM_RandomLong( table->lMin, table->lMax )); 
			return table->detail;
		}
	}

	return NULL;
}

void R_CreateDetailTexturesList( const char *filename )
{
	file_t		*detail_txt = NULL;
	float		xScale, yScale;
	const char	*detail_name;
	texture_t		*tex;
	rgbdata_t		*pic;
	int		i;

	for( i = 0; i < cl.worldmodel->numtextures; i++ )
	{
		tex = cl.worldmodel->textures[i];
		detail_name = R_DetailTextureForName( tex->name );
		if( !detail_name ) continue;

		// detailtexture detected
		if( detail_name )
		{
			if( !detail_txt ) detail_txt = FS_Open( filename, "w", false ); 
			if( !detail_txt )
			{
				MsgDev( D_ERROR, "Can't write %s\n", filename );
				break;
			}

			pic = FS_LoadImage( va( "gfx/detail/%s", detail_name ), NULL, 0 );

			if( pic )
			{
				xScale = (pic->width / (float)tex->width) * gl_detailscale->value;
				yScale = (pic->height / (float)tex->height) * gl_detailscale->value;
				FS_FreeImage( pic );
			}
			else xScale = yScale = 10.0f;

			// store detailtexture description
			FS_Printf( detail_txt, "%s detail/%s %.2f %.2f\n", tex->name, detail_name, xScale, yScale );
		}
	}

	if( detail_txt ) FS_Close( detail_txt );
}

void R_ParseDetailTextures( const char *filename )
{
	char	*afile, *pfile;
	string	token, texname;
	string	detail_texname;
	string	detail_path;
	float	xScale, yScale;
	texture_t	*tex;
	int	i;

	if( r_detailtextures->value >= 2 && !FS_FileExists( filename, false ))
	{
		// use built-in generator for detail textures
		R_CreateDetailTexturesList( filename );
	}

	afile = FS_LoadFile( filename, NULL, false );
	if( !afile ) return;

	pfile = afile;

	// format: 'texturename' 'detailtexture' 'xScale' 'yScale'
	while(( pfile = COM_ParseFile( pfile, token )) != NULL )
	{
		texname[0] = '\0';
		detail_texname[0] = '\0';

		// read texname
		if( token[0] == '{' )
		{
			// NOTE: COM_ParseFile handled some symbols seperately
			// this code will be fix it
			pfile = COM_ParseFile( pfile, token );
			Q_strncat( texname, "{", sizeof( texname ));
			Q_strncat( texname, token, sizeof( texname ));
		}
		else Q_strncpy( texname, token, sizeof( texname ));

		// read detailtexture name
		pfile = COM_ParseFile( pfile, token );
		Q_strncat( detail_texname, token, sizeof( detail_texname ));

		// trying the scales or '{'
		pfile = COM_ParseFile( pfile, token );

		// read second part of detailtexture name
		if( token[0] == '{' )
		{
			Q_strncat( detail_texname, token, sizeof( detail_texname ));
			pfile = COM_ParseFile( pfile, token ); // read scales
			Q_strncat( detail_texname, token, sizeof( detail_texname ));
			pfile = COM_ParseFile( pfile, token ); // parse scales
		}

		Q_snprintf( detail_path, sizeof( detail_path ), "gfx/%s", detail_texname );

		// read scales
		xScale = Q_atof( token );		

		pfile = COM_ParseFile( pfile, token );
		yScale = Q_atof( token );

		if( xScale <= 0.0f || yScale <= 0.0f )
			continue;

		// search for existing texture and uploading detail texture
		for( i = 0; i < cl.worldmodel->numtextures; i++ )
		{
			tex = cl.worldmodel->textures[i];

			if( Q_stricmp( tex->name, texname ))
				continue;

			tex->dt_texturenum = GL_LoadTexture( detail_path, NULL, 0, TF_FORCE_COLOR, NULL );

			// texture is loaded
			if( tex->dt_texturenum )
			{
				gltexture_t	*glt;

				glt = R_GetTexture( tex->gl_texturenum );
				glt->xscale = xScale;
				glt->yscale = yScale;
			}
			break;
		}
	}

	Mem_Free( afile );
}

void R_ParseTexFilters( const char *filename )
{
	char	*afile, *pfile;
	string	token, texname;
	dfilter_t	*tf;
	int	i;

	afile = FS_LoadFile( filename, NULL, false );
	if( !afile ) return;

	pfile = afile;

	// format: 'texturename' 'filtername' 'factor' 'bias' 'blendmode' 'grayscale'
	while(( pfile = COM_ParseFile( pfile, token )) != NULL )
	{
		imgfilter_t	filter;

		memset( &filter, 0, sizeof( filter ));
		Q_strncpy( texname, token, sizeof( texname ));

		// parse filter
		pfile = COM_ParseFile( pfile, token );
		if( !Q_stricmp( token, "blur" ))
			filter.filter = BLUR_FILTER;
		else if( !Q_stricmp( token, "blur2" ))
			filter.filter = BLUR_FILTER2;
		else if( !Q_stricmp( token, "edge" ))
			filter.filter = EDGE_FILTER;
		else if( !Q_stricmp( token, "emboss" ))
			filter.filter = EMBOSS_FILTER;

		// reading factor
		pfile = COM_ParseFile( pfile, token );
		filter.factor = Q_atof( token );

		// reading bias
		pfile = COM_ParseFile( pfile, token );
		filter.bias = Q_atof( token );

		// reading blendFunc
		pfile = COM_ParseFile( pfile, token );
		if( !Q_stricmp( token, "modulate" ) || !Q_stricmp( token, "GL_MODULATE" ))
			filter.blendFunc = GL_MODULATE;
		else if( !Q_stricmp( token, "replace" ) || !Q_stricmp( token, "GL_REPLACE" ))
			filter.blendFunc = GL_REPLACE;
		else if( !Q_stricmp( token, "add" ) || !Q_stricmp( token, "GL_ADD" ))
			filter.blendFunc = GL_ADD;
		else if( !Q_stricmp( token, "decal" ) || !Q_stricmp( token, "GL_DECAL" ))
			filter.blendFunc = GL_DECAL;
		else if( !Q_stricmp( token, "blend" ) || !Q_stricmp( token, "GL_BLEND" ))
			filter.blendFunc = GL_BLEND;
		else if( !Q_stricmp( token, "add_signed" ) || !Q_stricmp( token, "GL_ADD_SIGNED" ))
			filter.blendFunc = GL_ADD_SIGNED;
		else MsgDev( D_WARN, "unknown blendFunc '%s' specified for texture '%s'\n", texname, token );

		// reading flags
		pfile = COM_ParseFile( pfile, token );
		filter.flags = Q_atoi( token );

		// make sure what factor is not zeroed
		if( filter.factor == 0.0f )
		{
			MsgDev( D_WARN, "texfilter for texture %s has factor 0! Ignored\n", texname );
			continue;
		}

		// check if already existed
		for( i = 0; i < num_texfilters; i++ )
		{
			tf = tex_filters[i];

			if( !Q_stricmp( tf->texname, texname ))
			{
				MsgDev( D_WARN, "texture %s has specified multiple filters! Ignored\n", texname );
				break;
			}
		}

		if( i != num_texfilters )
			continue;	// already specified

		// allocate new texfilter
		tf = Z_Malloc( sizeof( dfilter_t ));
		tex_filters[num_texfilters++] = tf;

		Q_strncpy( tf->texname, texname, sizeof( tf->texname ));
		tf->filter = filter;
	}

	MsgDev( D_INFO, "%i texture filters parsed\n", num_texfilters );

	Mem_Free( afile );
}

imgfilter_t *R_FindTexFilter( const char *texname )
{
	dfilter_t	*tf;
	int	i;

	for( i = 0; i < num_texfilters; i++ )
	{
		tf = tex_filters[i];

		if( !Q_stricmp( tf->texname, texname ))
			return &tf->filter;
	}

	return NULL;
}

/*
=======================
R_ClearStaticEntities

e.g. by demo request
=======================
*/
void R_ClearStaticEntities( void )
{
	int	i;

	if( host.type == HOST_DEDICATED )
		return;

	// clear out efrags in case the level hasn't been reloaded
	for( i = 0; i < cl.worldmodel->numleafs; i++ )
		cl.worldmodel->leafs[i+1].efrags = NULL;

	clgame.numStatics = 0;

	CL_ClearEfrags ();
}

void R_NewMap( void )
{
	texture_t	*tx;
	int	i;

	R_ClearDecals(); // clear all level decals

	// upload detailtextures
	if( r_detailtextures->value )
	{
		string	mapname, filepath;

		Q_strncpy( mapname, cl.worldmodel->name, sizeof( mapname ));
		COM_StripExtension( mapname );
		Q_sprintf( filepath, "%s_detail.txt", mapname );

		R_ParseDetailTextures( filepath );
	}

	if( v_dark->value )
	{
		screenfade_t		*sf = &clgame.fade;
		client_textmessage_t	*title;

		title = CL_TextMessageGet( "GAMETITLE" );

		if( title )
		{
			// get settings from titles.txt
			sf->fadeEnd = title->holdtime + title->fadeout;
			sf->fadeReset = title->fadeout;
		}
		else sf->fadeEnd = sf->fadeReset = 5.0f;
	
		sf->fadeFlags = FFADE_IN;
		sf->fader = sf->fadeg = sf->fadeb = 0;
		sf->fadealpha = 255;
		sf->fadeSpeed = (float)sf->fadealpha / sf->fadeReset;
		sf->fadeReset += cl.time;
		sf->fadeEnd += sf->fadeReset;

		Cvar_SetValue( "v_dark", 0.0f );
	}

	// clear out efrags in case the level hasn't been reloaded
	for( i = 0; i < cl.worldmodel->numleafs; i++ )
		cl.worldmodel->leafs[i+1].efrags = NULL;

	tr.skytexturenum = -1;
	pglDisable( GL_FOG );

	// clearing texture chains
	for( i = 0; i < cl.worldmodel->numtextures; i++ )
	{
		if( !cl.worldmodel->textures[i] )
			continue;

		tx = cl.worldmodel->textures[i];

		if( !Q_strncmp( tx->name, "sky", 3 ) && tx->width == 256 && tx->height == 128 )
			tr.skytexturenum = i;

 		tx->texturechain = NULL;
	}

	R_SetupSky( clgame.movevars.skyName );

	GL_BuildLightmaps ();
}