#pragma once
#include <stdint.h>
#include <memory>
#include <cstring>
#include <mutex>
#include <thread>
/* 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 "VPusbIOdefs.h"
#include "VPPlatform.h"

#define VPUSB_DEFAULT_CMD_TIMEOUT_MS 50
#define VPUSB_RAW_READ_BUF_SIZE 1024
//#define VPUSB_DEFAULT_ASYNC_READ_TIMEOUT_MS  1000

//typedef
//void
//(CALLBACK *VP_DATAREADY_CALLBACK)(void* pInfo, uint8_t *pdata, uint32_t nbytes);
typedef
void
(CALLBACK *VP_DATAREADY_CALLBACK)(void* pInfo, uint8_t *pdata, uint32_t nbytes);


typedef struct VPUSB_API _vp_usbdevinfo{
	int     usbDevIndex;
	uint8_t usbBusNum;
	uint8_t usbDevNum;
	uint16_t usbVID;
	uint16_t usbPID;
	uint8_t usbNumInterfaces;
	uint8_t ep_in;
	uint8_t ep_out;
	uint16_t epout_maxPktsize;

	_vp_usbdevinfo() : usbDevIndex(-1), usbBusNum(0), usbDevNum(0), usbVID(0), usbPID(0), usbNumInterfaces(0), ep_in(0), ep_out(0), epout_maxPktsize(64){};
	_vp_usbdevinfo(_vp_usbdevinfo & rv) { std::memcpy(&usbDevIndex, &rv, sizeof(_vp_usbdevinfo)); }
	_vp_usbdevinfo & operator = (const _vp_usbdevinfo & rv) { memcpy(&usbDevIndex, &rv, sizeof(_vp_usbdevinfo)); return *this; }

}vp_usbdevinfo;

typedef enum
{
	E_VPUSB_ERR_SUCCESS = 0
	, E_VPUSB_ERR_WRITE_COUNT_WRONG
	, E_VPUSB_ERR_MAX
}eVPUSBerr;

class VPUSB_API CVPusbIO
{
public:
	CVPusbIO();
	~CVPusbIO();

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

	void SetTrace(bool, int l=3);

	// operations
	int ListVidPid(uint16_t, uint16_t, vp_usbdevinfo[], size_t & );
	int DiscoverVidPid(uint16_t, uint16_t);
	int Close();
	//int Reset(); // if libusb_reset does not seem to do anything to ft3.
	int Write(uint8_t *, int & );
	int Read(uint8_t *pbuf, int & count, bool bTOisErr=false);
	int ReadCont();
	//int ReadContStop();  // inadviseable:  Process will stop waiting at least 60 seconds if no data is coming in from device.
	//					 // best to just let the Close() or destructor do this.
	int LastAsyncRead(uint8_t * & pbuf, int & nbytes);

	// values - shared with serial io
	bool Initialized() { return m_pctx != 0; }
	bool IsOpen() { return m_pdevhnd != 0; }
	void DevReport(std::string &) const;
	bool AsyncReadRunning() { return m_bContReadRunning; }
	//Do we consider a read timeout to be an error? default: no.
	//bool & ReadCmdTOIsError() { return m_bReadCmdTOIsError; }
	// Command Response read timeout milliseconds.  Used for synchronous reads

	// unique to usb io
	int & ReadCmdTO() { return m_iReadCmdTO; }
	int & WriteCmdTO() { return m_iWriteCmdTO; }
	// Async Read timeout in milliseconds
	//int & AsyncReadTOMS() { return m_iAsyncReadTOMS; }

	// utils
	static const char * ErrorStr(int16_t);

protected:

	// libusb context: libusb_context
	void *m_pctx;
	// libusb device handle: libusb_device_handle
	void *m_pdevhnd;
	// libusb async read transfer: libusb_transfer
	void * m_parxfer;

	void * AsyncReadXfer() { return m_parxfer; }
	void  ClearARXfer();// { if (m_parxfer) { ClearTransfer(&m_parxfer); } }
	void  CancelARXfer();

	vp_usbdevinfo m_devinfo;
	// used for synchronous reads
	int m_iReadCmdTO;
	int m_iWriteCmdTO;
	//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[VPUSB_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();
	static bool ThreadRunning(std::thread::id thid, std::thread * pth);
	void thproc_Cont();
	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(libusb_transfer *transfer);
	static void MCB(void *transfer);

public:

};

inline bool CVPusbIO::DataReady(uint8_t *pdata, int nBytes)
{
	//CODETRACE_ALT;

	//DOUTHEXBUF_ALT(pdata, nBytes, true, __FUNCTION__);
	if (m_pDataReadyCallback)
	{
		m_pDataReadyCallback(m_pCBInfo, pdata, (uint32_t)nBytes);
	}
	// if no callback was specified, copy data into capture buffer
	else
	{
		//DOUT0("%s : no DataReady Callback specified\r\n", __FUNCTION__);

		std::lock_guard<std::mutex> lck(m_mtxRxData);

		if (m_iNextHostIndex + nBytes >= 2048)
			m_iNextHostIndex = 0;

		memcpy(&(m_cap_buf[m_iNextHostIndex]), pdata, nBytes);
		m_iHostFC++;
		m_pLastBufIn = &(m_cap_buf[m_iNextHostIndex]);
		m_iLastBytesIn = nBytes;
		m_iNextHostIndex += nBytes;
	}

	//CODETRACEEX_ALT;

	return m_bContReadRunning;
}
