/*
Copyright (C) 1996-1997 Id Software, Inc.

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.

*/
// r_misc.c

#include "cmd.h"
#include "console.h"
#include "glquake.h"
#include "protocol.h"
#include "quakedef.h"
#include "sys.h"

// FIXME - should only be needed in r_part.c or here, not both.
GLuint particletexture;

/*
==================
R_InitTextures
==================
*/
void
R_InitTextures(void)
{
    int x, y, m;
    byte *dest;

// create a simple checkerboard texture for the default
    r_notexture_mip =
	Hunk_AllocName(sizeof(texture_t) + 16 * 16 + 8 * 8 + 4 * 4 + 2 * 2,
		       "notexture");

    r_notexture_mip->width = r_notexture_mip->height = 16;
    r_notexture_mip->offsets[0] = sizeof(texture_t);
    r_notexture_mip->offsets[1] = r_notexture_mip->offsets[0] + 16 * 16;
    r_notexture_mip->offsets[2] = r_notexture_mip->offsets[1] + 8 * 8;
    r_notexture_mip->offsets[3] = r_notexture_mip->offsets[2] + 4 * 4;

    for (m = 0; m < 4; m++) {
	dest = (byte *)r_notexture_mip + r_notexture_mip->offsets[m];
	for (y = 0; y < (16 >> m); y++) {
	    for (x = 0; x < (16 >> m); x++) {
		if ((y < (8 >> m)) ^ (x < (8 >> m)))
		    *dest++ = 0;
		else
		    *dest++ = 0xff;
	    }
	}
    }
}

static byte dottexture[8][8] = {
    {0, 1, 1, 0, 0, 0, 0, 0},
    {1, 1, 1, 1, 0, 0, 0, 0},
    {1, 1, 1, 1, 0, 0, 0, 0},
    {0, 1, 1, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0},
};

static void
R_InitParticleTexture(void)
{
    int x, y;
    byte data[8][8][4];

    //
    // particle texture
    //
    glGenTextures(1, &particletexture);
    GL_Bind(particletexture);

    for (x = 0; x < 8; x++) {
	for (y = 0; y < 8; y++) {
	    data[y][x][0] = 255;
	    data[y][x][1] = 255;
	    data[y][x][2] = 255;
	    data[y][x][3] = dottexture[x][y] * 255;
	}
    }
    glTexImage2D(GL_TEXTURE_2D, 0, gl_alpha_format, 8, 8, 0, GL_RGBA,
		 GL_UNSIGNED_BYTE, data);

    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}

/*
===============
R_Envmap_f

Grab six views for environment mapping tests
===============
*/
static void
R_Envmap_f(void)
{
    byte buffer[256 * 256 * 4];

    glDrawBuffer(GL_FRONT);
    glReadBuffer(GL_FRONT);
    envmap = true;

    r_refdef.vrect.x = 0;
    r_refdef.vrect.y = 0;
    r_refdef.vrect.width = 256;
    r_refdef.vrect.height = 256;

    r_refdef.viewangles[0] = 0;
    r_refdef.viewangles[1] = 0;
    r_refdef.viewangles[2] = 0;
    GL_BeginRendering(&glx, &gly, &glwidth, &glheight);
    R_RenderView();
    glReadPixels(0, 0, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
    COM_WriteFile("env0.rgb", buffer, sizeof(buffer));

    r_refdef.viewangles[1] = 90;
    GL_BeginRendering(&glx, &gly, &glwidth, &glheight);
    R_RenderView();
    glReadPixels(0, 0, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
    COM_WriteFile("env1.rgb", buffer, sizeof(buffer));

    r_refdef.viewangles[1] = 180;
    GL_BeginRendering(&glx, &gly, &glwidth, &glheight);
    R_RenderView();
    glReadPixels(0, 0, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
    COM_WriteFile("env2.rgb", buffer, sizeof(buffer));

    r_refdef.viewangles[1] = 270;
    GL_BeginRendering(&glx, &gly, &glwidth, &glheight);
    R_RenderView();
    glReadPixels(0, 0, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
    COM_WriteFile("env3.rgb", buffer, sizeof(buffer));

    r_refdef.viewangles[0] = -90;
    r_refdef.viewangles[1] = 0;
    GL_BeginRendering(&glx, &gly, &glwidth, &glheight);
    R_RenderView();
    glReadPixels(0, 0, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
    COM_WriteFile("env4.rgb", buffer, sizeof(buffer));

    r_refdef.viewangles[0] = 90;
    r_refdef.viewangles[1] = 0;
    GL_BeginRendering(&glx, &gly, &glwidth, &glheight);
    R_RenderView();
    glReadPixels(0, 0, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
    COM_WriteFile("env5.rgb", buffer, sizeof(buffer));

    envmap = false;
    glDrawBuffer(GL_BACK);
    glReadBuffer(GL_BACK);
    GL_EndRendering();
}

// FIXME - locate somewhere else?
cvar_t r_lockpvs = { "r_lockpvs", "0" };
cvar_t r_lockfrustum = { "r_lockfrustum", "0" };
cvar_t r_drawflat = { "r_drawflat", "0" };

/*
===============
R_Init
===============
*/
void
R_Init(void)
{
    Cmd_AddCommand("envmap", R_Envmap_f);
    Cmd_AddCommand("pointfile", R_ReadPointFile_f);
    Cmd_AddCommand("timerefresh", R_TimeRefresh_f);

    Cvar_RegisterVariable(&r_speeds);
    Cvar_RegisterVariable(&r_fullbright);
    Cvar_RegisterVariable(&r_drawentities);
    Cvar_RegisterVariable(&r_drawviewmodel);
    Cvar_RegisterVariable(&r_drawflat);

    Cvar_RegisterVariable(&r_lockpvs);
    Cvar_RegisterVariable(&r_lockfrustum);

    Cvar_RegisterVariable(&r_norefresh);
    Cvar_RegisterVariable(&r_lightmap);
    Cvar_RegisterVariable(&r_shadows);
    Cvar_RegisterVariable(&r_mirroralpha);
    Cvar_RegisterVariable(&r_wateralpha);
    Cvar_RegisterVariable(&r_dynamic);
    Cvar_RegisterVariable(&r_novis);
    Cvar_RegisterVariable(&r_waterwarp);

    Cvar_RegisterVariable(&gl_finish);
    Cvar_RegisterVariable(&gl_clear);
    Cvar_RegisterVariable(&gl_texsort);

    Cvar_RegisterVariable(&_gl_allowgammafallback);
    Cvar_RegisterVariable(&_gl_drawhull);

    Cvar_RegisterVariable(&gl_cull);
    Cvar_RegisterVariable(&gl_smoothmodels);
    Cvar_RegisterVariable(&gl_affinemodels);
    Cvar_RegisterVariable(&gl_polyblend);
    Cvar_RegisterVariable(&gl_flashblend);
    Cvar_RegisterVariable(&gl_playermip);
    Cvar_RegisterVariable(&gl_nocolors);

    Cvar_RegisterVariable(&gl_keeptjunctions);
    Cvar_RegisterVariable(&gl_reporttjunctions);

    Cvar_RegisterVariable(&r_netgraph);

    R_InitBubble();

    R_InitParticles();
    R_InitParticleTexture();

    glGenTextures(1, &netgraphtexture);
    glGenTextures(MAX_CLIENTS, playertextures);
}

/*
===============
R_TranslatePlayerSkin

Translates a skin texture by the per-player color lookup
===============
*/
void
R_TranslatePlayerSkin(int playernum)
{
    int top, bottom;
    byte translate[256];
    unsigned translate32[256];
    int i, j;
    byte *original;
    unsigned pixels[512 * 256], *out;
    unsigned scaled_width, scaled_height;
    int inwidth, inheight;
    int tinwidth, tinheight;
    byte *inrow;
    unsigned frac, fracstep;
    player_info_t *player;
    char s[512];

    GL_DisableMultitexture();

    player = &cl.players[playernum];
    if (!player->name[0])
	return;

    strcpy(s, Info_ValueForKey(player->userinfo, "skin"));
    COM_StripExtension(s, s);
    if (player->skin && !strcasecmp(s, player->skin->name))
	player->skin = NULL;

    if (player->_topcolor != player->topcolor ||
	player->_bottomcolor != player->bottomcolor || !player->skin) {
	player->_topcolor = player->topcolor;
	player->_bottomcolor = player->bottomcolor;

	top = player->topcolor;
	bottom = player->bottomcolor;
	top = (top < 0) ? 0 : ((top > 13) ? 13 : top);
	bottom = (bottom < 0) ? 0 : ((bottom > 13) ? 13 : bottom);
	top *= 16;
	bottom *= 16;

	for (i = 0; i < 256; i++)
	    translate[i] = i;

	for (i = 0; i < 16; i++) {
	    if (top < 128) // the artists made some backwards ranges. sigh.
		translate[TOP_RANGE + i] = top + i;
	    else
		translate[TOP_RANGE + i] = top + 15 - i;

	    if (bottom < 128)
		translate[BOTTOM_RANGE + i] = bottom + i;
	    else
		translate[BOTTOM_RANGE + i] = bottom + 15 - i;
	}

	//
	// locate the original skin pixels
	//
	// real model width
	tinwidth = 296;
	tinheight = 194;

	if (!player->skin)
	    Skin_Find(player);
	if ((original = Skin_Cache(player->skin)) != NULL) {
	    //skin data width
	    inwidth = 320;
	    inheight = 200;
	} else {
	    original = player_8bit_texels;
	    inwidth = 296;
	    inheight = 194;
	}

	// because this happens during gameplay, do it fast
	// instead of sending it through gl_upload 8
	GL_Bind(playertextures[playernum]);

#if 0
	s = 320 * 200;
	byte translated[320 * 200];

	for (i = 0; i < s; i += 4) {
	    translated[i] = translate[original[i]];
	    translated[i + 1] = translate[original[i + 1]];
	    translated[i + 2] = translate[original[i + 2]];
	    translated[i + 3] = translate[original[i + 3]];
	}

	// don't mipmap these, because it takes too long
	GL_Upload8(translated, paliashdr->skinwidth, paliashdr->skinheight,
		   false, false, true);
#endif

	// allow users to crunch sizes down
	scaled_width = 512 >> (int)gl_playermip.value;
	scaled_height = 256 >> (int)gl_playermip.value;

	// make sure not still too big
	scaled_width = gl_max_size.value < scaled_width
	    ? gl_max_size.value : scaled_width;
	scaled_height = gl_max_size.value < scaled_height
	    ? gl_max_size.value : scaled_height;

	if (VID_Is8bit()) {	// 8bit texture upload
	    byte *out2;

	    out2 = (byte *)pixels;
	    memset(pixels, 0, sizeof(pixels));
	    fracstep = tinwidth * 0x10000 / scaled_width;
	    for (i = 0; i < scaled_height; i++, out2 += scaled_width) {
		inrow = original + inwidth * (i * tinheight / scaled_height);
		frac = fracstep >> 1;
		for (j = 0; j < scaled_width; j += 4) {
		    out2[j] = translate[inrow[frac >> 16]];
		    frac += fracstep;
		    out2[j + 1] = translate[inrow[frac >> 16]];
		    frac += fracstep;
		    out2[j + 2] = translate[inrow[frac >> 16]];
		    frac += fracstep;
		    out2[j + 3] = translate[inrow[frac >> 16]];
		    frac += fracstep;
		}
	    }

	    GL_Upload8_EXT((byte *)pixels, scaled_width, scaled_height,
			   false, false);
	    return;
	}

	for (i = 0; i < 256; i++)
	    translate32[i] = d_8to24table[translate[i]];

	out = pixels;
	memset(pixels, 0, sizeof(pixels));
	fracstep = tinwidth * 0x10000 / scaled_width;
	for (i = 0; i < scaled_height; i++, out += scaled_width) {
	    inrow = original + inwidth * (i * tinheight / scaled_height);
	    frac = fracstep >> 1;
	    for (j = 0; j < scaled_width; j += 4) {
		out[j] = translate32[inrow[frac >> 16]];
		frac += fracstep;
		out[j + 1] = translate32[inrow[frac >> 16]];
		frac += fracstep;
		out[j + 2] = translate32[inrow[frac >> 16]];
		frac += fracstep;
		out[j + 3] = translate32[inrow[frac >> 16]];
		frac += fracstep;
	    }
	}

	glTexImage2D(GL_TEXTURE_2D, 0, gl_solid_format,
		     scaled_width, scaled_height, 0, GL_RGBA,
		     GL_UNSIGNED_BYTE, pixels);

	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    }
}

/*
===============
R_NewMap
===============
*/
void
R_NewMap(void)
{
    int i;

    for (i = 0; i < 256; i++)
	d_lightstylevalue[i] = 264;	// normal light value

    memset(&r_worldentity, 0, sizeof(r_worldentity));
    r_worldentity.model = cl.worldmodel;

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

    r_viewleaf = NULL;
    R_ClearParticles();

    GL_BuildLightmaps();

    /* identify mirror texture */
    mirrortexturenum = -1;
    for (i = 0; i < cl.worldmodel->numtextures; i++) {
	if (!cl.worldmodel->textures[i])
	    continue;
	if (!strncmp(cl.worldmodel->textures[i]->name, "window02_1", 10))
	    mirrortexturenum = i;
	cl.worldmodel->textures[i]->texturechain = NULL;
    }
}


/*
====================
R_TimeRefresh_f

For program optimization
====================
*/
void
R_TimeRefresh_f(void)
{
    int i;
    float start, stop, time;

    glDrawBuffer(GL_FRONT);
    glFinish();

    start = Sys_DoubleTime();
    for (i = 0; i < 128; i++) {
	r_refdef.viewangles[1] = i / 128.0 * 360.0;
	R_RenderView();
    }

    glFinish();
    stop = Sys_DoubleTime();
    time = stop - start;
    Con_Printf("%f seconds (%f fps)\n", time, 128 / time);

    glDrawBuffer(GL_BACK);
    GL_EndRendering();
}

void
D_FlushCaches(void)
{
}
