#include "radio.h"

#include "asmUtil.h"
#include "board.h"
#include "cpu.h"
#include "printf.h"
#include "timer.h"

#define RX_BUFFER_SIZE (RADIO_MAX_PACKET_LEN + 1 /* len byte */ + 2 /* RSSI & LQI */)
#define RX_BUFFER_NUM 3

static volatile uint8_t __xdata mRxBufs[RX_BUFFER_NUM][RX_BUFFER_SIZE];
static volatile uint8_t __xdata mLastRSSI, mLastTxedSeq, mRxOn, mRxBufNextR, mRxBufNextW, mRxBufNumFree;
static volatile __bit mAckTimePassed, mGotAck;

const uint8_t __code channelList[6] = {11, 15, 20, 25, 26, 27};

// some things look like: https://www.ti.com/lit/ds/symlink/cc2430.pdf
// maybe a licensed and heavily modified version?

// maybe rx on and tx cal?
// see segmented_ota.<<code:00006D7B radioRxEnable>>

void RF_IRQ1(void) __interrupt(4) {
    uint8_t cause = RADIO_IRQ4_pending;
    static uint8_t __xdata lastRSSI;

    RADIO_IRQ4_pending = 0;

    if (cause & 0x40) {  // ack time happened

        // radio will report ACK if we (1) got an ack or (2) sent a packet that did not require it
        mAckTimePassed = true;
        mGotAck = !!(cause & 0x10);
    }
    if (cause & 0x20) {  // radio has RXed a packet into its internal buffer. vet it quickly and set up DMA

        uint8_t len = RADIO_GOTLEN;

        if (len < 3 || len >= 0x80 || RADIO_rxFirstByte + 1 != len || !mRxOn || !mRxBufNumFree) {  // detect invalid packets right away, or RX being off

            RADIO_command = RADIO_CMD_FLUSH_RX_FIFO;
        } else {
            uint8_t __xdata *buf = mRxBufs[mRxBufNextW];
            uint8_t bkp;

            buf[0] = len;

            bkp = CFGPAGE;
            CFGPAGE = 4;
            RADIO_RXLEN = len - 1;
            RADIO_RXPTRL = ((uint16_t)(buf + 1)) & 0xff;
            RADIO_RXPTRH = ((uint16_t)(buf + 1)) >> 8;
            TRIGGER |= 4;  // start rx dma

            TCON2 &= ~4;  // without this we trigger next irq too fast and get garbage

            CFGPAGE = bkp;
        }
    }
    if ((cause & 0x10) && !(RADIO_curRfState & 0x20)) {  // radio got a valid preamble and is RXing a packet. this is our chance to sample some RSSI
        uint8_t i;

        // we get here if radio is RXing a packet - tells us to capture some RSSI vals.
        // Seems there is an offset. Value is signed and offset by 56
        for (i = 0; i < 0x3c; i++)
            mLastRSSI = RADIO_currentRSSI;
    }
}

void RF_IRQ2(void) __interrupt(5) {
    uint8_t bck = CFGPAGE;
    CFGPAGE = 4;

    if (TCON2 & 0x04) {  // RX dma over - time to check packet for valid CRC

        uint8_t __xdata *buf = mRxBufs[mRxBufNextW];
        uint8_t len;

        TCON2 &= ~0x04;
        RADIO_command = RADIO_CMD_FLUSH_RX_FIFO;

        // last byte we got DMAed to us has top bit as flags for "crc ok"

        len = buf[0];

        if (!(buf[len] & 0x80)) {
            // CRC failed on packet
        } else {
            buf[len - 1] = mLastRSSI - 56;

            if (++mRxBufNextW == RX_BUFFER_NUM)
                mRxBufNextW = 0;
            mRxBufNumFree--;
        }
    }
    if (TCON2 & 0x02) {  // TX DMA completed
        TCON2 &= ~2;

        // nothing to do here
    }
    if (TCON2 & 0x08) {  // radio init over
        TCON2 &= ~0x48;
        SETTINGS &= ~0x10;
    }
    CFGPAGE = bck;
}

bool radioTx(const void __xdata *packetP)  // waits for tx end
{
    const uint8_t __xdata *packet = (const uint8_t __xdata *)packetP;
    uint16_t bkp, wait;
    __bit irqs;

    // this is how to do CCA. we do not bother because fuck it
    // this is how WE do CCA. 'Fuck it' still somewhat applies if we don't get a clear channel in a reasonable amount of time
    //  okay fuck it.
    /*
    for (uint8_t i = 0; i < 0x80; i++) {
            if (!(RADIO_curRfState & 1)) {
                    //pr("radio CCA fail\n");
                    timerDelay(TIMER_TICKS_PER_SECOND / 2000);
                    //return;
            }
    }
    */

    mAckTimePassed = false;
    mGotAck = false;
    mLastTxedSeq = packet[3];

    bkp = CFGPAGE;
    CFGPAGE = 4;

    irqs = IEN_EA;
    IEN_EA = 0;

    RADIO_TXLEN = packet[0] - 2;
    RADIO_TXPTRL = ((uint16_t)packet) & 0xff;
    RADIO_TXPTRH = ((uint16_t)packet) >> 8;
    RADIO_command = RADIO_CMD_LOAD_TX_FIFO;
    TRIGGER |= 2;  // start TX fifo DMA

    IEN_EA = irqs;

    CFGPAGE = bkp;

    // RADIO_unk_C8 = 0xff;  /// stock fw does this but seems unnecessary

    // wait for tx to start
    wait = 0;
    wait--;
    do {
        if (RADIO_curRfState & 0x80)
            break;
    } while (--wait);

    // wait for tx to end
    if (wait) {
        while (!mAckTimePassed)
            ;
        return true;
    } else {
        return false;
    }

    // RADIO_unk_C8 = 0x7f;  /// stock fw does this but seems unnecessary
}

void radioRxAckReset(void) {
    mGotAck = false;
}

int16_t radioRxAckGetLast(void) {
    if (mGotAck)
        return (uint16_t)mLastTxedSeq;
    else
        return -1;
}

void radioRxFilterCfg(const uint8_t __xdata *filterForLong, uint32_t myShortMac, uint16_t myPan) {
    uint16_t shortMac = (myShortMac == SHORT_MAC_UNUSED) ? 0xffff : myShortMac;
    uint8_t i;

    RADIO_PANID_Hi = myPan >> 8;
    RADIO_PANID_Lo = myPan;

    RADIO_ownShortAddress_Hi = shortMac >> 8;
    RADIO_ownShortAddress_Lo = shortMac;

    for (i = 0; i < 8; i++)
        ((volatile uint8_t __xdata *)&RADIO_ownMac_7)[i] = filterForLong[(uint8_t)((uint8_t)7 - (uint8_t)i)];
}

void radioRxEnable(__bit on, __bit autoAck) {
    if (!autoAck) {
        pr("auto ack forced for now\n");
        while (1)
            ;
    }
    mRxOn = on;
}

void radioSetTxPower(int8_t dBm) {
    if (dBm < -27)
        dBm = -27;
    else if (dBm > 8)
        dBm = 8;

    dBm += 27;

    RADIO_txPower = (uint8_t)(((uint8_t)dBm) + 3) / 5;
}

void radioSetChannel(uint8_t ch) {
    static const uint8_t perChannelSetting1[] = {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x22, 0x22, 0x22, 0x22, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33};
    static const uint8_t perChannelSetting2[] = {4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2};

    RADIO_channel = ch;  // confirmed to be at least RX channel
    RADIO_command = RADIO_CMD_RECEIVE;
    RADIO_perChannelSetting1 = perChannelSetting1[ch - 11];
    RADIO_perChannelSetting2 = perChannelSetting2[ch - 11];
}

void radioRxFlush(void) {
    mRxBufNumFree = RX_BUFFER_NUM;
}

int8_t radioRxDequeuePktGet(const void __xdata *__xdata *dstBufP, uint8_t __xdata *lqiP, int8_t __xdata *rssiP) {
    const uint8_t __xdata *buf = mRxBufs[mRxBufNextR];
    uint8_t lqi, len = buf[0];

    if (mRxBufNumFree == RX_BUFFER_NUM)
        return -1;

    lqi = (buf[len] & 0x7f);

    *lqiP = lqi;
    *rssiP = buf[len - 1];
    *dstBufP = buf + 1;

    return len - 2;
}

void radioRxDequeuedPktRelease(void) {
    if (++mRxBufNextR == RX_BUFFER_NUM)
        mRxBufNextR = 0;

    __critical {
        mRxBufNumFree++;
    }
}

void radioInit(void) {
    uint8_t bkp;

    mRxBufNextW = 0;
    mRxBufNextR = 0;
    mRxBufNumFree = RX_BUFFER_NUM;

    timerDelay(TIMER_TICKS_PER_SECOND / 1000);
    RADIO_unk_F0 |= 0x80;
    timerDelay(TIMER_TICKS_PER_SECOND / 1000);
    CFGPAGE = 0;
    RESET &= ~4;
    RESET |= 4;
    RESET &= ~4;
    timerDelay(TIMER_TICKS_PER_SECOND / 10000);

    RADIO_RadioPowerCtl |= 4;

    bkp = CFGPAGE;
    CFGPAGE = 4;
    SETTINGS |= 2;
    RADIO_INITSEQ0 = 2;
    RADIO_INITSEQ1 = 0xFA;
    RADIO_INITSEQ2 = 0xDD;
    SETTINGS |= 4;
    RADIO_INITSEQ3 = 1;
    RADIO_INITSEQ4 = 0xFA;
    RADIO_INITSEQ5 = 0xDD;
    IEN_RF2 = 1;
    CFGPAGE = bkp;

    RADIO_command = 0xC1;

    RADIO_unk_C1 = 0x02;
    RADIO_calibration_C2 = 0xf7;  // mdmctrl0L?
    RADIO_calibration_C3 = 0x05;
    RADIO_calibration_C4 = 0x35;
    RADIO_calibration_C5 = 0x24;
    RADIO_calibration_C6 = 0x33;
    RADIO_calibration_C7 = 0x70;
    RADIO_unk_CA = 0x58;
    RADIO_perChannelSetting2 = 0x02;
    RADIO_unk_CD = (RADIO_unk_CD & ~7) | (0x11 & 7);
    RADIO_txPower = 0;
    RADIO_calibration_CF = 0x30;
    RADIO_calibration_D0 = 0x00;
    RADIO_calibration_D1 = 0x49;
    RADIO_calibration_D2 = 0x06;
    RADIO_unk_D7 = 0x43;
    RADIO_unk_E2 = 0x08;  // setting bit 0x40 breaks rx entirely
    RADIO_unk_83 = 0xe5;  // maybe sync word?

    RADIO_unk_C8 = 0x7f;          // setting bit 0x80 breaks rx entirely
    RADIO_calibration_81 = 0xf0;  // removing bit 0x20 breask rx and tx
    RADIO_FLAGS |= 0x08;
    RADIO_unk_D8 = 0;
    RADIO_calibration_9D = 0x3f;  // firts untpouched calib val
    RADIO_calibration_A1 = 0x04;
    RADIO_calibration_94 = 0x7f;  // IOCFG0 ?
    RADIO_unk_CE = 0x55;
    RADIO_calibration_D3 = 0x30;
    RADIO_calibration_D4 = 0xcc;
    RADIO_calibration_D5 = 0xf6;
    RADIO_calibration_86 = 0x40;  // setting this to values like 0x20, 0x80, and 0x60 breaks auto-ack and maybe tx
    RADIO_calibration_95 = 0x08;
    RADIO_calibration_96 = 0xd3;
    RADIO_calibration_70 = 0x01;
    RADIO_calibration_71 = 0x40;

    RADIO_unk_A6 = 0x00;
    RADIO_command = 0xC8;
    RADIO_command = 0xC7;
    RADIO_command = 0xC6;
    RADIO_unk_AF = 0x60;  // int enable (we need 0x60)

    RADIO_unk_AF |= 0x10;

    IEN_RF1 = 1;

    RADIO_FLAGS |= 8;
}
