#ifndef VARICODE_H
#define VARICODE_H

/**
 * (C) 2018 Jordan Sherer <kn4crd@gmail.com> - All Rights Reserved
 **/

#include <QBitArray>
#include <QRegularExpression>
#include <QRegExp>
#include <QString>
#include <QVector>
#include <QThread>


class Varicode
{
public:
    // frame type transmitted via itype and decoded by the ft8 decoded
    enum TransmissionType {
        JS8Call          = 0, // [000] <- any other frame of the message
        JS8CallFirst     = 1, // [001] <- the first frame of a message
        JS8CallLast      = 2, // [010] <- the last frame of a message
        JS8CallFlag      = 4, // [100] <- flagged frame (no frame type header)
    };

    /*

    000 = heartbeat
    001 = compound
    010 = compound directed
    011 = directed
    1XX = data, with the X lsb bits dropped
    */
    enum FrameType {
        FrameUnknown          = 255, // [11111111] <- only used as a sentinel
        FrameHeartbeat        = 0,   // [000]
        FrameCompound         = 1,   // [001]
        FrameCompoundDirected = 2,   // [010]
        FrameDirected         = 3,   // [011]
        FrameData             = 4,   // [10X] // but this only encodes the first 2 msb bits and drops the lsb
        FrameDataCompressed   = 6,   // [11X] // but this only encodes the first 2 msb bits and drops the lsb
    };

    static const quint8 FrameTypeMax = 6;

    static QString frameTypeString(quint8 type) {
        const char* FrameTypeStrings[] = {
            "FrameHeartbeat",
            "FrameCompound",
            "FrameCompoundDirected",
            "FrameDirected",
            "FrameData",
            "FrameUnknown", // 5
            "FrameDataCompressed",
        };

        if(type > FrameTypeMax){
            return "FrameUnknown";
        }
        return FrameTypeStrings[type];
    }

    //Varicode();

    static QString rstrip(const QString& str);
    static QString lstrip(const QString& str);

    static QMap<QString, QString> defaultHuffTable();
    static QString cqString(int number);
    static QString hbString(int number);
    static bool startsWithCQ(QString text);
    static bool startsWithHB(QString text);
    static QString formatSNR(int snr);
    static QString formatPWR(int dbm);

    static QString checksum16(QString const &input);
    static bool checksum16Valid(QString const &checksum, QString const &input);

    static QString checksum32(QString const &input);
    static bool checksum32Valid(QString const &checksum, QString const &input);

    static QStringList parseCallsigns(QString const &input);
    static QStringList parseGrids(QString const &input);

    static QList<QPair<int, QVector<bool>>> huffEncode(const QMap<QString, QString> &huff, QString const& text);
    static QString huffDecode(const QMap<QString, QString> &huff, QVector<bool> const& bitvec);
    static QSet<QString> huffValidChars(const QMap<QString, QString> &huff);

    static QVector<bool> bytesToBits(char * bitvec, int n);
    static QVector<bool> strToBits(QString const& bitvec);
    static QString bitsToStr(QVector<bool> const& bitvec);

    static QVector<bool> intToBits(quint64 value, int expected=0);
    static quint64 bitsToInt(QVector<bool> const value);
    static quint64 bitsToInt(QVector<bool>::ConstIterator start, int n);
    static QVector<bool> bitsListToBits(QList<QVector<bool>> &list);

    static quint8 unpack5bits(QString const& value);
    static QString pack5bits(quint8 packed);

    static quint8 unpack6bits(QString const& value);
    static QString pack6bits(quint8 packed);

    static quint16 unpack16bits(QString const& value);
    static QString pack16bits(quint16 packed);

    static quint32 unpack32bits(QString const& value);
    static QString pack32bits(quint32 packed);

    static quint64 unpack64bits(QString const& value);
    static QString pack64bits(quint64 packed);

    static quint64 unpack72bits(QString const& value, quint8 *pRem);
    static QString pack72bits(quint64 value, quint8 rem);

    static quint32 packAlphaNumeric22(QString const& value, bool isFlag);
    static QString unpackAlphaNumeric22(quint32 packed, bool *isFlag);

    static quint64 packAlphaNumeric50(QString const& value);
    static QString unpackAlphaNumeric50(quint64 packed);

    static quint32 packCallsign(QString const& value);
    static QString unpackCallsign(quint32 value);

    static QString deg2grid(float dlong, float dlat);
    static QPair<float, float> grid2deg(QString const &grid);
    static quint16 packGrid(QString const& value);
    static QString unpackGrid(quint16 value);

    static quint8 packNum(QString const &num, bool *ok);
    static quint8 packPwr(QString const &pwr, bool *ok);
    static quint8 packCmd(quint8 cmd, quint8 num, bool *pPackedNum);
    static quint8 unpackCmd(quint8 value, quint8 *pNum);

    static bool isSNRCommand(const QString &cmd);
    static bool isCommandAllowed(const QString &cmd);
    static bool isCommandBuffered(const QString &cmd);
    static int isCommandChecksumed(const QString &cmd);
    static bool isValidCallsign(const QString &callsign, bool *pIsCompound);
    static bool isCompoundCallsign(const QString &callsign);

    static QString packHeartbeatMessage(QString const &text, QString const&callsign, int *n);
    static QStringList unpackHeartbeatMessage(const QString &text, quint8 *pType, bool *isAlt, quint8 *pBits3);

    static QString packCompoundMessage(QString const &text, int *n);
    static QStringList unpackCompoundMessage(const QString &text, quint8 *pType, quint8 *pBits3);

    static QString packCompoundFrame(const QString &callsign, quint8 type, quint16 num, quint8 bits3);
    static QStringList unpackCompoundFrame(const QString &text, quint8 *pType, quint16 *pNum, quint8 *pBits3);

    static QString packDirectedMessage(QString const& text, QString const& mycall, QString *pTo, bool *pToCompound, QString * pCmd, QString *pNum, int *n);
    static QStringList unpackDirectedMessage(QString const& text, quint8 *pType);

    static QString packDataMessage(QString const& text, int *n);
    static QString unpackDataMessage(QString const& text);

    static QList<QPair<QString, int>> buildMessageFrames(
        QString const& mycall,
        QString const& mygrid,
        QString const& selectedCall,
        QString const& text
    );
};


class BuildMessageFramesThread : public QThread
{
    Q_OBJECT
public:
    BuildMessageFramesThread(QString const& mycall,
                             QString const& mygrid,
                             QString const& selectedCall,
                             QString const& text,
                             QObject *parent=nullptr);
    void run() override;
signals:
    void resultReady(QString, int);

private:
    QString m_mycall;
    QString m_mygrid;
    QString m_selectedCall;
    QString m_text;
};

#endif // VARICODE_H
