#include "WimaArea.h"

/*!
 * \variable WimaArea::epsilonMeter
 * \brief The accuracy used for distance calculations (unit: m).
 */
const double WimaArea::epsilonMeter    = 1e-5;
/*!
 * \variable WimaArea::maxAltitudeName
 * \brief A string containing the name of the \c _maxAltitude member. Among other used for storing.
 */
const char* WimaArea::maxAltitudeName       = "maxAltitude";
/*!
 * \variable WimaArea::wimaAreaName
 * \brief A string containing the name of this \c WimaArea member. Among other used for storing.
 */
const char* WimaArea::wimaAreaName          = "WimaArea";
/*!
 * \variable WimaArea::areaTypeName
 * \brief A string containing \c {"AreaType"}. Among other used for stroing.
 */
const char* WimaArea::areaTypeName          = "AreaType";


const char* WimaArea::borderPolygonOffsetName  = "BorderPolygonOffset";
const char* WimaArea::showBorderPolygonName    = "ShowBorderPolygon";
const char* WimaArea::settingsGroup            = "MeasurementArea";

// Constructors
WimaArea::WimaArea(QObject *parent)
    :     QGCMapPolygon (parent)
    ,     _metaDataMap            (FactMetaData::createMapFromJsonFile(QStringLiteral(":/json/WimaArea.SettingsGroup.json"), this /* QObject parent */))
    ,     _borderPolygonOffset    (SettingsFact(settingsGroup, _metaDataMap[borderPolygonOffsetName], this /* QObject parent */))
    ,     _showBorderPolygon      (SettingsFact(settingsGroup, _metaDataMap[showBorderPolygonName], this /* QObject parent */))
    ,     _borderPolygon          (QGCMapPolygon(this))
    ,     _wimaAreaInteractive            (false)
{
    init();
    _maxAltitude = 30;
}

WimaArea::WimaArea(const WimaArea &other, QObject *parent)
    :     QGCMapPolygon (parent)
    ,     _metaDataMap            (FactMetaData::createMapFromJsonFile(QStringLiteral(":/json/WimaArea.SettingsGroup.json"), this /* QObject parent */))
    ,     _borderPolygonOffset    (SettingsFact(settingsGroup, _metaDataMap[borderPolygonOffsetName], this /* QObject parent */))
    ,     _showBorderPolygon      (SettingsFact(settingsGroup, _metaDataMap[showBorderPolygonName], this /* QObject parent */))
    ,     _borderPolygon          (QGCMapPolygon(this))
    ,     _wimaAreaInteractive            (false)
{
    init();
    *this = other;
}

/*!
 *\fn WimaArea &WimaArea::operator=(const WimaArea &other)
 *
 * Assigns \a other to this \c WimaArea and returns a reference to this \c WimaArea.
 *
 * Copies only path and maximum altitude.
 */
WimaArea &WimaArea::operator=(const WimaArea &other)
{
    QGCMapPolygon::operator=(other);
    this->_maxAltitude = other.maxAltitude();
    this->setPath(other.path());

    return *this;
}

void WimaArea::setWimaAreaInteractive(bool interactive)
{
    if (WimaArea::_wimaAreaInteractive != interactive) {
        WimaArea::_wimaAreaInteractive = interactive;

        emit WimaArea::wimaAreaInteractiveChanged();
    }
}

/*!
  \fn void WimaArea::setMaxAltitude(double altitude)

  Sets the \c _maxAltitude member to \a altitude and emits the signal \c maxAltitudeChanged()
  if \c _maxAltitude is not equal to altitude.
 */
void WimaArea::setMaxAltitude(double altitude)
{
    if ( altitude > 0 && qFuzzyCompare(altitude, _maxAltitude) ) {
        _maxAltitude = altitude;
        emit maxAltitudeChanged();
    }
}

void WimaArea::setShowBorderPolygon(bool showBorderPolygon)
{
    _showBorderPolygon.setRawValue(showBorderPolygon);
}



void WimaArea::setBorderPolygonOffset(double offset)
{
    if ( !qFuzzyCompare(_borderPolygonOffset.rawValue().toDouble(), offset) ) {
        _borderPolygonOffset.setRawValue(offset);

        emit borderPolygonOffsetChanged();
    }
}

void WimaArea::recalcPolygons()
{
    if (_showBorderPolygon.rawValue().toBool() == true) {

        if ( _borderPolygon.count() >= 3 ) {
            //_borderPolygon.verifyClockwiseWinding(); // causes seg. fault
            this->setPath(_borderPolygon.coordinateList());
            this->offset(-_borderPolygonOffset.rawValue().toDouble());
        }
    } else {

        if (this->count() >= 3){
            //this->verifyClockwiseWinding(); // causes seg. fault
            _borderPolygon.setPath(this->coordinateList());
            _borderPolygon.offset(_borderPolygonOffset.rawValue().toDouble());
        }

        emit borderPolygonChanged();
    }


}

void WimaArea::updatePolygonConnections(QVariant showBorderPolygon)
{
    if (showBorderPolygon.toBool() == true) {
        connect(&_borderPolygon, &QGCMapPolygon::pathChanged, this, &WimaArea::recalcPolygons);
        disconnect(this, &QGCMapPolygon::pathChanged, this, &WimaArea::recalcPolygons);
    } else {
        disconnect(&_borderPolygon, &QGCMapPolygon::pathChanged, this, &WimaArea::recalcPolygons);
        connect(this, &QGCMapPolygon::pathChanged, this, &WimaArea::recalcPolygons);
    }
}

void WimaArea::recalcInteractivity()
{
    if ( _wimaAreaInteractive == false) {
        this->setWimaAreaInteractive(false);
        _borderPolygon.setInteractive(false);
    } else {
        if (_showBorderPolygon.rawValue().toBool() == true) {
            _borderPolygon.setInteractive(true);
            this->setInteractive(false);
        } else {
            _borderPolygon.setInteractive(false);
            this->setInteractive(true);
        }
    }
}

/*!
 * \fn int WimaArea::getClosestVertexIndex(const QGeoCoordinate &coordinate) const
 * Returns the index of the vertex (element of the polygon path)
 * which has the least distance to \a coordinate.
 *
 * \sa QGeoCoordinate
 */
int WimaArea::getClosestVertexIndex(const QGeoCoordinate &coordinate) const
{
    if (this->count() == 0) {
        qWarning("Polygon count == 0!");
        return -1;
    }else if (this->count() == 1) {
        return 0;
    }else {
        int index = 0;
        double min_dist = coordinate.distanceTo(this->vertexCoordinate(index));
        for(int i = 1; i < this->count(); i++){
            double dist = coordinate.distanceTo(this->vertexCoordinate(i));
            if (dist < min_dist){
                min_dist = dist;
                index = i;
            }
        }

        return index;
    }
}

/*!
 * \fn  QGeoCoordinate WimaArea::getClosestVertex(const QGeoCoordinate& coordinate) const
 *  Returns the vertex of the polygon path with the least distance to \a coordinate.
 *
 * \sa QGeoCoordinate
 */
QGeoCoordinate WimaArea::getClosestVertex(const QGeoCoordinate& coordinate) const
{
    return this->vertexCoordinate(getClosestVertexIndex(coordinate));
}

/*!
 * \fn QGCMapPolygon WimaArea::toQGCPolygon(const WimaArea &area)
 * Converts the \c WimaArea \a area to \c QGCMapPolygon by copying the path only.
 */
QGCMapPolygon WimaArea::toQGCPolygon(const WimaArea &area)
{
    QGCMapPolygon qgcPoly;
    qgcPoly.setPath(area.path());

    return QGCMapPolygon(qgcPoly);
}

/*!
 * \fn QGCMapPolygon WimaArea::toQGCPolygon() const
 * Converts the calling \c WimaArea to \c QGCMapPolygon by copying the path only.
 */
QGCMapPolygon WimaArea::toQGCPolygon() const
{
    return toQGCPolygon(*this);
}

/*!
 * \fn bool WimaArea::join(WimaArea &area1, WimaArea &area2, WimaArea &joinedArea, QString &errorString)
 * Joins the areas \a area1 and \a area2 such that a \l {Simple Polygon} is created.
 * Stores the result inside \a joinedArea.
 * Stores error messages in \a errorString.
 * Returns \c true if the algorithm was able to join the areas; false else.
 * The algorithm will be able to join the areas, if either their edges intersect with each other,
 * or one area contains the other.
 */
bool WimaArea::join(const WimaArea &area1, const WimaArea &area2, WimaArea &joinedArea, QString &errorString)
{
    using namespace GeoUtilities;
    using namespace PolygonCalculus;

    QList<QGeoCoordinate> GeoPolygon1 = area1.coordinateList();
    QList<QGeoCoordinate> GeoPolygon2 = area2.coordinateList();

//    qWarning("befor joining");
//    qWarning() << GeoPolygon1;
//    qWarning() << GeoPolygon2;

    QGeoCoordinate origin = GeoPolygon1[0];

//    QGeoCoordinate tset = GeoPolygon1[2];

//    qWarning() << tset;qWarning() << toGeo(toCartesian2D(tset, origin), origin);



    QPolygonF polygon1 = toQPolygonF(toCartesian2D(GeoPolygon1, origin));
    QPolygonF polygon2 = toQPolygonF(toCartesian2D(GeoPolygon2, origin));

//    qWarning("after 1 transform");
//    qWarning() << polygon1;
//    qWarning() << polygon2;

    QPolygonF joinedPolygon;
    JoinPolygonError retValue = PolygonCalculus::join(polygon1, polygon2, joinedPolygon);


//    qWarning("after joining");
//    qWarning() << joinedPolygon;

    if (retValue == JoinPolygonError::Disjoint) {
        qWarning("Polygons are disjoint.");
    } else if (retValue == JoinPolygonError::NotSimplePolygon) {
        qWarning("Not a simple polygon.");
    } else if (retValue == JoinPolygonError::PathSizeLow) {
        qWarning("Polygon vertex count is low.");
    } else {
        QList<QGeoCoordinate> path = toGeo(toQPointFList(joinedPolygon), origin);
//        qWarning("after transform");
//        qWarning() << path;
        joinedArea.setPath(path);
        return true;
    }

    return false;
}


/*!
 * \fn bool WimaArea::join(WimaArea &area1, WimaArea &area2, WimaArea &joinedArea)
 * Joins the areas \a area1 and \a area2 such that a \l {Simple Polygon} is created.
 * Stores the result inside \a joinedArea.
 * Returns \c true if the algorithm was able to join the areas; false else.
 * The algorithm will be able to join the areas, if either their edges intersect with each other,
 * or one area contains the other.
 */
bool WimaArea::join(const WimaArea &area1, const WimaArea &area2, WimaArea &joinedArea)
{
    QString dummy;
    return join(area1, area2, joinedArea, dummy);
}

/*!
 * \fn bool WimaArea::join(WimaArea &area)
 * Joins the calling \c WimaArea and the \a area such that a \l {Simple Polygon} is created.
 * Overwrites the calling \c WimaArea with the result, if the algorithm was successful.
 * Returns \c true if the algorithm was able to join the areas; false else.
 * The algorithm will be able to join the areas, if either their edges intersect with each other,
 * or one area contains the other.
 */
bool WimaArea::join(WimaArea &area)
{
    WimaArea joinedArea;
    if ( join(*this, area, joinedArea) ) {
        //qWarning("WimaArea::join(WimaArea &area)");
        //qWarning() << joinedArea.coordinateList();
        this->setPath(joinedArea.path());
        return true;
    } else {
        return false;
    }
}


/*!
 * \fn bool WimaArea::join(WimaArea &area, QString &errorString)
 * Joins the calling \c WimaArea and the \a area such that a \l {Simple Polygon} is created.
 * Overwrites the calling \c WimaArea with the result, if the algorithm was successful.
 *
 * Returns \c true if the algorithm was able to join the areas; false else.
 * Stores error messages in \a errorString.
 *
 * The algorithm will be able to join the areas, if either their edges intersect with each other,
 * or one area contains the other.
 */
bool WimaArea::join(WimaArea &area, QString &errorString)
{
    WimaArea joinedArea;
    if ( join(*this, area, joinedArea, errorString) ) {
        this->setPath(joinedArea.path());
        return true;
    } else {
        return false;
    }
}

/*!
 * \fn int WimaArea::nextVertexIndex(int index) const
 * Returns the index of the next vertex (of the areas path), which is \a index + 1 if \a index is smaller than \c {area.count() - 1},
 * or 0 if \a index equals \c {area.count() - 1}, or -1 if the \a index is out of bounds.
 * \note The function \c {area.count()} (derived from \c QGCMapPolygon) returns the number of vertices defining the area.
 */
int WimaArea::nextVertexIndex(int index) const
{
    if (index >= 0 && index < count()-1) {
        return index + 1;
    } else if (index == count()-1) {
        return 0;
    } else {
        qWarning("WimaArea::nextVertexIndex(): Index out of bounds! index:count = %i:%i", index, count());
        return -1;
    }
}

/*!
 * \fn int WimaArea::previousVertexIndex(int index) const
 * Returns the index of the previous vertex (of the areas path), which is \a index - 1 if \a index is larger 0,
 * or \c {area.count() - 1} if \a index equals 0, or -1 if the \a index is out of bounds.
 * \note The function \c {area.count()} (derived from \c QGCMapPolygon) returns the number of vertices defining the area.
 */
int WimaArea::previousVertexIndex(int index) const
{
    if (index > 0 && index < count()) {
        return index - 1;
    } else if (index == 0) {
        return count()-1;
    } else {
        qWarning("WimaArea::previousVertexIndex(): Index out of bounds! index:count = %i:%i", index, count());
        return -1;
    }
}

/*!
 * \fn bool WimaArea::isSelfIntersecting()
 * Returns \c true if the calling area is self intersecting, \c false else.
 * \note If the calling area is self intersecting, it's not a \l {Simple Polygon}.
 */
bool WimaArea::isSimplePolygon() const
{
    using namespace PolygonCalculus;
    using namespace GeoUtilities;

    if (this->count() > 2) {
        QPolygonF polygon = toQPolygonF(toCartesian2D(this->coordinateList(), this->vertexCoordinate(0)));
        return PolygonCalculus::isSimplePolygon(polygon);
    } else
        return false;

}

bool WimaArea::containsCoordinate(const QGeoCoordinate &coordinate) const
{
    using namespace PlanimetryCalculus;
    using namespace PolygonCalculus;
    using namespace GeoUtilities;

    if (this->count() > 2) {
        QPolygonF polygon = toQPolygonF(toCartesian2D(this->coordinateList(), coordinate));
        return PlanimetryCalculus::contains(polygon, QPointF(0,0));
    } else
        return false;
}

/*!
 * \fn void WimaArea::saveToJson(QJsonObject &json)
 * Saves the calling area to \c QJsonObject object and stores it inside \a json.
 *
 * \sa QJsonObject
 */
void WimaArea::saveToJson(QJsonObject &json)
{
    this->QGCMapPolygon::saveToJson(json);

    json[maxAltitudeName]           = _maxAltitude;
    json[borderPolygonOffsetName]   = _borderPolygonOffset.rawValue().toDouble();
    json[showBorderPolygonName]     = _showBorderPolygon.rawValue().toDouble();
    json[areaTypeName]              = wimaAreaName;
}

/*!
 * \fn bool WimaArea::loadFromJson(const QJsonObject &json, QString& errorString)
 * Loads data from \a json and stores it inside the calling area.
 * Returns \c true if loading was successful, \c false else.
 * Stores error messages inside \a errorString.
 *
 * \sa QJsonObject
 */
bool WimaArea::loadFromJson(const QJsonObject &json, QString& errorString)
{
    if ( this->QGCMapPolygon::loadFromJson(json, false /*no poly required*/, errorString) ) {
        if ( json.contains(maxAltitudeName) && json[maxAltitudeName].isDouble()) {
            _maxAltitude = json[maxAltitudeName].toDouble();
        } else {
            errorString.append(tr("Could not load Maximum Altitude value!\n"));
            return false;
        }

        if ( json.contains(borderPolygonOffsetName) && json[borderPolygonOffsetName].isDouble()) {
            _borderPolygonOffset.setRawValue(json[borderPolygonOffsetName].toDouble());
        } else {
            errorString.append(tr("Could not load border polygon offset value!\n"));
            return false;
        }

        if ( json.contains(showBorderPolygonName) && json[showBorderPolygonName].isDouble()) {
            _showBorderPolygon.setRawValue(json[showBorderPolygonName].toBool());
        } else {
            errorString.append(tr("Could not load border polygon offset value!\n"));
            return false;
        }
    } else {
        qWarning() << errorString;
        return false;
    }

    return true;
}

/*!
 * \fn void WimaArea::init()
 * Funtion to be called during construction.
 */
void WimaArea::init()
{
    this->setObjectName(wimaAreaName);

    if (_showBorderPolygon.rawValue().toBool() == true){
        connect(&_borderPolygon, &QGCMapPolygon::pathChanged, this, &WimaArea::recalcPolygons);

    } else {
        connect(this, &QGCMapPolygon::pathChanged, this, &WimaArea::recalcPolygons);
    }

    connect(&_borderPolygonOffset,  &SettingsFact::rawValueChanged, this,   &WimaArea::recalcPolygons);
    connect(&_showBorderPolygon, &SettingsFact::rawValueChanged, this, &WimaArea::updatePolygonConnections);
    connect(&_showBorderPolygon, &SettingsFact::rawValueChanged, this, &WimaArea::recalcInteractivity);
    connect(this, &WimaArea::wimaAreaInteractiveChanged, this, &WimaArea::recalcInteractivity);
}

/*!
 * \fn void print(const WimaArea &area)
 * Prints the data contained in \a area to the console.
 */
void print(const WimaArea &area)
{
    QString message;
    print(area, message);
    qWarning() << message;
}

/*!
 * \fn void print(const WimaArea &area)
 * Prints the data contained in \a area to the \a outputString.
 */
void print(const WimaArea &area, QString &outputString)
{
    outputString.append(QString("Type: %1\n").arg(area.objectName()));
    print(static_cast<const QGCMapPolygon&>(area), outputString);
    outputString.append(QString("Maximum Altitude: %1\n").arg(area._maxAltitude));
    outputString.append(QString("Border Polygon Offset: %1\n").arg(area._borderPolygonOffset.rawValue().toDouble()));

    outputString.append(QString("Border Polygon Coordinates\n").arg(area._borderPolygonOffset.rawValue().toDouble()));
    for (int i = 0; i < area._borderPolygon.count(); i++) {
        QGeoCoordinate coordinate = area._borderPolygon.vertexCoordinate(i);
        outputString.append(QString("%1\n").arg(coordinate.toString(QGeoCoordinate::Degrees)));
    }
}


// QDoc Documentation

/*!
    \group WimaAreaGroup
    \title Group of WimaAreas

    Every \c WimaArea of the equally named group uses a \l {Simple Polygon} derived from \c {QGCMapPolygon}
    to define areas inside which certain taskts are performed.
*/

/*!
    \class WimaArea
    \inmodule Wima
    \ingroup WimaArea

    \brief The \c WimaArea class provides the a base class for
    all areas used within the Wima extension.

    \c WimaArea uses a \l {Simple Polygon} derived from \c {QGCMapPolygon}
    to define areas inside which certain taskts are performed. The polygon (often refered to as the path) can
    be displayed visually on a map.
*/

/*!
  \variable WimaArea::_maxAltitude
  \brief The maximum altitude vehicles are allowed to fly inside this area.
*/

/*!
  \property WimaArea::maxAltitude
  \brief The maximum altitude at which vehicles are allowed to fly.
*/

/*!
  \property WimaArea::mapVisualQML
  \brief A string containing the name of the QML file used to displays this area on a map.
*/

/*!
  \property WimaArea::editorQML
  \brief A string containing the name of the QML file allowing to edit the area's properties.
*/

/*!
    \externalpage https://en.wikipedia.org/wiki/Simple_polygon
    \title Simple Polygon
*/

/*!
    \externalpage https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
    \title Dijkstra Algorithm
*/


