/*
Copyright (C) 1996-1997 Id Software, Inc.
Copyright (C) 2007 Peter Mackay and Chris Swindle.

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 <algorithm>
#include <cstddef>

#include <pspdisplay.h>
#include <pspgu.h>
#include <pspgum.h>
#include <pspkernel.h>
#include <psputility.h>
#include <pspdebug.h>
#include <limits.h>
extern "C"
{
#include "../quakedef.h"
}

#include <vram.h>

#include "video_hardware_images.h"

#ifdef _WIN32
# define ALIGNED(x)
#else
# define ALIGNED(x) __attribute__((aligned(x)))
#endif

#define DLIST_SIZE_KB 512 //256

namespace quake
{
	namespace video
	{
		// Types.
        //ScePspRGB565;
        //ScePspRGBA8888
		typedef ScePspRGB565	pixel;
		typedef u8				texel;
		typedef u16				depth_value;

		// Constants.
		static const std::size_t	screen_width	= 480;
		static const std::size_t	screen_height	= 272;
		static const std::size_t	palette_size	= 256;
		static pixel* 				display_buffer	= 0;
		static pixel*				draw_buffer		= 0;
		static depth_value*			depth_buffer	= 0;

		//! The GU display list.
		//! @note	Aligned to 64 bytes so it doesn't share a cache line with anything.
		unsigned int ALIGNED(64)	display_list[DLIST_SIZE_KB * 1024];
	}
}

using namespace quake;
using namespace quake::video;

extern qboolean depthfl;

void* framebuffer;

// Regular globals.
ScePspRGBA8888 ALIGNED(16)	d_8to24table[palette_size];
ScePspRGBA8888 ALIGNED(16)	d_8to24tableLM[palette_size];
ScePspRGBA8888 ALIGNED(16)	d_8to24tableQ2[palette_size];
ScePspRGBA8888 ALIGNED(16)	d_8to24tableH2[palette_size];
int reloaded_pallete = 1;

extern cvar_t	r_vsync;
extern cvar_t	r_dithering;

void VID_ConvectPalette(ScePspRGBA8888 *out, unsigned char* in)
{
    // Convert the palette to PSP format.
	for (ScePspRGBA8888* color = &out[0]; color < &out[palette_size]; ++color)
	{
		const unsigned int r = *in++;
		const unsigned int g = *in++;
		const unsigned int b = *in++;
		*color = GU_RGBA(r, g, b, 0xff);
	}
}
void VID_InitPaleteLM()
{
	// Convert the palette to PSP format.
	ScePspRGBA8888* color2 = &d_8to24tableLM[0];
	for (unsigned int i=0; color2 < &d_8to24tableLM[palette_size]; ++color2, i++)
	{
		*color2 = GU_RGBA(i, i, i, i);
	}
}

qboolean last_palette_wasnt_tx;

void VID_SetPaletteLM()
{
	// Upload the palette.
	sceGuClutMode(GU_PSM_8888, 0, palette_size - 1, 0);
	sceKernelDcacheWritebackRange(d_8to24tableLM, sizeof(d_8to24tableLM));
	sceGuClutLoad(palette_size / 8, d_8to24tableLM);
    reloaded_pallete = 1;
	last_palette_wasnt_tx = qtrue;
}

void VID_SetPaletteTX()
{
	// Upload the palette.
	sceGuClutMode(GU_PSM_8888, 0, palette_size - 1, 0);
	sceKernelDcacheWritebackRange(d_8to24table, sizeof(d_8to24table));
	sceGuClutLoad(palette_size / 8, d_8to24table);
	reloaded_pallete = 1;
	last_palette_wasnt_tx = qfalse;
}

void VID_SetPalette4(unsigned char* clut4pal) {
	sceGuClutMode(GU_PSM_8888, 0, 0xFF, 0);
	sceKernelDcacheWritebackRange(clut4pal, sizeof(clut4pal));
	sceGuClutLoad(2, clut4pal);
	last_palette_wasnt_tx = qtrue;
}

void VID_SetPalette(unsigned char* palette)
{
	// Convert the palette to PSP format.
	for (ScePspRGBA8888* color = &d_8to24table[0]; color < &d_8to24table[palette_size]; ++color)
	{
		const unsigned int r = *palette++;
		const unsigned int g = *palette++;
		const unsigned int b = *palette++;
		*color = GU_RGBA(r, g, b, 0xff);
	}

	// Color 255 is transparent black.
	// This is a bit of a dirty hack.
	d_8to24table[255] = 0;

	// Upload the palette.
	sceGuClutMode(GU_PSM_8888, 0, palette_size - 1, 0);
	sceKernelDcacheWritebackRange(d_8to24table, sizeof(d_8to24table));
	sceGuClutLoad(palette_size / 8, d_8to24table);
	reloaded_pallete = 1;
	last_palette_wasnt_tx = qtrue;
}

void VID_ShiftPalette(unsigned char* palette)
{
	VID_SetPalette(palette);
}

void VID_Init(unsigned char* palette)
{
	Sys_Printf("VID_Init\n");

	// Allocate the buffers.
	display_buffer	= static_cast<pixel*>(vramalloc(screen_height * 512 * sizeof(pixel)));
	if (!display_buffer)
	{
		Sys_Error("Couldn't allocate display buffer");
	}

	draw_buffer	= static_cast<pixel*>(vramalloc(screen_height * 512 * sizeof(pixel)));
	if (!draw_buffer)
	{
		Sys_Error("Couldn't allocate draw buffer");
	}

	depth_buffer	= static_cast<depth_value*>(vramalloc(screen_height * 512 * sizeof(depth_value)));
	if (!depth_buffer)
	{
		Sys_Error("Couldn't allocate depth buffer");
	}

	// Initialise the GU.
	sceGuInit();

	// Set up the GU.
	sceGuStart(GU_DIRECT, display_list);
	{
		//sceGuDrawBuffer(GU_PSM_5650, vrelptr(draw_buffer), 512);
		sceGuDrawBuffer(GU_PSM_5650, vrelptr(draw_buffer), 512);
		sceGuDispBuffer(screen_width, screen_height, vrelptr(display_buffer), 512);
		sceGuDepthBuffer(vrelptr(depth_buffer), 512);

		// Set the rendering offset and viewport.
		sceGuOffset(2048 - (screen_width / 2), 2048 - (screen_height / 2));
		sceGuViewport(2048, 2048, screen_width, screen_height);

		// Set up scissoring.
		sceGuEnable(GU_SCISSOR_TEST);
		sceGuScissor(0, 0, screen_width, screen_height);

		// Set up texturing.
		sceGuEnable(GU_TEXTURE_2D);

		// Set up clearing.
		sceGuClearDepth(65535);
		sceGuClearColor(GU_RGBA(0x10,0x20,0x40,0xff));

		// Set up depth.
		sceGuDepthRange(0, 65535);
		sceGuDepthFunc(GU_LEQUAL);
		sceGuEnable(GU_DEPTH_TEST);

		// Set the matrices.
		sceGumMatrixMode(GU_PROJECTION);
		sceGumLoadIdentity();
		sceGumUpdateMatrix();
		sceGumMatrixMode(GU_VIEW);
		sceGumLoadIdentity();
		sceGumUpdateMatrix();
		sceGumMatrixMode(GU_MODEL);
		sceGumLoadIdentity();
		sceGumUpdateMatrix();

		// Set up culling.
		sceGuFrontFace(GU_CW);
		sceGuEnable(GU_CULL_FACE);
		sceGuEnable(GU_CLIP_PLANES);

		// Set up blending.
		sceGuBlendFunc(GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0);
	}
	
	sceGuFinish();
	sceGuSync(0,0);

	// Turn on the display.
	sceDisplayWaitVblankStart();
	sceGuDisplay(GU_TRUE);

	// Set up Quake's video parameters.
	vid.aspect			= (static_cast<float>(screen_height) / static_cast<float>(screen_width)) * (4.0f / 3.0f);
	vid.buffer			= 0;
	vid.colormap		= host_colormap;
	vid.colormap16		= 0;
	vid.conbuffer		= 0;
	vid.conheight		= screen_height;
	vid.conrowbytes		= 0;
	vid.conwidth		= screen_width;
	vid.direct			= 0;
	vid.fullbright		= palette_size - LittleLong(*((int *) vid.colormap + 2048));
	vid.height			= screen_height;
	vid.maxwarpheight	= screen_width;
	vid.maxwarpwidth	= screen_height;
	vid.numpages		= INT_MAX;
	vid.recalc_refdef	= 0;
	vid.rowbytes		= 0;
	vid.width			= screen_width;
	
	// Start a render.
	sceGuStart(GU_DIRECT, display_list);

	// Set the palette.
	VID_SetPalette(palette);
	VID_InitPaleteLM();
	VID_ConvectPalette(d_8to24tableQ2, host_q2pal);
	VID_ConvectPalette(d_8to24tableH2, host_h2pal);
}

void VID_Shutdown(void)
{
	// Finish rendering.
	sceGuFinish();
	sceGuSync(0,0);

	// Shut down the display.
	sceGuTerm();

	// Free the buffers.
	vfree(depth_buffer);
	depth_buffer = 0;
	vfree(draw_buffer);
	draw_buffer = 0;
	vfree(display_buffer);
	display_buffer = 0;
}

void GL_BeginRendering (int *x, int *y, int *width, int *height)
{
	*x = 2048;
	*y = 2048;
	*width = screen_width;
	*height = screen_height;
	
	if (r_dithering.value)
		sceGuEnable(GU_DITHER);
	else
		sceGuDisable(GU_DITHER);
}

void GL_EndRendering (void)
{
	// Finish rendering.
	sceGuFinish();
	sceGuSync(0, 0);

	// At the moment only do this if we are in network mode, once we get above
	// 60fps we might as well leave it on for all games
	if (tcpipAvailable || r_vsync.value)
		sceDisplayWaitVblankStart();

    // Switch the buffers.
	sceGuSwapBuffers();
	std::swap(draw_buffer, display_buffer);

	// Start a new render.
	sceGuStart(GU_DIRECT, display_list);
}

void GL_GetPixelsBGR(byte *buffer, int width, int height, int i)
{
    for (int y = 0; y < height; ++y)
	{
		const pixel* src = display_buffer + ((height - y - 1) * 512);
		for (int x = 0; x < width; ++x)
		{
			const pixel argb = *src++;
#if 0
            buffer[i++]	= ((argb >> 11) & 0x1f) << 3; //B
			buffer[i++]	= ((argb >> 5 ) & 0x3f) << 2; //G
			buffer[i++]	= (argb & 0x1f)         << 3; //R
#else
			buffer[i++]	= (argb >> 16) & 0xff;//B
			buffer[i++]	= (argb >> 8) & 0xff; //G
			buffer[i++]	= argb & 0xff;        //R
#endif
		}
	}

}

void GL_GetPixelsRGB(byte *buffer, int width, int height, int i)
{
    for (int y = 0; y < height; ++y)
	{
		const pixel* src = display_buffer + ((height - y - 1) * 512);
		for (int x = 0; x < width; ++x)
		{
			const pixel argb = *src++;
#if 0
            buffer[i++]	= (argb & 0x1f)         << 3; //R
			buffer[i++]	= ((argb >> 5 ) & 0x3f) << 2; //G
			buffer[i++]	= ((argb >> 11) & 0x1f) << 3; //B
#else
			buffer[i++]	= ((argb)& 0xff);       //R
            buffer[i++]	= ((argb >> 8)& 0xff);  //G
			buffer[i++]	= ((argb >> 16)& 0xff); //B
#endif
		}
	}

}

void GL_GetPixelsRGBA(byte *buffer, int width, int height, int i)
{
    for (int y = 0; y < height; ++y)
	{
		const pixel* src = display_buffer + ((height - y - 1) * 512);
		for (int x = 0; x < width; ++x)
		{
			const pixel argb = *src++;
#if 0
            buffer[i++]	= (argb & 0x1f)         << 3; //R
			buffer[i++]	= ((argb >> 5 ) & 0x3f) << 2; //G
			buffer[i++]	= ((argb >> 11) & 0x1f) << 3; //B
            buffer[i++]	= 0xff;                       //A
#else
			buffer[i++]	= ((argb)& 0xff);       //R
            buffer[i++]	= ((argb >> 8)& 0xff);  //G
			buffer[i++]	= ((argb >> 16)& 0xff); //B
            buffer[i++]	= ((argb >> 24)& 0xff); //A
#endif
		}
	}

}

u32 GL_GetDrawBuffer(void)
{
	return (u32)vrelptr(draw_buffer);
}

void D_StartParticles (void)
{
	sceGuDepthMask(GU_TRUE);
	if (r_particles_simple.value == qtrue)
	{
		sceGuDisable(GU_TEXTURE_2D);
	}
	else
	{
		sceGuDisable(GU_FOG);
		GL_Bind(particletexture);
		sceGuEnable(GU_BLEND);
		sceGuDepthMask(GU_TRUE);
		sceGuBlendFunc(GU_ADD, GU_SRC_ALPHA, GU_FIX, 0, 0xFFFFFFFF);
		sceGuTexFunc(GU_TFX_MODULATE , GU_TCC_RGBA);
	}
}

void D_EndParticles (void)
{
	sceGuDepthMask(GU_FALSE);
	if (r_particles_simple.value == qtrue)
	{
		 sceGuEnable(GU_TEXTURE_2D);
	}
	else
	{
	     sceGuEnable(GU_FOG);
		 sceGuDisable(GU_BLEND);
	     sceGuDepthMask(GU_FALSE);
		 sceGuTexFunc(GU_TFX_REPLACE, GU_TCC_RGBA);
		 sceGuBlendFunc(GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0);
	}
}



static int BufIdx  = 0;
static int BufSize = 0;

psp_particle* D_CreateBuffer (int size)
{
	psp_particle* const vertices = static_cast<psp_particle*>(sceGuGetMemory(size*sizeof(psp_particle)));
	
	BufSize = size;
	BufIdx  = 0;
	
	return vertices;
}


void D_DeleteBuffer (psp_particle* vertices)
{
    if (BufIdx > 0)
	{
    	sceGuDrawArray(GU_SPRITES, GU_VERTEX_32BITF|GU_TEXTURE_32BITF|GU_COLOR_8888, BufIdx, 0, vertices);
    	BufIdx = 0;
    	BufSize = -1;
    }
		
}

int D_DrawParticleBuffered (psp_particle* vertices, particle2_t *pparticle, vec3_t up, vec3_t right, float scale)
{
	unsigned int color = d_8to24table[static_cast<int>(pparticle->color)];
	int i = BufIdx;
	
	vertices[i].first.x = pparticle->org[0];
	vertices[i].first.y = pparticle->org[1];
	vertices[i].first.z = pparticle->org[2];
	vertices[i].first.s = 0.0;              
	vertices[i].first.t = 0.0;              
	vertices[i].first.color = color;        
			
	vertices[i].second.x = pparticle->org[0] + scale*(up[0] + right[0]);        
	vertices[i].second.y = pparticle->org[1] + scale*(up[1] + right[1]);        
	vertices[i].second.z = pparticle->org[2] + scale*(up[2] + right[2]);        
	vertices[i].second.s = 1.0;                                                 
	vertices[i].second.t = 1.0;                                                 
	vertices[i].second.color = color;                                           
               
    BufIdx++;
    
    if (BufIdx >= BufSize)
	{
    	sceGuDrawArray(GU_SPRITES, GU_VERTEX_32BITF|GU_TEXTURE_32BITF|GU_COLOR_8888, BufSize, 0, vertices);
    	BufIdx = 0;
    	BufSize = -1;
    	return -1;
    }
    
    return BufIdx;    	            
}                      
                       
                       
void D_DrawParticle (particle2_t *pparticle, vec3_t up, vec3_t right, float scale)
{
	unsigned int color = d_8to24table[static_cast<int>(pparticle->color)];
	
	struct part_vertex
	{
		float s, t;
		unsigned int color;
		float x, y, z;
	};

	part_vertex* const vertices = static_cast<part_vertex*>(sceGuGetMemory(2*sizeof(part_vertex)));

	vertices[0].x = pparticle->org[0];
	vertices[0].y = pparticle->org[1];
	vertices[0].z = pparticle->org[2];
	vertices[0].s = 0.0;
	vertices[0].t = 0.0;
	vertices[0].color = color;
	
	vertices[1].x = pparticle->org[0] + scale*(up[0] + right[0]);
	vertices[1].y = pparticle->org[1] + scale*(up[1] + right[1]);
	vertices[1].z = pparticle->org[2] + scale*(up[2] + right[2]);
	vertices[1].s = 1.0;
	vertices[1].t = 1.0;
	vertices[1].color = color;
	
	sceGuDrawArray(GU_SPRITES, GU_VERTEX_32BITF|GU_TEXTURE_32BITF|GU_COLOR_8888, 2, 0, vertices);

}


/* 
============================================================================== 
 
						SCREEN SHOTS 
 
============================================================================== 
*/
typedef struct _TargaHeader {
	unsigned char 	id_length, colormap_type, image_type;
	unsigned short	colormap_index, colormap_length;
	unsigned char	colormap_size;
	unsigned short	x_origin, y_origin, width, height;
	unsigned char	pixel_size, attributes;
} TargaHeader;

int Image_WriteJPEG (char *filename, int compression, byte *pixels, int width, int height);

/*
==================
SCR_ScreenShot_f
==================
*/
void SCR_ScreenShot_f (void)
{
	byte		*buffer;
	char		name[80];
	char		checkname[MAX_OSPATH];
    int		    buffersize = glwidth * glheight * 3;
//
// find a file name to save it to
//
	strcpy(name,"shots/DQshot00.jpg");

	int i;

	for (i=0 ; i<=100 ; i++)
	{
        name[12] = i/10 + '0';
		name[13] = i%10 + '0';
		sprintf (checkname, "%s/%s", com_gamedir, name);
		if (Sys_FileTime(checkname) == -1)
			break;	// file doesn't exist
	}

	if (i==100)
	{
		Con_Printf ("SCR_ScreenShot_f: Couldn't create a TGA file\n");
		return;
 	}

	buffer = static_cast<byte*>(Q_malloc(buffersize));
	memset(buffer, 0, buffersize);

	GL_GetPixelsRGB(buffer, glwidth, glheight, 0);

    Image_WriteJPEG (name, 75, buffer + buffersize - 3 * glwidth, -glwidth, glheight);

	free (buffer);

	Con_Printf ("Wrote %s\n", name);
}

#if 0
//======================Utility dialog functions================================

pspUtilityMsgDialogParams dialog;

static void ConfigureDialog(pspUtilityMsgDialogParams *dialog, size_t dialog_size)
{
    memset(dialog, 0, dialog_size);

    dialog->base.size = dialog_size;
    sceUtilityGetSystemParamInt(PSP_SYSTEMPARAM_ID_INT_LANGUAGE,
				&dialog->base.language); // Prompt language
    sceUtilityGetSystemParamInt(PSP_SYSTEMPARAM_ID_INT_UNKNOWN,
				&dialog->base.buttonSwap); // X/O button swap

    dialog->base.graphicsThread = 0x11;
    dialog->base.accessThread = 0x13;
    dialog->base.fontThread = 0x12;
    dialog->base.soundThread = 0x10;
}

void ShowErrorDialog(const unsigned int error)
{
    ConfigureDialog(&dialog, sizeof(dialog));
    dialog.mode = PSP_UTILITY_MSGDIALOG_MODE_ERROR;
	dialog.options = PSP_UTILITY_MSGDIALOG_OPTION_ERROR;
    dialog.errorValue = error;

    sceUtilityMsgDialogInitStart(&dialog);

    for(;;)
	{

	
	switch(sceUtilityMsgDialogGetStatus())
	{

	case PSP_UTILITY_DIALOG_VISIBLE:
	    sceUtilityMsgDialogUpdate(1);
	    break;

	case PSP_UTILITY_DIALOG_QUIT:
	    sceUtilityMsgDialogShutdownStart();
	    break;

	case PSP_UTILITY_DIALOG_NONE:
	    return;

	}

   ..upd main frame
	}
}

void ShowMessageDialog(const char *message, int enableYesno)
{
    ConfigureDialog(&dialog, sizeof(dialog));
    dialog.mode = PSP_UTILITY_MSGDIALOG_MODE_TEXT;
	dialog.options = PSP_UTILITY_MSGDIALOG_OPTION_TEXT;

	if(enableYesno)
		dialog.options |= PSP_UTILITY_MSGDIALOG_OPTION_YESNO_BUTTONS|PSP_UTILITY_MSGDIALOG_OPTION_DEFAULT_NO;

    strcpy(dialog.message, message);

    sceUtilityMsgDialogInitStart(&dialog);

    for(;;)
	{



	switch(sceUtilityMsgDialogGetStatus())
	{

	case 2:
	    sceUtilityMsgDialogUpdate(1);
	    break;

	case 3:
	    sceUtilityMsgDialogShutdownStart();
	    break;

	case 0:
	    return;

	}

	..upd main frame
	}

}

void M_Exit_f (void)
{
    ShowMessageDialog("      Really quit?     ", 1);

	if(dialog.buttonPressed == PSP_UTILITY_MSGDIALOG_RESULT_YES)
	{
		CL_Disconnect ();
	    Host_ShutdownServer(qfalse);
	    Sys_Quit ();
    }
}
#endif