///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com>               //
//                                                                               //
// Inspired by: https://github.com/myriadrf/LoRa-SDR                             //
//                                                                               //
// This program is free software; you can redistribute it and/or modify          //
// it under the terms of the GNU General Public License as published by          //
// the Free Software Foundation as version 3 of the License, or                  //
// (at your option) any later version.                                           //
//                                                                               //
// This program is distributed in the hope that it will be useful,               //
// but WITHOUT ANY WARRANTY; without even the implied warranty of                //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                  //
// GNU General Public License V3 for more details.                               //
//                                                                               //
// You should have received a copy of the GNU General Public License             //
// along with this program. If not, see <http://www.gnu.org/licenses/>.          //
///////////////////////////////////////////////////////////////////////////////////

#include "chirpchatdemodsettings.h"
#include "chirpchatdemoddecoderlora.h"

void ChirpChatDemodDecoderLoRa::decodeHeader(
        const std::vector<unsigned short>& inSymbols,
        unsigned int nbSymbolBits,
        bool& hasCRC,
        unsigned int& nbParityBits,
        unsigned int& packetLength,
        int& headerParityStatus,
        bool& headerCRCStatus
)
{
    // with header (H: header 8-bit codeword P: payload-8 bit codeword):
    // nbSymbolBits = 5 |H|H|H|H|H|      codewords => 8 symbols (always) : static headerSymbols = 8
    // nbSymbolBits = 7 |H|H|H|H|H|P|P|
    // without header (P: payload 8-bit codeword):
    // nbSymbolBits = 5 |P|P|P|P|P|      codewords => 8 symbols (always)
    // nbSymbolBits = 7 |P|P|P|P|P|P|P|
    // Actual header is always represented with 5 8-bit codewords : static headerCodewords = 5
    // These 8-bit codewords are encoded with Hamming(4,8) FEC : static headerParityBits = 4

    std::vector<uint16_t> symbols(headerSymbols);
    std::copy(inSymbols.begin(), inSymbols.begin() + headerSymbols, symbols.begin());

    //gray encode
    for (auto &sym : symbols) {
        sym = binaryToGray16(sym);
    }

    std::vector<uint8_t> codewords(nbSymbolBits);

    // Header symbols de-interleave thus headerSymbols (8) symbols into nbSymbolBits (5..12) codewords using header FEC (4/8)
    diagonalDeinterleaveSx(symbols.data(), headerSymbols, codewords.data(), nbSymbolBits, headerParityBits);

    // whitening does not apply to the header codewords
    Sx1272ComputeWhiteningLfsr(codewords.data() + headerCodewords, nbSymbolBits - headerCodewords, 0, headerParityBits);

    bool error = false;
    bool bad = false;
    uint8_t bytes[3];

    // decode actual header inside 8-bit codewords header with 4/8 FEC (5 first codewords)
    bytes[0] = decodeHamming84sx(codewords[1], error, bad) & 0xf;
    bytes[0] |= decodeHamming84sx(codewords[0], error, bad) << 4;	// length

    bytes[1] = decodeHamming84sx(codewords[2], error, bad) & 0xf;	// coding rate and crc enable

    bytes[2] = decodeHamming84sx(codewords[4], error, bad) & 0xf;
    bytes[2] |= decodeHamming84sx(codewords[3], error, bad) << 4;	// checksum

    bytes[2] ^= headerChecksum(bytes);

    if (bad)
    {
        headerParityStatus = (int) ChirpChatDemodSettings::ParityError;
    }
    else
    {
        if (error) {
            headerParityStatus = (int) ChirpChatDemodSettings::ParityCorrected;
        } else {
            headerParityStatus = (int) ChirpChatDemodSettings::ParityOK;
        }

        if (bytes[2] != 0) {
            headerCRCStatus = false;
        } else {
            headerCRCStatus = true;
        }
    }

    hasCRC = (bytes[1] & 1) != 0;
    nbParityBits = (bytes[1] >> 1) & 0x7;
    packetLength = bytes[0];
}

void ChirpChatDemodDecoderLoRa::decodeBytes(
        QByteArray& inBytes,
        const std::vector<unsigned short>& inSymbols,
        unsigned int nbSymbolBits,
        bool hasHeader,
        bool& hasCRC,
        unsigned int& nbParityBits,
        unsigned int& packetLength,
        bool& earlyEOM,
        int& headerParityStatus,
        bool& headerCRCStatus,
        int& payloadParityStatus,
        bool& payloadCRCStatus
)
{
    // need at least a header (8 symbols of 8 bit codewords) whether an actual header is sent or not
    if (inSymbols.size() < headerSymbols)
    {
        qDebug("ChirpChatDemodDecoderLoRa::decodeBytes: need at least %u symbols for header", headerSymbols);
        earlyEOM = true;
        return;
    }
    else
    {
        earlyEOM = false;
    }

    if (hasHeader)
    {
        decodeHeader(
            inSymbols,
            nbSymbolBits,
            hasCRC,
            nbParityBits,
            packetLength,
            headerParityStatus,
            headerCRCStatus
        );
    }

    qDebug("ChirpChatDemodDecoderLoRa::decodeBytes: crc: %s nbParityBits: %u packetLength: %u",
        hasCRC ? "on": "off", nbParityBits, packetLength);

    if (nbParityBits > 4)
    {
        qDebug("ChirpChatDemodDecoderLoRa::decodeBytes: invalid parity bits in header: %u", nbParityBits);
        return;
    }

    const unsigned int numSymbols = roundUp(inSymbols.size(), 4 + nbParityBits);
    const unsigned int numCodewords = (numSymbols / (4 + nbParityBits))*nbSymbolBits;
    std::vector<uint16_t> symbols(numSymbols);
    std::copy(inSymbols.begin(), inSymbols.end(), symbols.begin());

    //gray encode, when SF > PPM, depad the LSBs with rounding
    for (auto &sym : symbols) {
        sym = binaryToGray16(sym);
    }

    std::vector<uint8_t> codewords(numCodewords);

    // deinterleave / dewhiten the symbols into codewords
    unsigned int sOfs = 0;
    unsigned int cOfs = 0;

    // the first headerSymbols (8 symbols) are coded with 4/8 FEC (thus 8 bit codewords) whether an actual header is present or not
    // this corresponds to nbSymbolBits codewords (therefore LoRa imposes nbSymbolBits >= headerCodewords (5 codewords) this is controlled externally)

    if (nbParityBits != 4) // different FEC between header symbols and the rest of the packet
    {
        // Header symbols de-interleave thus headerSymbols (8) symbols into nbSymbolBits (5..12) codewords using header FEC (4/8)
        diagonalDeinterleaveSx(symbols.data(), headerSymbols, codewords.data(), nbSymbolBits, headerParityBits);

        if (hasHeader) { // whitening does not apply to the header codewords
            Sx1272ComputeWhiteningLfsr(codewords.data() + headerCodewords, nbSymbolBits - headerCodewords, 0, headerParityBits);
        } else {
            Sx1272ComputeWhiteningLfsr(codewords.data(), nbSymbolBits, 0, headerParityBits);
        }

        cOfs += nbSymbolBits;   // nbSymbolBits codewords in header
        sOfs += headerSymbols;  // headerSymbols symbols in header

        if (numSymbols - sOfs > 0) // remaining payload symbols after header symbols using their own FEC (4/5..4/7)
        {
            diagonalDeinterleaveSx(symbols.data() + sOfs, numSymbols - sOfs, codewords.data() + cOfs, nbSymbolBits, nbParityBits);

            if (hasHeader) {
                Sx1272ComputeWhiteningLfsr(codewords.data() + cOfs, numCodewords - cOfs, nbSymbolBits - headerCodewords, nbParityBits);
            } else {
                Sx1272ComputeWhiteningLfsr(codewords.data() + cOfs, numCodewords - cOfs, nbSymbolBits, nbParityBits);
            }
        }
    }
    else // uniform 4/8 FEC for all the packet
    {
        // De-interleave the whole packet thus numSymbols into nbSymbolBits (5..12) codewords using packet FEC (4/8)
        diagonalDeinterleaveSx(symbols.data(), numSymbols, codewords.data(), nbSymbolBits, nbParityBits);

        if (hasHeader) { // whitening does not apply to the header codewords
            Sx1272ComputeWhiteningLfsr(codewords.data() + headerCodewords, numCodewords - headerCodewords, 0, nbParityBits);
        } else {
            Sx1272ComputeWhiteningLfsr(codewords.data(), numCodewords, 0, nbParityBits);
        }
    }

    // Now we have nbSymbolBits 8-bit codewords (4/8 FEC) possibly containing the actual header followed by the rest of payload codewords with their own FEC (4/5..4/8)

    std::vector<uint8_t> bytes((codewords.size()+1) / 2);
    unsigned int dOfs = 0;
    cOfs = 0;

    unsigned int dataLength = packetLength + 3 + (hasCRC ? 2 : 0);  // include  header and CRC

    if (hasHeader)
    {
        cOfs = headerCodewords;
        dOfs = 6;
    }
    else
    {
        cOfs = 0;
        dOfs = 0;
    }

    if (dataLength > bytes.size())
    {
        qDebug("ChirpChatDemodDecoderLoRa::decodeBytes: not enough data %lu vs %u", bytes.size(), dataLength);
        earlyEOM = true;
        return;
    }

    // decode the rest of the payload inside 8-bit codewords header with 4/8 FEC
    bool error = false;
    bool bad = false;

    for (; cOfs < nbSymbolBits; cOfs++, dOfs++)
    {
        if (dOfs % 2 == 1) {
            bytes[dOfs/2] |= decodeHamming84sx(codewords[cOfs], error, bad) << 4;
        } else {
            bytes[dOfs/2] = decodeHamming84sx(codewords[cOfs], error, bad) & 0xf;
        }
    }

    if (dOfs % 2 == 1) // decode the start of the payload codewords with their own FEC when not on an even boundary
    {
        if (nbParityBits == 1) {
            bytes[dOfs/2] |= checkParity54(codewords[cOfs++], error) << 4;
        } else if (nbParityBits == 2) {
            bytes[dOfs/2] |= checkParity64(codewords[cOfs++], error) << 4;
        } else if (nbParityBits == 3){
            bytes[dOfs/2] |= decodeHamming74sx(codewords[cOfs++], error) << 4;
        } else if (nbParityBits == 4){
            bytes[dOfs/2] |= decodeHamming84sx(codewords[cOfs++], error, bad) << 4;
        } else {
            bytes[dOfs/2] |= codewords[cOfs++] << 4;
        }

        dOfs++;
    }

    dOfs /= 2;

    // decode the rest of the payload codewords with their own FEC

    if (nbParityBits == 1)
    {
        for (unsigned int i = dOfs; i < dataLength; i++)
        {
            bytes[i] = checkParity54(codewords[cOfs++],error);
            bytes[i] |= checkParity54(codewords[cOfs++], error) << 4;
        }
    }
    else if (nbParityBits == 2)
    {
        for (unsigned int i = dOfs; i < dataLength; i++)
        {
            bytes[i] = checkParity64(codewords[cOfs++], error);
            bytes[i] |= checkParity64(codewords[cOfs++],error) << 4;
        }
    }
    else if (nbParityBits == 3)
    {
        for (unsigned int i = dOfs; i < dataLength; i++)
        {
            bytes[i] = decodeHamming74sx(codewords[cOfs++], error) & 0xf;
            bytes[i] |= decodeHamming74sx(codewords[cOfs++], error) << 4;
        }
    }
    else if (nbParityBits == 4)
    {
        for (unsigned int i = dOfs; i < dataLength; i++)
        {
            bytes[i] = decodeHamming84sx(codewords[cOfs++], error, bad) & 0xf;
            bytes[i] |= decodeHamming84sx(codewords[cOfs++], error, bad) << 4;
        }
    }
    else
    {
        for (unsigned int i = dOfs; i < dataLength; i++)
        {
            bytes[i] = codewords[cOfs++] & 0xf;
            bytes[i] |= codewords[cOfs++] << 4;
        }
    }

    if (bad) {
        payloadParityStatus = (int) ChirpChatDemodSettings::ParityError;
    } else if (error) {
        payloadParityStatus = (int) ChirpChatDemodSettings::ParityCorrected;
    } else {
        payloadParityStatus = (int) ChirpChatDemodSettings::ParityOK;
    }

    // finalization:
    //   adjust offsets dpending on header and CRC presence
    //   compute and verify payload CRC if present

    if (hasHeader)
    {
        dOfs = 3;        // skip header
        dataLength -= 3; // remove header

        if (hasCRC) // always compute crc if present skipping the header
        {
            uint16_t crc = sx1272DataChecksum(bytes.data() + dOfs, packetLength);
            uint16_t packetCRC = bytes[dOfs + packetLength] | (bytes[dOfs + packetLength + 1] << 8);

            if (crc != packetCRC) {
                payloadCRCStatus = false;
            } else {
                payloadCRCStatus = true;
            }
        }
    }
    else
    {
        dOfs = 0;  // no header to skip

        if (hasCRC)
        {
            uint16_t crc = sx1272DataChecksum(bytes.data(), packetLength);
            uint16_t packetCRC = bytes[packetLength] | (bytes[packetLength + 1] << 8);

            if (crc != packetCRC) {
                payloadCRCStatus = false;
            } else {
                payloadCRCStatus = true;
            }
        }
    }

    inBytes.resize(dataLength);
    std::copy(bytes.data() + dOfs, bytes.data() + dOfs + dataLength, inBytes.data());
}

void ChirpChatDemodDecoderLoRa::getCodingMetrics(
    unsigned int nbSymbolBits,
    unsigned int nbParityBits,
    unsigned int packetLength,
    bool hasHeader,
    bool hasCRC,
    unsigned int& numSymbols,
    unsigned int& numCodewords
)
{
    numCodewords = roundUp((packetLength + (hasCRC ? 2 : 0))*2 + (hasHeader ? headerCodewords : 0), nbSymbolBits); // uses payload + CRC for encoding size
    numSymbols = headerSymbols + (numCodewords / nbSymbolBits - 1) * (4 + nbParityBits); // header is always coded with 8 bits and yields exactly 8 symbols (headerSymbols)
}
