#pragma once

#define _WINSOCKAPI_
#include <WinSock2.h>
#include <WS2tcpip.h>

#include <stdint.h>
#include <list>
#include <string>
#include <mutex>
#include <vector>
#include <thread>
#include <chrono>
#include <condition_variable>
#include <atomic>
#include <unordered_map>

#include "tstring.h"
#include "udpl_dll.h"
#include "ringbuffer.h"
#include "command_list.h"
#include "tap_switch.h"

#ifdef _DEBUG
	#define _CRTDBG_MAP_ALLOC
	#include <crtdbg.h>
	#define new ::new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif

// ****************************************************************
// プロトタイプ
class UdplManagerImp;
class UdplSocketIf;
class UdplChipImp;
// *****************************************************************
// 定数

// 要求するファームウェアバージョン
constexpr uint32_t REQ_FIRM_VER = 0x00090300;	// ver 0.9.3

// socket 関係
constexpr u_short udpport = 50003;			// デフォルト udp port
constexpr uint32_t RECV_TIMEOUT = 200;		// recv のタイムアウト[ms]

// interface 関係
constexpr uint32_t FILLEDBUF_SHIFT = 3;		// filledbuf のサイズ = PACKET_SIZE の 2^n 倍 SCC はバイト数食うので多めに取る
constexpr uint32_t WAIT_MS = 2;				// 空き時間パケット送出までに待つ時間

constexpr uint32_t IGNORE_DELAY = 10;		// ウェイトを無視する us

// chip関係
constexpr uint32_t MIN_PLLCLK = 500000;		// setclock できる最小値
constexpr uint32_t MAX_PLLCLK = 150000000;	// 最大値
constexpr uint32_t YM2608_PCM_WAIT = 2;		// PCM 転送時の timeout 追加待ち時間 (YM2608)
constexpr uint32_t Y8950_PCM_WAIT = 2;		// PCM 転送時の timeout 追加待ち時間 (Y8950)

const char SCALER_SETTING[] = "PRE13=";		// YM2203, YM2608 用プリスケーラ設定文字列

// *****************************************************************
// クラス宣言
// *****************************************************************

//********************************
// マネージャークラス（公開クラス）
//
class UdplManagerImp : public UdplManager
{
public:
	UdplManagerImp() {
		pBoardInfoList = nullptr;
		board_num = 0;
		pChipInfoList = nullptr;
		chip_num = 0;

		pBoardInfoExtList = nullptr;

		cur_iconID = 0;
		modicon = false;
		pnid = nullptr;
		vol_out_valid = nullptr;
		vol_out_value = nullptr;
		pre_vol_out_timing = nullptr;
		infotray_m = nullptr;
		infotray_h = nullptr;
		infotray_th = nullptr;
		infotray_on = nullptr;
		curColor = nullptr;
	};

	virtual ~UdplManagerImp() {
	};

	uint32_t __stdcall getChipCount(void);
	uint32_t __stdcall getBoardCount(void);
	void __stdcall getChipInfoList(UDPL_CHIP_INFO *plist, uint32_t num);
	void __stdcall getBoardInfoList(UDPL_BOARD_INFO *plist, uint32_t num);
	void __stdcall initialize(void);
	void __stdcall suicide(void);
	UdplChip * __stdcall getChip(const char *name, uint32_t clock);
	UdplChip * __stdcall getChip(const char *board_address, uint32_t slotno, uint32_t clock);
	UdplChip * __stdcall getChip(uint32_t index, uint32_t clock);
	bool __stdcall releseChip(UdplChip *pchip);
	void __stdcall releseChipAll(void);
	bool __stdcall reset(void);
	bool __stdcall init(void);
	uint32_t __stdcall 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 = nullptr, uint8_t w_len = 0, bool repeatedstart = false);
	bool __stdcall writeI2C(const char* baddress, uint8_t slave_addr, const uint8_t* pw_buf, uint8_t len);
	bool __stdcall checkI2CAddr(const char* baddress, uint8_t addr);
	bool __stdcall setVol(const char* board_address, uint32_t vol, unsigned int ch = STEREO_CH, unsigned int inout = BOARD_OUTPUT);
	bool __stdcall getVol(const char* board_address, uint32_t* vol, unsigned int ch, unsigned int inout = BOARD_OUTPUT);

private:
	typedef struct {
		bool		enable;
		UDPL_CHIP_INFO info;
	} UDPL_CHIP_INFO_ENABLE;

	typedef struct {
		std::string local_address;
		std::list<std::string> address_list;
	} LOCAL_NODE;

	typedef struct {
		SOCKET sock;
		std::string local_address;
		std::string target_address;
		u_short listen_port;
	} SOCKET_ELEM;

	typedef struct {
		UdplSocketIf *psif;
		std::string address;
	} IF_ADDR;

	typedef struct {
		uint8_t		hbcycle;
		uint8_t		blank;
	} UDPL_BOARD_INFO_EXT;

	void getAddapterList(std::list<std::string> &list);
	SOCKET getSocket(std::string &localip, u_short port);
	void getUdplAddress(std::list<LOCAL_NODE> &list);
	void getSocketList(std::list<SOCKET_ELEM> &list);
	void closeSocketList(std::list<SOCKET_ELEM> &list);
	void getBoardInfo(const std::list<SOCKET_ELEM> &list, UDPL_BOARD_INFO *pInfo, UDPL_BOARD_INFO_EXT *pInfoExt);
	void getChipInfo(const std::list<SOCKET_ELEM> &list, const UDPL_BOARD_INFO *pInfo, std::list<UDPL_CHIP_INFO_ENABLE> &infolist);
	void getValidChipInfo(std::list<UDPL_CHIP_INFO_ENABLE> &list);
	void completeChipInfo(const std::list<UDPL_CHIP_INFO_ENABLE> &list, UDPL_CHIP_INFO *pInfo);
	void createSocketIf(const std::list<SOCKET_ELEM> &slist, UDPL_BOARD_INFO *pbinfo, UDPL_BOARD_INFO_EXT *pbinfoExt, const uint32_t binfo_size, std::vector<UdplSocketIf *> &siflist, std::list<IF_ADDR> &ifaddrlist);
	void createUdplChip(const std::list<IF_ADDR> &ifaddrlist, UDPL_CHIP_INFO *pcinfo, const uint32_t cinfo_num, std::vector<UdplChipImp *> &uclist);
	void releaseUdplChip(std::vector<UdplChipImp *> &uclist);
	void firm_check(void);
	bool readConfig(std::vector<std::string>& list);
	
	static LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
	LRESULT localWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
	static INT_PTR CALLBACK DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
	INT_PTR localDlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
	static INT_PTR CALLBACK VolDlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
	INT_PTR localVolDlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
	static INT_PTR CALLBACK InfoDlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
	INT_PTR localInfoDlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

	void setTasktrayIcon(void);
	void delTasktrayIcon(void);
	void updateTasktrayIcon(void);
	void updateInfoDlg(HWND hwnd, uint32_t no);
	void msgloop(void);

	void onTapSwitch(void);
	void offTapSwitch(void);

	WSADATA data;	// winsock

	std::list<SOCKET_ELEM> sock_list;	// socket, local ipv6, target ipv6 のリスト
	UDPL_BOARD_INFO *pBoardInfoList;	// BOARD INFO
	UDPL_BOARD_INFO_EXT* pBoardInfoExtList;	//
	uint32_t board_num;					// BOARD INFO の数
	UDPL_CHIP_INFO *pChipInfoList;		// CHIP INFO
	uint32_t chip_num;					// CHIP INFO の数
	std::vector<UdplSocketIf *> ifList;	// UdplSockIf リスト
	std::vector<UdplChipImp *> cpList;	// UdplChip リスト
	
	std::unordered_map<std::string, std::vector<uint32_t>> ip2chipNoList_map;	// board の ipv6 アドレスを与えると、ボード上の chip の no を vector で返す
	std::unordered_map<std::string, std::unordered_map<uint8_t, bool>> i2caddr_map;		// board の ipv6 アドレスを与えると、i2c addr をチェックした結果を返す

	// タスクトレイ関連
	HWND hidWin;			// 隠れウインドウハンドル
	NOTIFYICONDATA *pnid;		// タスクトレイアイコンのデータ
	std::thread msgth;		// メッセージループのスレッド
	UINT cur_iconID;		// メニューを出したタスクアイコンの ID = ボード番号
	HICON hIconIdle;
	HICON hIconActive[2];
	std::atomic_bool modicon;
	std::thread iconth;
	bool *vol_out_valid;
	uint32_t *vol_out_value;
	std::chrono::steady_clock::time_point *pre_vol_out_timing;
	std::mutex *infotray_m;
	HWND *infotray_h;
	std::thread* infotray_th;
	std::atomic_bool* infotray_on;
	COLORREF** curColor;

	// tap switch関連
	HMODULE htapdll;
	unsigned int tapnum;
	_GETTAPSWITCHNUM getTapSwitchNum;
	_SWITCHTAPSWITCH switchTapSwitch;
};


// ******************************************
// ソケットインターフェイス（非公開クラス）
// バッファに書き込まれたデータをソケットを使って送信する
//
class UdplSocketIf
{
	typedef struct {
		std::chrono::steady_clock::time_point timing;
		uint32_t len;
	} TIMING_ELEM;

public:
	UdplSocketIf(SOCKET s, const char *address, uint16_t ms, uint32_t bsize, uint8_t hb, uint8_t bl);
	virtual ~UdplSocketIf();

	void resizeRbuf(uint32_t size) {};
	void pushData(const uint8_t *buf, uint32_t size);
	uint32_t pushRecv(const uint8_t *buf, uint32_t size, uint8_t *res, uint32_t res_size, uint32_t sec = 0U);	// リングバッファに入れて、recv を待つ。
	bool waitBufClear(unsigned int add_wait = 0U);
	bool reset(void);	// reset

	void setWait(uint32_t ms) {
		wait.store( (ms < blank_time) ? blank_time : ms, std::memory_order_release);		// 最小で blank_time
	}
	uint32_t getWait(void) {
		return wait.load(std::memory_order_acquire);
	}
	uint32_t getBufsize(void) {
		return bufsize;
	}
	bool getSendStatus(void) {
		return sendStat.exchange(false, std::memory_order_acq_rel);
	}
	void setHeartBeat(bool flg) {
		heartBeatEn.store(flg, std::memory_order_release);
	}
	const char* getBoardAddress(void) {
		return addressIpv6;
	}

private:

	void sendPktData(void);	//送信スレッド
	void fillLineBuf(void);	//送信スレッド

	SOCKET sock;			// ソケット
	std::mutex sock_m;		// socket 用 mutex
	struct sockaddr_in6 dest_addr;		// 送信先アドレス

	std::atomic_uint32_t wait;
	std::atomic_bool heartBeatEn;
	uint8_t blank_time;

	uint32_t max_packet_size;		// 一度に送信する(最大)サイズ

	RINGBUF<uint8_t> *rbuf;			// コマンドを貯め込むリングバッファ
	std::mutex rbuf_m;				// リングバッファ用 mutex

	uint8_t *sendbuf;				// socket 用ラインバッファ

	uint32_t bufsize;

	std::chrono::steady_clock::time_point cur_time;		// データを受け取った時刻
	std::chrono::steady_clock::time_point pre_time;	// 前回データを受け取った時刻

	std::thread send_pkt_th;	// 送信スレッド
	std::thread fill_buf_th;

	std::atomic_bool pktthreadOn;			// スレッドの停止、動作指定
	std::atomic_bool bufthreadOn;

	std::condition_variable filled_top_c;	//
	std::chrono::milliseconds max_wait_time;

	std::mutex filled_cycle_m;		// ウエイトステート用 mutex
	bool filled_cycle_break;
	std::condition_variable filled_cycle_c;	//
	std::chrono::milliseconds cycle_time;		//

	std::list<TIMING_ELEM> timing_list;
	std::mutex timing_m;				// timing_list 排他処理用
	std::condition_variable list_wait_c;			//リスト待ち
	
	std::mutex filled_buf_m;	// ラインバッファ用 mutex
	uint8_t *filledbuf;			// ラインバッファ
	uint32_t filled_size;		// ラインバッファ書き込みサイズ
	std::chrono::steady_clock::time_point filled_top_timing;	// ラインバッファの再生タイミング

	std::atomic_bool sendStat;		// 送信した後かどうか

	char addressIpv6[65];	// ip address
};

// **********************************************************************
// チップクラス（公開クラス）
// アプリから命令をもらうとコマンドを生成し、ソケットインターフェイスに渡す
// 継承して以下を実装する必要がある
//virtual bool __stdcall setRegister(uint32_t addr, uint32_t data) = 0;
//virtual bool __stdcall setBus(uint32_t addr, uint32_t data) = 0;
//virtual bool __stdcall getRegister(uint32_t addr) = 0;
//virtual void setWaits(uint32_t clk);		// アドレスウェイト、データウェイトの値の計算
//virtual void initRegs(void);			// レジスタの初期化
class UdplChipImp : public UdplChip
{
public:
	UdplChipImp(UdplSocketIf *pif, UDPL_CHIP_INFO *pinfo)
	{
		// メンバ変数初期化
		psockIf = pif;
		pchipInfo = pinfo;

		isUsed = false;
		slotno = pchipInfo->slotNo;
		slotno_bit = (uint8_t)(slotno << 4);	// set/getregister用
		variable_clk = pchipInfo->variable_clock;
		hwreset = pchipInfo->hardware_reset;

		max_send_size = psockIf->getBufsize() >> 1;		// 一度に送信できる量はこれくらいにしておく

		uint32_t plocalname_size = sizeof(pinfo->name) + 1;
		plocalname = new char[plocalname_size];
		memset(plocalname, '\0', plocalname_size);	// 念の為 pinfo->name より1大きいバッファに \0 エンドで持っておく
		memcpy_s(plocalname, plocalname_size, pinfo->name, sizeof(pinfo->name));

		plocalname_size = sizeof(pinfo->board_address_ipv6) + 1;
		pboard_address_ipv6 = new char[plocalname_size];
		memset(pboard_address_ipv6, '\0', plocalname_size);
		memcpy_s(pboard_address_ipv6, plocalname_size, pinfo->board_address_ipv6, sizeof(pinfo->board_address_ipv6));

		memset(send_command, 0, sizeof(send_command));
		memset(recv_command, 0, sizeof(recv_command));

		sendStat = false;	// 未送信

		// まずクロックを出力して
		if (variable_clk) {
			setClock(pchipInfo->clock);
		}
		
		// ハードウェアリセット
		reset();
	};

	virtual ~UdplChipImp()
	{
		delete[] pboard_address_ipv6;
		pboard_address_ipv6 = nullptr;

		delete[] plocalname;
		plocalname = nullptr;
		// 無音にする
	};

	// アプリ向け
	virtual bool __stdcall setRegister(uint32_t addr, uint32_t data) = 0;
	virtual bool __stdcall writeBus(uint8_t abit, uint8_t data) = 0;
	virtual uint32_t __stdcall getRegister(uint32_t addr) = 0;
	virtual bool __stdcall setPcmData(void *pdat) = 0;

	virtual bool __stdcall setClock(uint32_t clock, bool ignore_lock = false);	// ym2203, ym2608 ではオーバーライド
	uint32_t __stdcall getClock(void);
	bool __stdcall init(void);
	bool __stdcall reset(void);
	const char* __stdcall getBoardAddress(uint32_t interface_protocol = IPV6UDP);
	uint32_t __stdcall getSlotNo(void) {
		return slotno;
	};
	const char* __stdcall getChipName(void) {
		return plocalname;
	};

	// ライブラリ内向け
	void setIsUsed(bool used) {
		isUsed = used;
	};

	bool getIsUsed(void) {
		return isUsed;
	};

	bool getValiableClk(void) {
		return variable_clk;
	};

	bool getSendStatus(void) {	// for Manager
		return sendStat.exchange(false, std::memory_order_acq_rel);
	}


protected:
	virtual void setWaits(uint32_t clk) {};		// アドレスウェイト、データウェイトの値の計算
	virtual void initRegs(void) {};				// レジスタの初期化

	UdplSocketIf *psockIf;			// socket インターフェイスへのポインタ
	UDPL_CHIP_INFO *pchipInfo;		// チップの情報
	bool hwreset;					// ハードウェアリセットをかけるか
	bool variable_clk;				// 可変クロックか
	uint32_t slotno;				// スロット NO
	bool isUsed;					// getChip されているか

	uint8_t slotno_bit;

	char *plocalname;				// chipname
	char* pboard_address_ipv6;

	uint8_t send_command[256];	// 送信コマンド
	uint8_t recv_command[256];	// 受信コマンド

	uint32_t max_send_size;		// 一度に送信できる量

	std::atomic_bool sendStat;	// 送信したあとか
};
