/******************************************************************************/
/* Mednafen Virtual Boy Emulation Module                                      */
/******************************************************************************/
/* vb.cpp:
**  Copyright (C) 2010-2017 Mednafen Team
**
** 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.,
** 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include "vb.h"
#include "../emulibc/emulibc.h"
#include "../emulibc/waterboxcore.h"
#define EXPORT extern "C" ECL_EXPORT

namespace MDFN_IEN_VB
{
struct NativeSyncSettings
{
	int InstantReadHack;
	int DisableParallax;
};
struct NativeSettings
{
	int ThreeDeeMode;
	int SwapViews;
	int AnaglyphPreset;
	int AnaglyphCustomLeftColor;
	int AnaglyphCustomRightColor;
	int NonAnaglyphColor;
	int LedOnScale;
	int InterlacePrescale;
	int SideBySideSeparation;
};

static void (*input_callback)();
static bool lagged;

enum
{
	ANAGLYPH_PRESET_DISABLED = 0,
	ANAGLYPH_PRESET_RED_BLUE,
	ANAGLYPH_PRESET_RED_CYAN,
	ANAGLYPH_PRESET_RED_ELECTRICCYAN,
	ANAGLYPH_PRESET_RED_GREEN,
	ANAGLYPH_PRESET_GREEN_MAGENTA,
	ANAGLYPH_PRESET_YELLOW_BLUE,
};

static const uint32 AnaglyphPreset_Colors[][2] =
	{
		{0, 0},
		{0xFF0000, 0x0000FF},
		{0xFF0000, 0x00B7EB},
		{0xFF0000, 0x00FFFF},
		{0xFF0000, 0x00FF00},
		{0x00FF00, 0xFF00FF},
		{0xFFFF00, 0x0000FF},
};

static uint32 VB3DMode;

static uint8 *WRAM = NULL;

static uint8 *GPRAM = NULL;
static const uint32 GPRAM_Mask = 0xFFFF;

static uint8 *GPROM = NULL;
static uint32 GPROM_Mask;

V810 *VB_V810 = NULL;

VSU *VB_VSU = NULL;
static uint32 VSU_CycleFix;

static uint8 WCR;

static int32 next_vip_ts, next_timer_ts, next_input_ts;

static uint32 IRQ_Asserted;

static INLINE void RecalcIntLevel(void)
{
	int ilevel = -1;

	for (int i = 4; i >= 0; i--)
	{
		if (IRQ_Asserted & (1 << i))
		{
			ilevel = i;
			break;
		}
	}

	VB_V810->SetInt(ilevel);
}

void VBIRQ_Assert(int source, bool assert)
{
	assert(source >= 0 && source <= 4);

	IRQ_Asserted &= ~(1 << source);

	if (assert)
		IRQ_Asserted |= 1 << source;

	RecalcIntLevel();
}

static MDFN_FASTCALL uint8 HWCTRL_Read(v810_timestamp_t &timestamp, uint32 A)
{
	uint8 ret = 0;

	if (A & 0x3)
	{
		//puts("HWCtrl Bogus Read?");
		return (ret);
	}

	switch (A & 0xFF)
	{
	default: //printf("Unknown HWCTRL Read: %08x\n", A);
		break;

	case 0x18:
	case 0x1C:
	case 0x20:
		ret = TIMER_Read(timestamp, A);
		break;

	case 0x24:
		ret = WCR | 0xFC;
		break;

	case 0x10:
	case 0x14:
	case 0x28:
		lagged = false;
		if (input_callback)
			input_callback();
		ret = VBINPUT_Read(timestamp, A);
		break;
	}

	return (ret);
}

static MDFN_FASTCALL void HWCTRL_Write(v810_timestamp_t &timestamp, uint32 A, uint8 V)
{
	if (A & 0x3)
	{
		puts("HWCtrl Bogus Write?");
		return;
	}

	switch (A & 0xFF)
	{
	default: //printf("Unknown HWCTRL Write: %08x %02x\n", A, V);
		break;

	case 0x18:
	case 0x1C:
	case 0x20:
		TIMER_Write(timestamp, A, V);
		break;

	case 0x24:
		WCR = V & 0x3;
		break;

	case 0x10:
	case 0x14:
	case 0x28:
		VBINPUT_Write(timestamp, A, V);
		break;
	}
}

uint8 MDFN_FASTCALL MemRead8(v810_timestamp_t &timestamp, uint32 A)
{
	uint8 ret = 0;
	A &= (1 << 27) - 1;

	//if((A >> 24) <= 2)
	// printf("Read8: %d %08x\n", timestamp, A);

	switch (A >> 24)
	{
	case 0:
		ret = VIP_Read8(timestamp, A);
		break;

	case 1:
		break;

	case 2:
		ret = HWCTRL_Read(timestamp, A);
		break;

	case 3:
		break;
	case 4:
		break;

	case 5:
		ret = WRAM[A & 0xFFFF];
		break;

	case 6:
		if (GPRAM)
			ret = GPRAM[A & GPRAM_Mask];
		break;

	case 7:
		ret = GPROM[A & GPROM_Mask];
		break;
	}
	return (ret);
}

uint16 MDFN_FASTCALL MemRead16(v810_timestamp_t &timestamp, uint32 A)
{
	uint16 ret = 0;

	A &= (1 << 27) - 1;

	//if((A >> 24) <= 2)
	// printf("Read16: %d %08x\n", timestamp, A);

	switch (A >> 24)
	{
	case 0:
		ret = VIP_Read16(timestamp, A);
		break;

	case 1:
		break;

	case 2:
		ret = HWCTRL_Read(timestamp, A);
		break;

	case 3:
		break;

	case 4:
		break;

	case 5:
		ret = MDFN_de16lsb<true>(&WRAM[A & 0xFFFF]);
		break;

	case 6:
		if (GPRAM)
			ret = MDFN_de16lsb<true>(&GPRAM[A & GPRAM_Mask]);
		break;

	case 7:
		ret = MDFN_de16lsb<true>(&GPROM[A & GPROM_Mask]);
		break;
	}
	return ret;
}

void MDFN_FASTCALL MemWrite8(v810_timestamp_t &timestamp, uint32 A, uint8 V)
{
	A &= (1 << 27) - 1;

	//if((A >> 24) <= 2)
	// printf("Write8: %d %08x %02x\n", timestamp, A, V);

	switch (A >> 24)
	{
	case 0:
		VIP_Write8(timestamp, A, V);
		break;

	case 1:
		VB_VSU->Write((timestamp + VSU_CycleFix) >> 2, A, V);
		break;

	case 2:
		HWCTRL_Write(timestamp, A, V);
		break;

	case 3:
		break;

	case 4:
		break;

	case 5:
		WRAM[A & 0xFFFF] = V;
		break;

	case 6:
		if (GPRAM)
			GPRAM[A & GPRAM_Mask] = V;
		break;

	case 7: // ROM, no writing allowed!
		break;
	}
}

void MDFN_FASTCALL MemWrite16(v810_timestamp_t &timestamp, uint32 A, uint16 V)
{
	A &= (1 << 27) - 1;

	//if((A >> 24) <= 2)
	// printf("Write16: %d %08x %04x\n", timestamp, A, V);

	switch (A >> 24)
	{
	case 0:
		VIP_Write16(timestamp, A, V);
		break;

	case 1:
		VB_VSU->Write((timestamp + VSU_CycleFix) >> 2, A, V);
		break;

	case 2:
		HWCTRL_Write(timestamp, A, V);
		break;

	case 3:
		break;

	case 4:
		break;

	case 5:
		MDFN_en16lsb<true>(&WRAM[A & 0xFFFF], V);
		break;

	case 6:
		if (GPRAM)
			MDFN_en16lsb<true>(&GPRAM[A & GPRAM_Mask], V);
		break;

	case 7: // ROM, no writing allowed!
		break;
	}
}

static void FixNonEvents(void)
{
	if (next_vip_ts & 0x40000000)
		next_vip_ts = VB_EVENT_NONONO;

	if (next_timer_ts & 0x40000000)
		next_timer_ts = VB_EVENT_NONONO;

	if (next_input_ts & 0x40000000)
		next_input_ts = VB_EVENT_NONONO;
}

static void EventReset(void)
{
	next_vip_ts = VB_EVENT_NONONO;
	next_timer_ts = VB_EVENT_NONONO;
	next_input_ts = VB_EVENT_NONONO;
}

static INLINE int32 CalcNextTS(void)
{
	int32 next_timestamp = next_vip_ts;

	if (next_timestamp > next_timer_ts)
		next_timestamp = next_timer_ts;

	if (next_timestamp > next_input_ts)
		next_timestamp = next_input_ts;

	return (next_timestamp);
}

static void RebaseTS(const v810_timestamp_t timestamp)
{
	//printf("Rebase: %08x %08x %08x\n", timestamp, next_vip_ts, next_timer_ts);

	assert(next_vip_ts > timestamp);
	assert(next_timer_ts > timestamp);
	assert(next_input_ts > timestamp);

	next_vip_ts -= timestamp;
	next_timer_ts -= timestamp;
	next_input_ts -= timestamp;
}

void VB_SetEvent(const int type, const v810_timestamp_t next_timestamp)
{
	//assert(next_timestamp > VB_V810->v810_timestamp);

	if (type == VB_EVENT_VIP)
		next_vip_ts = next_timestamp;
	else if (type == VB_EVENT_TIMER)
		next_timer_ts = next_timestamp;
	else if (type == VB_EVENT_INPUT)
		next_input_ts = next_timestamp;

	if (next_timestamp < VB_V810->GetEventNT())
		VB_V810->SetEventNT(next_timestamp);
}

static int32 MDFN_FASTCALL EventHandler(const v810_timestamp_t timestamp)
{
	if (timestamp >= next_vip_ts)
		next_vip_ts = VIP_Update(timestamp);

	if (timestamp >= next_timer_ts)
		next_timer_ts = TIMER_Update(timestamp);

	if (timestamp >= next_input_ts)
		next_input_ts = VBINPUT_Update(timestamp);

	return (CalcNextTS());
}

// Called externally from debug.cpp in some cases.
void ForceEventUpdates(const v810_timestamp_t timestamp)
{
	next_vip_ts = VIP_Update(timestamp);
	next_timer_ts = TIMER_Update(timestamp);
	next_input_ts = VBINPUT_Update(timestamp);

	VB_V810->SetEventNT(CalcNextTS());
	//printf("FEU: %d %d %d\n", next_vip_ts, next_timer_ts, next_input_ts);
}

static void VB_Power(void)
{
	memset(WRAM, 0, 65536);

	VIP_Power();
	VB_VSU->Power();
	TIMER_Power();
	VBINPUT_Power();

	EventReset();
	IRQ_Asserted = 0;
	RecalcIntLevel();
	VB_V810->Reset();

	VSU_CycleFix = 0;
	WCR = 0;

	ForceEventUpdates(0); //VB_V810->v810_timestamp);
}

/*struct VB_HeaderInfo
{
	char game_title[256];
	uint32 game_code;
	uint16 manf_code;
	uint8 version;
};*/

/*static void ReadHeader(const uint8 *const rom_data, const uint64 rom_size, VB_HeaderInfo *hi)
{
        iconv_t sjis_ict = iconv_open("UTF-8", "shift_jis");

        if (sjis_ict != (iconv_t)-1)
        {
                char *in_ptr, *out_ptr;
                size_t ibl, obl;

                ibl = 20;
                obl = sizeof(hi->game_title) - 1;

                in_ptr = (char *)rom_data + (0xFFFFFDE0 & (rom_size - 1));
                out_ptr = hi->game_title;

                iconv(sjis_ict, (ICONV_CONST char **)&in_ptr, &ibl, &out_ptr, &obl);
                iconv_close(sjis_ict);

                *out_ptr = 0;

                MDFN_zapctrlchars(hi->game_title);
                MDFN_trim(hi->game_title);
        }
        else
                hi->game_title[0] = 0;

        hi->game_code = MDFN_de32lsb(rom_data + (0xFFFFFDFB & (rom_size - 1)));
        hi->manf_code = MDFN_de16lsb(rom_data + (0xFFFFFDF9 & (rom_size - 1)));
        hi->version = rom_data[0xFFFFFDFF & (rom_size - 1)];
}*/

void VB_ExitLoop(void)
{
	VB_V810->Exit();
}

/*MDFNGI EmulatedVB =
    {

        PortInfo,
        Load,
        TestMagic,
        NULL,
        NULL,
        CloseGame,

        SetLayerEnableMask,
        NULL, // Layer names, null-delimited

        NULL,
        NULL,

        VIP_CPInfo,
        1 << 0,

        CheatInfo_Empty,

        false,
        StateAction,
        Emulate,
        NULL,
        VBINPUT_SetInput,
        NULL,
        DoSimpleCommand,
        NULL,
        VBSettings,
        MDFN_MASTERCLOCK_FIXED(VB_MASTER_CLOCK),
        0,
        false, // Multires possible?

        0,    // lcm_width
        0,    // lcm_height
        NULL, // Dummy

        384, // Nominal width
        224, // Nominal height

        384, // Framebuffer width
        256, // Framebuffer height

        2, // Number of output sound channels
};*/
}

using namespace MDFN_IEN_VB;

EXPORT int Load(const uint8 *rom, int length, const NativeSyncSettings *syncSettings)
{
	const uint64 rom_size = length;
	V810_Emu_Mode cpu_mode = V810_EMU_MODE_ACCURATE;

	if (rom_size != round_up_pow2(rom_size))
	{
		return 0;
		// throw MDFN_Error(0, _("VB ROM image size is not a power of 2."));
	}

	if (rom_size < 256)
	{
		return 0;
		//throw MDFN_Error(0, _("VB ROM image size is too small."));
	}

	if (rom_size > (1 << 24))
	{
		return 0;
		//throw MDFN_Error(0, _("VB ROM image size is too large."));
	}

	VB_V810 = new V810();
	VB_V810->Init(cpu_mode, true);

	VB_V810->SetMemReadHandlers(MemRead8, MemRead16, NULL);
	VB_V810->SetMemWriteHandlers(MemWrite8, MemWrite16, NULL);

	VB_V810->SetIOReadHandlers(MemRead8, MemRead16, NULL);
	VB_V810->SetIOWriteHandlers(MemWrite8, MemWrite16, NULL);

	for (int i = 0; i < 256; i++)
	{
		VB_V810->SetMemReadBus32(i, false);
		VB_V810->SetMemWriteBus32(i, false);
	}

	std::vector<uint32> Map_Addresses;

	for (uint64 A = 0; A < 1ULL << 32; A += (1 << 27))
	{
		for (uint64 sub_A = 5 << 24; sub_A < (6 << 24); sub_A += 65536)
		{
			Map_Addresses.push_back(A + sub_A);
		}
	}

	WRAM = VB_V810->SetFastMap(alloc_plain, &Map_Addresses[0], 65536, Map_Addresses.size(), "WRAM");
	Map_Addresses.clear();

	// Round up the ROM size to 65536(we mirror it a little later)
	GPROM_Mask = (rom_size < 65536) ? (65536 - 1) : (rom_size - 1);

	for (uint64 A = 0; A < 1ULL << 32; A += (1 << 27))
	{
		for (uint64 sub_A = 7 << 24; sub_A < (8 << 24); sub_A += GPROM_Mask + 1)
		{
			Map_Addresses.push_back(A + sub_A);
			//printf("%08x\n", (uint32)(A + sub_A));
		}
	}

	GPROM = VB_V810->SetFastMap(alloc_sealed, &Map_Addresses[0], GPROM_Mask + 1, Map_Addresses.size(), "Cart ROM");
	Map_Addresses.clear();

	memcpy(GPROM, rom, rom_size);

	// Mirror ROM images < 64KiB to 64KiB
	for (uint64 i = rom_size; i < 65536; i += rom_size)
	{
		memcpy(GPROM + i, GPROM, rom_size);
	}

	/*VB_HeaderInfo hinfo;

	ReadHeader(GPROM, rom_size, &hinfo);

	MDFN_printf(_("Title:     %s\n"), hinfo.game_title);
	MDFN_printf(_("Game ID Code: %u\n"), hinfo.game_code);
	MDFN_printf(_("Manufacturer Code: %d\n"), hinfo.manf_code);
	MDFN_printf(_("Version:   %u\n"), hinfo.version);

	MDFN_printf(_("ROM:       %uKiB\n"), (unsigned)(rom_size / 1024));
	MDFN_printf(_("ROM MD5:   0x%s\n"), md5_context::asciistr(MDFNGameInfo->MD5, 0).c_str());*/

	/*MDFN_printf("\n");

	MDFN_printf(_("V810 Emulation Mode: %s\n"), (cpu_mode == V810_EMU_MODE_ACCURATE) ? _("Accurate") : _("Fast"));*/

	for (uint64 A = 0; A < 1ULL << 32; A += (1 << 27))
	{
		for (uint64 sub_A = 6 << 24; sub_A < (7 << 24); sub_A += GPRAM_Mask + 1)
		{
			//printf("GPRAM: %08x\n", A + sub_A);
			Map_Addresses.push_back(A + sub_A);
		}
	}

	GPRAM = VB_V810->SetFastMap(alloc_plain, &Map_Addresses[0], GPRAM_Mask + 1, Map_Addresses.size(), "Cart RAM");
	Map_Addresses.clear();

	memset(GPRAM, 0, GPRAM_Mask + 1);

	VIP_Init();
	VB_VSU = new VSU();
	VBINPUT_Init();

	VB3DMode = 0;
	uint32 prescale = 1;
	uint32 sbs_separation = 0;
	bool reverse = false;

	VIP_Set3DMode(VB3DMode, reverse, prescale, sbs_separation);

	VIP_SetParallaxDisable(syncSettings->DisableParallax);

	{
		auto presetColor = 1;

		uint32 lcolor = 0xff0000;
		uint32 rcolor = 0x00ff00;

		if (presetColor != ANAGLYPH_PRESET_DISABLED)
		{
			lcolor = AnaglyphPreset_Colors[presetColor][0];
			rcolor = AnaglyphPreset_Colors[presetColor][1];
		}
		VIP_SetAnaglyphColors(lcolor, rcolor);
		VIP_SetDefaultColor(0xffffff);
	}

	VBINPUT_SetInstantReadHack(syncSettings->InstantReadHack);

	VIP_SetLEDOnScale(1750 / 1000.0);

	VB_Power();

	/*switch (VB3DMode)
                {
                default:
                        break;

                case VB3DMODE_VLI:
                        MDFNGameInfo->nominal_width = 768 * prescale;
                        MDFNGameInfo->nominal_height = 224;
                        MDFNGameInfo->fb_width = 768 * prescale;
                        MDFNGameInfo->fb_height = 224;
                        break;

                case VB3DMODE_HLI:
                        MDFNGameInfo->nominal_width = 384;
                        MDFNGameInfo->nominal_height = 448 * prescale;
                        MDFNGameInfo->fb_width = 384;
                        MDFNGameInfo->fb_height = 448 * prescale;
                        break;

                case VB3DMODE_CSCOPE:
                        MDFNGameInfo->nominal_width = 512;
                        MDFNGameInfo->nominal_height = 384;
                        MDFNGameInfo->fb_width = 512;
                        MDFNGameInfo->fb_height = 384;
                        break;

                case VB3DMODE_SIDEBYSIDE:
                        MDFNGameInfo->nominal_width = 384 * 2 + sbs_separation;
                        MDFNGameInfo->nominal_height = 224;
                        MDFNGameInfo->fb_width = 384 * 2 + sbs_separation;
                        MDFNGameInfo->fb_height = 224;
                        break;
                }
                MDFNGameInfo->lcm_width = MDFNGameInfo->fb_width;
                MDFNGameInfo->lcm_height = MDFNGameInfo->fb_height;*/

	VB_VSU->SetSoundRate(44100);

	return 1;
}

EXPORT void SetSettings(const NativeSettings *settings)
{
	VB3DMode = settings->ThreeDeeMode;
	uint32 prescale = settings->InterlacePrescale;
	uint32 sbs_separation = settings->SideBySideSeparation;
	bool reverse = settings->SwapViews;

	VIP_Set3DMode(VB3DMode, reverse, prescale, sbs_separation);

	{
		auto presetColor = settings->AnaglyphPreset;

		uint32 lcolor = settings->AnaglyphCustomLeftColor;
		uint32 rcolor = settings->AnaglyphCustomRightColor;

		if (presetColor != ANAGLYPH_PRESET_DISABLED)
		{
			lcolor = AnaglyphPreset_Colors[presetColor][0];
			rcolor = AnaglyphPreset_Colors[presetColor][1];
		}
		VIP_SetAnaglyphColors(lcolor, rcolor);
		VIP_SetDefaultColor(settings->NonAnaglyphColor);
	}

	VIP_SetLEDOnScale(settings->LedOnScale / 1000.0);
}

EXPORT void GetMemoryAreas(MemoryArea *m)
{
	m[0].Data = WRAM;
	m[0].Name = "WRAM";
	m[0].Size = 65536;
	m[0].Flags = MEMORYAREA_FLAGS_WRITABLE | MEMORYAREA_FLAGS_PRIMARY | MEMORYAREA_FLAGS_WORDSIZE4;

	m[1].Data = GPRAM;
	m[1].Name = "CARTRAM";
	m[1].Size = GPRAM_Mask + 1;
	m[1].Flags = MEMORYAREA_FLAGS_WRITABLE | MEMORYAREA_FLAGS_SAVERAMMABLE | MEMORYAREA_FLAGS_WORDSIZE4;

	m[2].Data = GPROM;
	m[2].Name = "ROM";
	m[2].Size = GPROM_Mask + 1;
	m[2].Flags = MEMORYAREA_FLAGS_WORDSIZE4;
}

EXPORT void FrameAdvance(MyFrameInfo *frame)
{
	v810_timestamp_t v810_timestamp;
	lagged = true;

	VBINPUT_Frame(&frame->Buttons);

	VIP_StartFrame(frame);

	v810_timestamp = VB_V810->Run(EventHandler);

	FixNonEvents();
	ForceEventUpdates(v810_timestamp);

	frame->Samples = VB_VSU->EndFrame((v810_timestamp + VSU_CycleFix) >> 2, frame->SoundBuffer, 8192);

	VSU_CycleFix = (v810_timestamp + VSU_CycleFix) & 3;

	frame->Cycles = v810_timestamp;
	frame->Lagged = lagged;

	TIMER_ResetTS();
	VBINPUT_ResetTS();
	VIP_ResetTS();

	RebaseTS(v810_timestamp);

	VB_V810->ResetTS(0);
}

EXPORT void PredictFrameSize(MyFrameInfo *frame)
{
	VIP_CalcFrameSize(frame);
}

EXPORT void HardReset()
{
	VB_Power();
}

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

int main()
{
	return 0;
}
