#include "DefaultManager.h"
#include "Wima/Geometry/GeoUtilities.h"
#include "Wima/Geometry/PolygonCalculus.h"

#include "MissionSettingsItem.h"
#include "SimpleMissionItem.h"


WaypointManager::DefaultManager::DefaultManager(Settings &settings,
                                                AreaInterface &interface)
    : ManagerBase(settings)
    , _areaInterface(&interface)
{

}

void WaypointManager::DefaultManager::clear()
{
    _dirty = true;
    _waypoints.clear();
    _currentWaypoints.clear();
    _missionItems.clearAndDeleteContents();
    _currentMissionItems.clearAndDeleteContents();
    _waypointsVariant.clear();
    _currentWaypointsVariant.clear();
}

bool WaypointManager::DefaultManager::update()
{
    // extract waypoints
    _currentWaypoints.clear();
    Slicer::update(_waypoints, _currentWaypoints);
    return _worker();
}

bool WaypointManager::DefaultManager::next()
{
    // extract waypoints
    _currentWaypoints.clear();
    Slicer::next(_waypoints, _currentWaypoints);
    return _worker();
}

bool WaypointManager::DefaultManager::previous()
{
    // extract waypoints
    _currentWaypoints.clear();
    Slicer::previous(_waypoints, _currentWaypoints);
    return _worker();
}

bool WaypointManager::DefaultManager::reset()
{
    // extract waypoints
    _currentWaypoints.clear();
    Slicer::reset(_waypoints, _currentWaypoints);
    return _worker();
}

bool WaypointManager::DefaultManager::_insertMissionItem(const QGeoCoordinate &c,
                                                         size_t index,
                                                         QmlObjectListModel &list,
                                                         bool doUpdate)
{
    using namespace WaypointManager::Utils;

    if ( !insertMissionItem(c,
                            index /*insertion index*/,
                            list,
                            _settings->vehicle(),
                            _settings->isFlyView(),
                            &list /*parent*/,
                            doUpdate /*do update*/) )
    {
        qWarning("WaypointManager::DefaultManager::next(): insertMissionItem failed.");
        Q_ASSERT(false);
        return false;
    }
    return true;
}

bool WaypointManager::DefaultManager::_insertMissionItem(const QGeoCoordinate &c,
                                                         size_t index,
                                                         bool doUpdate)
{
    return _insertMissionItem(c, index, _currentMissionItems, doUpdate);
}

bool WaypointManager::DefaultManager::_calcShortestPath(
        const QGeoCoordinate &start,
        const QGeoCoordinate &destination,
        QVector<QGeoCoordinate> &path)
{
        using namespace GeoUtilities;
        using namespace PolygonCalculus;
        QPolygonF joinedArea2D;
        toCartesianList(_areaInterface->joinedArea()->coordinateList(), /*origin*/ start, joinedArea2D);
        QPointF start2D(0,0);
        QPointF end2D;
        toCartesian(destination, start, end2D);
        QVector<QPointF> path2DOut;

        bool retVal = PolygonCalculus::shortestPath(joinedArea2D, start2D, end2D, path2DOut);
        toGeoList(path2DOut, /*origin*/ start, path);

        return  retVal;
}

bool WaypointManager::DefaultManager::_worker()
{
    // Precondition:
    // _waypoints must contain valid coordinates.
    // Slicer must be called befor invoking this function.
    // E.g. Slicer::reset(_waypoints, _currentWaypoints);

    using namespace WaypointManager::Utils;

    if (_waypoints.count() < 1 || !_settings->valid()) {
        return false;
    }

    if (_dirty) {
        _missionItems.clearAndDeleteContents();
        _waypointsVariant.clear();
        // No initialization of _missionItems, don't need MissionSettingsItem here.
        for (size_t i = 0; i < size_t(_waypoints.size()); ++i) {
            auto &c = _waypoints.at(i);
            _insertMissionItem(c, _missionItems.count(), _missionItems, false /*update list*/);
            _waypointsVariant.push_back(QVariant::fromValue(c));
        }
        updateHirarchy(_missionItems);
        updateSequenceNumbers(_missionItems, 1); // Start with 1, since MissionSettingsItem missing.
        _dirty = false;
    }

    _currentMissionItems.clearAndDeleteContents();
    initialize(_currentMissionItems, _settings->vehicle(), _settings->isFlyView());

    // Calculate path from home to first waypoint.
    QVector<QGeoCoordinate> arrivalPath;
    if ( !_calcShortestPath(_settings->homePosition(), _currentWaypoints.first(), arrivalPath) ) {
        qWarning("WaypointManager::DefaultManager::next(): Not able to calc path from home position to first waypoint.");
        return false;
    }
    if (!arrivalPath.empty())
        arrivalPath.removeFirst();
    if (!arrivalPath.empty())
        arrivalPath.removeLast();

    // calculate path from last waypoint to home
    QVector<QGeoCoordinate> returnPath;
    if ( !_calcShortestPath(_currentWaypoints.last(), _settings->homePosition(), returnPath) ) {
        qWarning("WaypointManager::DefaultManager::next(): Not able to calc path from home position to last waypoint.");
        return false;
    }
    if (!returnPath.empty())
        returnPath.removeFirst();
    if (!returnPath.empty())
        returnPath.removeLast();



    // Create mission items.
    // Set home position of MissionSettingsItem.
    MissionSettingsItem* settingsItem = _currentMissionItems.value<MissionSettingsItem *>(0);
    if (settingsItem == nullptr) {
        Q_ASSERT(false);
        qWarning("WaypointManager::DefaultManager::next(): nullptr.");
        return false;
    }
    settingsItem->setCoordinate(_settings->homePosition());

    // First mission item is takeoff command.
    _insertMissionItem(_settings->homePosition(), 1 /*insertion index*/,  false /*do update*/);
    SimpleMissionItem *takeoffItem = _currentMissionItems.value<SimpleMissionItem*>(1);
    if (takeoffItem == nullptr) {
        qWarning("WaypointManager::DefaultManager::next(): nullptr.");
        Q_ASSERT(takeoffItem != nullptr);
        return false;
    }
    makeTakeOffCmd(takeoffItem, _settings->masterController()->managerVehicle());

    // Create change speed item.
    _insertMissionItem(_settings->homePosition(), 2 /*insertion index*/, false /*do update*/);
    SimpleMissionItem *speedItem = _currentMissionItems.value<SimpleMissionItem*>(2);
    if (speedItem == nullptr) {
        qWarning("WaypointManager::DefaultManager::next(): nullptr.");
        Q_ASSERT(speedItem != nullptr);
        return false;
    }
    makeSpeedCmd(speedItem, _settings->arrivalReturnSpeed());

    // insert arrival path
    for (auto coordinate : arrivalPath) {
        _insertMissionItem(coordinate,
                           _currentMissionItems.count() /*insertion index*/,
                           false /*do update*/);
    }

    // Create change speed item (after arrival path).
    int index = _currentMissionItems.count();
    _insertMissionItem(_currentWaypoints.first(), index /*insertion index*/, false /*do update*/);
    speedItem = _currentMissionItems.value<SimpleMissionItem*>(index);
    if (speedItem == nullptr) {
        Q_ASSERT(false);
        qWarning("WaypointManager::DefaultManager::next(): nullptr.");
        return false;
    }
    makeSpeedCmd(speedItem, _settings->flightSpeed());

    // Insert slice.
    for (auto coordinate : _currentWaypoints) {
        _insertMissionItem(coordinate,
                           _currentMissionItems.count() /*insertion index*/,
                           false /*do update*/);
    }

    // Create change speed item, after slice.
    index = _currentMissionItems.count();
    _insertMissionItem(_currentWaypoints.last(), index /*insertion index*/, false /*do update*/);
    speedItem = _currentMissionItems.value<SimpleMissionItem*>(index);
    if (speedItem == nullptr) {
        Q_ASSERT(false);
        qWarning("WaypointManager::DefaultManager::next(): nullptr.");
        return false;
    }
    makeSpeedCmd(speedItem, _settings->arrivalReturnSpeed());

    // Insert return path coordinates.
    for (auto coordinate : returnPath) {
        _insertMissionItem(coordinate,
                           _currentMissionItems.count() /*insertion index*/,
                           false /*do update*/);
    }

    // Set land command for last mission item.
    index = _currentMissionItems.count();
    _insertMissionItem(_settings->homePosition(), index /*insertion index*/, false /*do update*/);
    SimpleMissionItem *landItem = _currentMissionItems.value<SimpleMissionItem*>(index);
    if (landItem == nullptr) {
        Q_ASSERT(false);
        qWarning("WaypointManager::DefaultManager::next(): nullptr.");
        return false;
    }
    makeLandCmd(landItem, _settings->masterController()->managerVehicle());

    // Set altitude.
    for (int i = 1; i < _currentMissionItems.count(); ++i) {
        SimpleMissionItem *item = _currentMissionItems.value<SimpleMissionItem *>(i);
        if (item == nullptr) {
            Q_ASSERT(false);
            qWarning("WimaController::updateAltitude(): nullptr");
            return false;
        }
        item->altitude()->setRawValue(_settings->altitude());
    }

    // Update list _currentMissionItems.
    updateHirarchy(_currentMissionItems);
    updateSequenceNumbers(_currentMissionItems);


    // Prepend arrival path to slice.
    for ( long i = arrivalPath.size()-1; i >=0; --i )
        _currentWaypoints.push_front(arrivalPath[i]);
    _currentWaypoints.push_front(_settings->homePosition());
    // Append return path to slice.
    for ( auto c : returnPath )
        _currentWaypoints.push_back(c);
    _currentWaypoints.push_back(_settings->homePosition());

    // Create variant list.
    _currentWaypointsVariant.clear();
    for ( auto c : _currentWaypoints)
        _currentWaypointsVariant.push_back(QVariant::fromValue(c));

    return true;
}
