///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021, 2023 Jon Beniston, M7RCE <jon@beniston.com>               //
//                                                                               //
// 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 "openaip.h"

QList<Airspace *> Airspace::readXML(const QString &filename)
{
    QList<Airspace *> airspaces;
    QFile file(filename);
    if (file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        QXmlStreamReader xmlReader(&file);

        while(!xmlReader.atEnd() && !xmlReader.hasError())
        {
            if (xmlReader.readNextStartElement())
            {
                if (xmlReader.name() == QLatin1String("ASP"))
                {
                    Airspace *airspace = new Airspace();

                    airspace->m_category = xmlReader.attributes().value("CATEGORY").toString();

                    while(xmlReader.readNextStartElement())
                    {
                        if (xmlReader.name() == QLatin1String("COUNTRY"))
                        {
                            airspace->m_country = xmlReader.readElementText();
                        }
                        else if (xmlReader.name() == QLatin1String("NAME"))
                        {
                            airspace->m_name = xmlReader.readElementText();
                        }
                        else if (xmlReader.name() == QLatin1String("ALTLIMIT_TOP"))
                        {
                            while(xmlReader.readNextStartElement())
                            {
                                airspace->m_top.m_reference = xmlReader.attributes().value("REFERENCE").toString();
                                airspace->m_top.m_altUnit = xmlReader.attributes().value("UNIT").toString();
                                if (xmlReader.name() == QLatin1String("ALT"))
                                {
                                    airspace->m_top.m_alt = xmlReader.readElementText().toInt();
                                }
                                else
                                {
                                    xmlReader.skipCurrentElement();
                                }
                            }
                        }
                        else if (xmlReader.name() == QLatin1String("ALTLIMIT_BOTTOM"))
                        {
                            while(xmlReader.readNextStartElement())
                            {
                                airspace->m_bottom.m_reference = xmlReader.attributes().value("REFERENCE").toString();
                                airspace->m_bottom.m_altUnit = xmlReader.attributes().value("UNIT").toString();
                                if (xmlReader.name() == QLatin1String("ALT"))
                                {
                                    airspace->m_bottom.m_alt = xmlReader.readElementText().toInt();
                                }
                                else
                                {
                                    xmlReader.skipCurrentElement();
                                }
                            }
                        }
                        else if (xmlReader.name() == QLatin1String("GEOMETRY"))
                        {
                            while(xmlReader.readNextStartElement())
                            {
                                if (xmlReader.name() == QLatin1String("POLYGON"))
                                {
                                    QString pointsString = xmlReader.readElementText();
                                    QStringList points = pointsString.split(",");
                                    for (const auto& ps : points)
                                    {
                                        QStringList split = ps.trimmed().split(" ");
                                        if (split.size() == 2)
                                        {
                                            QPointF pf(split[0].toDouble(), split[1].toDouble());
                                            airspace->m_polygon.append(pf);
                                        }
                                        else
                                        {
                                            qDebug() << "Airspace::readXML - Unexpected polygon point format: " << ps;
                                        }
                                    }
                                }
                                else
                                {
                                    xmlReader.skipCurrentElement();
                                }
                            }
                        }
                        else
                        {
                           xmlReader.skipCurrentElement();
                        }
                    }

                    airspace->calculatePosition();
                    //qDebug() << "Adding airspace: " << airspace->m_name << " " << airspace->m_category;
                    airspaces.append(airspace);
                }
            }
        }

        file.close();
    }
    else
    {
        // Don't warn, as many countries don't have files
        //qDebug() << "Airspace::readXML: Could not open " << filename << " for reading.";
    }
    return airspaces;
}

QList<NavAid *> NavAid::readXML(const QString &filename)
{
    int uniqueId = 1;
    QList<NavAid *> navAidInfo;
    QFile file(filename);
    if (file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        QXmlStreamReader xmlReader(&file);

        while(!xmlReader.atEnd() && !xmlReader.hasError())
        {
            if (xmlReader.readNextStartElement())
            {
                if (xmlReader.name() == QLatin1String("NAVAID"))
                {
                    QStringView typeRef = xmlReader.attributes().value("TYPE");
                    if ((typeRef == QLatin1String("NDB"))
                        || (typeRef == QLatin1String("DME"))
                        || (typeRef == QLatin1String("VOR"))
                        || (typeRef == QLatin1String("VOR-DME"))
                        || (typeRef == QLatin1String("VORTAC"))
                        || (typeRef == QLatin1String("DVOR"))
                        || (typeRef == QLatin1String("DVOR-DME"))
                        || (typeRef == QLatin1String("DVORTAC")))
                    {
                        QString type = typeRef.toString();
                        QString name;
                        QString id;
                        float lat = 0.0f;
                        float lon = 0.0f;
                        float elevation = 0.0f;
                        float frequency = 0.0f;
                        QString channel;
                        int range = 25;
                        float declination = 0.0f;
                        bool alignedTrueNorth = false;
                        while(xmlReader.readNextStartElement())
                        {
                            if (xmlReader.name() == QLatin1String("NAME"))
                            {
                                name = xmlReader.readElementText();
                            }
                            else if (xmlReader.name() == QLatin1String("ID"))
                            {
                                id = xmlReader.readElementText();
                            }
                            else if (xmlReader.name() == QLatin1String("GEOLOCATION"))
                            {
                                while(xmlReader.readNextStartElement())
                                {
                                    if (xmlReader.name() == QLatin1String("LAT")) {
                                        lat = xmlReader.readElementText().toFloat();
                                    } else if (xmlReader.name() == QLatin1String("LON")) {
                                        lon = xmlReader.readElementText().toFloat();
                                    } else if (xmlReader.name() == QLatin1String("ELEV")) {
                                        elevation = xmlReader.readElementText().toFloat();
                                    } else {
                                        xmlReader.skipCurrentElement();
                                    }
                                }
                            }
                            else if (xmlReader.name() == QLatin1String("RADIO"))
                            {
                                while(xmlReader.readNextStartElement())
                                {
                                    if (xmlReader.name() == QLatin1String("FREQUENCY"))
                                    {
                                        if (type == "NDB") {
                                            frequency = xmlReader.readElementText().toFloat();
                                        } else {
                                            frequency = xmlReader.readElementText().toFloat() * 1000.0;
                                        }
                                    } else if (xmlReader.name() == QLatin1String("CHANNEL")) {
                                        channel = xmlReader.readElementText();
                                    } else {
                                        xmlReader.skipCurrentElement();
                                    }
                                }
                            }
                            else if (xmlReader.name() == QLatin1String("PARAMS"))
                            {
                                while(xmlReader.readNextStartElement())
                                {
                                    if (xmlReader.name() == QLatin1String("RANGE")) {
                                        range = xmlReader.readElementText().toInt();
                                    } else if (xmlReader.name() == QLatin1String("DECLINATION")) {
                                        declination = xmlReader.readElementText().toFloat();
                                    } else if (xmlReader.name() == QLatin1String("ALIGNEDTOTRUENORTH")) {
                                        alignedTrueNorth = xmlReader.readElementText() == "TRUE";
                                    } else {
                                        xmlReader.skipCurrentElement();
                                    }
                                }
                            }
                            else
                            {
                               xmlReader.skipCurrentElement();
                            }
                        }
                        NavAid *navAid = new NavAid();
                        navAid->m_id = uniqueId++;
                        navAid->m_ident = id;
                        // Check idents conform to our filtering rules
                        if (navAid->m_ident.size() < 2) {
                            qDebug() << "NavAid::readXML: Ident less than 2 characters: " << navAid->m_ident;
                        } else if (navAid->m_ident.size() > 3) {
                            qDebug() << "NavAid::readXML: Ident greater than 3 characters: " << navAid->m_ident;
                        }
                        navAid->m_type = type;
                        navAid->m_name = name;
                        navAid->m_frequencykHz = frequency;
                        navAid->m_channel = channel;
                        navAid->m_latitude = lat;
                        navAid->m_longitude = lon;
                        navAid->m_elevation = elevation;
                        navAid->m_range = range;
                        navAid->m_magneticDeclination = declination;
                        navAid->m_alignedTrueNorth = alignedTrueNorth;
                        navAidInfo.append(navAid);
                    }
                }
            }
        }

        file.close();
    }
    else
    {
        // Don't warn, as many countries don't have files
        //qDebug() << "NavAid::readNavAidsXML: Could not open " << filename << " for reading.";
    }
    return navAidInfo;
}

const QStringList OpenAIP::m_countryCodes = {
    "ad",
    "ae",
    "af",
    "ag",
    "ai",
    "al",
    "am",
    "an",
    "ao",
    "aq",
    "ar",
    "as",
    "at",
    "au",
    "aw",
    "ax",
    "az",
    "ba",
    "bb",
    "bd",
    "be",
    "bf",
    "bg",
    "bh",
    "bi",
    "bj",
    "bl",
    "bm",
    "bn",
    "bo",
    "bq",
    "br",
    "bs",
    "bt",
    "bv",
    "bw",
    "by",
    "bz",
    "ca",
    "cc",
    "cd",
    "cf",
    "cg",
    "ch",
    "ci",
    "ck",
    "cl",
    "cm",
    "cn",
    "co",
    "cr",
    "cu",
    "cv",
    "cw",
    "cx",
    "cy",
    "cz",
    "de",
    "dj",
    "dk",
    "dm",
    "do",
    "dz",
    "ec",
    "ee",
    "eg",
    "eh",
    "er",
    "es",
    "et",
    "fi",
    "fj",
    "fk",
    "fm",
    "fo",
    "fr",
    "ga",
    "gb",
    "ge",
    "gf",
    "gg",
    "gh",
    "gi",
    "gl",
    "gm",
    "gn",
    "gp",
    "gq",
    "gr",
    "gs",
    "gt",
    "gu",
    "gw",
    "gy",
    "hk",
    "hm",
    "hn",
    "hr",
    "hu",
    "id",
    "ie",
    "il",
    "im",
    "in",
    "io",
    "iq",
    "ir",
    "is",
    "it",
    "je",
    "jm",
    "jo",
    "jp",
    "ke",
    "kg",
    "kh",
    "ki",
    "km",
    "kn",
    "kp",
    "kr",
    "kw",
    "ky",
    "kz",
    "la",
    "lb",
    "lc",
    "li",
    "lk",
    "lr",
    "ls",
    "lt",
    "lu",
    "lv",
    "ly",
    "ma",
    "mc",
    "md",
    "me",
    "mf",
    "mg",
    "mh",
    "mk",
    "ml",
    "mm",
    "mn",
    "mo",
    "mp",
    "mq",
    "mr",
    "ms",
    "mt",
    "mu",
    "mv",
    "mw",
    "mx",
    "my",
    "mz",
    "na",
    "nc",
    "ne",
    "nf",
    "ng",
    "ni",
    "nl",
    "no",
    "np",
    "nr",
    "nu",
    "nz",
    "om",
    "pa",
    "pe",
    "pf",
    "pg",
    "ph",
    "pk",
    "pl",
    "pm",
    "pn",
    "pr",
    "ps",
    "pt",
    "pw",
    "py",
    "qa",
    "re",
    "ro",
    "rs",
    "ru",
    "rw",
    "sa",
    "sb",
    "sc",
    "sd",
    "se",
    "sg",
    "sh",
    "si",
    "sj",
    "sk",
    "sl",
    "sm",
    "sn",
    "so",
    "sr",
    "ss",
    "st",
    "sv",
    "sx",
    "sy",
    "sz",
    "tc",
    "td",
    "tf",
    "tg",
    "th",
    "tj",
    "tk",
    "tl",
    "tm",
    "tn",
    "to",
    "tr",
    "tt",
    "tv",
    "tw",
    "tz",
    "ua",
    "ug",
    "um",
    "us",
    "uy",
    "uz",
    "va",
    "vc",
    "ve",
    "vg",
    "vi",
    "vn",
    "vu",
    "wf",
    "ws",
    "ye",
    "yt",
    "za",
    "zm",
    "zw"
};

QSharedPointer<QList<Airspace *>> OpenAIP::m_airspaces;
QSharedPointer<QList<NavAid *>> OpenAIP::m_navAids;

QDateTime OpenAIP::m_airspacesModifiedDateTime;
QDateTime OpenAIP::m_navAidsModifiedDateTime;

OpenAIP::OpenAIP(QObject *parent) :
    QObject(parent)
{
    connect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &OpenAIP::downloadFinished);
}

OpenAIP::~OpenAIP()
{
    disconnect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &OpenAIP::downloadFinished);
}

QString OpenAIP::getDataDir()
{
    // Get directory to store app data in
    QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
    // First dir is writable
    return locations[0];
}

QString OpenAIP::getAirspaceFilename(int i)
{
    return getAirspaceFilename(m_countryCodes[i]);
}

QString OpenAIP::getAirspaceFilename(const QString& countryCode)
{
    return getDataDir() + "/" + countryCode + "_asp.xml";
}

QString OpenAIP::getAirspaceURL(int i)
{
    if (i < m_countryCodes.size()) {
        return QString(OPENAIP_AIRSPACE_URL).arg(m_countryCodes[i]);
    } else {
        return QString();
    }
}

void OpenAIP::downloadAirspaces()
{
    m_countryIndex = 0;
    downloadAirspace();
}

void OpenAIP::downloadAirspace()
{
    QString filename = getAirspaceFilename(m_countryIndex);
    QString urlString = getAirspaceURL(m_countryIndex);
    QUrl dbURL(urlString);
    qDebug() << "OpenAIP::downloadAirspace: Downloading " << urlString;
    emit downloadingURL(urlString);
    m_dlm.download(dbURL, filename);
}

QString OpenAIP::getNavAidsFilename(int i)
{
    return getNavAidsFilename(m_countryCodes[i]);
}

QString OpenAIP::getNavAidsFilename(const QString& countryCode)
{
    return getDataDir() + "/" + countryCode + "_nav.xml";
}

QString OpenAIP::getNavAidsURL(int i)
{
    if (i < m_countryCodes.size()) {
        return QString(OPENAIP_NAVAIDS_URL).arg(m_countryCodes[i]);
    } else {
        return QString();
    }
}

void OpenAIP::downloadNavAids()
{
    m_countryIndex = 0;
    downloadNavAid();
}

void OpenAIP::downloadNavAid()
{
    QString filename = getNavAidsFilename(m_countryIndex);
    QString urlString = getNavAidsURL(m_countryIndex);
    QUrl dbURL(urlString);
    qDebug() << "OpenAIP::downloadNavAid: Downloading " << urlString;
    emit downloadingURL(urlString);
    m_dlm.download(dbURL, filename);
}

void OpenAIP::downloadFinished(const QString& filename, bool success)
{
    // Not all countries have corresponding files, so we should expect some errors
    if (!success) {
        qDebug() << "OpenAIP::downloadFinished: Failed: " << filename;
    }

    if (filename == getNavAidsFilename(m_countryIndex))
    {
        m_countryIndex++;
        if (m_countryIndex < m_countryCodes.size()) {
            downloadNavAid();
        } else {
            emit downloadNavAidsFinished();
        }
    }
    else if (filename == getAirspaceFilename(m_countryIndex))
    {
        m_countryIndex++;
        if (m_countryIndex < m_countryCodes.size()) {
            downloadAirspace();
        } else {
            emit downloadAirspaceFinished();
        }
    }
    else
    {
        qDebug() << "OpenAIP::downloadFinished: Unexpected filename: " << filename;
        emit downloadError(QString("Unexpected filename: %1").arg(filename));
    }
}

// Read airspaces for all countries
QList<Airspace *> *OpenAIP::readAirspaces()
{
    QList<Airspace *> *airspaces = new QList<Airspace *>();
    for (const auto& countryCode : m_countryCodes) {
        airspaces->append(readAirspaces(countryCode));
    }
    return airspaces;
}

// Read airspaces for a single country
QList<Airspace *> OpenAIP::readAirspaces(const QString& countryCode)
{
    return Airspace::readXML(getAirspaceFilename(countryCode));
}

// Read NavAids for all countries
QList<NavAid *> *OpenAIP::readNavAids()
{
    QList<NavAid *> *navAids = new QList<NavAid *>();
    for (const auto& countryCode : m_countryCodes) {
        navAids->append(readNavAids(countryCode));
    }
    return navAids;
}

// Read NavAids for a single country
QList<NavAid *> OpenAIP::readNavAids(const QString& countryCode)
{
    return NavAid::readXML(getNavAidsFilename(countryCode));
}

QSharedPointer<const QList<Airspace *>> OpenAIP::getAirspaces()
{
    QDateTime filesDateTime = getAirspacesModifiedDateTime();

    if (!m_airspaces || (filesDateTime > m_airspacesModifiedDateTime))
    {
        // Using shared pointer, so old object, if it exists, will be deleted, when no longer user
        m_airspaces = QSharedPointer<QList<Airspace *>>(readAirspaces());
        m_airspacesModifiedDateTime = filesDateTime;
    }
    return m_airspaces;
}

QSharedPointer<const QList<NavAid *>> OpenAIP::getNavAids()
{
    QDateTime filesDateTime = getNavAidsModifiedDateTime();

    if (!m_navAids || (filesDateTime > m_navAidsModifiedDateTime))
    {
        // Using shared pointer, so old object, if it exists, will be deleted, when no longer user
        m_navAids = QSharedPointer<QList<NavAid *>>(readNavAids());
        m_navAidsModifiedDateTime = filesDateTime;
    }
    return m_navAids;
}

// Gets the date and time the airspaces files were most recently modified
QDateTime OpenAIP::getAirspacesModifiedDateTime()
{
    QDateTime dateTime;
    for (const auto& countryCode : m_countryCodes)
    {
        QFileInfo fileInfo(getAirspaceFilename(countryCode));
        QDateTime fileModifiedDateTime = fileInfo.lastModified();
        if (fileModifiedDateTime > dateTime) {
            dateTime = fileModifiedDateTime;
        }
    }
    return dateTime;
}

// Gets the date and time the navaid files were most recently modified
QDateTime OpenAIP::getNavAidsModifiedDateTime()
{
    QDateTime dateTime;
    for (const auto& countryCode : m_countryCodes)
    {
        QFileInfo fileInfo(getNavAidsFilename(countryCode));
        QDateTime fileModifiedDateTime = fileInfo.lastModified();
        if (fileModifiedDateTime > dateTime) {
            dateTime = fileModifiedDateTime;
        }
    }
    return dateTime;
}

