#include "udpl_dll_imp.h"
#include "ym2610_ft232h.h"
#include "command_list.h"

// ***********************************************************************************************
// YM2610
// ***********************************************************************************************
uint32_t YM2610_INIT_REGISTER[][2] =
{
	{ 0x0028, 0x00 },	// FM mute
	{ 0x0028, 0x01 },
	{ 0x0028, 0x02 },
	{ 0x0028, 0x04 },
	{ 0x0028, 0x05 },
	{ 0x0028, 0x06 },
	{ 0x0008, 0x00 },	// SSG mute
	{ 0x0009, 0x00 },
	{ 0x000a, 0x00 },
	{ 0x0010, 0x01 },	// ADPCM-B reset
	{ 0x001b, 0x00 },	// ADPCM-B mute
	{ 0x0100, 0x00 },	// ADPCM-A all
	{ 0x0101, 0x00 },	// ADPCM-A mute
	{ 0x0108, 0x00 },	// ADPCM-A 0 mute	
	{ 0x0109, 0x00 },	// ADPCM-A 1 mute	
	{ 0x010a, 0x00 },	// ADPCM-A 2 mute	
	{ 0x010b, 0x00 },	// ADPCM-A 3 mute	
	{ 0x010c, 0x00 },	// ADPCM-A 4 mute	
	{ 0x010d, 0x00 },	// ADPCM-A 5 mute	

	{ 0x0000, 0x00 },	// SSG
	{ 0x0001, 0x00 },
	{ 0x0002, 0x00 },
	{ 0x0003, 0x00 },
	{ 0x0004, 0x00 },
	{ 0x0005, 0x00 },
	{ 0x0006, 0x00 },
	{ 0x0007, 0x00 },
	{ 0x000b, 0x00 },
	{ 0x000c, 0x00 },
	{ 0x000d, 0x00 },
	{ 0x000e, 0x00 },
	{ 0x000f, 0x00 },

	{ 0x0011, 0x00 },	// ADPCM-B
	{ 0x0012, 0x00 },
	{ 0x0013, 0x00 },
	{ 0x0014, 0x00 },
	{ 0x0015, 0x00 },
	{ 0x0019, 0x00 },
	{ 0x001a, 0x00 },
	{ 0x001c, 0x00 },

	{ 0x0022, 0x00 },	// FM
	{ 0x0024, 0x00 },
	{ 0x0025, 0x00 },
	{ 0x0026, 0x00 },
	{ 0x0027, 0x00 },

	{ 0x0030, 0x00 },
	{ 0x0031, 0x00 },
	{ 0x0032, 0x00 },
	{ 0x0034, 0x00 },
	{ 0x0035, 0x00 },
	{ 0x0036, 0x00 },
	{ 0x0038, 0x00 },
	{ 0x0039, 0x00 },
	{ 0x003a, 0x00 },
	{ 0x003c, 0x00 },
	{ 0x003d, 0x00 },
	{ 0x003e, 0x00 },
	{ 0x0040, 0x00 },
	{ 0x0041, 0x00 },
	{ 0x0042, 0x00 },
	{ 0x0044, 0x00 },
	{ 0x0045, 0x00 },
	{ 0x0046, 0x00 },
	{ 0x0048, 0x00 },
	{ 0x0049, 0x00 },
	{ 0x004a, 0x00 },
	{ 0x004c, 0x00 },
	{ 0x004d, 0x00 },
	{ 0x004e, 0x00 },
	{ 0x0050, 0x00 },
	{ 0x0051, 0x00 },
	{ 0x0052, 0x00 },
	{ 0x0054, 0x00 },
	{ 0x0055, 0x00 },
	{ 0x0056, 0x00 },
	{ 0x0058, 0x00 },
	{ 0x0059, 0x00 },
	{ 0x005a, 0x00 },
	{ 0x005c, 0x00 },
	{ 0x005d, 0x00 },
	{ 0x005e, 0x00 },
	{ 0x0060, 0x00 },
	{ 0x0061, 0x00 },
	{ 0x0062, 0x00 },
	{ 0x0064, 0x00 },
	{ 0x0065, 0x00 },
	{ 0x0066, 0x00 },
	{ 0x0068, 0x00 },
	{ 0x0069, 0x00 },
	{ 0x006a, 0x00 },
	{ 0x006c, 0x00 },
	{ 0x006d, 0x00 },
	{ 0x006e, 0x00 },
	{ 0x0070, 0x00 },
	{ 0x0071, 0x00 },
	{ 0x0072, 0x00 },
	{ 0x0074, 0x00 },
	{ 0x0075, 0x00 },
	{ 0x0076, 0x00 },
	{ 0x0078, 0x00 },
	{ 0x0079, 0x00 },
	{ 0x007a, 0x00 },
	{ 0x007c, 0x00 },
	{ 0x007d, 0x00 },
	{ 0x007e, 0x00 },
	{ 0x0080, 0x00 },
	{ 0x0081, 0x00 },
	{ 0x0082, 0x00 },
	{ 0x0084, 0x00 },
	{ 0x0085, 0x00 },
	{ 0x0086, 0x00 },
	{ 0x0088, 0x00 },
	{ 0x0089, 0x00 },
	{ 0x008a, 0x00 },
	{ 0x008c, 0x00 },
	{ 0x008d, 0x00 },
	{ 0x008e, 0x00 },
	{ 0x0090, 0x00 },
	{ 0x0091, 0x00 },
	{ 0x0092, 0x00 },
	{ 0x0094, 0x00 },
	{ 0x0095, 0x00 },
	{ 0x0096, 0x00 },
	{ 0x0098, 0x00 },
	{ 0x0099, 0x00 },
	{ 0x009a, 0x00 },
	{ 0x009c, 0x00 },
	{ 0x009d, 0x00 },
	{ 0x009e, 0x00 },
	{ 0x00a0, 0x00 },
	{ 0x00a1, 0x00 },
	{ 0x00a2, 0x00 },
	{ 0x00a4, 0x00 },
	{ 0x00a5, 0x00 },
	{ 0x00a6, 0x00 },
	{ 0x00a8, 0x00 },
	{ 0x00a9, 0x00 },
	{ 0x00aa, 0x00 },
	{ 0x00ac, 0x00 },
	{ 0x00ad, 0x00 },
	{ 0x00ae, 0x00 },
	{ 0x00b0, 0x00 },
	{ 0x00b1, 0x00 },
	{ 0x00b2, 0x00 },
	{ 0x00b4, 0x00 },
	{ 0x00b5, 0x00 },
	{ 0x00b6, 0x00 },

	{ 0x0130, 0x00 },
	{ 0x0131, 0x00 },
	{ 0x0132, 0x00 },
	{ 0x0134, 0x00 },
	{ 0x0135, 0x00 },
	{ 0x0136, 0x00 },
	{ 0x0138, 0x00 },
	{ 0x0139, 0x00 },
	{ 0x013a, 0x00 },
	{ 0x013c, 0x00 },
	{ 0x013d, 0x00 },
	{ 0x013e, 0x00 },
	{ 0x0140, 0x00 },
	{ 0x0141, 0x00 },
	{ 0x0142, 0x00 },
	{ 0x0144, 0x00 },
	{ 0x0145, 0x00 },
	{ 0x0146, 0x00 },
	{ 0x0148, 0x00 },
	{ 0x0149, 0x00 },
	{ 0x014a, 0x00 },
	{ 0x014c, 0x00 },
	{ 0x014d, 0x00 },
	{ 0x014e, 0x00 },
	{ 0x0150, 0x00 },
	{ 0x0151, 0x00 },
	{ 0x0152, 0x00 },
	{ 0x0154, 0x00 },
	{ 0x0155, 0x00 },
	{ 0x0156, 0x00 },
	{ 0x0158, 0x00 },
	{ 0x0159, 0x00 },
	{ 0x015a, 0x00 },
	{ 0x015c, 0x00 },
	{ 0x015d, 0x00 },
	{ 0x015e, 0x00 },
	{ 0x0160, 0x00 },
	{ 0x0161, 0x00 },
	{ 0x0162, 0x00 },
	{ 0x0164, 0x00 },
	{ 0x0165, 0x00 },
	{ 0x0166, 0x00 },
	{ 0x0168, 0x00 },
	{ 0x0169, 0x00 },
	{ 0x016a, 0x00 },
	{ 0x016c, 0x00 },
	{ 0x016d, 0x00 },
	{ 0x016e, 0x00 },
	{ 0x0170, 0x00 },
	{ 0x0171, 0x00 },
	{ 0x0172, 0x00 },
	{ 0x0174, 0x00 },
	{ 0x0175, 0x00 },
	{ 0x0176, 0x00 },
	{ 0x0178, 0x00 },
	{ 0x0179, 0x00 },
	{ 0x017a, 0x00 },
	{ 0x017c, 0x00 },
	{ 0x017d, 0x00 },
	{ 0x017e, 0x00 },
	{ 0x0180, 0x00 },
	{ 0x0181, 0x00 },
	{ 0x0182, 0x00 },
	{ 0x0184, 0x00 },
	{ 0x0185, 0x00 },
	{ 0x0186, 0x00 },
	{ 0x0188, 0x00 },
	{ 0x0189, 0x00 },
	{ 0x018a, 0x00 },
	{ 0x018c, 0x00 },
	{ 0x018d, 0x00 },
	{ 0x018e, 0x00 },
	{ 0x0190, 0x00 },
	{ 0x0191, 0x00 },
	{ 0x0192, 0x00 },
	{ 0x0194, 0x00 },
	{ 0x0195, 0x00 },
	{ 0x0196, 0x00 },
	{ 0x0198, 0x00 },
	{ 0x0199, 0x00 },
	{ 0x019a, 0x00 },
	{ 0x019c, 0x00 },
	{ 0x019d, 0x00 },
	{ 0x019e, 0x00 },
	{ 0x01a0, 0x00 },
	{ 0x01a1, 0x00 },
	{ 0x01a2, 0x00 },
	{ 0x01a4, 0x00 },
	{ 0x01a5, 0x00 },
	{ 0x01a6, 0x00 },
	{ 0x01a8, 0x00 },
	{ 0x01a9, 0x00 },
	{ 0x01aa, 0x00 },
	{ 0x01ac, 0x00 },
	{ 0x01ad, 0x00 },
	{ 0x01ae, 0x00 },
	{ 0x01b0, 0x00 },
	{ 0x01b1, 0x00 },
	{ 0x01b2, 0x00 },
	{ 0x01b4, 0x00 },
	{ 0x01b5, 0x00 },
	{ 0x01b6, 0x00 },

	{ 0x0110, 0x00 },	// adpcm A
	{ 0x0111, 0x00 },
	{ 0x0112, 0x00 },
	{ 0x0113, 0x00 },
	{ 0x0114, 0x00 },
	{ 0x0115, 0x00 },
	{ 0x0118, 0x00 },
	{ 0x0119, 0x00 },
	{ 0x011a, 0x00 },
	{ 0x011b, 0x00 },
	{ 0x011c, 0x00 },
	{ 0x011d, 0x00 },
	{ 0x0120, 0x00 },
	{ 0x0121, 0x00 },
	{ 0x0122, 0x00 },
	{ 0x0123, 0x00 },
	{ 0x0124, 0x00 },
	{ 0x0125, 0x00 },
	{ 0x0128, 0x00 },
	{ 0x0129, 0x00 },
	{ 0x012a, 0x00 },
	{ 0x012b, 0x00 },
	{ 0x012c, 0x00 },
	{ 0x012d, 0x00 }
};

// 00-0F SSG 
// 10-28 ADPCM-B
// 30-B6 FM
//
// 100-12D ADPCM-A
// 130-1B6 FM
//
bool __stdcall UdplChipYM2610_H::setRegister(uint32_t addr, uint32_t data)
{
	uint8_t *p = send_command;
	memcpy(p, COMMAND_SET_REG, sizeof(COMMAND_SET_REG));
	p += sizeof(COMMAND_SET_REG);
	uint8_t addr8 = addr & 0xff;
	uint8_t addra = addr >> 8;
	*p++ = (0x01 << addra) | slotno_bit;
	*p++ = addr8;
	*p++ = data;

	//if ((addr & 0xfff0) == 0) {	// SSG
	//	*p++ = wait0;	// address wait
	//	*p++ = wait0;	// data wait
	//} else {
	//	*p++ = wait17;	// address wait
	//	if (addr == 0x0100) {	// ADPCM-A key on
	//		*p++ = wait_pcma_data;	// data wait == rythym
	//	} else if ((addr8 & 0xa0) == 0xa0) {
	//		*p++ = wait47;	// data wait FM
	//	} else {
	//		*p++ = wait83;	// data wait FM, ADPCM
	//	}
	//}
	
	if (ftvalid) {	// FT232H をオープンしている

		*p++ = wait17;	// address wait

		if (addr == 0x0100) {	// ADPCM-A key on
			*p++ = wait_pcma_data;	// data wait == rythym
		} else if (addr == 0x0010) {
			*p++ = wait_pcmb_data;
		} else {
			*p++ = wait83;	// data wait FM, ADPCM
		}

	} else {	// FT232H をオープンできてない -> ADPCM が転送されていない
		if (addr == 0x0100 || addr == 0x0010) {
			return true;		// ADPCM は鳴らさない
		}
		*p++ = wait17;	// address wait
		*p++ = wait83;	// data wait FM, ADPCM
	}

	{
		std::lock_guard<std::mutex> lock(ftdi_m);	// ADPCM 転送中は止めておく
		psockIf->pushData(send_command, COMMAND_SET_REG[0] + 1);
		sendStat.store(true, std::memory_order_release);
	}

	return true;
}

bool __stdcall UdplChipYM2610_H::writeBus(uint8_t abit, uint8_t data)
{
	return true;
}

uint32_t __stdcall UdplChipYM2610_H::getRegister(uint32_t addr)
{
	// ステータスのみ
	uint32_t ret = 0;
	uint8_t *p = send_command;
	memcpy(p, COMMAND_GET_REG, sizeof(COMMAND_GET_REG));
	p += sizeof(COMMAND_GET_REG);

	if ((addr & 0xFFFFF0) == 0) {
		*p++ = ((0x01 << (addr >> 8)) | 0x01) | slotno_bit;	// SSG レジスタリード
	} else {
		*p++ = ((0x01 << (addr >> 8)) & 0x0E) | slotno_bit;	// ステータスリード
	}
	*p++ = addr & 0xff;
	*p++ = wait17;
	if (psockIf->pushRecv(send_command, COMMAND_GET_REG[0] + 1, recv_command, sizeof(recv_command))) {
		ret = recv_command[recv_command[0]];
	}

	return ret;
}

bool __stdcall UdplChipYM2610_H::setPcmData(void *pdat)
{
	bool ret = false;
	YM2610_PCMDATA *pcminfo = (YM2610_PCMDATA*)pdat;

	if (pcminfo->ch < 2 && pcminfo->pdata && pcminfo->size != 0 && pcminfo->size <= ADPCM_SIZE && pcminfo->offset < ADPCM_SIZE) {
		
		if (sendth[pcminfo->ch].joinable()) {
			sendth[pcminfo->ch].join();
		}
		{
			std::lock_guard<std::mutex> lock(pcmbuf_m[pcminfo->ch]);
			memcpy(pcmbuf[pcminfo->ch], pcminfo->pdata, pcminfo->size);
			pcmbuf_len[pcminfo->ch] = pcminfo->size;
		}
		sendth[pcminfo->ch] = std::thread(&UdplChipYM2610_H::sendPcmBuf, this, pcminfo->ch, pcminfo->offset);
		
		/*
		// offset デバッグ
		uint32_t len = 1024 * 100;	// 100k
		uint32_t remain = pcminfo->size;
		uint32_t send = 0;
		uint32_t done = 0;

		while (remain != 0) {
			if (sendth[pcminfo->ch].joinable()) {
				sendth[pcminfo->ch].join();
			}

			send = len > remain ? remain : len;

			{
				std::lock_guard<std::mutex> lock(pcmbuf_m[pcminfo->ch]);
				memcpy(pcmbuf[pcminfo->ch], pcminfo->pdata+done, send);
				pcmbuf_len[pcminfo->ch] = send;
			}
			sendth[pcminfo->ch] = std::thread(&UdplChipYM2610_H::sendPcmBuf, this, pcminfo->ch, done);
			remain -= send;
			done += send;
		}
		*/
		ret = true;
	}
	return ret;
}

// アドレスウェイト、データウェイトの値の計算
void UdplChipYM2610_H::setWaits(uint32_t clk)
{
	wait17 = (uint8_t)((17.0f * 1000000 / (float)clk) + 0.99f);
	wait83 = (uint8_t)((83.0f * 1000000 / (float)clk) + 0.99f);
	wait47 = (uint8_t)((47.0f * 1000000 / (float)clk) + 0.99f);
	wait0 = wait17;
	wait_pcma_data = (uint8_t)((576.0f * 1000000 / (float)clk) + 0.99f); // YM2608 のリズムと同じ
	wait_pcmb_data = wait83 + 2;	// ちょっとウェイトかける
};

void UdplChipYM2610_H::initRegs(void)
{
	unsigned int sum = 0;
	for (unsigned int i = 0; i < _countof(YM2610_INIT_REGISTER); ++i) {
		setRegister(YM2610_INIT_REGISTER[i][0], YM2610_INIT_REGISTER[i][1]);
		sum += COMMAND_SET_REG[0] + 1;
		if (sum > max_send_size) {	// でかい
			psockIf->waitBufClear();
			sum = 0;
		}
	}
}

void UdplChipYM2610_H::openDevice(void)
{
	bool ret = false;

	// ftdi dll 用
	std::string serial = pchipInfo->extinfo;	// FT232H シリアルNO
	FT_DEVICE ftDevice;
	DWORD deviceId;
	char serialNum[16] = { '\0' };
	char desc[64] = { '\0' };
	FT_STATUS ft_ret = FT_OK;

	// dll ロード用
	std::tstring dllPath;	// ftdi dll のフルパス
	TCHAR sysdir[_MAX_PATH] = { _T('\0') };		// windows システム dir

	GetSystemDirectory(sysdir, _countof(sysdir));	// windows system ディレクトリ
	dllPath = sysdir;
	dllPath += _T("\\");
	dllPath += ftdiName;	// dll のフルパス作成

	hFtModule = LoadLibrary(dllPath.c_str());	// dll ロード
	if (hFtModule) {
		// 公開関数のポインタを取得する
		FT_Close = (_FT_Close)GetProcAddress(hFtModule, "FT_Close");
		FT_Write = (_FT_Write)GetProcAddress(hFtModule, "FT_Write");
		FT_ResetDevice = (_FT_ResetDevice)GetProcAddress(hFtModule, "FT_ResetDevice");
		FT_Purge = (_FT_Purge)GetProcAddress(hFtModule, "FT_Purge");
		FT_SetBitMode = (_FT_SetBitMode)GetProcAddress(hFtModule, "FT_SetBitMode");
		FT_GetBitMode = (_FT_GetBitMode)GetProcAddress(hFtModule, "FT_GetBitMode");
		FT_SetBaudRate = (_FT_SetBaudRate)GetProcAddress(hFtModule, "FT_SetBaudRate");
		FT_SetTimeouts = (_FT_SetTimeouts)GetProcAddress(hFtModule, "FT_SetTimeouts");
		FT_GetDeviceInfo = (_FT_GetDeviceInfo)GetProcAddress(hFtModule, "FT_GetDeviceInfo");
		FT_OpenEx = (_FT_OpenEx)GetProcAddress(hFtModule, "FT_OpenEx");
		FT_SetLatencyTimer = (_FT_SetLatencyTimer)GetProcAddress(hFtModule, "FT_SetLatencyTimer");

		ft_ret = FT_OpenEx(const_cast<char*>(serial.c_str()), FT_OPEN_BY_SERIAL_NUMBER, &hFtd2);

		if (ft_ret == FT_OK) {	// デバイスハンドル取得できた
			ft_ret = FT_GetDeviceInfo(hFtd2, &ftDevice, &deviceId, serialNum, desc, NULL);
			if (ft_ret == FT_OK) {
				if (ftDevice == FT_DEVICE_232H) {	// FT232H
					ft_ret = FT_ResetDevice(hFtd2);
					ft_ret = FT_Purge(hFtd2, FT_PURGE_RX | FT_PURGE_TX);
					ft_ret = FT_SetTimeouts(hFtd2, 0, 5000);	// write 5[s] timeout
					ft_ret = FT_SetBaudRate(hFtd2, 480000000);	// 480Mbps
					ft_ret = FT_SetLatencyTimer(hFtd2, 1);		// latency 1[ms]
					ret = true;
				}
				else {
					FT_Close(hFtd2);
					FreeLibrary(hFtModule);
					hFtModule = nullptr;
				}
			}
			else {
				FT_Close(hFtd2);
				FreeLibrary(hFtModule);
				hFtModule = nullptr;
			}
		}
		else {
			FreeLibrary(hFtModule);
			hFtModule = nullptr;
		}
	}

	ftvalid = ret;
}

void UdplChipYM2610_H::closeDevice(void)
{
	if (ftvalid) {
		FT_Close(hFtd2);
		FreeLibrary(hFtModule);
		hFtModule = nullptr;
		ftvalid = false;
	}
}

void UdplChipYM2610_H::sendPcmBuf(uint32_t ch, uint32_t offset)
{
	if (!ftvalid) {	// open できてない
		return;
	}

	FT_STATUS ret;
	DWORD doneLen;	// 転送したバイト数
	UCHAR bitMode;	// CBUS bitbang で受信する値

	uint8_t mode[4] = { 0 };	// adpcm ch 及びオフセット
	DWORD sendlen = 0;
	{
		std::lock_guard<std::mutex> lock(ftdi_m);	// ft232h デバイスを専有する

		//
		// ADPCM チャンネルを選択する
		//
		// SEL = 1
		ret = FT_SetBitMode(hFtd2, 0xd1, FT_BITMODE_CBUS_BITBANG);		// CBUS BIT BANG, CBUS5(OUTPUT) is HI, CBUS6 is INPUT
		std::this_thread::sleep_for(std::chrono::milliseconds(CBUS_WAIT));	// 最低 1ms は待つ

		if (offset == 0) {	// offset 指定なし
			mode[0] = ch & 0xff;	// adpcm channel
			sendlen = 1;
		} else {
			mode[0] = ch & 0xff;	// adpcm channel
			mode[1] = offset & 0xff;
			mode[2] = (offset >> 8) & 0xff;
			mode[3] = (offset >> 16) & 0xff;
			sendlen = 4;
		}

		doneLen = 0;
		bitMode = 0;
		ret = FT_Write(hFtd2, mode, sendlen, &doneLen);	// ADPCM チャンネル及びオフセットを送信
		do {
			ret = FT_GetBitMode(hFtd2, &bitMode);		// Bit Bang READ
		} while ((bitMode & 0x02) == 0);				// rxf == 1 を待つ

		// SEL = 0
		ret = FT_SetBitMode(hFtd2, 0xd0, FT_BITMODE_CBUS_BITBANG);		// CBUS BIT BANG, CBUS5 is LO
		std::this_thread::sleep_for(std::chrono::milliseconds(CBUS_WAIT));	// 最低 1ms は待つ

		//
		// ADPCM データ送信
		//
		doneLen = 0;
		bitMode = 0;
		{	// SDRAM への書き込み時間でウエイトをとれるので問題ない
			std::lock_guard<std::mutex> lock(pcmbuf_m[ch]);
			ret = FT_Write(hFtd2, pcmbuf[ch], pcmbuf_len[ch], &doneLen);		// ADPCM DATA 送信
		}
		do {
			ret = FT_GetBitMode(hFtd2, &bitMode);	// Bit Bang READ
		} while ((bitMode & 0x02) == 0);			// rxf == 1 を待つ	

		if (pcmbuf_len[ch] != doneLen) {
			return;		// 正しく書き込めなかった
		}
	}
}