#include <tchar.h>
#include "udpl_dll_imp.h"
#include "i2c_list.h"

// **************************************************************************
// インスタンスの初期化を行う
// **************************************************************************
void __stdcall UdplManagerImp::initialize(void)
{
	// tap switch を ON
	onTapSwitch();

	// 設定ファイルを読む
	std::vector<std::string> config_target_ipv6;
	readConfig(config_target_ipv6);


	// winsock の初期化
	if (WSAStartup(MAKEWORD(2, 2), &data) != 0) {
		MessageBox(NULL, _T("Error : Winsock を初期化できない"), _T("error"), MB_OK);
		return;
	}

	// タイマー精度を 1ms にする
	timeBeginPeriod(1);		// 1ms

	// ローカルの IPV6 アドレスを取得する
	std::list<std::string> local_list;
	getAddapterList(local_list);


	// UDPL ボードの情報
	std::list<LOCAL_NODE> target_list;	// local の IP と、そこに接続している複数の UDPL ボードのリスト

	LOCAL_NODE localnode;
	for (auto& i : local_list) {
		localnode.local_address = i;		// local ipv6 アドレスを設定する
		target_list.push_back(localnode);
	}
	// マルチキャストを使って UPDL を探す
	getUdplAddress(target_list);

	// local : target = 1:複数 のリストから、local : target = 1:1 のリストを作る
	sock_list.clear();	// 目標とするリスト
	SOCKET_ELEM sockelem;
	for (auto& local : target_list) {
		for (auto& target : local.address_list) {
			sockelem.sock = INVALID_SOCKET;
			sockelem.local_address = local.local_address;
			sockelem.target_address = target;
			sockelem.listen_port = 0;
			sock_list.push_back(sockelem);
		}
	}

	// 設定ファイルから読み込んだ場合、ターゲットを絞る
	if(!config_target_ipv6.empty()) {

		bool found = false;

		for (auto it = sock_list.begin(); it != sock_list.end();) {
			found = false;
			for (auto el : config_target_ipv6) {
				if (it->target_address == el) {
					found = true;	// 設定ファイルの中に記述がある
					break;
				}
			}
			if (!found) {
				it = sock_list.erase(it);	// 設定ファイルの中に記述がない -> 削除
			} else {
				++it;	// ->削除しない
			}
		}
	}


	// 一つの target につき、一つの socket を用意する
	getSocketList(sock_list);

	// BOARD INFO を取得する
	board_num = sock_list.size();	// UDPLのボード数 
	if (board_num) {
		pBoardInfoList = new UDPL_BOARD_INFO[board_num];
		pBoardInfoExtList = new UDPL_BOARD_INFO_EXT[board_num];
		memset(pBoardInfoList, 0, sizeof(UDPL_BOARD_INFO)*board_num);
		memset(pBoardInfoExtList, 0, sizeof(UDPL_BOARD_INFO_EXT) * board_num);
		getBoardInfo(sock_list, pBoardInfoList, pBoardInfoExtList);	// 情報取得
	}

	// ファームウェアバージョンをチェックする
	firm_check();

	// socketif class を実体化する
	std::list<IF_ADDR> ifaddr_list;		// UdplChip 実体化用。ボードのアドレスと UdplSockIf の対応付情報
	createSocketIf(sock_list, pBoardInfoList, pBoardInfoExtList, board_num, ifList, ifaddr_list);	// ifList に UdplSocketIf のリストが作成される

	// CHIP INFO を取得する
	std::list<UDPL_CHIP_INFO_ENABLE> chipinfolist;		// テンポラリの CHIP INFO リスト
	// enable が立ってないものも含めて取得する
	getChipInfo(sock_list, pBoardInfoList, chipinfolist);
	// enable が立ってないのは消す
	getValidChipInfo(chipinfolist);

	// テンポラリの list から CHIP INFO LIST を作成する
	chip_num = chipinfolist.size();		// チップの数
	if (chip_num) {
		pChipInfoList = new UDPL_CHIP_INFO[chip_num];
		memset(pChipInfoList, 0, sizeof(UDPL_CHIP_INFO)*chip_num);
		completeChipInfo(chipinfolist, pChipInfoList);	// 作成
	}

	// Chip クラスを実体化する
	createUdplChip(ifaddr_list, pChipInfoList, chip_num, cpList);	// cpList に作成される

	// タスクトレイアイコンの表示
	setTasktrayIcon();
}

// **************************************************************************
// インスタンスの終了処理を行う
// **************************************************************************
void __stdcall UdplManagerImp::suicide(void)
{
	// タスクトレイアイコンの消去
	delTasktrayIcon();

	// CHIP INFO を消去
	if (pChipInfoList) {
		delete[] pChipInfoList;
		pChipInfoList = nullptr;
	}

	// BOARD INFO を消去
	if (pBoardInfoList) {
		delete[] pBoardInfoList;
		pBoardInfoList = nullptr;
	}

	// BOARD INFO_EXT を消去
	if (pBoardInfoExtList) {
		delete[] pBoardInfoExtList;
		pBoardInfoExtList = nullptr;
	}

	// Chip を削除
	releaseUdplChip(cpList);

	// SocketIf を削除
	for (auto &elem : ifList) {
		delete elem;
		elem = nullptr;
	}

	// ソケットを閉じる
	closeSocketList(sock_list);

	// winsock 終了
	WSACleanup();

	// タイマー精度をもとに戻す
	timeEndPeriod(1);

	// tap switch を off
	offTapSwitch();

	delete this;
}

uint32_t __stdcall UdplManagerImp::getChipCount(void)
{
	return chip_num;
}

uint32_t __stdcall UdplManagerImp::getBoardCount(void)
{
	return board_num;
}

void __stdcall UdplManagerImp::getChipInfoList(UDPL_CHIP_INFO *plist, uint32_t num)
{
	if (plist) {
		memcpy(plist, pChipInfoList, sizeof(UDPL_CHIP_INFO) * ((chip_num > num) ? num : chip_num));
	}
}

void __stdcall UdplManagerImp::getBoardInfoList(UDPL_BOARD_INFO *plist, uint32_t num)
{
	if (plist) {
		memcpy(plist, pBoardInfoList, sizeof(UDPL_BOARD_INFO) * ((board_num > num) ? num : board_num));
	}
}

// *******************************************************************************************
// chipname で getchip する。複数の chipname がある場合、このライブラリが適当に選ぶ
// *******************************************************************************************
UdplChip * __stdcall UdplManagerImp::getChip(const char *name, uint32_t clock)
{
	UdplChip *ret = nullptr;
	for (auto chip : cpList) {
		if (!chip->getIsUsed()) {	// まだ get されてない
			if (strcmp(name, chip->getChipName()) == 0) {	// chip name が同じ
				if (clock == chip->getClock()) {			// clock が同じ
					ret = chip;
					chip->setIsUsed(true);		// used セット
					break;
				}
				else if (chip->getValiableClk()) {			// variable clock フラグ立ってる
					ret = chip;
					chip->setClock(clock);		// clock セット
					chip->setIsUsed(true);		// used セット
					break;
				}
			}
		}
	}

	return ret;
}

// **********************************************************
// board の ipv6 と slot no で getchip する
// **********************************************************
UdplChip * __stdcall UdplManagerImp::getChip(const char *board_address, uint32_t slotno, uint32_t clock)
{
	UdplChip *ret = nullptr;
	for (auto chip : cpList) {
		if (!chip->getIsUsed()) {											// get されてない
			if (strcmp(chip->getBoardAddress(), board_address) == 0) {	// board_address 同じ
				if (slotno == chip->getSlotNo()) {						// slot 同じ
					if (!chip->getValiableClk()) {						// variable clock でない
						if (clock == chip->getClock()) {				// clock 同じ
							ret = chip;
							chip->setIsUsed(true);
							break;
						}
					}
					else {											// variable clock である
						ret = chip;
						chip->setClock(clock);
						chip->setIsUsed(true);
						break;
					}
				}
			}
		}
	}

	return ret;
}

// *******************************************************************
// chip info の index で getchip する
// *******************************************************************
UdplChip * __stdcall UdplManagerImp::getChip(uint32_t index, uint32_t clock)
{
	UdplChip *ret = nullptr;
	if (index < chip_num) {
		if (!cpList[index]->getIsUsed()) {	// used でない
			if (!cpList[index]->getValiableClk()) {	// variable でない
				if (cpList[index]->getClock() == clock) {
					ret = cpList[index];
					cpList[index]->setIsUsed(true);
				}
			}
			else {	// variable である
				ret = cpList[index];
				cpList[index]->setClock(clock);
				cpList[index]->setIsUsed(true);
			}
		}
	}

	return ret;
}

// ************************************************************************************************************
// chip を開放
// ************************************************************************************************************
bool __stdcall UdplManagerImp::releseChip(UdplChip *pchip)
{
	bool ret = false;
	for (auto chip : cpList) {
		if (chip == pchip) {
			chip->setIsUsed(false);	// 未使用にする
			ret = true;
			break;
		}
		// ボリュームを 0 にする？
		// init() する？
	}
	return ret;
}

// ***********************************************************************
// chip をすべて開放
// ***********************************************************************
void __stdcall UdplManagerImp::releseChipAll(void)
{
	for (auto chip : cpList) {
		chip->setIsUsed(false);
		// ボリュームを 0 にする？
		// init() する？
	}
}

// ***********************************************************************
// board をすべてハードウェアリセット
// ***********************************************************************
bool __stdcall UdplManagerImp::reset(void)
{	
	bool ret = true;
	for (auto el : ifList) {
		ret &= el->reset();
	}
	return ret;
}

// ***********************************************************************
// chip をすべて初期化
// ***********************************************************************
bool __stdcall UdplManagerImp::init(void)
{
	bool ret = true;
	for (auto el : cpList) {
		ret &= el->init();
	}
	return ret;
}

// ***********************************************************************
// I2C デバイスを読む
// ***********************************************************************
uint32_t __stdcall UdplManagerImp::readI2C(const char* baddress, uint8_t slave_addr, uint8_t* pr_buf, uint8_t r_size, uint8_t r_len, const uint8_t* pw_buf, uint8_t w_len, bool repeatedstart)
{
	// 送信パケットの最大は256, 送信データは最大252 バイト
	uint8_t send_buf[256] = { 0 };
	uint32_t send_len;
	uint8_t recv_buf[256] = { 0 };
	uint32_t recv_len;
	uint32_t ret = 0;
	
	if (checkI2CAddr(baddress, slave_addr)) {	// slave address の存在チェック
		for (auto el : ifList) {
			if (strcmp(el->getBoardAddress(), baddress) == 0) {
				send_len = (w_len > 252) ? 252 : w_len;
				recv_len = (r_len > r_size) ? r_size : r_len;	// バッファよりも大きいサイズは読み出さない
				recv_len = (recv_len > 254) ? 254 : recv_len;	// プロトコル上読み込めるのは最大 254 バイト
				send_buf[0] = send_len + 3;
				send_buf[1] = COMMAND_READ_I2C;
				send_buf[2] = slave_addr;
				send_buf[3] = recv_len;
				if ((send_len != 0) && (pw_buf != nullptr)) {
					memcpy(send_buf + 4, pw_buf, send_len);
				}
				uint32_t l = el->pushRecv(send_buf, send_len + 4, recv_buf, sizeof(recv_buf));
				if ((l != 0) && (pr_buf != nullptr) && (r_size != 0) && (recv_buf[0] >= 2)) {
					uint32_t cp_len = recv_buf[0] - 1;
					memcpy_s(pr_buf, r_size, recv_buf + 2, cp_len);
					ret = cp_len;
				}
				break;
			}
		}
	}

	return ret;
}

// ***********************************************************************
// I2C デバイスに書き込む
// ***********************************************************************
bool __stdcall UdplManagerImp::writeI2C(const char* baddress, uint8_t slave_addr, const uint8_t* pw_buf, uint8_t len)
{
	// 送信パケットの最大は256, 送信データは最大253 バイト
	uint8_t send_buf[256] = { 0 };
	uint32_t send_len;
	uint32_t remain = len;
	const uint8_t* buf = pw_buf;
	bool ret = false;
	
	if (checkI2CAddr(baddress, slave_addr)) {	// slave address の存在チェック
		if (buf != nullptr) {
			for (auto el : ifList) {
				if (strcmp(el->getBoardAddress(), baddress) == 0) {
					while (remain != 0) {
						send_len = (remain > 253) ? 253 : remain;
						remain -= send_len;
						send_buf[0] = send_len + 2;
						send_buf[1] = COMMAND_WRITE_I2C;
						send_buf[2] = slave_addr;
						memcpy(send_buf + 3, buf, send_len);
						buf += send_len;

						el->pushData(send_buf, send_len + 3);
						el->waitBufClear();

						ret = true;
					}
					break;
				}
			}
		}
	}

	return ret;
}

// ***********************************************************************
// I2C デバイスの存在をチェックする。一度チェックしたデバイスはマップに保存してキャッシュする
// ***********************************************************************
bool __stdcall UdplManagerImp::checkI2CAddr(const char* baddress, uint8_t addr)
{
	uint8_t bufs[16] = { 0 };
	uint8_t* p = bufs;
	uint8_t bufr[16] = { 0 };

	bool ret = false;

	auto search_baddr = i2caddr_map.find(std::string(baddress));
	if (search_baddr != i2caddr_map.end()) {					// board address はあった
		auto search_slv = search_baddr->second.find(addr);
		if (search_slv != search_baddr->second.end()) {		// addr も登録されている
			ret = search_slv->second;
		} else {											// addr が登録されてない
			
			memcpy(p, COMMAND_CHECK_I2C, sizeof(COMMAND_CHECK_I2C));
			p += sizeof(COMMAND_CHECK_I2C);
			*p = addr;

			for (auto el : ifList) {
				if (strcmp(el->getBoardAddress(), baddress) == 0) {
					bool ifret = el->pushRecv(bufs, COMMAND_CHECK_I2C[0] + 1, bufr, sizeof(bufr));
					if (ifret) {
						if (bufr[bufr[0]] == 0) {
							ret = false;
						} else {
							ret = true;
						}
						search_baddr->second[addr] = ret;	// キャッシュする
					}
					break;
				}
			}
		}
	} else {	// キャッシュされてない
		memcpy(p, COMMAND_CHECK_I2C, sizeof(COMMAND_CHECK_I2C));
		p += sizeof(COMMAND_CHECK_I2C);
		*p = addr;

		for (auto el : ifList) {
			if (strcmp(el->getBoardAddress(), baddress) == 0) {
				bool ifret = el->pushRecv(bufs, COMMAND_CHECK_I2C[0] + 1, bufr, sizeof(bufr));
				if (ifret) {
					if (bufr[bufr[0]] == 0) {
						ret = false;
					} else {
						ret = true;
					}
					i2caddr_map[std::string(baddress)][addr] = ret;	// キャッシュする
				}
				break;
			}
		}
	}

	return ret;
}

// ***********************************************************************
// 電子ボリュームをセットする
// ***********************************************************************
bool __stdcall UdplManagerImp::setVol(const char* board_address, uint32_t vol, unsigned int ch, unsigned int inout)
{
	uint8_t sendbuf[16] = { 0 };
	uint8_t len = 0;

	bool ret = false;

	sendbuf[0] = 0x87;	// 33 tap mode

	switch (ch) {
	case LEFT_CH:
		sendbuf[1] = vol & 0x3f;
		len = 2;
		break;
	
	case RIGHT_CH:
		sendbuf[1] = (vol & 0x3f) | 0x40;
		len = 2;
		break;
	
	default:	// stereo
		sendbuf[1] = vol & 0x3f;
		sendbuf[2] = (vol & 0x3f) | 0x40;
		len = 3;
		break;
	}

	if (inout == BOARD_OUTPUT) {	// 現状出力のみ
		ret = writeI2C(board_address, POTENT_OUT_ADDR, sendbuf, len);
	}

	return ret;
}

// ***********************************************************************
// 電子ボリュームを読む
// ***********************************************************************
bool __stdcall UdplManagerImp::getVol(const char* board_address, uint32_t *vol, unsigned int ch, unsigned int inout)
{
	uint8_t readbuf[16] = { 0 };

	uint32_t r_len = 0;
	bool ret = false;

	if (inout == BOARD_OUTPUT && vol != nullptr) {
		r_len = readI2C(board_address, POTENT_OUT_ADDR, readbuf, sizeof(readbuf), 3);
	}
	for (uint32_t i = 0; i < r_len; ++i) {
		if (ch == LEFT_CH) {
			if ((readbuf[i] & 0xc0) == 0x00) {
				*vol = readbuf[i] & 0x3f;
				ret = true;
				break;
			}
		} else if (ch == RIGHT_CH) {
			if ((readbuf[i] & 0xc0) == 0x40) {
				*vol = readbuf[i] & 0x3f;
				ret = true;
				break;
			}
		}
	}

	return ret;
}