/* FCE Ultra - NES/Famicom Emulator
*
* Copyright notice for this file:
*  Copyright (C) 2002 Xodnizel
*
* 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
*/

#ifndef WIN32
#include <stdint.h>
#endif

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <zlib.h>

#include "types.h"
#include "video.h"
#include "fceu.h"
#include "file.h"
#include "utils/memory.h"
#include "utils/crc32.h"
#include "state.h"
#include "movie.h"
#include "palette.h"
#include "nsf.h"
#include "input.h"
#include "vsuni.h"
#include "drawing.h"
#include "driver.h"
#ifdef _S9XLUA_H
#include "fceulua.h"
#endif

#ifdef WIN32
#include "drivers/win/common.h" //For DirectX constants
#include "drivers/win/input.h"
#endif

#ifdef CREATE_AVI
#include "drivers/videolog/nesvideos-piece.h"
#endif

uint8 *XBuf=NULL;
uint8 *XBackBuf=NULL;
int ClipSidesOffset=0;	//Used to move displayed messages when Clips left and right sides is checked
static uint8 *xbsave=NULL;

GUIMESSAGE guiMessage;
GUIMESSAGE subtitleMessage;

//for input display
extern int input_display;
extern uint32 cur_input_display;

bool oldInputDisplay = false;

std::string AsSnapshotName ="";			//adelikat:this will set the snapshot name when for s savesnapshot as function

void FCEUI_SetSnapshotAsName(std::string name) { AsSnapshotName = name; }
std::string FCEUI_GetSnapshotAsName() { return AsSnapshotName; }

void FCEU_KillVirtualVideo(void)
{
	//mbg merge TODO 7/17/06 temporarily removed
	//if(xbsave)
	//{
	// free(xbsave);
	// xbsave=0;
	//}
	//if(XBuf)
	//{
	//UnmapViewOfFile(XBuf);
	//CloseHandle(mapXBuf);
	//mapXBuf=NULL;
	//}
	//if(XBackBuf)
	//{
	// free(XBackBuf);
	// XBackBuf=0;
	//}
}

/**
* Return: Flag that indicates whether the function was succesful or not.
*
* TODO: This function is Windows-only. It should probably be moved.
**/
int FCEU_InitVirtualVideo(void)
{
	if(!XBuf)		/* Some driver code may allocate XBuf externally. */
		/* 256 bytes per scanline, * 240 scanline maximum, +16 for alignment,
		*/

		if(!(XBuf= (uint8*) (FCEU_malloc(256 * 256 + 16))) ||
			!(XBackBuf= (uint8*) (FCEU_malloc(256 * 256 + 16))))
		{
			return 0;
		}

		xbsave = XBuf;

		if( sizeof(uint8*) == 4 )
		{
			uintptr_t m = (uintptr_t)XBuf;
			m = ( 8 - m) & 7;
			XBuf+=m;
		}

		memset(XBuf,128,256*256); //*240);
		memset(XBackBuf,128,256*256);

		return 1;
}


#ifdef FRAMESKIP

//#define SHOWFPS
void ShowFPS(void);
void FCEU_PutImageDummy(void)
{
#ifdef SHOWFPS
	ShowFPS();
#endif
	if(GameInfo->type!=GIT_NSF)
	{
		FCEU_DrawNTSCControlBars(XBuf);
		FCEU_DrawSaveStates(XBuf);
		FCEU_DrawMovies(XBuf);
	}
	if(guiMessage.howlong) guiMessage.howlong--; /* DrawMessage() */
}
#endif

static int dosnapsave=0;
void FCEUI_SaveSnapshot(void)
{
	dosnapsave=1;
}

void FCEUI_SaveSnapshotAs(void)
{
	dosnapsave=2;
}


static void ReallySnap(void)
{
	int x=SaveSnapshot();
	if(!x)
		FCEU_DispMessage("Error saving screen snapshot.",0);
	else
		FCEU_DispMessage("Screen snapshot %d saved.",0,x-1);
}

void FCEU_PutImage(void)
{
#ifdef SHOWFPS
	ShowFPS();
#endif
	if(dosnapsave==2)	//Save screenshot as, currently only flagged & run by the Win32 build. //TODO SDL: implement this?
	{
		char nameo[512];
		strcpy(nameo,FCEUI_GetSnapshotAsName().c_str());
		if (nameo)
		{
			SaveSnapshot(nameo);
			FCEU_DispMessage("Snapshot Saved.",0);
		}
		dosnapsave=0;
	}
	if(GameInfo->type==GIT_NSF)
	{
		DrawNSF(XBuf);

		//Save snapshot after NSF screen is drawn.  Why would we want to do it before?
		if(dosnapsave==1)
		{
			ReallySnap();
			dosnapsave=0;
		}
	} 
	else
	{
		//Save backbuffer before overlay stuff is written.
		if(!FCEUI_EmulationPaused())
			memcpy(XBackBuf, XBuf, 256*256);

		//Some messages need to be displayed before the avi is dumped
		DrawMessage(true);

		#ifdef _S9XLUA_H
		//Lua gui should draw before the avi is dumped.
		FCEU_LuaGui(XBuf);
		#endif

		//Update AVI before overlay stuff is written
		if(!FCEUI_EmulationPaused())
			FCEUI_AviVideoUpdate(XBuf);

		//Save snapshot before overlay stuff is written.
		if(dosnapsave==1)
		{
			ReallySnap();
			dosnapsave=0;
		}
		if(GameInfo->type==GIT_VSUNI)
			FCEU_VSUniDraw(XBuf);

		FCEU_DrawSaveStates(XBuf);
		FCEU_DrawMovies(XBuf);
		FCEU_DrawLagCounter(XBuf);
		FCEU_DrawNTSCControlBars(XBuf);
		FCEU_DrawRecordingStatus(XBuf);
	}

	DrawMessage(false);

	if(FCEUD_ShouldDrawInputAids())
		FCEU_DrawInput(XBuf);

	//Fancy input display code
	if(input_display)
	{
		extern uint32 JSAutoHeld;
		uint32 held;

		int controller, c, ci, color; 
		int i, j;
		uint32 on  = FCEUMOV_Mode(MOVIEMODE_PLAY) ? 0x90:0xA7;	//Standard, or Gray depending on movie mode
		uint32 oni = 0xA0;		//Color for immediate keyboard buttons
		uint32 blend = 0xB6;		//Blend of immiate and last held buttons
		uint32 ahold = 0x87;		//Auto hold
		uint32 off = 0xCF;
		
		uint8 *t = XBuf+(FSettings.LastSLine-9)*256 + 20;		//mbg merge 7/17/06 changed t to uint8*
		if(input_display > 4) input_display = 4;
		for(controller = 0; controller < input_display; controller++, t += 56)
		{
			for(i = 0; i < 34;i++)
				for(j = 0; j <9 ; j++)
					t[i+j*256] = (t[i+j*256] & 0x30) | 0xC1;
			for(i = 3; i < 6; i++)
				for(j = 3; j< 6; j++)
					t[i+j*256] = 0xCF;
			c = cur_input_display >> (controller * 8);

			// This doesn't work in anything except windows for now.
			// It doesn't get set anywhere in other ports.
#ifdef WIN32
			if (!oldInputDisplay) ci = FCEUMOV_Mode(MOVIEMODE_PLAY) ? 0:GetGamepadPressedImmediate() >> (controller * 8);
			else ci = 0;

			if (!oldInputDisplay && !FCEUMOV_Mode(MOVIEMODE_PLAY)) held = (JSAutoHeld >> (controller * 8));
			else held = 0;
#else
			// Put other port info here
			ci = 0;
			held = 0;
#endif

			//adelikat: I apologize to anyone who ever sifts through this color assignment
			//A
			if (held&1)	{ //If auto-hold
				if (!(ci&1) ) color = ahold;	
				else 
					color = (c&1) ? on : off; //If the button is pressed down (immediate) that negates auto hold, however it is only off if the previous frame the button wasn't pressed!
			}
			else {
				if (c&1) color = (ci&1) ? blend : on;	//If immedaite buttons are pressed and they match the previous frame, blend the colors
				else color = (ci&1) ? oni : off;
			}
			for(i=0; i < 4; i++)
			{
				for(j = 0; j < 4; j++)
				{
					if(i%3==0 && j %3 == 0)
						continue;
					t[30+4*256+i+j*256] = color;
				}
			}
			//B
			if (held&2)	{ //If auto-hold
				if (!(ci&2) ) color = ahold;	
				else 
					color = (c&2) ? on : off; //If the button is pressed down (immediate) that negates auto hold, however it is only off if the previous frame the button wasn't pressed!
			}
			else {
				if (c&2) color = (ci&2) ? blend : on;	//If immedaite buttons are pressed and they match the previous frame, blend the colors
				else color = (ci&2) ? oni : off;
			}
			for(i=0; i < 4; i++)
			{
				for(j = 0; j < 4; j++)
				{
					if(i%3==0 && j %3 == 0)
						continue;
					t[24+4*256+i+j*256] = color;
				}
			}
			//Select
			if (held&4)	{ //If auto-hold
				if (!(ci&4) ) color = ahold;	
				else 
					color = (c&4) ? on : off; //If the button is pressed down (immediate) that negates auto hold, however it is only off if the previous frame the button wasn't pressed!
			}
			else {
				if (c&4) color = (ci&4) ? blend : on;	//If immedaite buttons are pressed and they match the previous frame, blend the colors
				else color = (ci&4) ? oni : off;
			}
			for(i = 0; i < 4; i++)
			{
				t[11+5*256+i] = color;
				t[11+6*256+i] = color;
			}
			//Start
			if (held&8)	{ //If auto-hold
				if (!(ci&8) ) color = ahold;	
				else 
					color = (c&8) ? on : off; //If the button is pressed down (immediate) that negates auto hold, however it is only off if the previous frame the button wasn't pressed!
			}
			else {
				if (c&8) color = (ci&8) ? blend : on;	//If immedaite buttons are pressed and they match the previous frame, blend the colors
				else color = (ci&8) ? oni : off;
			}
			for(i = 0; i < 4; i++)
			{
				t[17+5*256+i] = color;
				t[17+6*256+i] = color;
			}
			//Up
			if (held&16)	{ //If auto-hold
				if (!(ci&16) ) color = ahold;	
				else 
					color = (c&16) ? on : off; //If the button is pressed down (immediate) that negates auto hold, however it is only off if the previous frame the button wasn't pressed!
			}
			else {
				if (c&16) color = (ci&16) ? blend : on;	//If immedaite buttons are pressed and they match the previous frame, blend the colors
				else color = (ci&16) ? oni : off;
			}
			for(i = 0; i < 3; i++)
			{
				for(j = 0; j < 3; j++)
				{
					t[3+i+256*j] = color;
				}
			}
			//Down
			if (held&32)	{ //If auto-hold
				if (!(ci&32) ) color = ahold;	
				else
					color = (c&32) ? on : off; //If the button is pressed down (immediate) that negates auto hold, however it is only off if the previous frame the button wasn't pressed!
			}
			else {
				if (c&32) color = (ci&32) ? blend : on;	//If immedaite buttons are pressed and they match the previous frame, blend the colors
				else color = (ci&32) ? oni : off;
			}
			for(i = 0; i < 3; i++)
			{
				for(j = 0; j < 3; j++)
				{
					t[3+i+256*j+6*256] = color;
				}
			}
			//Left
			if (held&64)	{ //If auto-hold
				if (!(ci&64) ) color = ahold;	
				else 
					color = (c&64) ? on : off; //If the button is pressed down (immediate) that negates auto hold, however it is only off if the previous frame the button wasn't pressed!
			}
			else {
				if (c&64) color = (ci&64) ? blend : on;	//If immedaite buttons are pressed and they match the previous frame, blend the colors
				else color = (ci&64) ? oni : off;
			}
			for(i = 0; i < 3; i++)
			{
				for(j = 0; j < 3; j++)
				{
					t[3*256+i+256*j] = color;
				}
			}
			//Right
			if (held&128)	{ //If auto-hold
				if (!(ci&128) ) color = ahold;	
				else 
					color = (c&128) ? on : off; //If the button is pressed down (immediate) that negates auto hold, however it is only off if the previous frame the button wasn't pressed!
			}
			else {
				if (c&128) color = (ci&128) ? blend : on;	//If immedaite buttons are pressed and they match the previous frame, blend the colors
				else color = (ci&128) ? oni : off;
			}
			for(i = 0; i < 3; i++)
			{
				for(j = 0; j < 3; j++)
				{
					t[6+3*256+i+256*j] = color;
				}
			}
		}
	}
}

void FCEU_DispMessageOnMovie(char *format, ...)
{
	va_list ap;

	va_start(ap,format);
	vsnprintf(guiMessage.errmsg,sizeof(guiMessage.errmsg),format,ap);
	va_end(ap);

	guiMessage.howlong = 180;
	guiMessage.isMovieMessage = true;
	guiMessage.linesFromBottom = 0;

	if (FCEUI_AviIsRecording() && FCEUI_AviDisableMovieMessages())
		guiMessage.howlong = 0;
}

void FCEU_DispMessage(char *format, int disppos=0, ...)
{
	va_list ap;

	va_start(ap,disppos);
	vsnprintf(guiMessage.errmsg,sizeof(guiMessage.errmsg),format,ap);
	va_end(ap);

	guiMessage.howlong = 180;
	guiMessage.isMovieMessage = false;

	guiMessage.linesFromBottom = disppos;

	//adelikat: Pretty sure this code fails, Movie playback stopped is done with FCEU_DispMessageOnMovie()
	#ifdef CREATE_AVI
	if(LoggingEnabled == 2)
	{
		/* While in AVI recording mode, only display bare minimum
		 * of messages
		 */
		if(strcmp(guiMessage.errmsg, "Movie playback stopped.") != 0)
			guiMessage.howlong = 0;
	}
	#endif
}

void FCEU_ResetMessages()
{
	guiMessage.howlong = 0;
	guiMessage.isMovieMessage = false;
	guiMessage.linesFromBottom = 0;
}


static int WritePNGChunk(FILE *fp, uint32 size, char *type, uint8 *data)
{
	uint32 crc;

	uint8 tempo[4];

	tempo[0]=size>>24;
	tempo[1]=size>>16;
	tempo[2]=size>>8;
	tempo[3]=size;

	if(fwrite(tempo,4,1,fp)!=1)
		return 0;
	if(fwrite(type,4,1,fp)!=1)
		return 0;

	if(size)
		if(fwrite(data,1,size,fp)!=size)
			return 0;

	crc=CalcCRC32(0,(uint8 *)type,4);
	if(size)
		crc=CalcCRC32(crc,data,size);

	tempo[0]=crc>>24;
	tempo[1]=crc>>16;
	tempo[2]=crc>>8;
	tempo[3]=crc;

	if(fwrite(tempo,4,1,fp)!=1)
		return 0;
	return 1;
}

uint32 GetScreenPixel(int x, int y, bool usebackup) {
	
	uint8 r,g,b;

	if (((x < 0) || (x > 255)) || ((y < 0) || (y > 255)))
		return -1;

	if (usebackup)
		FCEUD_GetPalette(XBackBuf[(y*256)+x],&r,&g,&b);
	else
		FCEUD_GetPalette(XBuf[(y*256)+x],&r,&g,&b);


	return ((int) (r) << 16) | ((int) (g) << 8) | (int) (b);
}

int GetScreenPixelPalette(int x, int y, bool usebackup) {

	if (((x < 0) || (x > 255)) || ((y < 0) || (y > 255)))
		return -1;

	if (usebackup)
		return XBackBuf[(y*256)+x] & 0x3f;
	else
		return XBuf[(y*256)+x] & 0x3f;

}

int SaveSnapshot(void)
{
	unsigned int lastu=0;

	char *fn=0;
	int totallines=FSettings.LastSLine-FSettings.FirstSLine+1;
	int x,u,y;
	FILE *pp=NULL;
	uint8 *compmem=NULL;
	uLongf compmemsize=totallines*263+12;

	if(!(compmem=(uint8 *)FCEU_malloc(compmemsize)))
		return 0;

	for(u=lastu;u<99999;u++)
	{
		pp=FCEUD_UTF8fopen((fn=strdup(FCEU_MakeFName(FCEUMKF_SNAP,u,"png").c_str())),"rb");
		if(pp==NULL) break;
		fclose(pp);
	}

	lastu=u;

	if(!(pp=FCEUD_UTF8fopen(fn,"wb")))
	{
		free(fn);
		free(compmem);
		return 0;
	}
	free(fn);
	{
		static uint8 header[8]={137,80,78,71,13,10,26,10};
		if(fwrite(header,8,1,pp)!=1)
			goto PNGerr;
	}

	{
		uint8 chunko[13];

		chunko[0]=chunko[1]=chunko[3]=0;
		chunko[2]=0x1;			// Width of 256

		chunko[4]=chunko[5]=chunko[6]=0;
		chunko[7]=totallines;			// Height

		chunko[8]=8;				// bit depth
		chunko[9]=3;				// Color type; indexed 8-bit
		chunko[10]=0;				// compression: deflate
		chunko[11]=0;				// Basic adapative filter set(though none are used).
		chunko[12]=0;				// No interlace.

		if(!WritePNGChunk(pp,13,"IHDR",chunko))
			goto PNGerr;
	}

	{
		uint8 pdata[256*3];
		for(x=0;x<256;x++)
			FCEUD_GetPalette(x,pdata+x*3,pdata+x*3+1,pdata+x*3+2);
		if(!WritePNGChunk(pp,256*3,"PLTE",pdata))
			goto PNGerr;
	}

	{
		uint8 *tmp=XBuf+FSettings.FirstSLine*256;
		uint8 *dest,*mal,*mork;

		if(!(mal=mork=dest=(uint8 *)FCEU_dmalloc((totallines<<8)+totallines)))
			goto PNGerr;
		//   mork=dest=XBuf;

		for(y=0;y<totallines;y++)
		{
			*dest=0;			// No filter.
			dest++;
			for(x=256;x;x--,tmp++,dest++)
				*dest=*tmp; 	
		}

		if(compress(compmem,&compmemsize,mork,(totallines<<8)+totallines)!=Z_OK)
		{
			if(mal) free(mal);
			goto PNGerr;
		}
		if(mal) free(mal);
		if(!WritePNGChunk(pp,compmemsize,"IDAT",compmem))
			goto PNGerr;
	}
	if(!WritePNGChunk(pp,0,"IEND",0))
		goto PNGerr;

	free(compmem);
	fclose(pp);

	return u+1;


PNGerr:
	if(compmem)
		free(compmem);
	if(pp)
		fclose(pp);
	return(0);
}

//overloaded SaveSnapshot for "Savesnapshot As" function
int SaveSnapshot(char fileName[512])
{
	int totallines=FSettings.LastSLine-FSettings.FirstSLine+1;
	int x,y;
	FILE *pp=NULL;
	uint8 *compmem=NULL;
	uLongf compmemsize=totallines*263+12;

	if(!(compmem=(uint8 *)FCEU_malloc(compmemsize)))
		return 0;

	pp = fopen(fileName, "w");

	if(!(pp=FCEUD_UTF8fopen(fileName,"wb")))
	{
		free(compmem);
		return 0;
	}

	{
		static uint8 header[8]={137,80,78,71,13,10,26,10};
		if(fwrite(header,8,1,pp)!=1)
			goto PNGerr;
	}

	{
		uint8 chunko[13];

		chunko[0]=chunko[1]=chunko[3]=0;
		chunko[2]=0x1;			// Width of 256

		chunko[4]=chunko[5]=chunko[6]=0;
		chunko[7]=totallines;			// Height

		chunko[8]=8;				// bit depth
		chunko[9]=3;				// Color type; indexed 8-bit
		chunko[10]=0;				// compression: deflate
		chunko[11]=0;				// Basic adapative filter set(though none are used).
		chunko[12]=0;				// No interlace.

		if(!WritePNGChunk(pp,13,"IHDR",chunko))
			goto PNGerr;
	}

	{
		uint8 pdata[256*3];
		for(x=0;x<256;x++)
			FCEUD_GetPalette(x,pdata+x*3,pdata+x*3+1,pdata+x*3+2);
		if(!WritePNGChunk(pp,256*3,"PLTE",pdata))
			goto PNGerr;
	}

	{
		uint8 *tmp=XBuf+FSettings.FirstSLine*256;
		uint8 *dest,*mal,*mork;

		if(!(mal=mork=dest=(uint8 *)FCEU_dmalloc((totallines<<8)+totallines)))
			goto PNGerr;
		//   mork=dest=XBuf;

		for(y=0;y<totallines;y++)
		{
			*dest=0;			// No filter.
			dest++;
			for(x=256;x;x--,tmp++,dest++)
				*dest=*tmp; 	
		}

		if(compress(compmem,&compmemsize,mork,(totallines<<8)+totallines)!=Z_OK)
		{
			if(mal) free(mal);
			goto PNGerr;
		}
		if(mal) free(mal);
		if(!WritePNGChunk(pp,compmemsize,"IDAT",compmem))
			goto PNGerr;
	}
	if(!WritePNGChunk(pp,0,"IEND",0))
		goto PNGerr;

	free(compmem);
	fclose(pp);

	return 0;


PNGerr:
	if(compmem)
		free(compmem);
	if(pp)
		fclose(pp);
	return(0);
}
//TODO mbg - this needs to be implemented in a better way
#ifdef SHOWFPS
uint64 FCEUD_GetTime(void);
uint64 FCEUD_GetTimeFreq(void);

static uint64 boop[60];
static int boopcount = 0;

void ShowFPS(void)
{ 
	uint64 da = FCEUD_GetTime() - boop[boopcount];
	char fpsmsg[16];
	int booplimit = PAL?50:60;
	boop[boopcount] = FCEUD_GetTime();

	sprintf(fpsmsg, "%8.1f",(double)booplimit / ((double)da / FCEUD_GetTimeFreq()));
	DrawTextTrans(ClipSidesOffset+XBuf + (256-8-8*8) + (FSettings.FirstSLine+4)*256,256,fpsmsg,4);
	// It's not averaging FPS over exactly 1 second, but it's close enough.
	boopcount = (boopcount + 1) % booplimit;
}
#endif
