#pragma once
#include "VPCmdIF.h"
#include <algorithm>
#include <mutex>
#include <queue>
#include <deque>
#include <condition_variable>

class CVPusbIO;
class CVPcontext;
class CVPserIO;


// Pno raw buf
#define FSIZE MAX_VP_PNO_FRAME_SIZE	    // max seu frame size  540
#define	DUR		30						// 30 secs			30
#define FRATE	960						// 240 frames/sec		
#define RAWBUFSIZE	FSIZE * DUR * FRATE	// = 777,600	(< 1MB)	388,800

#define FINFO_QUE_SIZE	FRATE * 2		// FIFO QUE will hold 2 seconds worth of frame pointers.


class CFrameInfo;

///Device Information structure.
///Contains all identifying information about a device
///Currently used only internally to the VPcmdIF.
typedef struct _tagDevInfo
{
	WHOAMI_STRUCT tag_wai;	//!< WHOAMI data retrieved from device
	char	 tag_AscID[32];	//!< ascii dev name/serial num placeholder

	char tag_Desc[200];		//!< ASCII Description of dev placeholder 

	BLOCK_CONFIG tag_cfg;	//!< Active configuration retrieved from device

} vpDevInfo;


class CVPdevIO : public vpDevInfo
{
public:
	CVPdevIO(CVPcontext *);
	CVPdevIO::CVPdevIO(const CVPdevIO & rv)
	{
		memcpy((vpDevInfo *)this, (vpDevInfo *)&rv, sizeof(vpDevInfo));
		m_uiOwnerTag = rv.m_uiOwnerTag;
		m_uiCmdPreamble = rv.m_uiCmdPreamble;
		m_uiPnoPreamble = rv.m_uiPnoPreamble;
		InitIO(); //initialize buffers
	}
	virtual ~CVPdevIO();

	void InitIO();
	void Initdev();
	int ConnectUSB(uint16_t vid, uint16_t pid);
	int Disconnect();
	int DisconnectUSB();
	int ConnectSerial( int32_t serport, uint32_t sercfg = 0);
	int DisconnectSerial();
	int EnableTimestamp(bool bEn);
	int AllowPnoFramerrs(bool bAllow);
	int USBbuffer(uint32_t count);

	//pull one frame off of the front of the deque; they are added from the back
	int FIFORxPno(CFrameInfo & Fin)
	{
		return RxPnoDequeueFIFO(Fin);
	}
	//count frames and total bytes in Pno FIFO
	int FIFOPnoCount(uint32_t & fc, uint32_t & bc);
	//pull all available frames off the front
	int FIFORxPnoCount(CFrameInfo & Fin, uint32_t & fc, uint32_t maxbytes);

	int LastRxPno(CFrameInfo & Fin)
	{
		return RxPnoDequeueLAST(Fin);
	}
	int NextRxPno(CFrameInfo & Fin)
	{
		return WaitPnoRespA(Fin);
	}

	int CmdIO(const CFrameInfo & Fout, CFrameInfo & Fin, bool bWantRsp = true);

	void SetSEUID( uint32_t id ) { tag_cfg.seuid = id; }
	void SetAscID(char * asc, int size)
	{
		memset(tag_wai.device_name, 0, sizeof(tag_wai.device_name));
		memcpy(tag_wai.device_name, asc, std::min<int>(NAME_SIZE, size));
		tag_wai.device_name[NAME_SIZE - 1] = 0;
	}
	void SetWhoAmI(WHOAMI_STRUCT *pw)
	{
		if (pw)
			memcpy_s(&tag_wai, sizeof(WHOAMI_STRUCT), pw, sizeof(WHOAMI_STRUCT));
	}

	bool operator<(const CVPdevIO & rv) const
	{	// test if _Less < _Right for _Trees
		return (SEUID() < rv.SEUID());
	}
	bool operator<(const uint32_t & rInt) const
	{
		return (SEUID() < rInt);
	}
	CVPdevIO& operator=(const CVPdevIO & rv)
	{
		memcpy((vpDevInfo *)this, (vpDevInfo *)&rv, sizeof(vpDevInfo));
		return *this;
	}
	operator vpDevInfo * ()
	{
		return (vpDevInfo*)(&tag_wai);
	}
	const char *  AscID() const { return tag_wai.device_name; }
	//const uint8_t SEUID() const { return tag_cfg.seuid; }
	const VIPER_SEUID SEUID() const { return tag_cfg.seuid; }
	const char * SerialNum() const { return tag_wai.hw_ser_no; }
	const WHOAMI_STRUCT * WhoAmI() const { return &tag_wai; }
	int & CmdTimeoutMS() { return m_cmdTimeOutMS; }
	void InitCmdTimeoutMS();

	uint32_t & OwnerTag() { return m_uiOwnerTag; }
	uint32_t & CmdPreamble() { return m_uiCmdPreamble; }
	uint32_t & PnoPreamble() { return m_uiPnoPreamble; }

	static const char * ErrorStr(int32_t e);
	static const char * ErrorStr_USB(int16_t);
	static const char * ErrorStr_SER(int16_t e);

	void Report(std::string &);

	int m_eTraceLev;
	void SetTraceLev(int);
	
	VP_PNOREADY_CALLBACK m_pClientPnoCallback;
	PVOID m_pCCBInfo;
	void SetPnoCB(VP_PNOREADY_CALLBACK cb, PVOID pInfo)
	{
		m_pClientPnoCallback = cb;
		m_pCCBInfo = pInfo;
	}
	int Write(uint8_t *pbuf, int & count);
	int Read(uint8_t *pbuf, int & count);

	void PurgePnoFrames()
	{
		queRxPnoFrames.clear();
		m_uiPnoDropCount = 0;
		m_uiPnoHostFrameCount = 0;
		m_lastPnoFInfo.Init();
	}

protected:
	CVPcontext * m_pctx;
	CVPusbIO * m_pusb;
	CVPserIO * m_pser;

	int32_t m_cmdTimeOutMS;

	bool m_bUseTimestamp;
	bool m_bAllowFrameErrs;
	uint32_t m_uiUSBBufcount;

	// private info for use by object owner.  currently used for experimental dev with G4Plus hardware.
	uint32_t m_uiOwnerTag;

	uint32_t m_uiCmdPreamble;
	uint32_t m_uiPnoPreamble;

	static void CALLBACK USBSer_CB(void * pinfo, uint8_t *pbuf, uint32_t nbytes);

	int DataReady(CFrameInfo &);
	void RxCmdEnqueue(CFrameInfo &);
	void RxPnoEnqueue(CFrameInfo &);
	bool RxCmdDequeue(CFrameInfo & Fin, uint32_t cmd, int32_t & iErr);
	int RxPnoDequeueFIFO(CFrameInfo & Fin);
	int RxPnoDequeueLAST(CFrameInfo & Fin);

	uint32_t m_uiPnoHostFrameCount;
	uint32_t m_uiPnoDropCount;
	CFrameInfo m_lastPnoFInfo;

	//uint8_t hdr_inbuffer[10];
	uint8_t pno_inbuffer[RAWBUFSIZE]; // < 1 MB
	uint8_t cmd_inbuffer[RAWBUFSIZE]; // < 1 MB

	uint8_t * p_nextPno_inbuffer;
	int32_t  iPnoRawIndex;
	uint8_t * p_nextCmd_inbuffer;
	int32_t  iCmdRawIndex;

	// RxFrames Queues
	std::mutex mtxRxCmdQueue; //For pushing/popping/testing cmd deque
	std::mutex mtxRxPnoQueue; //For pushing/popping/testing pno deque
	std::deque<CFrameInfo> queRxPnoFrames;
	std::queue<CFrameInfo> queRxCmdFrames;


	void PurgeCmdFrames()
	{
		while (!(queRxCmdFrames.empty()))
		{
			queRxCmdFrames.pop();
		}
	}

	bool RxCmdVecAdd(eViperCmds e);
	bool RxCmdVecFind(eViperCmds cmd);
	bool RxCmdVecRemove(eViperCmds e);

	bool m_bNewRxCmdReady;
	std::mutex mtxCmdIO; //For CmdIO() method concurrency
	std::mutex mtxRxCmd; //For Cmd Rsp event notification, with cvRxCmd
	std::condition_variable cvRxCmd;
	// list of commands that we are waiting response to.
	std::vector<eViperCmds> m_vecRxCmds; // only use this with m_mtxVecRxCmds
	std::mutex m_mtxVecRxCmds;

	std::mutex mtxRxPno; //For Pno Rx event notification, with cvRxPno
	std::condition_variable cvRxPno;
	bool m_bNewRxPnoReady;



	int WaitCmdRespSync(CFrameInfo & Fin, const CFrameInfo & Fcmd);
	int WaitCmdRespA(CFrameInfo &Fin, const CFrameInfo & Fcmd); //uint32_t cmd, uint32_t action, int32_t & iErr);
	int WaitPnoRespA(CFrameInfo &Fin); //uint32_t cmd, uint32_t action, int32_t & iErr);

//	friend void CALLBACK USB_CB(void * pinfo, uint8_t *pbuf, uint32_t nbytes);
};

