// nullDC.cpp : Makes magic cookies
//

//initialse Emu
#include "types.h"
#include "hw/mem/_vmem.h"
#include "hw/mem/vmem32.h"
#include "stdclass.h"

#include "types.h"
#include "hw/flashrom/flashrom.h"
#include "hw/maple/maple_cfg.h"
#include "hw/sh4/sh4_mem.h"
#include "hw/arm7/arm7.h"

#include "hw/naomi/naomi_cart.h"

#include "reios/reios.h"
#include <libretro.h>

extern RomChip sys_rom;
extern SRamChip sys_nvmem_sram;
extern DCFlashChip sys_nvmem_flash;
unsigned FLASH_SIZE;
unsigned BBSRAM_SIZE;
unsigned BIOS_SIZE;
unsigned RAM_SIZE;
unsigned ARAM_SIZE;
unsigned VRAM_SIZE;
unsigned RAM_MASK;
unsigned ARAM_MASK;
unsigned VRAM_MASK;

settings_t settings;

extern char game_dir[1024];
extern char *game_data;
extern bool boot_to_bios;
extern bool reset_requested;

extern void init_mem();
extern void arm_Init();
extern void term_mem();

/*
	libndc

	//initialise (and parse the command line)
	ndc_init(argc,argv);

	...
	//run a dreamcast slice
	//either a frame, or up to 25 ms of emulation
	//returns 1 if the frame is ready (fb needs to be flipped -- i'm looking at you android)
	ndc_step();

	...
	//terminate (and free everything)
	ndc_term()
*/

int GetFile(char *szFileName, char *szParse=0,u32 flags=0) 
{
   if (!boot_to_bios)
   {
      strcpy(szFileName, game_data);
      strcpy(settings.imgread.DefaultImage, szFileName);
   }

	return 1; 
}


s32 plugins_Init()
{
   if (s32 rv = libPvr_Init())
      return rv;

   if (s32 rv = libGDR_Init())
      return rv;
   if (settings.System != DC_PLATFORM_DREAMCAST)
   {
      if (!naomi_cart_SelectFile())
         return -1;
   }

   if (s32 rv = libAICA_Init())
      return rv;

   init_mem();
   arm_Init();

   //if (s32 rv = libExtDevice_Init())
   //	return rv;



   return 0;
}

void plugins_Term(void)
{
   //term all plugins
   //libExtDevice_Term();
   
	term_mem();
	//arm7_Term ?
   libAICA_Term();

   libGDR_Term();
   libPvr_Term();
}

void plugins_Reset(bool Manual)
{
	libPvr_Reset(Manual);
	libGDR_Reset(Manual);
	libAICA_Reset(Manual);

	arm_Reset();
	arm_SetEnabled(false);

	//libExtDevice_Reset(Manual);
}

#include "rom_luts.h"

static void LoadSpecialSettingsCPU(void)
{
#if FEAT_SHREC != DYNAREC_NONE
	if(settings.dynarec.Enable)
	{
		Get_Sh4Recompiler(&sh4_cpu);
		INFO_LOG(DYNAREC, "Using Recompiler");
	}
	else
#endif
	{
		Get_Sh4Interpreter(&sh4_cpu);
		INFO_LOG(INTERPRETER, "Using Interpreter");
	}
   sh4_cpu.Reset(false);
}

static void LoadSpecialSettings(void)
{
   unsigned i;

   char prod_id[sizeof(ip_meta.product_number) + 1] = {0};
   memcpy(prod_id, ip_meta.product_number, sizeof(ip_meta.product_number));

   NOTICE_LOG(BOOT, "[LUT]: Product number: %s.", prod_id);
	if (ip_meta.isWindowsCE() || settings.dreamcast.ForceWinCE
			|| !strncmp("T26702N", prod_id, 7)) // PBA Tour Bowling 2001
	{
		NOTICE_LOG(BOOT, "Enabling Full MMU and Extra depth scaling for Windows CE game");
		settings.rend.ExtraDepthScale = 0.1;
		settings.dreamcast.FullMMU = true;
		settings.aica.NoBatch = 1;
	}
   for (i = 0; i < sizeof(lut_games)/sizeof(lut_games[0]); i++)
   {
      if (!strncmp(lut_games[i].product_number, prod_id, sizeof(prod_id) - 1))
      {
      	INFO_LOG(BOOT, "[LUT]: Found game in LUT database..");

         if (lut_games[i].alpha_sort_mode != -1)
         {
         	NOTICE_LOG(BOOT, "[Hack]: Applying alpha sort hack.");
            settings.pvr.Emulation.AlphaSortMode = lut_games[i].alpha_sort_mode;
         }

         if (lut_games[i].updatemode_type != -1)
         {
         	NOTICE_LOG(BOOT, "[Hack]: Applying update mode type hack.");
            settings.UpdateModeForced = 1;
         }

         if (lut_games[i].translucentPolygonDepthMask != -1)
         {
         	NOTICE_LOG(BOOT, "[Hack]: Applying translucent polygon depth mask hack.");
            settings.rend.TranslucentPolygonDepthMask = lut_games[i].translucentPolygonDepthMask;
         }

         if (lut_games[i].rendertotexturebuffer != -1)
         {
         	NOTICE_LOG(BOOT, "[Hack]: Applying rendertotexture hack.");
            settings.rend.RenderToTextureBuffer = lut_games[i].rendertotexturebuffer;
         }

         if (lut_games[i].disable_div != -1)
         {
         	NOTICE_LOG(BOOT, "[Hack]: Applying Disable DIV hack.");
         	settings.dynarec.ForceDisableDivMatching = settings.dynarec.DisableDivMatching = lut_games[i].disable_div;
         }
         if (lut_games[i].extra_depth_scale != 1 && settings.rend.AutoExtraDepthScale)
         {
         	NOTICE_LOG(BOOT, "[Hack]: Applying auto extra depth scale.");
         	settings.rend.ExtraDepthScale = lut_games[i].extra_depth_scale;
         }
         if (lut_games[i].disable_vmem32 == 1)
         {
         	NOTICE_LOG(BOOT, "[Hack]: Disabling WinCE virtual memory.");
            settings.dynarec.disable_vmem32 = true;
         }
         if (lut_games[i].no_rgb_component == 1)
         {
         	NOTICE_LOG(BOOT, "Disabling RGB component output");
            settings.dreamcast.cable = 3;		// TV composite
         }

         break;
      }
   }
	std::string areas(ip_meta.area_symbols, sizeof(ip_meta.area_symbols));
	bool region_usa = areas.find('U') != std::string::npos;
	bool region_eu = areas.find('E') != std::string::npos;
	bool region_japan = areas.find('J') != std::string::npos;
	if (region_usa || region_eu || region_japan)
	{
		switch (settings.dreamcast.region)
		{
		case 0: // Japan
			if (!region_japan)
			{
				NOTICE_LOG(BOOT, "Japan region not supported. Using %s instead", region_usa ? "USA" : "Europe");
				settings.dreamcast.region = region_usa ? 1 : 2;
			}
			break;
		case 1: // USA
			if (!region_usa)
			{
				NOTICE_LOG(BOOT, "USA region not supported. Using %s instead", region_eu ? "Europe" : "Japan");
				settings.dreamcast.region = region_eu ? 2 : 0;
			}
			break;
		case 2: // Europe
			if (!region_eu)
			{
				NOTICE_LOG(BOOT, "Europe region not supported. Using %s instead", region_usa ? "USA" : "Japan");
				settings.dreamcast.region = region_usa ? 1 : 0;
			}
			break;
		case 3: // Default
			if (region_usa)
				settings.dreamcast.region = 1;
			else if (region_eu)
				settings.dreamcast.region = 2;
			else
				settings.dreamcast.region = 0;
				break;
		}
	}
	else
		WARN_LOG(BOOT, "No region specified in IP.BIN");
	if (settings.dreamcast.cable <= 1 && !ip_meta.supportsVGA())
	{
		NOTICE_LOG(BOOT, "Game doesn't support VGA. Using TV Composite instead");
		settings.dreamcast.cable = 3;
	}
}

static void LoadSpecialSettingsNaomi(const char *name)
{
   unsigned i;

   NOTICE_LOG(BOOT, "[LUT]: Naomi ROM name is: %s.", name);
   for (i = 0; i < sizeof(lut_games_naomi)/sizeof(lut_games_naomi[0]); i++)
   {
      if (strstr(lut_games_naomi[i].product_number, name))
      {
      	INFO_LOG(BOOT, "[LUT]: Found game in LUT database..");

         if (lut_games_naomi[i].alpha_sort_mode != -1)
         {
         	NOTICE_LOG(BOOT, "[Hack]: Applying alpha sort hack.");
            settings.pvr.Emulation.AlphaSortMode = lut_games_naomi[i].alpha_sort_mode;
         }

         if (lut_games_naomi[i].updatemode_type != -1)
         {
         	NOTICE_LOG(BOOT, "[Hack]: Applying update mode type hack.");
            settings.UpdateModeForced = 1;
         }

         if (lut_games_naomi[i].translucentPolygonDepthMask != -1)
         {
         	NOTICE_LOG(BOOT, "[Hack]: Applying translucent polygon depth mask hack.");
            settings.rend.TranslucentPolygonDepthMask = lut_games_naomi[i].translucentPolygonDepthMask;
         }

         if (lut_games_naomi[i].rendertotexturebuffer != -1)
         {
         	NOTICE_LOG(BOOT, "[Hack]: Applying rendertotexture hack.");
            settings.rend.RenderToTextureBuffer = lut_games_naomi[i].rendertotexturebuffer;
         }

         if (lut_games_naomi[i].disable_div != -1)
         {
         	NOTICE_LOG(BOOT, "[Hack]: Applying Disable DIV hack.");
         	settings.dynarec.ForceDisableDivMatching = settings.dynarec.DisableDivMatching = lut_games_naomi[i].disable_div;
         }

         if (lut_games_naomi[i].jamma_setup != JVS::Default)
         {
         	NOTICE_LOG(BOOT, "[Hack]: Applying alternate Jamma I/O board setup.");
            settings.mapping.JammaSetup = lut_games_naomi[i].jamma_setup;
         }

         if (lut_games_naomi[i].extra_depth_scale != 1 && settings.rend.AutoExtraDepthScale)
         {
         	NOTICE_LOG(BOOT, "[Hack]: Applying auto extra depth scale.");
            settings.rend.ExtraDepthScale = lut_games_naomi[i].extra_depth_scale;
         }

         if (lut_games_naomi[i].game_inputs != NULL)
         {
         	NOTICE_LOG(BOOT, "Setting custom input descriptors\n");
         	naomi_game_inputs = lut_games_naomi[i].game_inputs;
         }

         break;
      }
   }
}

void dc_prepare_system(void)
{
   BBSRAM_SIZE             = (32*1024);

   switch (settings.System)
   {
      case DC_PLATFORM_DREAMCAST:
         //DC : 16 mb ram, 8 mb vram, 2 mb aram, 2 mb bios, 128k flash
         FLASH_SIZE        = (128*1024);
         BIOS_SIZE         = (2*1024*1024);
         RAM_SIZE          = (16*1024*1024);
         ARAM_SIZE         = (2*1024*1024);
         VRAM_SIZE         = (8*1024*1024);
         sys_nvmem_flash.Allocate(FLASH_SIZE);
         sys_rom.Allocate(BIOS_SIZE);
         break;
      case DC_PLATFORM_DEV_UNIT:
         //Devkit : 32 mb ram, 8? mb vram, 2? mb aram, 2? mb bios, ? flash
         FLASH_SIZE        = (128*1024);
         BIOS_SIZE         = (2*1024*1024);
         RAM_SIZE          = (32*1024*1024);
         ARAM_SIZE         = (2*1024*1024);
         VRAM_SIZE         = (8*1024*1024);
         sys_nvmem_flash.Allocate(FLASH_SIZE);
         sys_rom.Allocate(BIOS_SIZE);
         break;
      case DC_PLATFORM_NAOMI:
         //Naomi : 32 mb ram, 16 mb vram, 8 mb aram, 2 mb bios, ? flash
         BIOS_SIZE         = (2*1024*1024);
         RAM_SIZE          = (32*1024*1024);
         ARAM_SIZE         = (8*1024*1024);
         VRAM_SIZE         = (16*1024*1024);
         sys_nvmem_sram.Allocate(BBSRAM_SIZE);
         sys_rom.Allocate(BIOS_SIZE);
         break;
      case DC_PLATFORM_NAOMI2:
         //Naomi2 : 32 mb ram, 16 mb vram, 8 mb aram, 2 mb bios, ? flash
         BIOS_SIZE         = (2*1024*1024);
         RAM_SIZE          = (32*1024*1024);
         ARAM_SIZE         = (8*1024*1024);
         VRAM_SIZE         = (16*1024*1024);
         sys_nvmem_sram.Allocate(BBSRAM_SIZE);
         sys_rom.Allocate(BIOS_SIZE);
         break;
      case DC_PLATFORM_ATOMISWAVE:
         //Atomiswave : 16 mb ram, 8 mb vram, 8 mb aram, 128kb bios+flash, 128k BBSRAM
         FLASH_SIZE        = 0;
         BIOS_SIZE         = (128*1024);
         RAM_SIZE          = (16*1024*1024);
         ARAM_SIZE         = (8*1024*1024);
         VRAM_SIZE         = (8*1024*1024);
         BBSRAM_SIZE       = (128*1024);
         sys_nvmem_flash.Allocate(BIOS_SIZE);
         sys_nvmem_flash.write_protect_size = BIOS_SIZE / 2;
         sys_nvmem_sram.Allocate(BBSRAM_SIZE);
         break;
   }

   RAM_MASK         = (RAM_SIZE-1);
   ARAM_MASK        = (ARAM_SIZE-1);
   VRAM_MASK        = (VRAM_SIZE-1);
}

void dc_reset()
{
	plugins_Reset(true);
	mem_Reset(true);

	sh4_cpu.Reset(true);
}

int dc_init(int argc,wchar* argv[])
{
	setbuf(stdin,0);
	setbuf(stdout,0);
	setbuf(stderr,0);
   extern void common_libretro_setup(void);
   common_libretro_setup();

	if (!_vmem_reserve())
	{
		ERROR_LOG(VMEM, "Failed to alloc mem");
		return -1;
	}
	reios_init();

	LoadSettings();

	int rv= 0;

   extern char game_dir_no_slash[1024];

   char new_system_dir[1024];

#ifdef _WIN32
   sprintf(new_system_dir, "%s\\", game_dir_no_slash);
#else
   sprintf(new_system_dir, "%s/", game_dir_no_slash);
#endif

   if (settings.System == DC_PLATFORM_DREAMCAST)
   {
      if (settings.bios.UseReios || !LoadRomFiles(new_system_dir))
      {
      	if (boot_to_bios)
      		// Booting the BIOS requires a BIOS file
      		return -3;
         if (!LoadHle(new_system_dir))
            return -3;
         WARN_LOG(COMMON, "Did not load bios, using reios");
      }
   }
   else
   {
   	LoadRomFiles(new_system_dir);
   }
   LoadSpecialSettingsCPU();

	sh4_cpu.Init();
	mem_Init();

#ifdef _WIN64
	extern void setup_seh();
	setup_seh();
#endif

	if (plugins_Init())
	   return -4;
	
	mem_map_default();

	mcfg_CreateDevices();

	dc_reset();

	switch (settings.System)
	{
		case DC_PLATFORM_DREAMCAST:
			if (libGDR_GetDiscType() == NoDisk)
			{
				// Content loading failed so force HLE off and boot the BIOS
				settings.bios.UseReios = false;
				if (!LoadRomFiles(new_system_dir))
					return -3;
				settings.imgread.DefaultImage[0] = '\0';
			}
			reios_disk_id();
			LoadSpecialSettings();
			break;
		case DC_PLATFORM_ATOMISWAVE:
		case DC_PLATFORM_NAOMI:
			LoadSpecialSettingsNaomi(naomi_game_id);
			break;
	}
	FixUpFlash();

	return rv;
}

void dc_run(void)
{
   sh4_cpu.Run();
}

void dc_term(void)
{
	SaveRomFiles(get_writable_data_path(""));
	sh4_cpu.Term();
	naomi_cart_Close();
	plugins_Term();
	mem_Term();
	_vmem_release();
}

void dc_stop()
{
	sh4_cpu.Stop();
}


void dc_start()
{
	sh4_cpu.Start();
}

bool dc_is_running()
{
	return sh4_cpu.IsCpuRunning();
}

// Called on the emulator thread for soft reset
void dc_request_reset()
{
	reset_requested = true;
	dc_stop();
}

void LoadSettings(void)
{
   settings.dynarec.Enable			= 1;
	settings.dynarec.idleskip		= 1;
	settings.dynarec.unstable_opt	= 0; 
   //settings.dynarec.DisableDivMatching       = 0;
	//disable_nvmem can't be loaded, because nvmem init is before cfg load
	settings.dynarec.disable_vmem32 = false;
   settings.UpdateModeForced     = 0;
	settings.dreamcast.RTC			= GetRTC_now();
	settings.dreamcast.FullMMU		= false;
	settings.aica.LimitFPS			= 0;
   settings.aica.NoSound			= 0;
	settings.pvr.subdivide_transp	= 0;
   //settings.pvr.Emulation.AlphaSortMode= 0;
   settings.pvr.Emulation.zMin         = 0.f;
   settings.pvr.Emulation.zMax         = 1.0f;

	settings.pvr.MaxThreads			       = 3;
#ifndef __LIBRETRO__
   settings.pvr.Emulation.ModVol       = true;
   settings.rend.RenderToTextureBuffer  = false;
   settings.rend.RenderToTextureUpscale = 1;
   settings.rend.MaxFilteredTextureSize = 256;
   settings.pvr.SynchronousRendering	 = 0;
#endif
   settings.rend.AutoExtraDepthScale    = true;
   settings.rend.ExtraDepthScale        = 1.f;

   settings.rend.Clipping               = true;


   settings.rend.ModifierVolumes        = true;
   settings.rend.TranslucentPolygonDepthMask = false;

	settings.debug.SerialConsole         = 0;

	settings.reios.ElfFile               = "";

	settings.validate.OpenGlChecks      = 0;
}

void SaveSettings(void)
{
}

