/***************************************************************************
                          adif.cpp  -  description
                             -------------------
    begin                : jun 2015
    copyright            : (C) 2015 by Jaime Robles
    email                : jaime@robles.es
 ***************************************************************************/

/*****************************************************************************
 * This file is part of KLog.                                                *
 *                                                                           *
 *    KLog 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, either version 3 of the License, or      *
 *    (at your option) any later version.                                    *
 *                                                                           *
 *    KLog 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 for more details.                           *
 *                                                                           *
 *    You should have received a copy of the GNU General Public License      *
 *    along with KLog.  If not, see <https://www.gnu.org/licenses/>.         *
 *                                                                           *
 *****************************************************************************/
//#include "global.h"
#include "adif.h"
#include "callsign.h"

Adif::Adif(const QString &_parentName) : QObject(nullptr),
    parentName(_parentName),
    logLevel(Info)
{
    parentName = _parentName;
    setARRLSect();
    setContinents();
    setSponsorsList();
    setModes();
    InitializeHash();
    //qDebug() << Q_FUNC_INFO << " (" << _parentName << ")";
    //init();
}

Adif::~Adif()
{
}

ADIFField Adif::setPair(const QString &pair)
{
    ADIFField result;
    result.field = QString();
    result.value = QString();
    result.valid = false;

    // Must start with '<' and contain exactly one '>'
    if (!pair.startsWith('<') || pair.count('>') != 1)
        return result;

    int closePos = pair.indexOf('>');
    if (closePos == -1)
        return result;

    QString header = pair.mid(1, closePos - 1);
    QString value = pair.mid(closePos + 1);
   //qDebug() << Q_FUNC_INFO << " - header: " << header;
   //qDebug() << Q_FUNC_INFO << " - value : " << value;

    QStringList parts = header.split(':');
    if (parts.size() < 2 || parts.size() > 3)
        return result;

    QString fieldName = parts[0].trimmed();
    bool ok = false;
    int length = parts[1].toInt(&ok);
   //qDebug() << Q_FUNC_INFO << " - length : " << length;
    if (!ok || length < 0)
        return result;
   //qDebug() << Q_FUNC_INFO << " - 050";
    // Optional: data type
    if (parts.size() == 3) {
        static QStringList validTypes = {"B","N","D","T","S","I","M","G","E","L"};
        if (!validTypes.contains(parts[2].trimmed().toUpper()))
            return result;
    }
   //qDebug() << Q_FUNC_INFO << " - 060";
    if (value.length() != length)
    {
        if (value.length() > length)
            value = value.left(length);
        else
            return result;
    }

    if (value.length() != length)
        return result;
   //qDebug() << Q_FUNC_INFO << " - 070";
    // Field name must be non-empty and contain only allowed chars
    QRegularExpression fieldRe("^[A-Z0-9_]+$", QRegularExpression::CaseInsensitiveOption);
    if (!fieldRe.match(fieldName).hasMatch())
        return result;
   //qDebug() << Q_FUNC_INFO << " - 080";
    result.field = fieldName;
    result.value = value;
    result.valid = true;
   //qDebug() << Q_FUNC_INFO << " - header: " << result.field;
   //qDebug() << Q_FUNC_INFO << " - value : " << result.value;
    return result;
}

void Adif::InitializeHash() {
    ADIFHash = {
        {"ADDRESS", "MultiLineString"},
        {"AGE", "Number"},
        {"ALTITUDE", "Number"},
        {"ANT_AZ", "Number"},
        {"ANT_EL", "Number"},
        {"ANT_PATH", "Enumeration"},
        {"ARRL_SECT", "Enumeration"},
        {"AWARD_SUBMITTED", "SponsoredAwardList"},
        {"AWARD_GRANTED", "SponsoredAwardList"},
        {"A_INDEX", "Number"},
        {"BAND", "Enumeration"},
        {"BAND_RX", "Enumeration"},
        {"CALL", "String"},
        {"CHECK", "String"},
        {"CLASS", "String"},
        {"CLUBLOG_QSO_UPLOAD_DATE", "Date"},
        {"CLUBLOG_QSO_UPLOAD_STATUS", "Enumeration"},
        {"CNTY", "Enumeration"},
        {"COMMENT", "String"},
        {"CONT", "Enumeration"},
        {"CONTACTED_OP", "String"},
        {"CONTEST_ID", "String"},
        {"COUNTRY", "String"},
        {"CQZ", "PositiveInteger"},
        {"CREDIT_SUBMITTED", "CreditList"},
        {"CREDIT_GRANTED", "CreditList"},
        {"DARC_DOK", "Enumeration"},
        {"DISTANCE", "Number"},
        {"DXCC", "Enumeration"},
        {"EMAIL", "String"},
        {"EQ_CALL", "String"},
        {"EQSL_QSLRDATE", "Date"},
        {"EQSL_QSLSDATE", "Date"},
        {"EQSL_QSL_RCVD", "Enumeration"},
        {"EQSL_QSL_SENT", "Enumeration"},
        {"FISTS", "PositiveInteger"},
        {"FISTS_CC", "PositiveInteger"},
        {"FORCE_INIT", "Boolean"},
        {"FREQ", "Number"},
        {"FREQ_RX", "Number"},
        {"GRIDSQUARE", "GridSquare"},
        {"GRIDSQUARE_EXT", "GridSquareExt"},
        {"GUEST_OP", "String"},
        {"HAMLOGEU_QSO_UPLOAD_DATE", "Date"},
        {"HAMLOGEU_UPLOAD_STATUS", "Enumeration"},
        {"HAMQTH_QSO_UPLOAD_DATE", "Date"},
        {"HAMQTH_UPLOAD_STATUS", "Enumeration"},
        {"HRDLOG_QSO_UPLOAD_DATE", "Date"},
        {"HRDLOG_QSO_UPLOAD_STATUS", "Enumeration"},
        {"IOTA", "IOTARefNo"},
        {"IOTA_ISLAND_ID", "PositiveInteger"},
        {"ITUZ", "PositiveInteger"},
        {"K_INDEX", "Integer"},
        {"LAT", "Location"},
        {"LON", "Location"},
        {"LOTW_QSLRDATE", "Date"},
        {"LOTW_QSLSDATE", "Date"},
        {"LOTW_QSL_RCVD", "Enumeration"},
        {"LOTW_QSL_SENT", "Enumeration"},
        {"MAX_BURSTS", "Number"},
        {"MODE", "Enumeration"},
        {"MS_SHOWER", "String"},
        {"MY_ALTITUDE", "Number"},
        {"MY_ANTENNA", "String"},
        {"MY_ARRL_SECT", "Enumeration"},
        {"MY_CITY", "String"},
        {"MY_CNTY", "Enumeration"},
        {"MY_COUNTRY", "String"},
        {"MY_CQ_ZONE", "PositiveInteger"},
        {"MY_DXCC", "Enumeration"},
        {"MY_FISTS", "PositiveInteger"},
        {"MY_GRIDSQUARE", "GridSquare"},
        {"MY_GRIDSQUARE_EXT", "GridSquareExt"},
        {"MY_IOTA", "IOTARefNo"},
        {"MY_IOTA_ISLAND_ID", "PositiveInteger"},
        {"MY_ITU_ZONE", "PositiveInteger"},
        {"MY_LAT", "Location"},
        {"MY_LON", "Location"},
        {"MY_NAME", "String"},
        {"MY_POSTAL_CODE", "String"},
        {"MY_POTA_REF", "POTARef"},
        {"MY_RIG", "String"},
        {"MY_SIG", "String"},
        {"MY_SIG_INFO", "String"},
        {"MY_SOTA_REF", "SOTARef"},
        {"MY_STATE", "Enumeration"},
        {"MY_STREET", "String"},
        {"MY_USACA_COUNTIES", "SecondarySubdivisionList"},
        {"MY_VUCC_GRIDS", "GridSquareList"},
        {"MY_WWFF_REF", "WWFFRef"},
        {"NAME", "String"},
        {"NOTES", "MultilineString"},
        {"NR_BURSTS", "Integer"},
        {"NR_PINGS", "Integer"},
        {"OPERATOR", "String"},
        {"OWNER_CALLSIGN", "String"},
        {"PFX", "String"},
        {"POTA_REF", "POTARef"},
        {"PRECEDENCE", "String"},
        {"PROP_MODE", "Enumeration"},
        {"PUBLIC_KEY", "String"},
        {"QRZCOM_QSO_UPLOAD_DATE", "Date"},
        {"QRZCOM_QSO_UPLOAD_STATUS", "Enumeration"},
        {"QSLMSG", "MultilineString"},
        {"QSLRDATE", "Date"},
        {"QSLSDATE", "Date"},
        {"QSL_RCVD", "Enumeration"},
        {"QSL_RCVD_VIA", "Enumeration"},
        {"QSL_SENT", "Enumeration"},
        {"QSL_SENT_VIA", "Enumeration"},
        {"QSL_VIA", "String"},
        {"QSO_COMPLETE", "Enumeration"},
        {"QSO_DATE", "Date"},
        {"QSO_DATE_OFF", "Date"},
        {"QSO_RANDOM", "Boolean"},
        {"QTH", "String"},
        {"REGION", "Enumeration"},
        {"RIG", "MultilineString"},
        {"RST_RCVD", "String"},
        {"RST_SENT", "String"},
        {"RX_PWR", "Number"},
        {"SAT_MODE", "String"},
        {"SAT_NAME", "String"},
        {"SFI", "Integer"},
        {"SIG", "String"},
        {"SIG_INFO", "String"},
        {"SILENT_KEY", "Boolean"},
        {"SKCC", "String"},
        {"SOTA_REF", "SOTARef"},
        {"SRX", "Integer"},
        {"SRX_STRING", "String"},
        {"STATE", "Enumeration"},
        {"STATION_CALLSIGN", "String"},
        {"STX", "Integer"},
        {"STX_STRING", "String"},
        {"SUBMODE", "String"},
        {"SWL", "Boolean"},
        {"TEN_TEN", "PositiveInteger"},
        {"TIME_OFF", "Time"},
        {"TIME_ON", "Time"},
        {"TX_PWR", "Number"},
        {"UKSMG", "PositiveInteger"},
        {"USACA_COUNTIES", "SecondarySubdivisionList"},
        {"VE_PROV", "String"},
        {"VUCC_GRIDS", "GridSquareList"},
        {"WEB", "String"},
        {"WWFF_REF", "WWFFRef"},
        {"APP_LOTW_RXQSL", "Date"},
        {"APP_LOTW_RXQSO", "String"},
        {"APP_LOTW_QSO_TIMESTAMP", "Date"}
    };
    return;
}

void Adif::setLogLevel(DebugLogLevel _l)
{
    logLevel = _l;
}

void Adif::setARRLSect()
{
    ARRL_sects = {"AL", "AK", "AB", "AR", "AZ", "BC", "CO", "CT", "DE", "EB", "EMA", "ENY", "EPA", "EWA"
        "GA", "GTA", "ID", "IL", "IN", "IA", "KS", "KY", "LAX", "LA",
        "ME", "MB", "MAR", "MDC", "MI", "MN", "MS", "MO", "MT",
        "NE", "NV", "NH", "NM", "NLI", "NL", "NC", "ND", "NTX",
        "NFL", "NNJ", "NNY", "NT", "NWT", "OH", "OK", "ON",
        "ONE", "ONN", "ONS", "ORG", "OR", "PAC", "PE", "PR", "QC",
        "RI", "SV", "SDG", "SF", "SJV", "SB", "SCV", "SK", "SC", "SD",
        "STX", "SFL", "SNJ", "TN", "VI", "UT", "VT", "VA",
        "WCF", "WTX", "WV", "WMA", "WNY", "WPA", "WWA", "WI", "WY"};
    //ARRL_sects = pARRL_sects;
}

void Adif::setContinents()
{
    //QStringList pcontinents = {"NA", "SA", "EU", "AF", "OC", "AS", "AN"};
    continents = {"AF", "AN", "AS", "EU", "NA", "OC", "SA"};
}

QStringList Adif::getContinents()
{
    return continents;
}

bool Adif::isValidContinent(const QString &_s)
{
    return continents.contains(_s.toUpper());
}

void Adif::setSponsorsList()
{
    //QStringList psponsorsList = {"ADIF_", "ARI_", "ARRL_", "CQ_", "DARC_", "EQSL_", "IARU_", "JARL_", "RSGB_", "TAG_", "WABAG_"};
    sponsorsList = {"ADIF_", "ARI_", "ARRL_", "CQ_", "DARC_", "EQSL_", "IARU_", "JARL_", "RSGB_", "TAG_", "WABAG_"};
}

bool Adif::isValidMode (const QString &_s) const
{
    for (const AdifMode &group : modeList)
    {
        if (group.mode == _s)
            return true;
    }
    return false;
}

bool Adif::isValidSubMode (const QString &_s) const
{
    for (const AdifMode &group : modeList)
    {
        if (group.mode == _s || group.submodes.contains(_s))
            return true;
    }
    return false;
}

QString Adif::getModeFromSubmode(const QString &_submode) const
{
    for (const AdifMode &group : modeList)
    {
        // The parent mode is also a valid submode
        if (group.mode == _submode)
            return group.mode;
        if (group.submodes.contains(_submode))
            return group.mode;
    }
    return QString();
}

QList<AdifMode> Adif::getModeList() const
{
    return modeList;
}

void Adif::setModes()
{
    modeList = {
        {"AM", "PH", {}},
        {"ARDOP", "DG", {}},
        {"ATV", "DG", {}},
        {"CHIP", "PH", {"CHIP64", "CHIP128"}},
        {"CLO", "DG", {}},
        {"CONTESTI", "DG", {}},
        {"CW",  "CW", {"PCW"}},
        {"DIGITALVOICE", "PH", {"C4FM", "DMR", "DSTAR", "FREEDV", "M17"}},
        {"DOMINO", "DG", {"DOM-M", "DOM4", "DOM5", "DOM8", "DOM11", "DOM16", "DOM22", "DOM44",
                          "DOM88", "DOMINOEX", "DOMINOF" }},
        {"DYNAMIC", "DG", {"VARA HF", "VARA SATELLITE", "VARA FM 1200", "VARA FM 9600" }},
        {"FAX", "DG", {}},
        {"FM", "FM", {}},
        {"FSK441", "DG", {}},
        {"FSK", "DG", {"SCAMP_FAST", "SCAMP_SLOW", "SCAMP_VSLOW"}},
        {"FT8", "DG", {}},
        {"HELL", "DG", {"FMHELL", "FSKH105", "FSKH245", "FSKHELL", "HELL80", "HELLX5", "HELLX9",
                        "HFSK", "PSKHELL", "SLOWHELL" }},
        {"ISCAT", "DG", {"ISCAT-A", "ISCAT-B"}},
        {"JT4", "DG", {"JT4A", "JT4B", "JT4C", "JT4D", "JT4E", "JT4F", "JT4G"}},
        {"JT6M", "DG", {}},
        {"JT9", "DG", {"JT9-1", "JT9-2", "JT9-5", "JT9-10", "JT9-30", "JT9A", "JT9B", "JT9C",
                       "JT9D", "JT9E", "JT9E FAST", "JT9F", "JT9F FAST", "JT9G", "JT9G FAST", "JT9H", "JT9H FAST"}},
        {"JT44", "DG", {}},
        {"JT65", "DG", {"JT65A", "JT65B", "JT65B2", "JT65C", "JT65C2"}},
        {"MFSK", "DG", {"FSQCALL", "FST4", "FST4W", "FT2", "FT4", "JS8", "JTMS", "MFSK4", "MFSK8",
                        "MFSK11", "MFSK16", "MFSK22", "MFSK31", "MFSK32", "MFSK64", "MFSK64L", "MFSK128 MFSK128L", "Q65" }},
        {"MSK144", "DG", {}},
        {"MTONE", "DG", {"SCAMP_OO", "SCAMP_OO_SLW" }},
        {"MT63", "DG", {}},
        {"OLIVIA", "DG", {"OLIVIA 4/125", "OLIVIA 4/250", "OLIVIA 8/250", "OLIVIA 8/500",
                          "OLIVIA 16/500", "OLIVIA 16/1000", "OLIVIA 32/1000"}},
        {"OPERA", "DG", {"OPERA-BEACON", "OPERA-QSO"}},
        {"PAC", "DG", {"PAC2", "PAC3", "PAC4"}},
        {"PAX", "DG", {"PAX2"}},
        {"PKT", "DG", {}},
        {"PSK", "DG", {"8PSK125", "8PSK125F", "8PSK125FL", "8PSK250", "8PSK250F", "8PSK250FL", "8PSK500",
                       "8PSK500F", "8PSK1000", "8PSK1000F", "8PSK1200F", "FSK31", "PSK10", "PSK31", "PSK63",
                       "PSK63F", "PSK63RC4", "PSK63RC5", "PSK63RC10", "PSK63RC20", "PSK63RC32", "PSK125",
                       "PSK125C12", "PSK125R", "PSK125RC10", "PSK125RC12", "PSK125RC16", "PSK125RC4",
                       "PSK125RC5", "PSK250", "PSK250C6", "PSK250R", "PSK250RC2", "PSK250RC3", "PSK250RC5",
                       "PSK250RC6", "PSK250RC7", "PSK500", "PSK500C2", "PSK500C4", "PSK500R", "PSK500RC2",
                       "PSK500RC3", "PSK500RC4", "PSK800C2", "PSK800RC2", "PSK1000", "PSK1000C2", "PSK1000R",
                       "PSK1000RC2", "PSKAM10", "PSKAM31", "PSKAM50", "PSKFEC31", "QPSK31", "QPSK63", "QPSK125",
                       "QPSK250", "QPSK500", "SIM31"}},
        {"PSK2K", "DG", {}},
        {"Q15", "DG", {}},
        {"QRA64", "DG", {"QRA64A", "QRA64B", "QRA64C", "QRA64D", "QRA64E"}},
        {"ROS", "DG", {"ROS-EME", "ROS-HF", "ROS-MF"}},
        {"RTTY", "DG", {"ASCI"}},
        {"RTTYM", "DG", {}},
        {"SSB", "PH", {"LSB", "USB"}},
        {"SSTV", "DG", {}},
        {"T10", "DG", {}},
        {"THOR", "DG", {"THOR-M", "THOR4", "THOR5", "THOR8", "THOR11", "THOR16", "THOR22", "THOR25X4", "THOR50X1", "THOR50X2", "THOR100"}},
        {"THRB", "DG", {"THRBX", "THRBX1", "THRBX2", "THRBX4", "THROB1", "THROB2", "THROB4"}},
        {"TOR", "DG", {"AMTORFEC", "GTOR", "NAVTEX", "SITORB"}},
        {"V4", "DG", {}},
        {"VOI", "DG", {}},
        {"WINMOR", "DG", {}},
        {"WSPR", "DG", {}}
    };
}

bool Adif::isValidFreq(const QString &_b)
{
    //qDebug() << Q_FUNC_INFO << ": freq: " << _b;
    bool _ok;
    double f = _b.toDouble(&_ok);
    if ((_ok) && f>0)
    {
        //qDebug() << Q_FUNC_INFO << ": Freq OK" ;
        return true;
    }
   /* if (!_ok)
        //qDebug() << Q_FUNC_INFO << ": Freq NOK: NOK" ;
    if (f<=0)
        //qDebug() << Q_FUNC_INFO << ": Freq NOK: f=0" ;
    else
        //qDebug() << Q_FUNC_INFO << ": Freq NOK: NOT-KNOWN" ;*/
    return false;
}

bool Adif::isValidPower(const double _b)
{
    return (_b >0.0);
}

QString Adif::getADIFBoolFromBool(const bool _b)
{// Will produce the ADIF format if a bool is received
    if (_b)
        return "Y";
    return "N";
}


bool Adif::isValidK_Index(const int _b)
{
    return ((_b>=0) && (_b<=9));
}

bool Adif::isValidCQz(const int _b)
{
   return ((_b>=1) && (_b<=40));
}

bool Adif::isValidITUz(const int _b)
{
    return ((_b>=1) && (_b<=90));
}

bool Adif::isValidDXCC(const int _b)
{
    return ((_b>=0) && (_b<=522));
}

bool Adif::isValidAge(const double _b)
{
    return ((_b>0.0) && (_b<=120.0));
}

bool Adif::isValidAnt_EL(const double _b)
{
    return ((_b>=-90.0) && (_b<=900.0));
}

bool Adif::isValidAnt_AZ(const double _b)
{
    return ((_b>=0.0) && (_b<=360.0));
}

bool Adif::isValidA_Index(const double _b)
{
    return ((_b>=0.0) && (_b<=400.0));
}

bool Adif::isValidDistance(const double _b)
{
    return (_b>0.0);
}

//bool Adif::isValidAltitude(const double _b)
//{
//    return true;
//}

bool Adif::isValidIOTA_islandID(const int _b)
{
    return ((_b>=1) && (_b<=99999999));
}

bool Adif::isValidNRBursts(const int _b)
{
    return (_b>0);
}

bool Adif::isValidPings(const int _b)
{
    return (_b>0);
}

bool Adif::isValidSFI(const int _b)
{
    return ((_b>=0) && (_b<=300));
}

bool Adif::isValidSTX(const int _b)
{
    return (_b>=0);
}

bool Adif::isValidSRX(const int _b)
{
    return (_b>=0);
}

bool Adif::isValidFISTS(const int _b)
{
   return (_b>0);
}

bool Adif::isValidUKSMG(const int _b)
{
    return (_b>0);
}

bool Adif::isValidTenTen(const int _b)
{
    return (_b>0);
}

bool Adif::isValidLogId(const int _b)
{
    return (_b>0);
}

bool Adif::isValidAntPath(const QString &_s)
{
    return ((_s == "G") || (_s == "O") || (_s == "S") || (_s == "L"));
}

bool Adif::isValidQSO_COMPLETE(const QString &_s)
{    // "Y", "N", "NIL", "?"
    return ((_s == "Y") || (_s == "N") || (_s == "NIL") || (_s == "?"));
}

int Adif::setQSO_COMPLETEToDB(const QString &_s)
{
    if (_s == "Y")
        return 1;
    if (_s == "N")
        return 2;
    if (_s == "NIL")
        return 3;
    return 4;
}

QString Adif::getQSO_COMPLETEFromDB(const QString &_s)
{// Returns the ADIF QSO_COMPLETE
    //1=Y, 2=N, 3=NIL, 4=?
    //qDebug() << Q_FUNC_INFO << ": " << _s;
    int i = _s.toInt();
    switch (i)
    {
        case 2:
        {
            return "N";
        }
        case 3:
        {
            return "NIL";
        }
        case 4:
        {
            return "?";
        }
        default:
        {
            return "Y";
        }
    }
}

bool Adif::isValidPOTA(const QString &_s)
{ //https://www.adif.org/316/ADIF_316.htm#POTARef
    // POTA reference pattern:
    // xxxx-nnnnn[@yyyyyy]
    // xxxx: 1-4 letters/digits (case-insensitive)
    // nnnnn: 4 or 5 digits/letters (per POTA, often digits, but allow letters)
    // Optional: @yyyyyy (4-6 letters/digits)
    // Total length: 6 to 17
    //qDebug() << Q_FUNC_INFO << " - " << _s;

    if (_s.length() < 6 || _s.length() > 17)
        return false;

    // Regex:
    // ^           start of string
    // [A-Za-z0-9]{1,4}  - xxxx: 1-4 alphanumeric
    // -           literal dash
    // [A-Za-z0-9]{4,5}  - nnnnn: 4 or 5 alphanumeric (POTA allows e.g. '1234', 'A2345')
    // (                start optional
    //   @
    //   [A-Za-z0-9]{4,6} - yyyyyy: 4-6 alphanumeric
    // )?               optional
    // $           end of string

    QRegularExpression potaRegex(R"(^[A-Za-z0-9]{1,4}-[A-Za-z0-9]{4,5}(@[A-Za-z0-9]{4,6})?$)", QRegularExpression::CaseInsensitiveOption);
    bool ok = potaRegex.match(_s).hasMatch();
    QString test = "FALSE";
    if (ok)
        test = "TRUE";

    //qDebug() << Q_FUNC_INFO << " - " << test;
    //return ok;
    return potaRegex.match(_s).hasMatch();
}


bool Adif::isValidWWFF_Ref(const QString &_s)
{
    // Must be 8 to 11 characters: ^[A-Za-z0-9]{1,4}FF-[0-9]{4}$
    if (_s.length() < 8 || _s.length() > 11)
        return false;

    // Regex pattern:
    // ^           Start of string
    // [A-Za-z0-9]{1,4}  1 to 4 alphanumeric national program
    // FF-         literal "FF-"
    // [0-9]{4}    exactly 4 digits (with leading zeros permitted)
    // $           End of string
    QRegularExpression wwffRegex(R"(^[A-Za-z0-9]{1,4}FF-[0-9]{4}$)", QRegularExpression::CaseInsensitiveOption);
    return wwffRegex.match(_s).hasMatch();
}

QStringList Adif::getQSOUploadStatus (bool _fullName)
{
    QStringList uploadStatus;
    if (_fullName)
        uploadStatus = {"Y - " + tr("Uploaded"), "N - " + tr("Do not Upload"), "M - " + tr("Modified")};
    else
        uploadStatus = {"Y", "N", "M"};
    return uploadStatus;
}

QStringList Adif::getQSLSentStatus (bool _fullName)
{
    QStringList qslStatus;
    if (_fullName)
        qslStatus = {"Y - " + tr("Sent"), "N - " + tr("Do not send"), "R - " + tr("Requested"), "Q - " + tr("Queued"), "I - " + tr("Invalid/Ignore")};
    else
        qslStatus = {"Y", "N", "R", "Q", "I"};
    return qslStatus;
}

QStringList Adif::getQSLRecStatus (bool _fullName)
{
    QStringList qslStatus;
    if (_fullName)
        qslStatus = {"Y - " + tr("Yes"), "N - " + tr("No"), "R - " + tr("Requested"), "I - " + tr("Invalid/Ignore"), "V - " + tr("Verified")};
    else
        qslStatus = {"Y", "N", "R", "I", "V"};
    return qslStatus;
}

QString Adif::getADIFField(const QString &_fieldName, const QString &_data)
{// Receives the ADIF field and the data and returns the ADIF field with a blank space at the end.
    // Check if _fieldName is a valid ADIF
    //qDebug() << Q_FUNC_INFO << " - " << _fieldName << "/" << _data;
    if ((_data.length()<=0) || (_data.isNull()))
    {
        //qDebug() << Q_FUNC_INFO << " - Not Valid";
        return QString();
    }
    if (ADIFHash.empty()) {
        InitializeHash();
    }

    QString fieldN = _fieldName.toUpper();
    //qDebug() << Q_FUNC_INFO << " - toUpper: " << fieldN;

    if (!ADIFHash.contains(fieldN)) {
        //qDebug() << Q_FUNC_INFO << " - No valid ADIF: " << _fieldName;
        return QString();
    }
    if (fieldN == "DISTANCE" )
        if (_data.toDouble() <= 0.0)
            return QString();
    //qDebug() << Q_FUNC_INFO << " - Returning: " << QString ("<%1:%2>%3 ").arg(fieldN).arg(_data.length ()).arg(_data);
    return QString ("<%1:%2>%3 ").arg(fieldN).arg(_data.length ()).arg(_data);
}

bool Adif::isValidCall(const QString &_c)
{
    Callsign _callsign(_c);
    return _callsign.isValid();
    //if (_c.length ()>=3)
    //    return true;
    //return false;
}

bool Adif::isValidQSLRCVD(const QString &_s, bool _importing)
{    // Y, I, or V) (V on import only)    
    return ((_s == "Y") || (_s == "I") || ((_s == "V") && _importing));
}

bool Adif::isValidQSLSENT(const QString &_s)
{// Y, Q, or I
    return ((_s == "Y") || (_s == "Q") || (_s == "I"));
}

void Adif::logEvent(const QString &_func, const QString &_msg,  DebugLogLevel _level)
{
    emit debugLog (_func, _msg, _level);
}

QString Adif::getADIFDateStringFromLoTWDateTime(const QString &_lotwdatetime)
{
    //qDebug() << Q_FUNC_INFO << ": " << _lotwdatetime;
    // LoTW can provide data in two formats:
    //            2022-04-23T18:08:00Z
    //            YYYY-MM-DD HH:MM:SS
    // DateTime should have this format: YYYY-MM-DD HH:MM:SS
    // It returns a date on YYYYMMDD adif format
    QDateTime dateTime = QDateTime::fromString(_lotwdatetime, "yyyy-MM-dd HH:mm:ss");
    if (dateTime.isValid())
        dateTime.setTimeZone(QTimeZone::UTC);
    else
    {
        // Second try, just in case LoTW changes format
        dateTime = QDateTime::fromString(_lotwdatetime, Qt::ISODate);
        if (dateTime.isValid())
            dateTime.setTimeZone(QTimeZone::UTC);
    }

    if (dateTime.isValid())
        return (dateTime.date()).toString("yyyyMMdd");
    return QString();

    //yyyy-MM-ddTHH:mm:ss.zzz (e.g. 2017-07-24T15:46:29.739)
    //dateTime.fromString(_lotwdatetime, "yyyy-MM-dd HH:mm:ss");
    //qDebug() << Q_FUNC_INFO << ": Modified date: " << (dateTime.date()).toString("yyyyMMdd");

}

