#include "WimaController.h"

#include "utilities.h"

#include "MissionController.h"
#include "MissionSettingsItem.h"
#include "PlanMasterController.h"
#include "QGCApplication.h"
#include "SettingsManager.h"
#include "SimpleMissionItem.h"

#include "WimaSettings.h"

#include "Snake/QNemoHeartbeat.h"
#include "Snake/QNemoProgress.h"
#include "Snake/SnakeTile.h"

#include "QVector3D"
#include <QScopedPointer>

#define CLIPPER_SCALE 10000
#include "clipper/clipper.hpp"

#include <memory>

template <typename T>
constexpr typename std::underlying_type<T>::type integral(T value) {
  return static_cast<typename std::underlying_type<T>::type>(value);
}

#define SMART_RTL_MAX_ATTEMPTS 3       // times
#define SMART_RTL_ATTEMPT_INTERVAL 200 // ms
#define EVENT_TIMER_INTERVAL 50        // ms

const char *WimaController::areaItemsName = "AreaItems";
const char *WimaController::missionItemsName = "MissionItems";
const char *WimaController::settingsGroup = "WimaController";
const char *WimaController::enableWimaControllerName = "EnableWimaController";
const char *WimaController::overlapWaypointsName = "OverlapWaypoints";
const char *WimaController::maxWaypointsPerPhaseName = "MaxWaypointsPerPhase";
const char *WimaController::startWaypointIndexName = "StartWaypointIndex";
const char *WimaController::showAllMissionItemsName = "ShowAllMissionItems";
const char *WimaController::showCurrentMissionItemsName =
    "ShowCurrentMissionItems";
const char *WimaController::flightSpeedName = "FlightSpeed";
const char *WimaController::arrivalReturnSpeedName = "ArrivalReturnSpeed";
const char *WimaController::altitudeName = "Altitude";

WimaController::StatusMap WimaController::_nemoStatusMap{
    std::make_pair<int, QString>(0, "No Heartbeat"),
    std::make_pair<int, QString>(1, "Connected"),
    std::make_pair<int, QString>(-1, "Timeout"),
    std::make_pair<int, QString>(-2, "Error")};

WimaController::WimaController(QObject *parent)
    : QObject(parent), _joinedArea(), _measurementArea(), _serviceArea(),
      _corridor(), _planDataValid(false),
      _areaInterface(&_measurementArea, &_serviceArea, &_corridor,
                     &_joinedArea),
      _WMSettings(), _defaultWM(_WMSettings, _areaInterface),
      _snakeWM(_WMSettings, _areaInterface),
      _rtlWM(_WMSettings, _areaInterface),
      _currentWM(&_defaultWM), _WMList{&_defaultWM, &_snakeWM, &_rtlWM},
      _metaDataMap(FactMetaData::createMapFromJsonFile(
          QStringLiteral(":/json/WimaController.SettingsGroup.json"), this)),
      _enableWimaController(settingsGroup,
                            _metaDataMap[enableWimaControllerName]),
      _overlapWaypoints(settingsGroup, _metaDataMap[overlapWaypointsName]),
      _maxWaypointsPerPhase(settingsGroup,
                            _metaDataMap[maxWaypointsPerPhaseName]),
      _nextPhaseStartWaypointIndex(settingsGroup,
                                   _metaDataMap[startWaypointIndexName]),
      _showAllMissionItems(settingsGroup,
                           _metaDataMap[showAllMissionItemsName]),
      _showCurrentMissionItems(settingsGroup,
                               _metaDataMap[showCurrentMissionItemsName]),
      _flightSpeed(settingsGroup, _metaDataMap[flightSpeedName]),
      _arrivalReturnSpeed(settingsGroup, _metaDataMap[arrivalReturnSpeedName]),
      _altitude(settingsGroup, _metaDataMap[altitudeName]),
      _lowBatteryHandlingTriggered(false), _measurementPathLength(-1),
      _nemoInterface(this),
      _batteryLevelTicker(EVENT_TIMER_INTERVAL, 1000 /*ms*/) {

  // Set up facts for waypoint manager.
  _showAllMissionItems.setRawValue(true);
  _showCurrentMissionItems.setRawValue(true);
  connect(&_overlapWaypoints, &Fact::rawValueChanged, this,
          &WimaController::_updateOverlap);
  connect(&_maxWaypointsPerPhase, &Fact::rawValueChanged, this,
          &WimaController::_updateMaxWaypoints);
  connect(&_nextPhaseStartWaypointIndex, &Fact::rawValueChanged, this,
          &WimaController::_setStartIndex);
  connect(&_flightSpeed, &Fact::rawValueChanged, this,
          &WimaController::_updateflightSpeed);
  connect(&_arrivalReturnSpeed, &Fact::rawValueChanged, this,
          &WimaController::_updateArrivalReturnSpeed);
  connect(&_altitude, &Fact::rawValueChanged, this,
          &WimaController::_updateAltitude);

  // Nemo stuff.
  connect(&_nemoInterface, &NemoInterface::progressChanged, this,
          &WimaController::_progressChangedHandler);
  connect(&_nemoInterface, &NemoInterface::statusChanged, this,
          &WimaController::nemoStatusChanged);
  connect(&_nemoInterface, &NemoInterface::statusChanged, this,
          &WimaController::nemoStatusStringChanged);

  // Init waypoint managers.
  bool value;
  size_t overlap = _overlapWaypoints.rawValue().toUInt(&value);
  Q_ASSERT(value);
  size_t N = _maxWaypointsPerPhase.rawValue().toUInt(&value);
  Q_ASSERT(value);
  size_t startIdx = _nextPhaseStartWaypointIndex.rawValue().toUInt(&value);
  Q_ASSERT(value);
  (void)value;
  for (auto manager : _WMList) {
    manager->setOverlap(overlap);
    manager->setN(N);
    manager->setStartIndex(startIdx);
  }

  // Periodic.
  connect(&_eventTimer, &QTimer::timeout, this,
          &WimaController::_eventTimerHandler);
  _eventTimer.start(EVENT_TIMER_INTERVAL);

  // NemoInterface.
  connect(&_nemoInterface, &NemoInterface::progressChanged, this,
          &WimaController::_progressChangedHandler);
  connect(&_nemoInterface, &NemoInterface::statusChanged, this,
          &WimaController::nemoStatusChanged);
  connect(&_nemoInterface, &NemoInterface::statusChanged, this,
          &WimaController::nemoStatusStringChanged);

  // Enable/disable snake.
  connect(&_enableSnake, &Fact::rawValueChanged, this,
          &WimaController::_enableSnakeChangedHandler);
  _enableSnakeChangedHandler();
  connect(&_enableWimaController, &Fact::rawValueChanged, [this] {
    if (!this->_enableWimaController.rawValue().toBool()) {
      this->_enableSnake.setCookedValue(QVariant(false));
    }
  });

  // Snake Waypoint Manager.
  connect(&_enableSnake, &Fact::rawValueChanged, this,
          &WimaController::_switchToSnakeWaypointManager);
  _switchToSnakeWaypointManager(_enableSnake.rawValue());

  // Routing
  connect(&_routingThread, &RoutingThread::result, this,
          &WimaController::_storeRoute);
  connect(&_routingThread, &RoutingThread::calculatingChanged, this,
          &WimaController::snakeCalcInProgressChanged);
}

PlanMasterController *WimaController::masterController() {
  return _masterController;
}

MissionController *WimaController::missionController() {
  return _missionController;
}

QmlObjectListModel *WimaController::visualItems() { return &_areas; }

QmlObjectListModel *WimaController::missionItems() {
  return const_cast<QmlObjectListModel *>(&_currentWM->missionItems());
}

QmlObjectListModel *WimaController::currentMissionItems() {
  return const_cast<QmlObjectListModel *>(&_currentWM->currentMissionItems());
}

QVariantList WimaController::waypointPath() {
  return const_cast<QVariantList &>(_currentWM->waypointsVariant());
}

QVariantList WimaController::currentWaypointPath() {
  return const_cast<QVariantList &>(_currentWM->currentWaypointsVariant());
}

Fact *WimaController::enableWimaController() { return &_enableWimaController; }

Fact *WimaController::overlapWaypoints() { return &_overlapWaypoints; }

Fact *WimaController::maxWaypointsPerPhase() { return &_maxWaypointsPerPhase; }

Fact *WimaController::startWaypointIndex() {
  return &_nextPhaseStartWaypointIndex;
}

Fact *WimaController::showAllMissionItems() { return &_showAllMissionItems; }

Fact *WimaController::showCurrentMissionItems() {
  return &_showCurrentMissionItems;
}

Fact *WimaController::flightSpeed() { return &_flightSpeed; }

Fact *WimaController::arrivalReturnSpeed() { return &_arrivalReturnSpeed; }

Fact *WimaController::altitude() { return &_altitude; }

QmlObjectListModel *WimaController::snakeTiles() {
  return this->_measurementArea.tiles();
}

QVariantList WimaController::snakeTileCenterPoints() {
  return this->_measurementArea.tileCenterPoints();
}

QVector<int> WimaController::nemoProgress() {
  return this->_measurementArea.progress();
}

double WimaController::phaseDistance() const {
  qWarning() << "using phaseDistance dummy";
  return 0.0;
}

double WimaController::phaseDuration() const {
  qWarning() << "using phaseDuration dummy";
  return 0.0;
}

int WimaController::nemoStatus() const {
  return integral(_nemoInterface.status());
}

QString WimaController::nemoStatusString() const {
  return _nemoStatusMap.at(nemoStatus());
}

bool WimaController::snakeCalcInProgress() const {
  return _routingThread.calculating();
}

void WimaController::setMasterController(PlanMasterController *masterC) {
  _masterController = masterC;
  _WMSettings.setMasterController(masterC);
  emit masterControllerChanged();
}

void WimaController::setMissionController(MissionController *missionC) {
  _missionController = missionC;
  _WMSettings.setMissionController(missionC);
  emit missionControllerChanged();
}

void WimaController::nextPhase() { _calcNextPhase(); }

void WimaController::previousPhase() {
  if (!_currentWM->previous()) {
    Q_ASSERT(false);
  }

  emit missionItemsChanged();
  emit currentMissionItemsChanged();
  emit currentWaypointPathChanged();
  emit waypointPathChanged();
}

void WimaController::resetPhase() {
  if (!_currentWM->reset()) {
    Q_ASSERT(false);
  }

  emit missionItemsChanged();
  emit currentMissionItemsChanged();
  emit currentWaypointPathChanged();
  emit waypointPathChanged();
}

void WimaController::requestSmartRTL() {
#ifdef DEBUG_SRTL
  qWarning() << "WimaController::requestSmartRTL() called";
#endif
  QString errorString("Smart RTL requested.");
  if (!_SRTLPrecondition(errorString)) {
    qgcApp()->showMessage(errorString);
    return;
  }
  emit smartRTLRequestConfirm();
}

bool WimaController::upload() {
  auto &items = _currentWM->currentMissionItems();
  if (_masterController && _masterController->managerVehicle() &&
      items.count() > 0) {
    if (!_joinedArea.containsCoordinate(
            _masterController->managerVehicle()->coordinate())) {
      emit forceUploadConfirm();
      return false;
    } else {
      return forceUpload();
    }
  } else {
    return false;
  }
}

bool WimaController::forceUpload() {
  auto &currentMissionItems = _currentWM->currentMissionItems();
  if (currentMissionItems.count() < 1 || !_missionController ||
      !_masterController) {
    qWarning() << "WimaController::forceUpload(): error:";
    qWarning() << "currentMissionItems.count(): "
               << currentMissionItems.count();
    qWarning() << "_missionController: " << _missionController;
    qWarning() << "_masterController: " << _masterController;
    return false;
  } else {
    _missionController->removeAll();
    // Set homeposition of settingsItem.
    QmlObjectListModel *visuals = _missionController->visualItems();
    MissionSettingsItem *settingsItem =
        visuals->value<MissionSettingsItem *>(0);
    if (settingsItem == nullptr) {
      Q_ASSERT(false);
      qWarning("WimaController::updateCurrentMissionItems(): nullptr");
      return false;
    } else {
      settingsItem->setCoordinate(_WMSettings.homePosition());

      // Copy mission items to _missionController and send them.
      for (int i = 1; i < currentMissionItems.count(); i++) {
        auto *item = currentMissionItems.value<const SimpleMissionItem *>(i);
        _missionController->insertSimpleMissionItem(*item, visuals->count());
      }
      _masterController->sendToVehicle();
      return true;
    }
  }
}

void WimaController::removeFromVehicle() {
  if (_masterController && _missionController) {
    _masterController->removeAllFromVehicle();
    _missionController->removeAll();
  }
}

void WimaController::executeSmartRTL() {
#ifdef DEBUG_SRTL
  qWarning() << "WimaController::executeSmartRTL() called";
#endif
  if (_masterController && _masterController->managerVehicle()) {
    forceUpload();
    _masterController->managerVehicle()->startMission();
  }
}

void WimaController::initSmartRTL() {
#ifdef DEBUG_SRTL
  qWarning() << "WimaController::initSmartRTL() called";
#endif
  _initSmartRTL();
}

void WimaController::removeVehicleTrajectoryHistory() {
  if (_masterController && _masterController->managerVehicle()) {
    _masterController->managerVehicle()
        ->trajectoryPoints()
        ->clearAndDeleteContents();
  }
}

bool WimaController::_calcShortestPath(const QGeoCoordinate &start,
                                       const QGeoCoordinate &destination,
                                       QVector<QGeoCoordinate> &path) {
  using namespace GeoUtilities;
  using namespace PolygonCalculus;
  QPolygonF polygon2D;
  toCartesianList(_joinedArea.coordinateList(), /*origin*/ start, polygon2D);
  QPointF start2D(0, 0);
  QPointF end2D;
  toCartesian(destination, start, end2D);
  QVector<QPointF> path2D;

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

  return retVal;
}

bool WimaController::setWimaPlanData(QSharedPointer<WimaPlanData> planData) {
  // reset visual items
  _areas.clear();
  _defaultWM.clear();
  _snakeWM.clear();
  _measurementArea = WimaMeasurementAreaData();
  _serviceArea = WimaServiceAreaData();
  _corridor = WimaCorridorData();
  _joinedArea = WimaJoinedAreaData();

  emit visualItemsChanged();
  emit missionItemsChanged();
  emit currentMissionItemsChanged();
  emit waypointPathChanged();
  emit currentWaypointPathChanged();
  emit snakeTilesChanged();
  emit nemoProgressChanged();

  _planDataValid = false;

  // extract list with WimaAreas
  QList<const WimaAreaData *> areaList = planData->areaList();

  int areaCounter = 0;
  const int numAreas = 4; // extract only numAreas Areas, if there are more
                          // they are invalid and ignored
  for (int i = 0; i < areaList.size(); i++) {
    const WimaAreaData *areaData = areaList[i];

    if (areaData->type() ==
        WimaServiceAreaData::typeString) { // is it a service area?
      _serviceArea = *qobject_cast<const WimaServiceAreaData *>(areaData);
      areaCounter++;
      _areas.append(&_serviceArea);

      continue;
    }

    if (areaData->type() ==
        WimaMeasurementAreaData::typeString) { // is it a measurement area?
      _measurementArea =
          *qobject_cast<const WimaMeasurementAreaData *>(areaData);
      areaCounter++;
      _areas.append(&_measurementArea);

      continue;
    }

    if (areaData->type() == WimaCorridorData::typeString) { // is it a corridor?
      _corridor = *qobject_cast<const WimaCorridorData *>(areaData);
      areaCounter++;
      //_visualItems.append(&_corridor); // not needed

      continue;
    }

    if (areaData->type() ==
        WimaJoinedAreaData::typeString) { // is it a corridor?
      _joinedArea = *qobject_cast<const WimaJoinedAreaData *>(areaData);
      areaCounter++;
      _areas.append(&_joinedArea);

      continue;
    }

    if (areaCounter >= numAreas)
      break;
  }

  if (areaCounter != numAreas) {
    Q_ASSERT(false);
    return false;
  }

  emit visualItemsChanged();
  emit snakeTilesChanged();

  // Copy raw transects.
  this->_rawTransects = planData->transects();

  // Copy missionItems.
  auto tempMissionItems = planData->missionItems();
  if (tempMissionItems.size() < 1) {
    qWarning("WimaController: Mission items from WimaPlaner empty!");
    return false;
  }

  for (auto *item : tempMissionItems) {
    _snakeWM.push_back(item->coordinate());
    _defaultWM.push_back(item->coordinate());
  }
  _WMSettings.setHomePosition(QGeoCoordinate(
      _serviceArea.depot().latitude(), _serviceArea.depot().longitude(), 0));
  //  auto tempMissionItems = planData->missionItems();
  if (!_defaultWM.reset()) {
    Q_ASSERT(false);
    return false;
  }
  if (!_snakeWM.reset()) {
    Q_ASSERT(false);
    return false;
  }

  if (_currentWM == &_defaultWM || _currentWM == &_snakeWM) {
    emit missionItemsChanged();
    emit currentMissionItemsChanged();
    emit waypointPathChanged();
    emit currentWaypointPathChanged();
  }

  // Publish tiles, set progress if necessary.
  if (!this->_nemoInterface.hasTileData(this->_measurementArea.tileData())) {
    if (_enableSnake.rawValue().toBool()) {
      this->_nemoInterface.publishTileData(this->_measurementArea.tileData());
    }
    this->_measurementArea.progress() =
        QVector<int>(this->_measurementArea.tiles()->count(), 0);
    emit nemoProgressChanged();

  } else if (this->_enableSnake.rawValue().toBool()) {
    const auto progress = this->_nemoInterface.progress();
    if (progress.size() == this->_measurementArea.tiles()->count()) {
      this->_measurementArea.progress() = std::move(progress);
      emit nemoProgressChanged();
      this->_updateRoute();
    }
  }

  _planDataValid = true;
  return true;
}

WimaController *WimaController::thisPointer() { return this; }

bool WimaController::_calcNextPhase() {
  if (!_currentWM->next()) {
    Q_ASSERT(false);
    return false;
  }

  emit missionItemsChanged();
  emit currentMissionItemsChanged();
  emit currentWaypointPathChanged();
  emit waypointPathChanged();

  return true;
}

bool WimaController::_setStartIndex() {
  bool value;
  _currentWM->setStartIndex(
      _nextPhaseStartWaypointIndex.rawValue().toUInt(&value) - 1);
  Q_ASSERT(value);
  (void)value;

  if (!_currentWM->update()) {
    Q_ASSERT(false);
    return false;
  }

  emit missionItemsChanged();
  emit currentMissionItemsChanged();
  emit currentWaypointPathChanged();
  emit waypointPathChanged();

  return true;
}

void WimaController::_recalcCurrentPhase() {
  if (!_currentWM->update()) {
    Q_ASSERT(false);
  }

  emit missionItemsChanged();
  emit currentMissionItemsChanged();
  emit currentWaypointPathChanged();
  emit waypointPathChanged();
}

void WimaController::_updateOverlap() {
  bool value;
  _currentWM->setOverlap(_overlapWaypoints.rawValue().toUInt(&value));
  Q_ASSERT(value);
  (void)value;

  if (!_currentWM->update()) {
    assert(false);
  }

  emit missionItemsChanged();
  emit currentMissionItemsChanged();
  emit currentWaypointPathChanged();
  emit waypointPathChanged();
}

void WimaController::_updateMaxWaypoints() {
  bool value;
  _currentWM->setN(_maxWaypointsPerPhase.rawValue().toUInt(&value));
  Q_ASSERT(value);
  (void)value;

  if (!_currentWM->update()) {
    Q_ASSERT(false);
  }

  emit missionItemsChanged();
  emit currentMissionItemsChanged();
  emit currentWaypointPathChanged();
  emit waypointPathChanged();
}

void WimaController::_updateflightSpeed() {
  bool value;
  _WMSettings.setFlightSpeed(_flightSpeed.rawValue().toDouble(&value));
  Q_ASSERT(value);
  (void)value;

  if (!_currentWM->update()) {
    Q_ASSERT(false);
  }

  emit missionItemsChanged();
  emit currentMissionItemsChanged();
  emit currentWaypointPathChanged();
  emit waypointPathChanged();
}

void WimaController::_updateArrivalReturnSpeed() {
  bool value;
  _WMSettings.setArrivalReturnSpeed(
      _arrivalReturnSpeed.rawValue().toDouble(&value));
  Q_ASSERT(value);
  (void)value;

  if (!_currentWM->update()) {
    Q_ASSERT(false);
  }

  emit missionItemsChanged();
  emit currentMissionItemsChanged();
  emit currentWaypointPathChanged();
  emit waypointPathChanged();
}

void WimaController::_updateAltitude() {
  bool value;
  _WMSettings.setAltitude(_altitude.rawValue().toDouble(&value));
  Q_ASSERT(value);
  (void)value;

  if (!_currentWM->update()) {
    Q_ASSERT(false);
  }

  emit missionItemsChanged();
  emit currentMissionItemsChanged();
  emit currentWaypointPathChanged();
  emit waypointPathChanged();
}

void WimaController::_checkBatteryLevel() {
  if (_missionController && _masterController &&
      _masterController->managerVehicle()) {
    Vehicle *managerVehicle = masterController()->managerVehicle();
    WimaSettings *wimaSettings =
        qgcApp()->toolbox()->settingsManager()->wimaSettings();
    int threshold = wimaSettings->lowBatteryThreshold()->rawValue().toInt();
    bool enabled = _enableWimaController.rawValue().toBool();
    unsigned int minTime =
        wimaSettings->minimalRemainingMissionTime()->rawValue().toUInt();

    if (enabled) {
      Fact *battery1percentRemaining =
          managerVehicle->battery1FactGroup()->getFact(
              VehicleBatteryFactGroup::_percentRemainingFactName);
      Fact *battery2percentRemaining =
          managerVehicle->battery2FactGroup()->getFact(
              VehicleBatteryFactGroup::_percentRemainingFactName);

      if (battery1percentRemaining->rawValue().toDouble() < threshold &&
          battery2percentRemaining->rawValue().toDouble() < threshold) {
        if (!_lowBatteryHandlingTriggered) {
          _lowBatteryHandlingTriggered = true;
          if (!(_missionController->remainingTime() <= minTime)) {
            requestSmartRTL();
          }
        }
      } else {
        _lowBatteryHandlingTriggered = false;
      }
    }
  }
}

void WimaController::_eventTimerHandler() {
  // Battery level check necessary?
  Fact *enableLowBatteryHandling = qgcApp()
                                       ->toolbox()
                                       ->settingsManager()
                                       ->wimaSettings()
                                       ->enableLowBatteryHandling();
  if (enableLowBatteryHandling->rawValue().toBool() &&
      _batteryLevelTicker.ready())
    _checkBatteryLevel();
}

void WimaController::_smartRTLCleanUp(bool flying) {
  if (!flying && _missionController) { // vehicle has landed
    _switchWaypointManager(_defaultWM);
    _missionController->removeAll();
    disconnect(masterController()->managerVehicle(), &Vehicle::flyingChanged,
               this, &WimaController::_smartRTLCleanUp);
  }
}

void WimaController::_setPhaseDistance(double distance) {
  (void)distance;
  //    if (!qFuzzyCompare(distance, _phaseDistance)) {
  //        _phaseDistance = distance;

  //        emit phaseDistanceChanged();
  //    }
}

void WimaController::_setPhaseDuration(double duration) {
  (void)duration;
  //    if (!qFuzzyCompare(duration, _phaseDuration)) {
  //        _phaseDuration = duration;

  //        emit phaseDurationChanged();
  //    }
}

bool WimaController::_SRTLPrecondition(QString &errorString) {
  if (!_planDataValid) {
    errorString.append(tr("No WiMA data available. Please define at least a "
                          "measurement and a service area."));
    return false;
  }

  return _rtlWM.checkPrecondition(errorString);
}

void WimaController::_switchWaypointManager(
    WaypointManager::ManagerBase &manager) {
  if (_currentWM != &manager) {
    _currentWM = &manager;

    disconnect(&_overlapWaypoints, &Fact::rawValueChanged, this,
               &WimaController::_updateOverlap);
    disconnect(&_maxWaypointsPerPhase, &Fact::rawValueChanged, this,
               &WimaController::_updateMaxWaypoints);
    disconnect(&_nextPhaseStartWaypointIndex, &Fact::rawValueChanged, this,
               &WimaController::_setStartIndex);

    _maxWaypointsPerPhase.setRawValue(_currentWM->N());
    _overlapWaypoints.setRawValue(_currentWM->overlap());
    _nextPhaseStartWaypointIndex.setRawValue(_currentWM->startIndex());

    connect(&_overlapWaypoints, &Fact::rawValueChanged, this,
            &WimaController::_updateOverlap);
    connect(&_maxWaypointsPerPhase, &Fact::rawValueChanged, this,
            &WimaController::_updateMaxWaypoints);
    connect(&_nextPhaseStartWaypointIndex, &Fact::rawValueChanged, this,
            &WimaController::_setStartIndex);

    emit missionItemsChanged();
    emit currentMissionItemsChanged();
    emit waypointPathChanged();
    emit currentWaypointPathChanged();

    qWarning() << "WimaController::_switchWaypointManager: statistics update "
                  "missing.";
  }
}

void WimaController::_initSmartRTL() {
  static int attemptCounter = 0;
  attemptCounter++;
  QString errorString;

  if (_SRTLPrecondition(errorString)) {
    if (_missionController && _masterController &&
        _masterController->managerVehicle()) {
      _masterController->managerVehicle()->pauseVehicle();
      connect(_masterController->managerVehicle(), &Vehicle::flyingChanged,
              this, &WimaController::_smartRTLCleanUp);
      if (_rtlWM.update()) { // Calculate return path.
        _switchWaypointManager(_rtlWM);
        removeFromVehicle();
        attemptCounter = 0;
        emit smartRTLPathConfirm();
      }
    }
  } else if (attemptCounter > SMART_RTL_MAX_ATTEMPTS) {
    errorString.append(
        tr("Smart RTL: No success after maximum number of attempts."));
    qgcApp()->showMessage(errorString);
    attemptCounter = 0;
  } else {
    _smartRTLTimer.singleShot(SMART_RTL_ATTEMPT_INTERVAL, this,
                              &WimaController::_initSmartRTL);
  }
}

void WimaController::_storeRoute(RoutingThread::PtrRoutingData data) {
#ifdef SNAKE_SHOW_TIME
  auto start = std::chrono::high_resolution_clock::now();
#endif
  // Copy waypoints to waypoint manager.
  _snakeWM.clear();

  if (data->route.size() > 0) {
    // Store route.
    const auto &transectsENU = data->transects;
    const auto &transectsInfo = data->transectsInfo;
    const auto &route = data->route;
    // Find index of first waypoint.
    std::size_t idxFirst = 0;
    const auto &infoFirst = transectsInfo.front();
    const auto &firstTransect = transectsENU[infoFirst.index];
    const auto &firstWaypoint =
        infoFirst.reversed ? firstTransect.back() : firstTransect.front();
    double th = 0.001;
    for (std::size_t i = 0; i < route.size(); ++i) {
      auto dist = bg::distance(route[i], firstWaypoint);
      if (dist < th) {
        idxFirst = i;
        break;
      }
    }
    // Find index of last waypoint.
    std::size_t idxLast = route.size() - 1;
    const auto &infoLast = transectsInfo.back();
    const auto &lastTransect = transectsENU[infoLast.index];
    const auto &lastWaypoint =
        infoLast.reversed ? lastTransect.front() : lastTransect.back();
    for (long i = route.size() - 1; i >= 0; --i) {
      auto dist = bg::distance(route[i], lastWaypoint);
      if (dist < th) {
        idxLast = i;
        break;
      }
    }
    // Convert to geo coordinates and append to waypoint manager.
    const auto &ori = this->_origin;
    for (std::size_t i = idxFirst; i <= idxLast; ++i) {
      auto &vertex = route[i];
      QGeoCoordinate c;
      snake::fromENU(ori, vertex, c);
      _snakeWM.push_back(c);
    }
    // Do update.
    this->_snakeWM.update(); // this can take a while (ca. 200ms)

    emit missionItemsChanged();
    emit currentMissionItemsChanged();
    emit currentWaypointPathChanged();
    emit waypointPathChanged();
  }
#ifdef SNAKE_SHOW_TIME
  auto end = std::chrono::high_resolution_clock::now();
  std::cout << "WimaController::_threadFinishedHandler(): waypoints: "
            << std::chrono::duration_cast<std::chrono::milliseconds>(end -
                                                                     start)
                   .count()
            << " ms" << std::endl;
#endif
}

void WimaController::_switchToSnakeWaypointManager(QVariant variant) {
  if (variant.value<bool>()) {
    _switchWaypointManager(_snakeWM);
  } else {
    _switchWaypointManager(_defaultWM);
  }
}

void WimaController::_progressChangedHandler() {
  const auto progress = this->_nemoInterface.progress();
  if (this->_measurementArea.tiles()->count() == progress.size()) {
    this->_measurementArea.progress() = std::move(progress);
    emit nemoProgressChanged();
    this->_updateRoute();
  } else if (_planDataValid) {
    this->_nemoInterface.publishTileData(this->_measurementArea.tileData());
  }
}

void WimaController::_enableSnakeChangedHandler() {
  if (this->_enableSnake.rawValue().toBool()) {
    qDebug() << "WimaController: enabling snake.";
    _switchWaypointManager(_snakeWM);
    this->_nemoInterface.start();
    if (_planDataValid) {
      if (!this->_nemoInterface.hasTileData(
              this->_measurementArea.tileData())) {
        this->_nemoInterface.publishTileData(this->_measurementArea.tileData());
      }
    }
  } else {
    qDebug() << "WimaController: disabling snake.";
    _switchWaypointManager(_defaultWM);
    this->_nemoInterface.stop();
  }
}

void WimaController::_updateRoute() {
  // precondtion
  if (_planDataValid && this->_joinedArea.coordinateList().size() > 0) {
    const auto progress = this->_nemoInterface.progress();
    const auto *tiles = this->_measurementArea.tiles();
    // precondtion
    if (tiles->count() > 0 && progress.size() == tiles->count() &&
        this->_rawTransects.size() > 0 &&
        this->_joinedArea.coordinateList().size() > 0) {
      // Fetch tiles and convert to ENU.
      this->_origin = this->_joinedArea.coordinateList().first();
      this->_origin.setAltitude(0);
      const auto &origin = this->_origin;
      auto pTiles = std::make_shared<std::vector<snake::BoostPolygon>>();
      std::size_t numTiles = 0;
      for (int i = 0; i < progress.size(); ++i) {
        if (progress[i] == 100) {
          ++numTiles;
          const auto *obj = tiles->get(i);
          const auto *tile = qobject_cast<const SnakeTile *>(obj);
          if (tile != nullptr) {
            snake::BoostPolygon t;
            snake::areaToEnu(origin, tile->coordinateList(), t);
            pTiles->push_back(std::move(t));
          } else {
            return;
          }
        }
      }
      if (numTiles > 0) {
        // Fetch safe area and convert to ENU.
        auto safeArea = this->_joinedArea.coordinateList();
        for (auto &v : safeArea) {
          v.setAltitude(0);
        }
        snake::BoostPolygon safeAreaENU;
        snake::areaToEnu(origin, safeArea, safeAreaENU);
        const auto &depot = this->_serviceArea.depot();
        snake::BoostPoint depotENU;
        snake::toENU(origin, depot, depotENU);
        // Fetch geo transects and convert to ENU.
        const auto &geoTransects = this->_rawTransects;
        auto pUnclippedTransects = std::make_shared<snake::Transects>();
        for (auto &geoTransect : geoTransects) {
          snake::BoostLineString t;
          for (auto &geoVertex : geoTransect) {
            snake::BoostPoint v;
            snake::toENU(origin, geoVertex, v);
            t.push_back(v);
          }
          pUnclippedTransects->push_back(t);
        }
        std::size_t minLength = 1; // meter
        qWarning() << "using minLength dummy lkjfdslooij";
        auto generator = [depotENU, pUnclippedTransects, pTiles,
                          minLength](snake::Transects &transects) -> bool {
          // Convert transects to clipper path.
          vector<ClipperLib::Path> transectsClipper;
          for (const auto &t : *pUnclippedTransects) {
            ClipperLib::Path path;
            for (const auto &v : t) {
              ClipperLib::IntPoint c{
                  static_cast<ClipperLib::cInt>(v.get<0>() * CLIPPER_SCALE),
                  static_cast<ClipperLib::cInt>(v.get<1>() * CLIPPER_SCALE)};
              path.push_back(c);
            }
            transectsClipper.push_back(std::move(path));
          }
          // Add transects to clipper.
          ClipperLib::Clipper clipper;
          clipper.AddPaths(transectsClipper, ClipperLib::ptSubject, false);
          // Add tiles to clipper.
          vector<ClipperLib::Path> tilesClipper;
          for (const auto &t : *pTiles) {
            ClipperLib::Path path;
            for (const auto &v : t.outer()) {
              path.push_back(ClipperLib::IntPoint{
                  static_cast<ClipperLib::cInt>(v.get<0>() * CLIPPER_SCALE),
                  static_cast<ClipperLib::cInt>(v.get<1>() * CLIPPER_SCALE)});
            }
            tilesClipper.push_back(std::move(path));
          }

          clipper.AddPaths(tilesClipper, ClipperLib::ptClip, true);
          // Clip transects.
          ClipperLib::PolyTree clippedTransecs;
          clipper.Execute(ClipperLib::ctDifference, clippedTransecs,
                          ClipperLib::pftNonZero, ClipperLib::pftNonZero);
          // Extract transects from  PolyTree and convert them to
          // BoostLineString
          transects.push_back(snake::BoostLineString{depotENU});
          for (const auto &child : clippedTransecs.Childs) {
            snake::BoostLineString transect;
            for (const auto &v : child->Contour) {
              snake::BoostPoint c{static_cast<double>(v.X) / CLIPPER_SCALE,
                                  static_cast<double>(v.Y) / CLIPPER_SCALE};
              transect.push_back(c);
            }

            if (bg::length(transect) >= minLength) {
              transects.push_back(std::move(transect));
            }
          }
          if (transects.size() > 1) {
            return true;
          } else {
            return false;
          }
        }; // generator
        this->_routingThread.route(safeAreaENU, generator);
      } else {
        this->_storeRoute(RoutingThread::PtrRoutingData(new RoutingData()));
      }
    }
  }
}
