#include "ss.h"
#include <memory>
#include "cdrom/cdromif.h"
#include "cdb.h"
#include "smpc.h"
#include "cart.h"
#include <ctime>

#define EXPORT extern "C" ECL_EXPORT
using namespace MDFN_IEN_SS;

int32 (*FirmwareSizeCallback)(const char *filename);
void (*FirmwareDataCallback)(const char *filename, uint8 *dest);

EXPORT void SetFirmwareCallbacks(int32 (*sizecallback)(const char *filename), void (*datacallback)(const char *filename, uint8 *dest))
{
	FirmwareSizeCallback = sizecallback;
	FirmwareDataCallback = datacallback;
}

struct FrontendTOC
{
	int32 FirstTrack;
	int32 LastTrack;
	int32 DiskType;
	struct
	{
		int32 Adr;
		int32 Control;
		int32 Lba;
		int32 Valid;
	} Tracks[101];
};

static void (*ReadTOCCallback)(int disk, FrontendTOC *dest);
static void (*ReadSector2448Callback)(int disk, int lba, uint8 *dest);

EXPORT void SetCDCallbacks(void (*toccallback)(int disk, FrontendTOC *dest), void (*sectorcallback)(int disk, int lba, uint8 *dest))
{
	ReadTOCCallback = toccallback;
	ReadSector2448Callback = sectorcallback;
}

class MyCDIF : public CDIF
{
  private:
	int disk;

  public:
	MyCDIF(int disk) : disk(disk)
	{
		FrontendTOC t;
		ReadTOCCallback(disk, &t);
		disc_toc.first_track = t.FirstTrack;
		disc_toc.last_track = t.LastTrack;
		disc_toc.disc_type = t.DiskType;
		for (int i = 0; i < 101; i++)
		{
			disc_toc.tracks[i].adr = t.Tracks[i].Adr;
			disc_toc.tracks[i].control = t.Tracks[i].Control;
			disc_toc.tracks[i].lba = t.Tracks[i].Lba;
			disc_toc.tracks[i].valid = t.Tracks[i].Valid;
		}
	}

	virtual void HintReadSector(int32 lba) {}
	virtual bool ReadRawSector(uint8 *buf, int32 lba)
	{
		ReadSector2448Callback(disk, lba, buf);
		return true;
	}
	virtual bool ReadRawSectorPWOnly(uint8 *pwbuf, int32 lba, bool hint_fullread)
	{
		uint8 buff[2448];
		ReadSector2448Callback(disk, lba, buff);
		memcpy(pwbuf, buff + 2352, 96);
		return true;
	}
};

static std::vector<CDIF *> CDInterfaces;
static uint32 *FrameBuffer;
static uint8 IsResetPushed; // 1 or 0

namespace MDFN_IEN_SS
{
extern bool LoadCD(std::vector<CDIF *> *CDInterfaces);
}
EXPORT bool Init(int numDisks, int cartType, int regionDefault, int regionAutodetect)
{
	setting_ss_cart = cartType;
	setting_ss_region_autodetect = regionAutodetect;
	setting_ss_region_default = regionDefault;

	FrameBuffer = (uint32 *)alloc_invisible(1024 * 1024 * sizeof(*FrameBuffer));
	for (int i = 0; i < numDisks; i++)
		CDInterfaces.push_back(new MyCDIF(i));
	auto ret = LoadCD(&CDInterfaces);
	if (ret)
		SMPC_SetInput(12, nullptr, &IsResetPushed);
	return ret;
}

EXPORT void HardReset()
{
	// soft reset is handled as a normal button
	SS_Reset(true);
}

EXPORT void SetDisk(int disk, bool open)
{
	CDB_SetDisc(open, disk < 0 ? nullptr : CDInterfaces[disk]);
}

int setting_ss_slstartp = 0;
int setting_ss_slendp = 255;
int setting_ss_slstart = 0;
int setting_ss_slend = 239;
int setting_ss_region_default = SMPC_AREA_JP;
int setting_ss_cart = CART_NONE;
bool setting_ss_correct_aspect = true;
bool setting_ss_h_blend = false;
bool setting_ss_h_overscan = true;
bool setting_ss_region_autodetect = true;
bool setting_ss_input_sport1_multitap = false;
bool setting_ss_input_sport0_multitap = false;

namespace MDFN_IEN_SS
{
extern void Emulate(EmulateSpecStruct *espec_arg);
}

static uint8 ControllerInput[12 * 32];
bool InputLagged;

EXPORT void SetControllerData(const uint8_t* controllerData)
{
	memcpy(ControllerInput, controllerData, sizeof(ControllerInput));
}

struct MyFrameInfo: public FrameInfo
{
	int32_t ResetPushed;
};

EXPORT void FrameAdvance(MyFrameInfo& f)
{
	EmulateSpecStruct e;
	int32 LineWidths[1024];
	memset(LineWidths, 0, sizeof(LineWidths));
	e.pixels = FrameBuffer;
	e.pitch32 = 1024;
	e.LineWidths = LineWidths;
	e.SoundBuf = f.SoundBuffer;
	e.SoundBufMaxSize = 8192;
	IsResetPushed = f.ResetPushed;
	InputLagged = true;
	Emulate(&e);
	f.Samples = e.SoundBufSize;
	f.Cycles = e.MasterCycles;
	f.Lagged = InputLagged;

	int w = 256;
	for (int i = 0; i < e.h; i++)
		w = std::max(w, LineWidths[i]);

	const uint32 *src = FrameBuffer;
	uint32 *dst = f.VideoBuffer;
	const int srcp = 1024;
	const int dstp = w;
	src += e.y * srcp + e.x;

	for (int j = 0; j < e.h; j++, src += srcp, dst += dstp)
	{
		memcpy(dst, src, LineWidths[j + e.y] * sizeof(*dst));
	}
	f.Width = w;
	f.Height = e.h;
}

static const char *DeviceNames[] =
	{
		"none",
		"gamepad",
		"3dpad",
		"mouse",
		"wheel",
		"mission",
		"dmission",
		"keyboard"};

EXPORT void SetupInput(const int *portdevices, const int *multitaps)
{
	for (int i = 0; i < 2; i++)
		SMPC_SetMultitap(i, multitaps[i]);
	for (int i = 0; i < 12; i++)
		SMPC_SetInput(i, DeviceNames[portdevices[i]], ControllerInput + i * 32);
}

void (*InputCallback)();
EXPORT void SetInputCallback(void (*callback)())
{
	InputCallback = callback;
}

static std::vector<MemoryArea> MemoryAreas;

void AddMemoryDomain(const char *name, const void *ptr, int size, int flags)
{
	MemoryArea m;
	m.Data = (void*)ptr;
	m.Name = name;
	m.Size = size;
	m.Flags = flags;
	MemoryAreas.push_back(m);
}

EXPORT void GetMemoryAreas(MemoryArea* m)
{
	memcpy(m, MemoryAreas.data(), MemoryAreas.size() * sizeof(MemoryArea));
}

EXPORT void SetRtc(int64 ticks, int language)
{
	time_t time = ticks;
	const struct tm *tm = gmtime(&time);
	SMPC_SetRTC(tm, language);
}

namespace MDFN_IEN_SS
{
extern bool CorrectAspect;
extern bool ShowHOverscan;
extern bool DoHBlend;
extern int LineVisFirst;
extern int LineVisLast;
}
EXPORT void SetVideoParameters(bool correctAspect, bool hBlend, bool hOverscan, int sls, int sle)
{
	CorrectAspect = correctAspect;
	ShowHOverscan = hOverscan;
	DoHBlend = hBlend;
	LineVisFirst = sls;
	LineVisLast = sle;
}

// if (BackupRAM_Dirty)SaveBackupRAM();
// if (CART_GetClearNVDirty())SaveCartNV();

/*static MDFN_COLD void CloseGame(void)
{
 try { SaveBackupRAM(); } catch(std::exception& e) { MDFN_PrintError("%s", e.what()); }
 try { SaveCartNV();    } catch(std::exception& e) { MDFN_PrintError("%s", e.what()); }
 try { SaveRTC();	} catch(std::exception& e) { MDFN_PrintError("%s", e.what()); }

 Cleanup();
}*/

/*

static MDFN_COLD void BackupCartNV(void)
{
 const char* ext = nullptr;
 void* nv_ptr = nullptr;
 uint64 nv_size = 0;

 CART_GetNVInfo(&ext, &nv_ptr, &nv_size);

 if(ext)
  MDFN_BackupSavFile(10, ext);
}*/

/*static MDFN_COLD void LoadCartNV(void)
{
 const char* ext = nullptr;
 void* nv_ptr = nullptr;
 uint64 nv_size = 0;

 CART_GetNVInfo(&ext, &nv_ptr, &nv_size);

 if(ext)
 {
  //FileStream nvs(MDFN_MakeFName(MDFNMKF_SAV, 0, ext), FileStream::MODE_READ);
  GZFileStream nvs(MDFN_MakeFName(MDFNMKF_SAV, 0, ext), GZFileStream::MODE::READ);

  nvs.read(nv_ptr, nv_size);
 }
}

static MDFN_COLD void SaveCartNV(void)
{
 const char* ext = nullptr;
 void* nv_ptr = nullptr;
 uint64 nv_size = 0;

 CART_GetNVInfo(&ext, &nv_ptr, &nv_size);

 if(ext)
 {
  //FileStream nvs(MDFN_MakeFName(MDFNMKF_SAV, 0, ext), FileStream::MODE_WRITE_INPLACE);
  GZFileStream nvs(MDFN_MakeFName(MDFNMKF_SAV, 0, ext), GZFileStream::MODE::WRITE);

  nvs.write(nv_ptr, nv_size);

  nvs.close();
 }
}*/

/*static MDFN_COLD void SaveRTC(void)
{
 FileStream sds(MDFN_MakeFName(MDFNMKF_SAV, 0, "smpc"), FileStream::MODE_WRITE_INPLACE);

 SMPC_SaveNV(&sds);

 sds.close();
}

static MDFN_COLD void LoadRTC(void)
{
 FileStream sds(MDFN_MakeFName(MDFNMKF_SAV, 0, "smpc"), FileStream::MODE_READ);

 SMPC_LoadNV(&sds);
}*/

int main()
{
	return 0;
}
