// Example program
#include <iostream>
#include <string>
#include <cstdint>

#define WORD uint16_t
#define DWORD uint32_t
#define BYTE uint8_t
#define PBYTE uint8_t*
#define TRUE 1
#define BOOL int
#define QWORD uint64_t
#define FALSE 0
#define PDWORD DWORD*

#define ENDIAN_SWAP_DWORD(x)    (x = (x << 24) | ((x >> 8) & 0xff00) | ((x << 8) & 0xff0000) | (x >> 24))

#define TLP_MRd32       0x00
#define TLP_MRd64       0x20
#define TLP_MRdLk32     0x01
#define TLP_MRdLk64     0x21
#define TLP_MWr32       0x40
#define TLP_MWr64       0x60
#define TLP_IORd        0x02
#define TLP_IOWr        0x42
#define TLP_CfgRd0      0x04
#define TLP_CfgRd1      0x05
#define TLP_CfgWr0      0x44
#define TLP_CfgWr1      0x45
#define TLP_Cpl         0x0A
#define TLP_CplD        0x4A
#define TLP_CplLk       0x0B
#define TLP_CplDLk      0x4B

#define MAX_SIZE_TX     0x3f0

typedef struct tdTLP_HDR {
    WORD Length : 10;
    WORD _AT : 2;
    WORD _Attr : 2;
    WORD _EP : 1;
    WORD _TD : 1;
    BYTE _R1 : 4;
    BYTE _TC : 3;
    BYTE _R2 : 1;
    BYTE TypeFmt;
} TLP_HDR, *PTLP_HDR;

typedef struct tdTLP_HDR_MRdWr32 {
    TLP_HDR h;
    BYTE FirstBE : 4;
    BYTE LastBE : 4;
    BYTE Tag;
    WORD RequesterID;
    DWORD Address;
} TLP_HDR_MRdWr32, *PTLP_HDR_MRdWr32;

typedef struct tdTLP_HDR_MRdWr64 {
    TLP_HDR h;
    BYTE FirstBE : 4;
    BYTE LastBE : 4;
    BYTE Tag;
    WORD RequesterID;
    DWORD AddressHigh;
    DWORD AddressLow;
} TLP_HDR_MRdWr64, *PTLP_HDR_MRdWr64;

int pfnFT_WritePipe(PBYTE pb, DWORD cb) {
    printf("writepipe: %d bytes\n", cb);
    
    for (int i = 0; i < cb; i++) {
        printf("%d, ", (int)pb[i]);
    }

    printf("\n\n");
    
    return 0;
}

BOOL DeviceFPGA_TxTlp(PBYTE pbTlp,  DWORD cbTlp,  BOOL fRdKeepalive, BOOL fFlush)
{
    PBYTE txbuf_pb = (PBYTE)malloc(0x1000 * 32);
    DWORD txbuf_cb = 0;
    
    DWORD status;
    PBYTE pbTx;
    QWORD i;
    DWORD cbTx, cbTxed = 0;
    if(cbTlp & 0x3) { return FALSE; }
    if(cbTlp > 4 * 4 + 128) { return FALSE; }
    if(cbTlp && (txbuf_cb + (cbTlp << 1) + (fFlush ? 8 : 0) >= MAX_SIZE_TX)) {
        if(!DeviceFPGA_TxTlp(NULL, 0, FALSE, TRUE)) { return FALSE; }
    }
    //if(ctxLC->fPrintf[LC_PRINTF_VVV]) {
    //    TLP_Print(ctxLC, pbTlp, cbTlp, TRUE);
    //}
    // prepare transmit buffer
    pbTx = txbuf_pb + txbuf_cb;
    cbTx = 2 * cbTlp;
    for(i = 0; i < cbTlp; i += 4) {
        *(PDWORD)(pbTx + (i << 1)) = *(PDWORD)(pbTlp + i);
        *(PDWORD)(pbTx + ((i << 1) + 4)) = 0x77000000;    // TX TLP
    }
    if(cbTlp) {
        *(PDWORD)(pbTx + ((i << 1) - 4)) = 0x77040000;    // TX TLP VALID LAST
    }
    if(fRdKeepalive) {
        cbTx += 8;
        *(PDWORD)(pbTx + (i << 1)) = 0xffeeddcc;
        *(PDWORD)(pbTx + ((i << 1) + 4)) = 0x77020000;    // LOOPBACK TX
    }
    txbuf_cb += cbTx;
    // transmit
    if((txbuf_cb >= MAX_SIZE_TX) || (fFlush && txbuf_cb)) {
        status = pfnFT_WritePipe(txbuf_pb, txbuf_cb);
        if(status == 0x20) {
            //DeviceFPGA_ReInitializeFTDI(ctx); // try recovery if possible.
            status = pfnFT_WritePipe(txbuf_pb, txbuf_cb);
        }
        txbuf_cb = 0;
        //usleep(ctx->perf.DELAY_WRITE);
        return (0 == status);
    }
    return TRUE;
}



int main()
{
    DWORD tx[4] = { 0 };
    PBYTE txb = (PBYTE)tx;
    PTLP_HDR_MRdWr64 hdrRd64 = (PTLP_HDR_MRdWr64)tx;
    PTLP_HDR_MRdWr32 hdrRd32 = (PTLP_HDR_MRdWr32)tx;
    
    printf("stuff: 0x%x\n", (WORD)(0x123 >> 2));
    /*
    hdrRd32->h.TypeFmt = TLP_MRd64;
    hdrRd32->h.Length = (WORD)(0x123 >> 2);
    */
    hdrRd32->h.TypeFmt = TLP_MRd32;
    hdrRd32->h.Length = (WORD)(0x123 >> 2);
    hdrRd32->RequesterID = 17;
    hdrRd32->Tag = 0x80;
    hdrRd32->FirstBE = 0xf;
    hdrRd32->LastBE = 0xf;
    
    hdrRd32->Address = (DWORD)(0x6000);
    //uint64_t addr = 0x100000000 + 0x6000;
    //hdrRd64->AddressHigh = (DWORD)(addr >> 32);
    //hdrRd64->AddressLow = (DWORD)(addr);
    
    //printf("sizeof 32: 0x%x\n", sizeof(TLP_HDR_MRdWr32));
    //printf("sizeof 64: 0x%x\n", sizeof(TLP_HDR_MRdWr64));

    printf("tlp:\n");
    for (int i = 0; i < 16; i++) {
        printf("%d ", (int)txb[i]);
        //printf("%X ", tx[i]);
    }
    printf("\n");

    for(int j = 0; j < 4; j++) {
        ENDIAN_SWAP_DWORD(tx[j]);
    }

    DeviceFPGA_TxTlp((PBYTE)tx, /*is32 ? 12 : 16*/ 12, FALSE, TRUE);
}
