#ifndef VARICODE_H
#define VARICODE_H

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

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

class Varicode {
  public:
    // extra information out of buildMessageFrames
    struct MessageInfo {
        QString dirTo;
        QString dirCmd;
        QString dirNum;
    };

    // submode types
    enum SubmodeType {
        JS8CallNormal = 0,
        JS8CallFast = 1,
        JS8CallTurbo = 2,
        JS8CallSlow = 4,
        JS8CallUltra = 8
    };

    // 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
        JS8CallData = 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 SubmodeType intToSubmode(int sm);

    static QString extendedChars();

    static QString escape(const QString &text);
    static QString unescape(const QString &text);

    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, bool *pPortable);
    static QString unpackCallsign(quint32 value, bool portable);

    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 isCommandAutoreply(const QString &cmd);
    static bool isValidCallsign(const QString &callsign, bool *pIsCompound);
    static bool isCompoundCallsign(const QString &callsign);
    static bool isGroupAllowed(const QString &group);

    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 QString packFastDataMessage(QString const &text, int *n);
    static QString unpackFastDataMessage(QString const &text);

    static QList<QPair<QString, int>>
    buildMessageFrames(QString const &mycall, QString const &mygrid,
                       QString const &selectedCall, QString const &text,
                       bool forceIdentify, bool forceData, int submode,
                       MessageInfo *pInfo = nullptr);
};

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

  private:
    QString m_mycall;
    QString m_mygrid;
    QString m_selectedCall;
    QString m_text;
    bool m_forceIdentify;
    bool m_forceData;
    int m_submode;
};

#endif // VARICODE_H
