#include "mednafen.h"
#include <stdio.h>
#include <vector>

#include "drivers/input.h"
#include "drivers/main.h"
#include "mempatcher.h"
#include "settings-driver.h"
#include "FileWrapper.h"

MDFNGI *CurGame=NULL;

extern MDFNGI EmulatedPSX;
void BuildPortsInfo(MDFNGI *gi);

void MDFND_PrintError(const char *s)
{
	printf(s);
}

void MDFND_Message(const char *s)
{
	printf(s);
}

static MDFNSetting MednafenSettings[] =
{
  { "filesys.untrusted_fip_check", MDFNSF_NOFLAGS, gettext_noop("Enable untrusted file-inclusion path security check."), gettext_noop("When this setting is set to \"1\", the default, paths to files referenced from files like CUE sheets and PSF rips are checked for certain characters that can be used in directory traversal, and if found, loading is aborted.  Set it to \"0\" if you want to allow constructs like absolute paths in CUE sheets, but only if you understand the security implications of doing so(see \"Security Issues\" section in the documentation)."), MDFNST_BOOL, "0" },
	{ "filesys.path_firmware", MDFNSF_NOFLAGS, gettext_noop("Path to directory for firmware."), NULL, MDFNST_STRING, "firmware" },
	{ "filesys.path_sav", MDFNSF_NOFLAGS, gettext_noop("Path to directory for save games and nonvolatile memory."), gettext_noop("WARNING: Do not set this path to a directory that contains Famicom Disk System disk images, or you will corrupt them when you load an FDS game and exit Mednafen."), MDFNST_STRING, "sav" },
	{ "filesys.fname_sav", MDFNSF_NOFLAGS, gettext_noop("Format string for save games filename."), gettext_noop("WARNING: %x should always be included, otherwise you run the risk of overwriting save data for games that create multiple save data files.\n\nSee fname_format.txt for more information.  Edit at your own risk."), MDFNST_STRING, "%F.%M%x" },
	{ "filesys.path_cheat", MDFNSF_NOFLAGS, gettext_noop("Path to directory for cheats."), NULL, MDFNST_STRING, "cheats" },
	
	//"dynamic" settings needed probably for each console
	{ "psx.tblur", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_BOOL, "0" },
	{ "psx.forcemono", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_BOOL, "0" },

	//       BuildDynamicSetting(&setting, sysname, "tblur.accum", MDFNSF_COMMON_TEMPLATE | MDFNSF_CAT_VIDEO, CSD_tblur_accum, MDFNST_BOOL, "0");
  //       BuildDynamicSetting(&setting, sysname, "tblur.accum.amount", MDFNSF_COMMON_TEMPLATE | MDFNSF_CAT_VIDEO, CSD_tblur_accum_amount, MDFNST_FLOAT, "50", "0", "100");

	{ NULL }
};


static MDFN_Surface *VTBuffer[2] = { NULL, NULL };
static MDFN_Rect *VTLineWidths[2] = { NULL, NULL };
int16 soundbuf[1024*1024]; //how big? big enough.
int VTBackBuffer = 0;
static MDFN_Rect VTDisplayRects[2];
void FrameAdvance()
{
	EmulateSpecStruct espec;
	memset(&espec, 0, sizeof(EmulateSpecStruct));

	uint32 pitch32 = CurGame->fb_width; 
	MDFN_PixelFormat nf(MDFN_COLORSPACE_RGB, 16, 8, 0, 24);

	if(VTBuffer[0] == NULL || VTBuffer[0]->w != CurGame->fb_width || VTBuffer[0]->h != CurGame->fb_height)
	{
		if(VTBuffer[0]) delete VTBuffer[0];
		if(VTLineWidths[0]) free(VTLineWidths[0]);
		VTBuffer[0] = new MDFN_Surface(NULL, CurGame->fb_width, CurGame->fb_height, CurGame->fb_width, nf);
		VTLineWidths[0] = (MDFN_Rect *)calloc(CurGame->fb_height, sizeof(MDFN_Rect));
	}

	espec.surface = (MDFN_Surface *)VTBuffer[VTBackBuffer];
	espec.LineWidths = (MDFN_Rect *)VTLineWidths[VTBackBuffer];
	espec.skip = false;
	espec.soundmultiplier = 1.0;
	espec.NeedRewind = false;

	//espec.SoundRate = GetSoundRate();
	//espec.SoundBuf = GetEmuModSoundBuffer(&espec.SoundBufMaxSize);
	//espec.SoundVolume = (double)MDFN_GetSettingUI("sound.volume") / 100;

	espec.SoundBufMaxSize = 1024*1024;
	espec.SoundRate = 44100;
	espec.SoundBuf = soundbuf;
	espec.SoundVolume = 1.0;


	MDFNI_Emulate(&espec);

	VTDisplayRects[VTBackBuffer] = espec.DisplayRect;

	//TODO sound
	//sound = espec.SoundBuf + (espec.SoundBufSizeALMS * CurGame->soundchan);
	//ssize = espec.SoundBufSize - espec.SoundBufSizeALMS;
}

extern "C" __declspec(dllexport) void close()
{
	MDFNI_CloseGame();
}

enum eProp
{
	GetPtr_FramebufferPointer,
	GetPtr_FramebufferPitchPixels,
	GetPtr_FramebufferWidth,
	GetPtr_FramebufferHeight,
	SetPtr_FopenCallback,
	SetPtr_FcloseCallback,
	SetPtr_FopCallback
};

typedef void* (*t_FopenCallback)(const char* fname, const char* mode);
static t_FopenCallback FopenCallback;

typedef int (*t_FcloseCallback)(void* fp);
static t_FcloseCallback FcloseCallback;

typedef int64 (*t_FopCallback)(int op, void* ptr, int64 a, int64 b, FILE* fp);
static t_FopCallback FopCallback;

extern "C" __declspec(dllexport) void dll_SetPropPtr(int prop, void* ptr)
{
	switch((eProp)prop)
	{
	case SetPtr_FopenCallback: FopenCallback = (t_FopenCallback)ptr; break;
	case SetPtr_FcloseCallback: FcloseCallback = (t_FcloseCallback)ptr; break;
	case SetPtr_FopCallback: FopCallback = (t_FopCallback)ptr; break;
	}
}

extern "C" __declspec(dllexport) void* dll_GetPropPtr(int prop)
{
	switch((eProp)prop)
	{
	case GetPtr_FramebufferPointer:
		return VTBuffer[0]->pixels;
	case GetPtr_FramebufferPitchPixels:
		return (void*)VTBuffer[0]->pitchinpix;
	case GetPtr_FramebufferWidth:
		return (void*)VTBuffer[0]->w;
	case GetPtr_FramebufferHeight:
		return (void*)VTBuffer[0]->h;
	default:
		return 0;
	}
}

extern "C" __declspec(dllexport) bool psx_LoadCue(const char* path)
{
	MDFNI_CloseGame(); //todo - copy CloseGame into api.. dont like everything i see in there

	//load the psx game
	MDFNGI *gi = CurGame = MDFNI_LoadCD("psx", path);
	
	//does some kind of input hooking-up
	//BuildPortsInfo(gi);

	return true;
}

extern "C" __declspec(dllexport) void psx_FrameAdvance()
{
	FrameAdvance();
}

extern "C" __declspec(dllexport) bool dll_Initialize()
{
	//initialize emulator cores according to a list maintained inside this method
	std::vector<MDFNGI *> ExternalSystems; //wtf are external systems then? custom ones not known to mednafen's internals?
	MDFNI_InitializeModules(ExternalSystems);

	MDFNI_SetBaseDirectory("$psx");

	//add settings specific to each core
	for(unsigned int x = 0; x < MDFNSystems.size(); x++)
	{
	 if(MDFNSystems[x]->Settings)
	  MDFN_MergeSettings(MDFNSystems[x]->Settings);
	}

	//prep settings. for now we reuse a lot of mednafen's internal settings building. i would prefer to have supported settings enumerated in here
	static std::vector <MDFNSetting> NeoDriverSettings; //these better be static or else shit will explode
	//MakeInputSettings(NeoDriverSettings);
	MDFN_MergeSettings(NeoDriverSettings);
	MDFN_MergeSettings(MednafenSettings);

	//settings hacks
	MDFNI_SetSetting("filesys.path_firmware","firmware");
	MDFNI_SetSetting("filesys.path_sav","sav");

	//cheats.. crashes without it, maybe we'll want to use the cheat system later
	MDFN_MergeSettings(MDFNMP_Settings);

	return true;
}
	

//---------

FILE* headless_fopen(const char* path, const char* mode)
{
	return (FILE*)FopenCallback(path,mode);
}

int headless_fclose(FILE* fp)
{
	return FcloseCallback(fp);
}

int64 headless_fop(FOP op, void* ptr, int64 a, int64 b, FILE* fp)
{
	return FopCallback((int)op, ptr, a, b, fp);
}