#pragma once
#include <stdint.h>
#include <memory>
#include <string>
#include <mutex>
#include <thread>
#include "VPPlatform.h"
/* Disable: warning C4251: 'CVPusbIO::mtxRxData': class 'std::mutex' needs to have dll-interface to be used by clients of class 'CVPusbIO' */
#pragma warning(disable: 4251)

#include "VPserIOdefs.h"


#define VPSERIO_DEFAULT_PORT	 1
#define VPSERIO_DEFAULT_BAUD	 VPIO_BR_115200
#define VPSERIO_DEFAULT_PARITY   VPIO_PARITY_NONE
#define VPSERIO_DEFAULT_DATABITS 8
#define VPSERIO_DEFAULT_STOPBITS 1
#define VPSERIO_DEFAULT_FLOWCTL  0

// defs shared by usbIO and serIO
typedef
void
(CALLBACK *VP_DATAREADY_CALLBACK)(void* pInfo, uint8_t *pdata, uint32_t nbytes);
//#ifndef VPUSB_DEFAULT_CMD_TIMEOUT MS
//#define VPUSB_DEFAULT_CMD_TIMEOUT_MS 50
//#endif
#define VPSER_RAW_READ_BUF_SIZE 1024
//#define VPUSB_DEFAULT_ASYNC_READ_TIMEOUT_MS  1000
typedef enum
{
	VPIO_NOERR
	, VPIO_TIMEOUT
	, VPIO_NORESP
	, VPIO_SETUPERR
	, VPIO_SYSERR
	, VPIO_FRAMERR

	, VPIO_MAXERR
}VPIO_ERR;

typedef uint32_t _VPPREAMBLE;
typedef uint32_t _VPSIZEFIELD;

typedef struct _VPSERHDR {
	_VPPREAMBLE pre;
	_VPSIZEFIELD size;
}VPSERHDR;

typedef uint16_t _LYPREAMBLE;
typedef uint16_t _LYSIZEFIELD;
typedef struct _LYSERHDR {
	_LYPREAMBLE pre;
	uint8_t sta;
	uint8_t cmd;
	uint8_t err;
	uint8_t res;
	_LYSIZEFIELD size;
}LYSERHDR;

typedef enum
{
	VPIO_BR_1200 = 0		// not used...
	, VPIO_BR_2400			// not valid for viper
	, VPIO_BR_4800			// not valid for viper
	, VPIO_BR_9600			// not valid for viper
	, VPIO_BR_19200		// not valid for viper
	, VPIO_BR_38400
	, VPIO_BR_57600
	, VPIO_BR_115200
	, VPIO_BR_230400       //!< 230,400 baud
	, VPIO_BR_460800       //!< 460,800 baud
	, VPIO_BR_921600       //!< 921,600 baud
	, VPIO_BR_1843200      //!< 1,843,200 baud
	, VPIO_BR_3686400      //!< 3,686,400 baud
	, VPIO_BR_7372800      //!< 7,372,800 baud

	, VPIO_BR_MAX
} eVPIO_BAUD;

// Parity
typedef enum
{
	VPIO_PARITY_NONE
	, VPIO_PARITY_ODD
	, VPIO_PARITY_EVEN

	, VPIO_PARITY_MAX
} eVPIO_PARITY;

#ifndef VIPER_PNO_PREAMBLE
#define VIPER_PNO_PREAMBLE 0x50525056
#endif

#define VIPER_FRAME_PREAMBLE VIPER_PNO_PREAMBLE & 0x00ffffff

typedef struct _vp_serportcfg
{
	union {
		uint32_t cfg;
		struct {
			uint32_t baudrate : 4;  // uses values from eBaud
			uint32_t parity : 2;	// uses values from eParity
			uint32_t res : 26;
		}bf;
	};
	_vp_serportcfg() { bf.baudrate = VPIO_BR_115200; bf.parity = VPIO_PARITY_NONE; }
	_vp_serportcfg(uint32_t c) { cfg = c; }
}vp_serportcfg;

typedef struct VPSER_API _vp_serportinfo {
	uint32_t    serPort;
	tstring		strPortName;		// e.g. "COM1"
	tstring		strFriendlyName;	// port description
	_VPPREAMBLE	preamble;

	vp_serportcfg	serCfg;

	_vp_serportinfo() : serPort(1), preamble(VIPER_FRAME_PREAMBLE) {};
	_vp_serportinfo( uint32_t p) : serPort(p), preamble(VIPER_FRAME_PREAMBLE) {};
	_vp_serportinfo(uint32_t p, tstring& n, tstring& fn) : serPort(p), preamble(VIPER_FRAME_PREAMBLE) { strPortName = n; strFriendlyName = fn; };
	_vp_serportinfo(const _vp_serportinfo & rv) {
		serPort = rv.serPort; strPortName = rv.strPortName; strFriendlyName = rv.strFriendlyName; serCfg.cfg = rv.serCfg.cfg;
	}//memcpy(&serPort, &rv, sizeof(_vp_serportinfo)); }
	_vp_serportinfo & operator = (const _vp_serportinfo & rv) {
		serPort = rv.serPort; strPortName = rv.strPortName; strFriendlyName = rv.strFriendlyName; serCfg.cfg = rv.serCfg.cfg; return *this;
	}//memcpy(&serPort, &rv, sizeof(_vp_serportinfo)); return *this; }
	~_vp_serportinfo(){ 
		//int i = 0;
	};

}vp_serportinfo;

class VPSER_API CVPserIO
{
public:
	CVPserIO( uint32_t sercfg=0);
	~CVPserIO();

	// setup
	void SetMsgCallback(VP_DATAREADY_CALLBACK cb, PVOID pInfo)
	{
		m_pDataReadyCallback = cb; m_pCBInfo = pInfo;
	}

	void SetTrace(bool, int l = 3);
	void SetModeLY();
	//sets baud/parity serial config.  Don't change this if comport already open.
	void SetCfg(uint32_t sercfg);

	// operations
	static int ListSerPorts(vp_serportinfo[], size_t &);
	// opens first available port in discovered list
	int DiscoverPort();
	int Open(uint32_t port);
	int Close();

	//int Reset(); // if libusb_reset does not seem to do anything to ft3.
	int Write(uint8_t *, int &);
	// read up to count bytes into pbuf, number ob bytes returned in count. TimeOut not an error by default
	int Read(uint8_t *pbuf, int & count, bool bTOisErr=false);
	// read a frame with VPSERHDR into pbuf, output num bytes in count
	int Readframe(uint8_t * pbuf, int & count);
	// find a frame with VPSERHDR, read into pbuf, output num bytes in count
	int ReadCont( );
	int ReadContStop();  
	int LastAsyncRead(uint8_t * & pbuf, int & nbytes);

	// values - shared with usb io
	bool Initialized() { return m_pdevhnd != 0; }
	bool IsOpen() { return m_pdevhnd != 0; }
	void DevReport(tstring &) const;
	bool AsyncReadRunning() { return m_bContReadRunning; }
	//bool & ReadCmdTOIsError() { return m_bReadCmdTOIsError; }

	// unique to serial io
	// Must set Frame Preamble in order to discover complete frames. Defaults to Viper preamble. Set Manually for another tracker.
	uint32_t & FramePreamble() { return m_devinfo.preamble; }
	bool & AsciiContMode() { return m_bAsciiContMode; }
	const vp_serportinfo * DevInfo() const { return &m_devinfo; }
	static void PortReport(vp_serportinfo & info, tstring & s);
	static void PortListReport(tstring & s);
	// Command Response read timeout milliseconds.  Not used in serial IO
	//int & ReadCmdTO() { return m_iReadCmdTO; }
	//Do we consider a read timeout to be an error? default: no.
	// Async Read timeout in milliseconds
	//int & AsyncReadTOMS() { return m_iAsyncReadTOMS; }
	
	// utils
	static const char * ErrorStr(int);
	static const wchar_t * ErrorStrW(int);


protected:

	vp_serportinfo m_devinfo;

	void * m_pdevhnd;
	void * m_hReadEvent;
	void * m_hWriteEvent;
	unsigned long m_baud;
	uint8_t m_parity;

	bool m_bAsciiContMode;

	//int ReadExactCmd(PBYTE pBuf, DWORD * pdwCount, DWORD dwBytesToRead, BOOL bTimeOut);
	// Return true if input preamble matches compare preamble
	//bool PreambleTest(_VPPREAMBLE pIN, _VPPREAMBLE pCmp);
	bool PreambleTest(uint8_t *pIN);

	int SetupSerComm(HANDLE hCnx);
	int FlushComm();
	void DumpDCB(LPSTR sTitle, DCB & dcb);
	int InitHandles(HANDLE hCnx);
	void CloseHandles();


	// used for synchronous reads
	enum eMode {
		E_MODE_VP
		, E_MODE_LY
	};

	eMode m_Mode;

	//int m_iReadCmdTO;
	//bool m_bReadCmdTOIsError;
	std::mutex m_mtxRxData;

	//int m_iAsyncReadTOMS;

	std::thread::id m_thidCont;
	std::thread *m_pthCont;
	bool m_bContReadRunning;

	uint8_t m_data_in[VPSER_RAW_READ_BUF_SIZE];
	int m_iNextHostIndex;
	int m_iHostFC;
	int m_iLastBytesIn;
	uint8_t * m_pLastBufIn;
	uint8_t m_cap_buf[2048];

	int  StartAsyncRead();
	void StartContThread();
	void thproc_Cont();
	int Findframe(uint8_t * pbuf, int & count);
	void ContLoop();
	// reads continuously but doesn't test frame header.. used for ascii cont mode
	void ContLoopFreeform();
	void ClearContThreadFlag()
	{
		m_bContReadRunning = false;
	}
	void StopContThread();

	//void TransferFatal(void* transfer);
	// return t/f keep reading
	bool DataReady(uint8_t *pdata, int nBytes);

	VP_DATAREADY_CALLBACK m_pDataReadyCallback;
	PVOID				  m_pCBInfo;

	//static void MCB(void *transfer);

public:

};
