// SPDX-License-Identifier: LGPL-2.1-or-later
//
// SPDX-FileCopyrightText: 2011 Thibaut Gridel <tgridel@free.fr>
// SPDX-FileCopyrightText: 2012 Bernhard Beschow <bbeschow@cs.tu-berlin.de>

#include "PntRunner.h"

#include "GeoDataDocument.h"
#include "GeoDataLineString.h"
#include "GeoDataLinearRing.h"
#include "GeoDataPlacemark.h"
#include "MarbleDebug.h"
#include "MarbleGlobal.h"

#include <QDataStream>
#include <QFile>
#include <QFileInfo>
#include <qmath.h>

namespace Marble
{

// distance of 180deg in arcminutes
const qreal INT2RAD = M_PI / 10800.0;

PntRunner::PntRunner(QObject *parent)
    : ParsingRunner(parent)
{
}

PntRunner::~PntRunner() = default;

GeoDataDocument *PntRunner::parseFile(const QString &fileName, DocumentRole role, QString &errorString)
{
    QFileInfo fileinfo(fileName);
    if (fileinfo.suffix().compare(QLatin1StringView("pnt"), Qt::CaseInsensitive) != 0) {
        errorString = QStringLiteral("File %1 does not have a pnt suffix").arg(fileName);
        mDebug() << errorString;
        return nullptr;
    }

    QFile file(fileName);
    if (!file.exists()) {
        errorString = QStringLiteral("File %1 does not exist").arg(fileName);
        mDebug() << errorString;
        return nullptr;
    }

    file.open(QIODevice::ReadOnly);
    QDataStream stream(&file); // read the data serialized from the file
    stream.setByteOrder(QDataStream::LittleEndian);

    auto document = new GeoDataDocument();
    document->setDocumentRole(role);
    GeoDataPlacemark *placemark = nullptr;

    int count = 0;
    bool error = false;
    while (!stream.atEnd() || error) {
        short header = -1;
        short iLat = -5400 - 1;
        short iLon = -10800 - 1;

        stream >> header >> iLat >> iLon;

        // make sure iLat is within valid range
        if (!(-5400 <= iLat && iLat <= 5400)) {
            mDebug() << "invalid iLat =" << iLat << "(" << (iLat * INT2RAD) * RAD2DEG << ") in dataset" << count << "of file" << fileName;
            error = true;
        }

        // make sure iLon is within valid range
        if (!(-10800 <= iLon && iLon <= 10800)) {
            mDebug() << "invalid iLon =" << iLon << "(" << (iLon * INT2RAD) * RAD2DEG << ") in dataset" << count << "of file" << fileName;
            error = true;
        }

        if (header >= 1000 && !document->isEmpty()) {
            auto const polyline = static_cast<GeoDataLineString *>(placemark->geometry());
            if (polyline->size() == 1) {
                mDebug() << fileName << "contains single-point polygon at" << count << ". Aborting.";
                error = true;
                break;
            }
        }

        if (header < 1) {
            /* invalid header */
            mDebug() << "invalid header:" << header << "in" << fileName << "at" << count;
            error = true;
            break;
        } else if (header <= 5) {
            /* header represents level of detail */
            /* nothing to do */
        } else if (header < 1000) {
            /* invalid header */
            mDebug() << "invalid header:" << header << "in" << fileName << "at" << count;
            error = true;
            break;
        } else if (header < 2000) {
            /* header represents start of coastline */
            placemark = new GeoDataPlacemark;
            document->append(placemark);
            placemark->setGeometry(new GeoDataLinearRing);
        } else if (header < 4000) {
            /* header represents start of country border */
            placemark = new GeoDataPlacemark;
            document->append(placemark);
            placemark->setGeometry(new GeoDataLineString);
        } else if (header < 5000) {
            /* header represents start of internal political border */
            placemark = new GeoDataPlacemark;
            document->append(placemark);
            placemark->setGeometry(new GeoDataLineString);
        } else if (header < 6000) {
            /* header represents start of island */
            placemark = new GeoDataPlacemark;
            document->append(placemark);
            placemark->setGeometry(new GeoDataLinearRing);
        } else if (header < 7000) {
            /* header represents start of lake */
            placemark = new GeoDataPlacemark;
            document->append(placemark);
            placemark->setGeometry(new GeoDataLinearRing);
        } else if (header < 8000) {
            /* header represents start of river */
            placemark = new GeoDataPlacemark;
            document->append(placemark);
            placemark->setGeometry(new GeoDataLineString);
        } else if (header < 9000) {
            /* custom header represents start of glaciers, lakes or islands */
            placemark = new GeoDataPlacemark;
            document->append(placemark);
            placemark->setGeometry(new GeoDataLinearRing);
        } else if (header < 10000) {
            /* custom header represents start of political borders */
            placemark = new GeoDataPlacemark;
            document->append(placemark);
            placemark->setGeometry(new GeoDataLineString);
        } else if (header < 14000) {
            /* invalid header */
            mDebug() << "invalid header:" << header << "in" << fileName << "at" << count;
            error = true;
            break;
        } else if (header < 15000) {
            /* custom header represents start of political borders */
            placemark = new GeoDataPlacemark;
            document->append(placemark);
            placemark->setGeometry(new GeoDataLineString);
        } else if (header < 19000) {
            /* invalid header */
            mDebug() << "invalid header:" << header << "in" << fileName << "at" << count;
            error = true;
            break;
        } else if (header < 20000) {
            /* custom header represents start of dateline */
            placemark = new GeoDataPlacemark;
            document->append(placemark);
            placemark->setGeometry(new GeoDataLineString);
        } else {
            /* invalid header */
            mDebug() << "invalid header:" << header << "in" << fileName << "at" << count;
            error = true;
            break;
        }

        auto polyline = static_cast<GeoDataLineString *>(placemark->geometry());

        // Transforming Range of Coordinates to iLat [0,ARCMINUTE] , iLon [0,2 * ARCMINUTE]
        polyline->append(GeoDataCoordinates((qreal)(iLon)*INT2RAD,
                                            (qreal)(iLat)*INT2RAD,
                                            0.0,
                                            GeoDataCoordinates::Radian,
                                            5 - qMin(5, (int)header))); // if 1 <= header <= 5, header contains level of detail
                                                                        // else pick most sparse level of detail, which equals 0

        ++count;
    }

    file.close();
    if (document->isEmpty() || error) {
        delete document;
        document = nullptr;
        return nullptr;
    }
    document->setFileName(fileName);
    return document;
}

}

#include "moc_PntRunner.cpp"
