// Copyright (c) 2020 Metrolab Technology S.A., Geneva, Switzerland (www.metrolab.com)
// See the included file LICENSE.txt for the licensing conditions.

#pragma once

#include "IEEE488InstrumentTypes.h"

using namespace MTL::Instrument;

//----------------------------------------------------------------------//
/// \brief Constants for USBTMC and USB488.
// Some USBTMC-specific enums, as defined in the USBTMC standard.
static const U8 SUBCLASS_USBTMC 						= 0x03;
static const U8 USBTMC_USB488   						= 0x01;

// USBTMC control requests
enum {
	INITIATE_ABORT_BULK_OUT								=   1,
	CHECK_ABORT_BULK_OUT_STATUS							=   2,
	INITIATE_ABORT_BULK_IN								=   3,
	CHECK_ABORT_BULK_IN_STATUS							=   4,
	INITIATE_CLEAR										=   5,
	CHECK_CLEAR_STATUS									=   6,
	GET_CAPABILITIES									=   7,
	INDICATOR_PULSE										=  64,
	
	// USB488 control requests
	READ_STATUS_BYTE									= 128,
	REN_CONTROL											= 160,
	GO_TO_LOCAL											= 161,
	LOCAL_LOCKOUT										= 162,
};

// Constants for control endpoint transfers.
static const I32 USB_MAX_PORT_DEPTH						= 7;
static const U16 USB_CONTROL_ENDPOINT_NUMBER			= 0;
static const U32 USB_DESCRIPTOR_STRING_LENGTH			= 256;
static const U8	USBTMC_BM_REQUEST_TYPE_IN_CLS_IF		= 0xA1;
static const U8 USBTMC_BM_REQUEST_TYPE_IN_CLS_EP		= 0xA2;
static const U16 USBTMC_READ_BUFFER_SIZE				= 256;
static const U16 USBTMC_READSTB_CTRL_RESP_LENGTH		= 3;
static const U16 USBTMC_READSTB_INTR_RESP_LENGTH		= 2;
static const U16 USBTMC_INITIATE_CLEAR_RESP_LENGTH		= 1;
static const U16 USBTMC_CHECK_CLEAR_STATUS_RESP_LENGTH	= 2;
static const U32 USBTMC_CHECK_CLEAR_STATUS_INTERVAL		= 10;
static const U16 USBTMC_INITIATE_ABORT_BULKIN_RESP_LENGTH		= 2;
static const U16 USBTMC_CHECK_ABORT_BULKIN_STATUS_RESP_LENGTH	= 8;
static const U16 USBTMC_INITIATE_ABORT_BULKOUT_RESP_LENGTH		= 2;
static const U16 USBTMC_CHECK_ABORT_BULKOUT_STATUS_RESP_LENGTH	= 8;

// USBTMC status codes
enum
{
	USBTMC_STATUS_SUCCESS								= 0x01,
	USBTMC_STATUS_PENDING								= 0x02,
	USBTMC_STATUS_INTERRUPT_IN_BUSY						= 0x20,
	USBTMC_STATUS_FAILED								= 0x80,
	USBTMC_STATUS_TRANSFER_NOT_IN_PROGRESS				= 0x81,
	USBTMC_STATUS_SPLIT_NOT_IN_PROGRESS					= 0x82,
	USBTMC_STATUS_SPLIT_IN_PROGRESS						= 0x83,
};

// USBTMC capabilities
static const U8 USBTMC_INT_CAP_LISTEN_ONLY				= 0x01;
static const U8 USBTMC_INT_CAP_TALK_ONLY				= 0x02;
static const U8 USBTMC_INT_CAP_INDICATOR				= 0x04;

static const U8 USBTMC_DEV_CAP_TERMCHAR					= 0x01;

static const U8 USB488_DEV_CAP_DT1						= 0x01;
static const U8 USB488_DEV_CAP_RL1						= 0x02;
static const U8 USB488_DEV_CAP_SR1						= 0x04;
static const U8 USB488_DEV_CAP_SCPI						= 0x08;

// Bulk message constants
static const I32 USBTMC_BULK_HEADER_SIZE				= 12;
static const I32 USBTMC_BULK_MIN_BUFFER_SIZE			= 64;
static const I32 USBTMC_BULK_TRIGGER_MSG_SIZE			= 12;

// Bulk MsgID values
static const U8 DEV_DEP_MSG_OUT							= 1;
static const U8 REQUEST_DEV_DEP_MSG_IN					= 2;
static const U8 DEV_DEP_MSG_IN							= 2;
static const U8 TRIGGER									= 128;

// bmTransferAttributes
static const U8 EOM										= 0x01;
static const U8 TERM_CHAR_ENABLED						= 0x02;

// bmClear
static const U8 BULKIN_FIFO_BYTES						= 0x01;

//----------------------------------------------------------------------//
/// \brief Error codes. Complementary to libusb errors (0 to -99).
enum USBTMCError
{
	USBTMC_ERROR_FIND_RESRC_BAD_FILTER					= 1,
	USBTMC_ERROR_FIND_RESRC_NO_USBTMC_USB488,
	USBTMC_ERROR_GET_RESRC_NOT_FOUND,
	USBTMC_ERROR_WRITE_INVALID_TRANSFER_COUNT,
	USBTMC_ERROR_READ_INVALID_BUFFER_FOR_APPEND,
	USBTMC_ERROR_READ_INVALID_HEADER,
	USBTMC_ERROR_READ_WRONG_MSG_SIZE,
	USBTMC_ERROR_CLEAR_RESPONSE_ERROR,
	USBTMC_ERROR_CLEAR_BULKIN_RESPONSE_ERROR,
	USBTMC_ERROR_CLEAR_BULKOUT_RESPONSE_ERROR,
	USBTMC_ERROR_READSTB_CONTROL_RESPONSE_ERROR,
	USBTMC_ERROR_READSTB_INTERRUPT_RESPONSE_ERROR,
	USBTMC_ERROR_INSTRUMENT_LOCKED,
    USBTMC_ERROR_DEVICE_NOT_OPEN,
    USBTMC_ERROR_SRQ_NOT_ENABLED
};

/// \brief Error explanations.
static const char * USBTMC_ERROR_EXPLANATION[] =
{
	"USBTMC: Success",
	"USBTMC FindResources: Invalid filter",
	"USBTMC FindResources: Failed to find USBTMC-USB488 interface",
	"USBTMC GetDeviceListEntry: Resource not found",
	"USBTMC Write: invalid transfer count",
	"USBTMC Read: Buffer is invalid for append read (no room for read header)",
	"USBTMC Read: Read header is invalid",
	"USBTMC Read: Message size is wrong",
	"USBTMC Clear: Response error",
	"USBTMC Clear BulkIn: Response error",
	"USBTMC Clear BulkOut: Response error",
	"USBTMC ReadSTB: Control endpoint response error",
	"USBTMC ReadSTB: Interrupt endpoint response error",
	"USBTMC: Instrument is locked by someone else",
    "USBTMC: Instrument is not open",
    "USBTMC: Service Requests not enabled"
};

//----------------------------------------------------------------------//
/// \brief Memory access macros.
/**
 * Read a 8 bits unsigned integer out of memory.
 * @param x a pointer to the input memory
 * @return the corresponding unsigned integer
 */
#define R8(x)     ((unsigned)((const U8*)(x))[0])

/**
 * Read a 16 bits big endian unsigned integer out of memory.
 * @param x a pointer to the input memory
 * @return the corresponding unsigned integer
 */
#define RB16(x)  (((unsigned)((const U8*)(x))[0] <<  8) | \
(unsigned)((const U8*)(x))[1])

/**
 * Read a 16 bits little endian unsigned integer out of memory.
 * @param x a pointer to the input memory
 * @return the corresponding unsigned integer
 */
#define RL16(x)  (((unsigned)((const U8*)(x))[1] <<  8) | \
(unsigned)((const U8*)(x))[0])

/**
 * Read a 16 bits big endian signed integer out of memory.
 * @param x a pointer to the input memory
 * @return the corresponding signed integer
 */
#define RB16S(x)  ((int16_t) \
(((unsigned)((const U8*)(x))[0] <<  8) | \
(unsigned)((const U8*)(x))[1]))

/**
 * Read a 16 bits little endian signed integer out of memory.
 * @param x a pointer to the input memory
 * @return the corresponding signed integer
 */
#define RL16S(x)  ((int16_t) \
(((unsigned)((const U8*)(x))[1] <<  8) | \
(unsigned)((const U8*)(x))[0]))

/**
 * Read a 32 bits big endian unsigned integer out of memory.
 * @param x a pointer to the input memory
 * @return the corresponding unsigned integer
 */
#define RB32(x)  (((unsigned)((const U8*)(x))[0] << 24) | \
((unsigned)((const U8*)(x))[1] << 16) | \
((unsigned)((const U8*)(x))[2] <<  8) | \
(unsigned)((const U8*)(x))[3])

/**
 * Read a 32 bits little endian unsigned integer out of memory.
 * @param x a pointer to the input memory
 * @return the corresponding unsigned integer
 */
#define RL32(x)  (((unsigned)((const U8*)(x))[3] << 24) | \
((unsigned)((const U8*)(x))[2] << 16) | \
((unsigned)((const U8*)(x))[1] <<  8) | \
(unsigned)((const U8*)(x))[0])

/**
 * Read a 32 bits big endian signed integer out of memory.
 * @param x a pointer to the input memory
 * @return the corresponding signed integer
 */
#define RB32S(x)  ((int32_t) \
(((unsigned)((const U8*)(x))[0] << 24) | \
((unsigned)((const U8*)(x))[1] << 16) | \
((unsigned)((const U8*)(x))[2] <<  8) | \
(unsigned)((const U8*)(x))[3]))

/**
 * Read a 32 bits little endian signed integer out of memory.
 * @param x a pointer to the input memory
 * @return the corresponding signed integer
 */
#define RL32S(x)  ((int32_t) \
(((unsigned)((const U8*)(x))[3] << 24) | \
((unsigned)((const U8*)(x))[2] << 16) | \
((unsigned)((const U8*)(x))[1] <<  8) | \
(unsigned)((const U8*)(x))[0]))

/**
 * Read a 64 bits big endian unsigned integer out of memory.
 * @param x a pointer to the input memory
 * @return the corresponding unsigned integer
 */
#define RB64(x)  (((uint64_t)((const U8*)(x))[0] << 56) | \
((uint64_t)((const U8*)(x))[1] << 48) | \
((uint64_t)((const U8*)(x))[2] << 40) | \
((uint64_t)((const U8*)(x))[3] << 32) | \
((uint64_t)((const U8*)(x))[4] << 24) | \
((uint64_t)((const U8*)(x))[5] << 16) | \
((uint64_t)((const U8*)(x))[6] <<  8) | \
(uint64_t)((const U8*)(x))[7])

/**
 * Read a 64 bits little endian unsigned integer out of memory.
 * @param x a pointer to the input memory
 * @return the corresponding unsigned integer
 */
#define RL64(x)  (((uint64_t)((const U8*)(x))[7] << 56) | \
((uint64_t)((const U8*)(x))[6] << 48) | \
((uint64_t)((const U8*)(x))[5] << 40) | \
((uint64_t)((const U8*)(x))[4] << 32) | \
((uint64_t)((const U8*)(x))[3] << 24) | \
((uint64_t)((const U8*)(x))[2] << 16) | \
((uint64_t)((const U8*)(x))[1] <<  8) | \
(uint64_t)((const U8*)(x))[0])

/**
 * Read a 64 bits little endian signed integer out of memory.
 * @param x a pointer to the input memory
 * @return the corresponding unsigned integer
 */
#define RL64S(x)  ((int64_t) \
(((uint64_t)((const U8*)(x))[7] << 56) | \
((uint64_t)((const U8*)(x))[6] << 48) | \
((uint64_t)((const U8*)(x))[5] << 40) | \
((uint64_t)((const U8*)(x))[4] << 32) | \
((uint64_t)((const U8*)(x))[3] << 24) | \
((uint64_t)((const U8*)(x))[2] << 16) | \
((uint64_t)((const U8*)(x))[1] <<  8) | \
(uint64_t)((const U8*)(x))[0]))

/**
 * Read a 32 bits big endian float out of memory.
 * @param x a pointer to the input memory
 * @return the corresponding float
 */
#define RBFL(x)  ((union { uint32_t u; float f; }) { .u = RB32(x) }.f)

/**
 * Read a 32 bits little endian float out of memory.
 * @param x a pointer to the input memory
 * @return the corresponding float
 */
#define RLFL(x)  ((union { uint32_t u; float f; }) { .u = RL32(x) }.f)

/**
 * Write a 8 bits unsigned integer to memory.
 * @param p a pointer to the output memory
 * @param x the input unsigned integer
 */
#define W8(p, x)    do { ((U8*)(p))[0] = (U8) (x);      } while (0)

/**
 * Write a 16 bits unsigned integer to memory stored as big endian.
 * @param p a pointer to the output memory
 * @param x the input unsigned integer
 */
#define WB16(p, x)  do { ((U8*)(p))[1] = (U8) (x);      \
((U8*)(p))[0] = (U8)((x)>>8);  } while (0)

/**
 * Write a 16 bits unsigned integer to memory stored as little endian.
 * @param p a pointer to the output memory
 * @param x the input unsigned integer
 */
#define WL16(p, x)  do { ((U8*)(p))[0] = (U8) (x);      \
((U8*)(p))[1] = (U8)((x)>>8);  } while (0)

/**
 * Write a 32 bits unsigned integer to memory stored as big endian.
 * @param p a pointer to the output memory
 * @param x the input unsigned integer
 */
#define WB32(p, x)  do { ((U8*)(p))[3] = (U8) (x);      \
((U8*)(p))[2] = (U8)((x)>>8);  \
((U8*)(p))[1] = (U8)((x)>>16); \
((U8*)(p))[0] = (U8)((x)>>24); } while (0)

/**
 * Write a 32 bits unsigned integer to memory stored as little endian.
 * @param p a pointer to the output memory
 * @param x the input unsigned integer
 */
#define WL32(p, x)  do { ((U8*)(p))[0] = (U8) (x);      \
((U8*)(p))[1] = (U8)((x)>>8);  \
((U8*)(p))[2] = (U8)((x)>>16); \
((U8*)(p))[3] = (U8)((x)>>24); } while (0)

/**
 * Write a 32 bits float to memory stored as big endian.
 * @param p a pointer to the output memory
 * @param x the input float
 */
#define WBFL(p, x)  WB32(p, (union { uint32_t u; float f; }) { .f = x }.u)

/**
 * Write a 32 bits float to memory stored as little endian.
 * @param p a pointer to the output memory
 * @param x the input float
 */
#define WLFL(p, x)  WL32(p, (union { uint32_t u; float f; }) { .f = x }.u)
