#include "meshframing.h"
#include "ch32v30x_gpio.h"
#include "lib/config.h"
#include "meshcore/stats.h"
#include "string.h"
#include "stdio.h"
#include "sx1262.h"
#include "util/hexdump.h"
#include "util/log.h"
#include "lib/cifra/sha2.h"

#define TAG "Meshframing"

int ReadFrame (FrameStruct *frame, int8_t *rssiPacket, int8_t *snrPacket, int8_t *rawSnr) {
    uint16_t irqRegs = GetIrqStatus();
    // uint8_t status = GetStatus();

    if (irqRegs & SX126X_IRQ_RX_DONE) {
        // ClearIrqStatus(SX126X_IRQ_RX_DONE);
        ClearIrqStatus (SX126X_IRQ_ALL);

        uint8_t offset = 0;
        uint8_t payloadLength = 0;
        GetRxBufferStatus (&payloadLength, &offset);

        if (payloadLength == 0) {
            return 0;
        }

        GetPacketStatus (rssiPacket, snrPacket, rawSnr);

        memset (frame, 0, sizeof (FrameStruct));
        WaitForIdle (BUSY_WAIT, "start ReadBuffer", 1);

        uint8_t cmd[3] = {SX126X_CMD_READ_BUFFER, offset, SX126X_CMD_NOP};

        GPIO_WriteBit (GPIOA, GPIO_Pin_4, 0);

        uint16_t curPayloadIndex = 0;
        uint8_t pathIndex = 0;
        uint8_t transportIndex = 0;
        uint8_t state = 0;  // 0=header, 1=transport, 2=pathLen, 3=path, 4=payload

        for (uint16_t i = 0; i < payloadLength + sizeof (cmd); i++) {
            uint8_t out = (i < sizeof (cmd)) ? cmd[i] : 0xFF;

            // Wait TX ready
            while (!SPI_I2S_GetFlagStatus (SPI1, SPI_I2S_FLAG_TXE));
            SPI_I2S_SendData (SPI1, out);

            // Wait RX ready
            while (!SPI_I2S_GetFlagStatus (SPI1, SPI_I2S_FLAG_RXNE));
            uint8_t in = (uint8_t)SPI_I2S_ReceiveData (SPI1);

            // Only process payload bytes
            if (i >= sizeof (cmd)) {
                switch (state) {
                case 0:  // header
                    frame->header = in;
                    state = ((frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_DIRECT ||
                             (frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_FLOOD)
                                ? 1
                                : 2;
                    break;

                case 1:  // transportCodes[4]
                    frame->transportCodes[transportIndex++] = in;
                    if (transportIndex >= 4)
                        state = 2;
                    break;

                case 2:  // pathLen
                    frame->path.pathLen = in;
                    if (frame->path.pathLen > 64) {
                        frame->path.pathLen = 64;
                    }
                    pathIndex = 0;
                    state = (frame->path.pathLen > 0) ? 3 : 4;
                    break;

                case 3:  // path
                    frame->path.path[pathIndex++] = in;
                    if (pathIndex >= frame->path.pathLen)
                        state = 4;
                    break;

                case 4:  // payload
                    frame->payload[curPayloadIndex++] = in;
                    break;
                }
            }
        }

        frame->payloadLen = curPayloadIndex;

        GPIO_WriteBit (GPIOA, GPIO_Pin_4, 1);

        WaitForIdle (BUSY_WAIT, "end ReadBuffer", 0);
        return payloadLength;
    }
    return 0;
}

/*
FrameStruct decodeFrame (unsigned char *data, unsigned char dataLen) {
    FrameStruct frame;
    memset (&frame, 0, sizeof (frame));
    unsigned char index = 0;
    frame.header = data[index++];
    if ((frame.header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_DIRECT ||
        (frame.header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_FLOOD) {
        memcpy (frame.transportCodes, data + index, 4);
        index += 4;
    }
    frame.path.pathLen = data[index++];

    memcpy (frame.path.path, data + index, frame.path.pathLen);
    index += frame.path.pathLen;
    frame.payloadLen = dataLen - index;
    memcpy (frame.payload, data + index, frame.payloadLen);

    return frame;
}
*/
void printframeHeader (const FrameStruct *frame) {
    switch (frame->header & ROUTE_TYPE_MASK) {
    case ROUTE_TYPE_TRANSPORT_FLOOD:
        iprintf ("transport flood");
        break;

    case ROUTE_TYPE_FLOOD:
        iprintf ("flood");
        break;

    case ROUTE_TYPE_DIRECT:
        iprintf ("direct");
        break;

    case ROUTE_TYPE_TRANSPORT_DIRECT:
        iprintf ("transport direct");
        break;
    }

    iprintf (", payload type is ");

    switch (frame->header & PAYLOAD_TYPE_MASK) {
    case PAYLOAD_TYPE_REQ:
        iprintf ("request");
        break;

    case PAYLOAD_TYPE_RESPONSE:
        iprintf ("response");
        break;

    case PAYLOAD_TYPE_TXT_MSG:
        iprintf ("text message");
        break;

    case PAYLOAD_TYPE_ACK:
        iprintf ("acknowledgement");
        break;

    case PAYLOAD_TYPE_ADVERT:
        iprintf ("advert");
        break;

    case PAYLOAD_TYPE_GRP_TXT:
        iprintf ("group text");
        break;

    case PAYLOAD_TYPE_GRP_DATA:
        iprintf ("group data");
        break;

    case PAYLOAD_TYPE_ANON_REQ:
        iprintf ("anon request");
        break;

    case PAYLOAD_TYPE_PATH:
        iprintf ("path");
        break;

    case PAYLOAD_TYPE_TRACE:
        iprintf ("trace");
        break;

    case PAYLOAD_TYPE_MULTIPART:
        iprintf ("multipart");
        break;

    case PAYLOAD_TYPE_CONTROL:
        iprintf ("control");
        break;

    case PAYLOAD_TYPE_RAW_CUSTOM:
        iprintf ("raw");
        break;
    }
    char version[2];
    version[0] = (frame->header >> 6) + '0';
    version[1] = 0;

    iprintf (", payload version is %s ", version);

    if ((frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_DIRECT ||
        (frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_FLOOD) {
        iprintf ("Transport codes: %d %d\n", *((uint16_t *)frame->transportCodes),
                *((uint16_t *)&(frame->transportCodes[2])));
    }
    iprintf ("Path is %d nodes long", frame->path.pathLen);

    for (uint8_t pathIndex = 0; pathIndex < frame->path.pathLen; pathIndex++) {
        iprintf ("node %d - %02X, ", pathIndex, frame->path.path[pathIndex]);
    }
    putchar ('\n');
}

void LoRaTransmit (const FrameStruct *frame) {
    addToNotReTX(frame);
    uint16_t len = 2;  // header + path_len

    if (
        ((frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_DIRECT) ||
        ((frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_FLOOD)
        ) {
        len += 4;
    }

    len += frame->path.pathLen;
    len += frame->payloadLen;

    if (len > 255) {
        MESH_LOGE(TAG, "%d is too big for sx1262", len);
    }

    uint16_t irqStatus;
    char rv = 0;

    size_t outCounter = 0;

    if (txActive == 0) {
        txActive = 1;

        if (PacketParams[2] == 0x00) {  // explicit header, variable length
            PacketParams[3] = len;
        }

        WriteCommand (SX126X_CMD_SET_PACKET_PARAMS, PacketParams, 6);
        ClearIrqStatus (SX126X_IRQ_ALL);

        WaitForIdle (BUSY_WAIT, "start WriteBuffer", 1);

        uint8_t cmdBuf[2] = {SX126X_CMD_WRITE_BUFFER, 0x00};
        uint8_t cmdLen = sizeof (cmdBuf);

        GPIO_WriteBit (GPIOA, GPIO_Pin_4, 0);

        uint16_t payloadIndex = 0;
        uint8_t state = 0;
        uint8_t pathIndex = 0;
        uint8_t transportIndex = 0;

        for (uint16_t i = 0; i < len + cmdLen; i++) {
            uint8_t out = 0xFF;

            if (i < cmdLen) {
                out = cmdBuf[i];
            } else {
                switch (state) {
                case 0:  // header
                    out = frame->header;
                    outCounter = 0;
                    state = ((frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_DIRECT ||
                             (frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_FLOOD)
                                ? 1
                                : 2;
                    break;

                case 1:  // transport codes
                    out = frame->transportCodes[transportIndex++];
                    if (transportIndex >= 4)
                        state = 2;
                    break;

                case 2:  // path length
                    out = frame->path.pathLen;
                    pathIndex = 0;
                    state = (frame->path.pathLen > 0) ? 3 : 4;
                    break;

                case 3:  // path
                    out = frame->path.path[pathIndex++];
                    if (pathIndex >= frame->path.pathLen)
                        state = 4;
                    break;

                case 4:  // payload
                    out = frame->payload[payloadIndex++];
                    break;
                }
            }

            while (SPI_I2S_GetFlagStatus (SPI1, SPI_I2S_FLAG_TXE) == RESET);
            outCounter++;
            SPI_I2S_SendData (SPI1, out);

            while (SPI_I2S_GetFlagStatus (SPI1, SPI_I2S_FLAG_RXNE) == RESET);
            (void)SPI_I2S_ReceiveData (SPI1);  // discard
        }

        GPIO_WriteBit (GPIOA, GPIO_Pin_4, 1);

        WaitForIdle (BUSY_WAIT, "end WriteBuffer", 0);

        MESH_LOGD(TAG,
        "TX payloadLen=%u pathLen=%u totalLen=%u header=0x%02X",
        frame->payloadLen,
        frame->path.pathLen,
        len,
        frame->header);


        MESH_LOGD(TAG, "Starting tx mode, sent %d, should %d", outCounter, len);
        
        SetTx (3000);

        MESH_LOGD(TAG, "SetTx running");

        irqStatus = GetIrqStatus();
        while (!(irqStatus & (SX126X_IRQ_TX_DONE | SX126X_IRQ_TIMEOUT))) {
            Delay_Ms(10);
            irqStatus = GetIrqStatus();
            if (debugPrint) {
                MESH_LOGD(TAG, "irq: 0x%04X", irqStatus);
            }
        }

        MESH_LOGD(TAG, "Finished tx");
        txActive = 0;
        SetRx (0xFFFFFF);

        if (irqStatus & SX126X_IRQ_TX_DONE) {
            rv = 1;
        }
    }

    if (rv == 0) {
        txLost++;
    }
}

/*
void sendFrame (const FrameStruct *frame) {
    uint8_t txBuf[256];
    size_t offset = 0;

    txBuf[offset++] = frame->header;

    if ((frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_DIRECT ||
        (frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_FLOOD) {
        memcpy (txBuf + offset, frame->transportCodes, 4);
        offset += 4;
    }

    if ((frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_FLOOD ||
        (frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_FLOOD) {
        stats.sentFloodCount++;
    }

    if ((frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_DIRECT ||
        (frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_DIRECT) {
        stats.sentDirectCount++;
    }

    stats.packetsSentCount++;

    uint8_t pathLen = frame->path.pathLen;


    if (pathLen > 64) {
        pathLen = 64;
    }

    txBuf[offset++] = pathLen;

    memcpy (txBuf + offset, frame->path.path, pathLen);
    hexdump ("TxDump Path", frame->path.path, frame->path.pathLen);
    offset += pathLen;

    uint16_t maxPayloadLen = 256 - offset;

    uint16_t payloadLen = frame->payloadLen > maxPayloadLen ? maxPayloadLen : frame->payloadLen;

    memcpy (txBuf + offset, frame->payload, payloadLen);
    offset += payloadLen;


    hexdump ("TxDump", txBuf, offset);
    hexdump ("TxDumpPayload", frame->payload, frame->payloadLen);
    TickType_t start, end;
    start = xTaskGetTickCount();
    LoRaSend (txBuf, offset, SX126x_TXMODE_SYNC);
    end = xTaskGetTickCount();
    tickAirtime += end - start;
}
*/

KnownPacketHistoryType dontReTXHistory[NON_RETX_HISTORY];
uint8_t dontReTXHistoryIndex = 0;

void getFrameHash(const FrameStruct *frame, uint8_t *hash) {
    cf_sha256_context ctx;

    // 1. Initialize
    cf_sha256_init (&ctx);

    // 2. Feed in your data
    cf_sha256_update (&ctx, frame->payload, frame->payloadLen);

    // 3. Compute digest
    cf_sha256_digest (&ctx, hash);
}

void addToNotReTX(const FrameStruct *frame) {
    KnownPacketHistoryType * knownHist = &(dontReTXHistory[dontReTXHistoryIndex]);
    
    getFrameHash(frame, knownHist->hash);
    knownHist->populated = 1;

    dontReTXHistoryIndex++;
    if (dontReTXHistoryIndex >= NON_RETX_HISTORY) {
        dontReTXHistoryIndex = 0;
    }
}

void retransmitFrame (FrameStruct *frame) {

    uint8_t hashTmp[CF_SHA256_HASHSZ];
    getFrameHash(frame, hashTmp);

    uint8_t found = 0;
    for (uint8_t i = 0; i < NON_RETX_HISTORY; i++) {
        KnownPacketHistoryType * knownHist = &(dontReTXHistory[dontReTXHistoryIndex]);
        if (knownHist->populated == 0) {
            continue;
        }
        if (memcmp(knownHist->hash, hashTmp, CF_SHA256_HASHSZ) == 0) {
            found = 1;
            break;
        }
    }
    if (found) {
        return;
    }

    /* -------- FLOOD -------- */
    if (frame->header & ROUTE_TYPE_FLOOD ||
        frame->header & ROUTE_TYPE_TRANSPORT_FLOOD) {

        if (frame->header != DONT_RETRANSMIT_HEADER &&
            frame->path.pathLen + 1 < MAX_FLOOD_TTL) {

            // append self to END of path
            frame->path.path[frame->path.pathLen++] = persistent.pubkey[0];
            LoRaTransmit (frame);
        }
    }

    /* -------- DIRECT -------- */
    if (frame->header & ROUTE_TYPE_DIRECT ||
        frame->header & ROUTE_TYPE_TRANSPORT_DIRECT) {

        // are we the next hop?
        if (frame->path.pathLen > 0 &&
            frame->path.path[0] == persistent.pubkey[0]) {

            // remove self from START of path
            frame->path.pathLen--;
            memmove (frame->path.path,
                     frame->path.path + 1,
                     frame->path.pathLen);

            // forward only if there's another hop
            if (frame->path.pathLen > 0) {
                LoRaTransmit (frame);
            }
        }
    }
}

// Verify MAC + Decrypt

int encrypt_then_mac (const uint8_t *aes_key, const uint8_t keySize, const uint8_t *plaintext, size_t plen, uint8_t *output, size_t *olen) {
    if (plen == 0)
        return -1;

    size_t padded_len = ((plen + 15) / 16) * 16;

    // prepare padded buffer
    uint8_t padded[padded_len];
    memset (padded, 0, padded_len);    // zero padding
    memcpy (padded, plaintext, plen);  // copy plaintext

    // ciphertext will go right after HMAC
    uint8_t *ciphertext = output + HMAC_SIZE;

    // encrypt plaintext
    aes_encrypt_ecb (aes_key, 16, padded, padded_len, ciphertext);

    // compute HMAC over ciphertext
    uint8_t mac[32];  // full SHA-256
    hmac_sha256 (aes_key, keySize, ciphertext, padded_len, mac);

    // copy only HMAC_SIZE bytes of MAC
    memcpy (output, mac, HMAC_SIZE);

    // return total length = HMAC + ciphertext
    *olen = HMAC_SIZE + padded_len;
    return 0;
}

int mac_then_decrypt (const uint8_t *aes_key, const uint8_t keySize, const uint8_t *input, size_t ilen, uint8_t *plaintext) {
    if (ilen <= HMAC_SIZE)
        return -1;

    const uint8_t *mac = input;
    const uint8_t *ciphertext = input + HMAC_SIZE;
    size_t clen = ilen - HMAC_SIZE;

    if (clen % 16 != 0)
        return -2;         // must be multiple of block size

    uint8_t calc_mac[32];  // full SHA-256
    hmac_sha256 (aes_key, keySize, ciphertext, clen, calc_mac);

    if (memcmp (mac, calc_mac, HMAC_SIZE) != 0)
        return -2;

    return aes_decrypt_ecb (aes_key, 16, ciphertext, clen, plaintext);
}

uint16_t getTransportCode (const FrameStruct *frame) {
    uint16_t code;
    /*
    // compute HMAC over ciphertext
    uint8_t mac[32];  // full SHA-256
    cf_hmac_ctx ctx;
    cf_hmac_init (&ctx, &cf_sha256, key, sizeof(key));
    cf_hmac_update (&ctx, &(frame->header), sizeof (frame->header));
    cf_hmac_update (&ctx, frame->payload, frame->payloadLen);
    cf_hmac_finish (&ctx, mac);

    // copy only HMAC_SIZE bytes of MAC
    memcpy (&code, mac, HMAC_SIZE);
    */
    if (code == 0) {  // reserve codes 0000 and FFFF
        code++;
    } else if (code == 0xFFFF) {
        code--;
    }
    return code;
}