#pragma once
#include "types.h"
#include "maple_cfg.h"
#include "maple_helper.h"
#include <cmath>
#include "input/gamepad.h"
#include "serialize.h"
#include "hw/hwreg.h"

#include <memory>
#include <vector>

enum MapleFunctionID
{
	MFID_0_Input       = 0x01000000, //DC Controller, Lightgun buttons, arcade stick .. stuff like that
	MFID_1_Storage     = 0x02000000, //VMU , VMS
	MFID_2_LCD         = 0x04000000, //VMU
	MFID_3_Clock       = 0x08000000, //VMU
	MFID_4_Mic         = 0x10000000, //DC Mic (, dreameye too ?)
	MFID_5_ARGun       = 0x20000000, //Artificial Retina gun ? seems like this one was never developed or smth -- I only remember of lightguns
	MFID_6_Keyboard    = 0x40000000, //DC Keyboard
	MFID_7_LightGun    = 0x80000000, //DC Lightgun
	MFID_8_Vibration   = 0x00010000, //Puru Puru Puur~~~
	MFID_9_Mouse       = 0x00020000, //DC Mouse
	MFID_10_StorageExt = 0x00040000, //Storage ? probably never used
	MFID_11_Camera     = 0x00080000, //DreamEye
};

enum MapleDeviceCommand
{
	MDC_DeviceRequest   = 0x01, //7 words.Note : Initialises device
	MDC_AllStatusReq    = 0x02, //7 words + device dependent ( seems to be 8 words)
	MDC_DeviceReset     = 0x03, //0 words
	MDC_DeviceKill      = 0x04, //0 words
	MDC_DeviceStatus    = 0x05, //Same as MDC_DeviceRequest ?
	MDC_DeviceAllStatus = 0x06, //Same as MDC_AllStatusReq ?

	//Various Functions
	MDCF_GetCondition   = 0x09, //FT
	MDCF_GetMediaInfo   = 0x0A, //FT,PT,3 pad
	MDCF_BlockRead      = 0x0B, //FT,PT,Phase,Block #
	MDCF_BlockWrite     = 0x0C, //FT,PT,Phase,Block #,data ...
	MDCF_GetLastError   = 0x0D, //FT,PT,Phase,Block #
	MDCF_SetCondition   = 0x0E, //FT,data ...
	MDCF_MICControl     = 0x0F, //FT,MIC data ...
	MDCF_ARGunControl   = 0x10, //FT,AR-Gun data ...

	MDC_JVSUploadFirmware = 0x80, // JVS bridge firmware
	MDC_JVSGetId		 = 0x82,
	MDC_JVSSelfTest		 = 0x84, // JVS Self Test
	MDC_JVSCommand		 = 0x86, // JVS I/O
};

enum MapleDeviceRV
{
	MDRS_JVSNone		 = 0x00, // No reply, used for multiple JVS I/O boards

	MDRS_DeviceStatus    = 0x05, //28 words
	MDRS_DeviceStatusAll = 0x06, //28 words + device dependent data
	MDRS_DeviceReply     = 0x07, //0 words
	MDRS_DataTransfer    = 0x08, //FT,depends on the command

	MDRE_UnknownFunction = 0xFE, //0 words
	MDRE_UnknownCmd      = 0xFD, //0 words
	MDRE_TransmitAgain   = 0xFC, //0 words
	MDRE_FileError       = 0xFB, //1 word, bitfield
	MDRE_LCDError        = 0xFA, //1 word, bitfield
	MDRE_ARGunError      = 0xF9, //1 word, bitfield
	MDRS_JVSSelfTestReply= 0x85, // JVS I/O SelfTest

	MDRS_JVSReply		 = 0x87, // JVS I/O
};

enum NAOMI_KEYS
{
	NAOMI_START_KEY = 1 << 15,
	NAOMI_SERVICE_KEY = 1 << 14,

	NAOMI_UP_KEY = 1 << 13,
	NAOMI_DOWN_KEY = 1 << 12,
	NAOMI_LEFT_KEY = 1 << 11,
	NAOMI_RIGHT_KEY = 1 << 10,

	NAOMI_BTN0_KEY = 1 << 9,
	NAOMI_BTN1_KEY = 1 << 8,
	NAOMI_BTN2_KEY = 1 << 7,
	NAOMI_BTN3_KEY = 1 << 6,
	NAOMI_BTN4_KEY = 1 << 5,
	NAOMI_BTN5_KEY = 1 << 4,
	NAOMI_BTN6_KEY = 1 << 3,
	NAOMI_BTN7_KEY = 1 << 2,

	// Not actual JVS buttons
	NAOMI_BTN8_KEY = 1 << 16,

	NAOMI_TEST_KEY = 1 << 18,
	NAOMI_COIN_KEY = 1 << 19,
	NAOMI_RELOAD_KEY = 1 << 17,
};

enum AWAVE_KEYS
{
	AWAVE_START_KEY = 1 << 3,

	AWAVE_BTN0_KEY  = 1 << 2,
	AWAVE_BTN1_KEY  = 1 << 1,
	AWAVE_BTN2_KEY  = 1 << 0,
	AWAVE_BTN3_KEY  = 1 << 10,
	AWAVE_BTN4_KEY  = 1 << 9,

	AWAVE_UP_KEY    = 1 << 4,
	AWAVE_DOWN_KEY  = 1 << 5,
	AWAVE_LEFT_KEY  = 1 << 6,
	AWAVE_RIGHT_KEY = 1 << 7,

	AWAVE_SERVICE_KEY = 1 << 13,
	AWAVE_TEST_KEY 	  = 1 << 14,

	// Not actual AW buttons
	AWAVE_COIN_KEY    = 1 << 16,
	AWAVE_TRIGGER_KEY = 1 << 17,
};

struct maple_device : public std::enable_shared_from_this<maple_device>
{
	u8 maple_port;          //raw maple port
	u8 bus_port;            //0 .. 5
	u8 bus_id;              //0 .. 3
	u8 player_num;			// for Atomiswave
	char logical_port[3];  //A0, etc
	MapleConfigMap* config;

	//fill in the info
	void Setup(u32 bus, u32 port = 5, int playerNum = -1);

	virtual void OnSetup() {};
	virtual ~maple_device();

	virtual u32 RawDma(u32* buffer_in, u32 buffer_in_len, u32* buffer_out) = 0;

	virtual void serialize(Serializer& ser) const {
		ser << player_num;
	}
	virtual void deserialize(Deserializer& deser) {
		deser >> player_num;
	}

	virtual MapleDeviceType get_device_type() = 0;
	virtual bool get_lightgun_pos() { return false; }
	virtual const void *getData(size_t& size) const { size = 0; return nullptr; }
};

std::shared_ptr<maple_device> maple_Create(MapleDeviceType type);

#define MAPLE_PORTS 4

template<int Magnitude>
void limit_joystick_magnitude(s8& joyx, s8& joyy)
{
	float mag = (float)joyx * joyx + (float)joyy * joyy;
	if (mag > (float)Magnitude * Magnitude)
	{
		mag = sqrtf(mag) / (float)Magnitude;
		joyx = (s8)lroundf(joyx / mag);
		joyy = (s8)lroundf(joyy / mag);
	}
}

extern u8 *EEPROM;

#define SWAP32(a) ((((a) & 0xff) << 24)  | (((a) & 0xff00) << 8) | (((a) >> 8) & 0xff00) | (((a) >> 24) & 0xff))

const char *GetCurrentGameButtonName(DreamcastKey key);
const char *GetCurrentGameAxisName(DreamcastKey axis);

/*
	Base class with dma helpers and stuff
*/
struct maple_base: maple_device
{
	u8* dma_buffer_out;
	u32* dma_count_out;

	u8* dma_buffer_in;
	u32 dma_count_in;

	void w8(u8 data) { *(u8*)dma_buffer_out = data; dma_buffer_out += 1; dma_count_out[0] += 1; }
	void w16(u16 data) { *(u16*)dma_buffer_out = data; dma_buffer_out += 2; dma_count_out[0] += 2; }
	void w32(u32 data) { *(u32*)dma_buffer_out = data; dma_buffer_out += 4; dma_count_out[0] += 4; }

	void wptr(const void* src, u32 len)
	{
		u8* src8 = (u8*)src;
		while (len--)
			w8(*src8++);
	}

	void wstr(const char* str, u32 len)
	{
		u32 ln = (u32)strlen(str);
		verify(len >= ln);
		len -= ln;
		while (ln--)
			w8(*str++);

		while (len--)
			w8(' ');
	}

	u8 r8() { u8  rv = *(u8*)dma_buffer_in; dma_buffer_in += 1; dma_count_in -= 1; return rv; }
	u16 r16() { u16 rv = *(u16*)dma_buffer_in; dma_buffer_in += 2; dma_count_in -= 2; return rv; }
	u32 r32() { u32 rv = *(u32*)dma_buffer_in; dma_buffer_in += 4; dma_count_in -= 4; return rv; }
	void skip(u32 len) {
		dma_buffer_in += len;
		dma_count_in -= len;
	}

	void rptr(void* dst, u32 len)
	{
		u8* dst8 = (u8*)dst;
		while (len--)
			*dst8++ = r8();
	}
	u32 r_count() { return dma_count_in; }

	u32 Dma(u32 Command, u32* buffer_in, u32 buffer_in_len, u32* buffer_out, u32& buffer_out_len)
	{
		dma_buffer_out = (u8*)buffer_out;
		dma_count_out = &buffer_out_len;

		dma_buffer_in = (u8*)buffer_in;
		dma_count_in = buffer_in_len;

		return dma(Command);
	}
	virtual u32 dma(u32 cmd) = 0;

	u32 RawDma(u32* buffer_in, u32 buffer_in_len, u32* buffer_out) override
	{
		u32 command=buffer_in[0] &0xFF;
		//Recipient address
		u32 reci = (buffer_in[0] >> 8) & 0xFF;
		//Sender address
		u32 send = (buffer_in[0] >> 16) & 0xFF;
		u32 outlen = 0;
		u32 resp = Dma(command, &buffer_in[1], buffer_in_len - 4, &buffer_out[1], outlen);

		if (reci & 0x20)
			reci |= maple_GetAttachedDevices(bus_id);

		verify(u8(outlen / 4) * 4 == outlen);
		buffer_out[0] = (resp << 0 ) | (send << 8) | (reci << 16) | ((outlen / 4) << 24);

		return outlen + 4;
	}
};

class jvs_io_board;

struct maple_naomi_jamma : maple_base, SerialPort
{
	static constexpr u8 ALL_NODES = 0xff;

	std::vector<std::unique_ptr<jvs_io_board>> io_boards;
	bool crazy_mode = false;
	bool hotd2p = false;

	u8 jvs_repeat_request[32][256];
	u8 jvs_receive_buffer[32][258];
	u32 jvs_receive_length[32] = { 0 };
	u8 eeprom[128];

	maple_naomi_jamma();
	~maple_naomi_jamma() override;

	MapleDeviceType get_device_type() override
	{
		return MDT_NaomiJamma;
	}

	u8 sense_line(u32 node_id)
	{
		bool last_node = node_id == io_boards.size();
		return last_node ? 0x8E : 0x8F;
	}

	void send_jvs_message(u32 node_id, u32 channel, u32 length, u8 *data);
	void send_jvs_messages(u32 node_id, u32 channel, bool use_repeat, u32 length, u8 *data, bool repeat_first);
	bool receive_jvs_messages(u32 channel);

	void handle_86_subcommand();

	u32 RawDma(u32* buffer_in, u32 buffer_in_len, u32* buffer_out) override;
	u32 dma(u32 cmd) override { return 0; }

	void serialize(Serializer& ser) const override;
	void deserialize(Deserializer& deser) override;

	void setPipe(Pipe *pipe) override {
		serialPipe = pipe;
	}
	void updateStatus() override {}

	Pipe *serialPipe = nullptr;
};
