#include "StateMachine.h"

#include "QGCLoggingCategory.h"

#include <QDebug>

const QLoggingCategory &WimaPlanerLog();

namespace wima_planer_detail {

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

StateMachine::StateMachine(QObject *parent)
    : QObject(parent), _state(STATE::NEEDS_INIT) {}

STATE StateMachine::state() { return this->_state; }

void StateMachine::updateState(EVENT e) {
  qCDebug(WimaPlanerLog) << "StateMachine::updateState(): event:" << e;
  switch (this->_state) {
  case STATE::NEEDS_INIT:
    switch (e) {
    case EVENT::INIT_DONE:
      setState(STATE::NEEDS_J_AREA_UPDATE);
      break;
    case EVENT::M_AREA_NOT_READY:
    case EVENT::M_AREA_READY:
    case EVENT::M_AREA_PATH_CHANGED:
    case EVENT::S_AREA_PATH_CHANGED:
    case EVENT::CORRIDOR_PATH_CHANGED:
    case EVENT::M_AREA_TILES_CHANGED:
    case EVENT::M_AREA_PROGRESS_CHANGED:
    case EVENT::J_AREA_UPDATED:
    case EVENT::DEPOT_CHANGED:
    case EVENT::SURVEY_DESTROYED:
    case EVENT::MISSION_ITEMS_DESTROYED:
    case EVENT::SURVEY_UPDATE_TRIGGERED:
    case EVENT::SURVEY_UPDATED:
    case EVENT::PATH_CHANGED:
    case EVENT::PATH_UPDATED:
      break;
      qCCritical(WimaPlanerLog)
          << "StateMachine::updateState: Unknown event: " << e;
      Q_ASSERT(false);
    }
    break; // STATE::NEEDS_INIT
  case STATE::WAITING_FOR_TILE_UPDATE:
    switch (e) {
    case EVENT::INIT_DONE:
    case EVENT::M_AREA_NOT_READY:
      break;
    case EVENT::M_AREA_READY:
      setState(STATE::NEEDS_J_AREA_UPDATE);
      break;
    case EVENT::M_AREA_PATH_CHANGED:
    case EVENT::S_AREA_PATH_CHANGED:
    case EVENT::CORRIDOR_PATH_CHANGED:
    case EVENT::M_AREA_TILES_CHANGED:
    case EVENT::M_AREA_PROGRESS_CHANGED:
    case EVENT::J_AREA_UPDATED:
    case EVENT::DEPOT_CHANGED:
    case EVENT::SURVEY_DESTROYED:
    case EVENT::MISSION_ITEMS_DESTROYED:
    case EVENT::SURVEY_UPDATE_TRIGGERED:
    case EVENT::SURVEY_UPDATED:
    case EVENT::PATH_CHANGED:
    case EVENT::PATH_UPDATED:
      break;
      qCCritical(WimaPlanerLog)
          << "StateMachine::updateState: Unknown event: " << e;
      Q_ASSERT(false);
    }
    break; // STATE::NEEDS_INIT
  case STATE::NEEDS_J_AREA_UPDATE:
    switch (e) {
    case EVENT::INIT_DONE:
      break;
    case EVENT::M_AREA_NOT_READY:
      setState(STATE::WAITING_FOR_TILE_UPDATE);
      break;
    case EVENT::M_AREA_READY:
    case EVENT::M_AREA_PATH_CHANGED:
    case EVENT::S_AREA_PATH_CHANGED:
    case EVENT::CORRIDOR_PATH_CHANGED:
    case EVENT::M_AREA_TILES_CHANGED:
    case EVENT::M_AREA_PROGRESS_CHANGED:
      break;
    case EVENT::J_AREA_UPDATED:
      setState(STATE::NEEDS_SURVEY_UPDATE);
    case EVENT::DEPOT_CHANGED:
    case EVENT::SURVEY_DESTROYED:
    case EVENT::MISSION_ITEMS_DESTROYED:
    case EVENT::SURVEY_UPDATE_TRIGGERED:
    case EVENT::SURVEY_UPDATED:
    case EVENT::PATH_CHANGED:
    case EVENT::PATH_UPDATED:
      break;
      qCCritical(WimaPlanerLog)
          << "StateMachine::updateState: Unknown event: " << e;
      Q_ASSERT(false);
    }
    break; // STATE::NEEDS_J_AREA_UPDATE
  case STATE::NEEDS_SURVEY_UPDATE:
    switch (e) {
    case EVENT::INIT_DONE:
    case EVENT::M_AREA_NOT_READY:
      setState(STATE::WAITING_FOR_TILE_UPDATE);
      break;
    case EVENT::M_AREA_READY:
    case EVENT::M_AREA_PATH_CHANGED:
    case EVENT::S_AREA_PATH_CHANGED:
    case EVENT::CORRIDOR_PATH_CHANGED:
      setState(STATE::NEEDS_J_AREA_UPDATE);
      break;
    case EVENT::M_AREA_TILES_CHANGED:
    case EVENT::M_AREA_PROGRESS_CHANGED:
    case EVENT::J_AREA_UPDATED:
    case EVENT::DEPOT_CHANGED:
    case EVENT::SURVEY_DESTROYED:
    case EVENT::MISSION_ITEMS_DESTROYED:
      break;
    case EVENT::SURVEY_UPDATE_TRIGGERED:
      setState(STATE::WAITING_FOR_SURVEY_UPDATE);
      break;
    case EVENT::SURVEY_UPDATED:
    case EVENT::PATH_CHANGED:
    case EVENT::PATH_UPDATED:
      break;
      qCCritical(WimaPlanerLog)
          << "StateMachine::updateState: Unknown event: " << e;
      Q_ASSERT(false);
    }
    break; // STATE::NEEDS_SURVEY_UPDATE
  case STATE::WAITING_FOR_SURVEY_UPDATE:
    switch (e) {
    case EVENT::INIT_DONE:
    case EVENT::M_AREA_NOT_READY:
      setState(STATE::WAITING_FOR_TILE_UPDATE);
      break;
    case EVENT::M_AREA_READY:
    case EVENT::M_AREA_PATH_CHANGED:
    case EVENT::S_AREA_PATH_CHANGED:
    case EVENT::CORRIDOR_PATH_CHANGED:
      setState(STATE::NEEDS_J_AREA_UPDATE);
      break;
    case EVENT::M_AREA_TILES_CHANGED:
    case EVENT::M_AREA_PROGRESS_CHANGED:
    case EVENT::J_AREA_UPDATED:
    case EVENT::DEPOT_CHANGED:
    case EVENT::SURVEY_DESTROYED:
    case EVENT::MISSION_ITEMS_DESTROYED:
      setState(STATE::NEEDS_SURVEY_UPDATE);
      break;
    case EVENT::SURVEY_UPDATE_TRIGGERED:
      break;
    case EVENT::SURVEY_UPDATED:
      setState(STATE::NEEDS_PATH_UPDATE);
    case EVENT::PATH_CHANGED:
    case EVENT::PATH_UPDATED:
      break;
      qCCritical(WimaPlanerLog)
          << "StateMachine::updateState: Unknown event: " << e;
      Q_ASSERT(false);
    }
    break; // STATE::WAYTING_FOR_SURVEY_UPDATE
  case STATE::NEEDS_PATH_UPDATE:
    switch (e) {
    case EVENT::INIT_DONE:
    case EVENT::M_AREA_NOT_READY:
      setState(STATE::WAITING_FOR_TILE_UPDATE);
      break;
    case EVENT::M_AREA_READY:
    case EVENT::M_AREA_PATH_CHANGED:
    case EVENT::S_AREA_PATH_CHANGED:
    case EVENT::CORRIDOR_PATH_CHANGED:
      setState(STATE::NEEDS_J_AREA_UPDATE);
      break;
    case EVENT::M_AREA_TILES_CHANGED:
    case EVENT::M_AREA_PROGRESS_CHANGED:
    case EVENT::J_AREA_UPDATED:
    case EVENT::DEPOT_CHANGED:
    case EVENT::SURVEY_DESTROYED:
    case EVENT::MISSION_ITEMS_DESTROYED:
      setState(STATE::NEEDS_SURVEY_UPDATE);
      break;
    case EVENT::SURVEY_UPDATE_TRIGGERED:
      setState(STATE::WAITING_FOR_SURVEY_UPDATE);
      break;
    case EVENT::SURVEY_UPDATED:
    case EVENT::PATH_CHANGED:
      break;
    case EVENT::PATH_UPDATED:
      setState(STATE::UP_TO_DATE);
      break;
      qCCritical(WimaPlanerLog)
          << "StateMachine::updateState: Unknown event: " << e;
      Q_ASSERT(false);
    }
    break; // STATE::NEEDS_PATH_UPDATE
  case STATE::UP_TO_DATE:
    switch (e) {
    case EVENT::INIT_DONE:
    case EVENT::M_AREA_NOT_READY:
      setState(STATE::WAITING_FOR_TILE_UPDATE);
      break;
    case EVENT::M_AREA_READY:
    case EVENT::M_AREA_PATH_CHANGED:
    case EVENT::S_AREA_PATH_CHANGED:
    case EVENT::CORRIDOR_PATH_CHANGED:
      setState(STATE::NEEDS_J_AREA_UPDATE);
      break;
    case EVENT::M_AREA_TILES_CHANGED:
    case EVENT::M_AREA_PROGRESS_CHANGED:
    case EVENT::J_AREA_UPDATED:
    case EVENT::DEPOT_CHANGED:
    case EVENT::SURVEY_DESTROYED:
    case EVENT::MISSION_ITEMS_DESTROYED:
      setState(STATE::NEEDS_SURVEY_UPDATE);
      break;
    case EVENT::SURVEY_UPDATE_TRIGGERED:
      setState(STATE::WAITING_FOR_SURVEY_UPDATE);
      break;
    case EVENT::SURVEY_UPDATED:
    case EVENT::PATH_CHANGED:
      setState(STATE::NEEDS_PATH_UPDATE);
      break;
    case EVENT::PATH_UPDATED:
      break;
      qCCritical(WimaPlanerLog)
          << "StateMachine::updateState: Unknown event: " << e;
      Q_ASSERT(false);
    }
    break; // STATE::UP_TO_DATE
    qCCritical(WimaPlanerLog)
        << "StateMachine::updateState: Unknown state: " << this->_state;
    Q_ASSERT(false);
  }
}

bool StateMachine::upToDate() { return upToDate(this->_state); }

bool StateMachine::surveyReady() { return surveyReady(this->_state); }

void StateMachine::setState(STATE s) {
  if (this->_state != s) {
    auto oldState = this->_state;
    this->_state = s;
    emit stateChanged();
    if (upToDate(oldState) != upToDate(s)) {
      emit upToDateChanged();
    }
    if (surveyReady(oldState) != surveyReady(s)) {
      emit surveyReadyChanged();
    }
    qCDebug(WimaPlanerLog) << "StateMachine::setState():" << oldState << "->"
                           << s;
  }
}

bool StateMachine::surveyReady(STATE s) {
  // Using a switch to enable compiler checking of used states.
  bool value = false;
  switch (s) {
  case STATE::NEEDS_INIT:
  case STATE::WAITING_FOR_TILE_UPDATE:
  case STATE::NEEDS_J_AREA_UPDATE:
  case STATE::NEEDS_SURVEY_UPDATE:
  case STATE::WAITING_FOR_SURVEY_UPDATE:
    break;
  case STATE::NEEDS_PATH_UPDATE:
  case STATE::UP_TO_DATE:
    value = true;
    break;
  }
  return value;
}

bool StateMachine::upToDate(STATE s) {
  // Using a switch to enable compiler checking of used states.
  bool value = false;
  switch (s) {
  case STATE::NEEDS_INIT:
  case STATE::WAITING_FOR_TILE_UPDATE:
  case STATE::NEEDS_J_AREA_UPDATE:
  case STATE::NEEDS_SURVEY_UPDATE:
  case STATE::WAITING_FOR_SURVEY_UPDATE:
  case STATE::NEEDS_PATH_UPDATE:
    break;
  case STATE::UP_TO_DATE:
    value = true;
    break;
  }
  return value;
}

QDebug &operator<<(QDebug &ds, STATE s) {
  switch (s) {
  case STATE::NEEDS_INIT:
    ds << "NEEDS_INIT";
    break;
  case STATE::WAITING_FOR_TILE_UPDATE:
    ds << "WAITING_FOR_TILE_UPDATE";
    break;
  case STATE::NEEDS_J_AREA_UPDATE:
    ds << "NEEDS_J_AREA_UPDATE";
    break;
  case STATE::NEEDS_SURVEY_UPDATE:
    ds << "NEEDS_SURVEY_UPDATE";
    break;
  case STATE::WAITING_FOR_SURVEY_UPDATE:
    ds << "WAITING_FOR_SURVEY_UPDATE";
    break;
  case STATE::NEEDS_PATH_UPDATE:
    ds << "NEEDS_PATH_UPDATE";
    break;
  case STATE::UP_TO_DATE:
    ds << "UP_TO_DATE";
    break;
  }
  return ds;
}

QDebug &operator<<(QDebug &ds, EVENT s) {
  switch (s) {
  case EVENT::INIT_DONE:
    ds << "INIT_DONE";
    break;
  case EVENT::M_AREA_NOT_READY:
    ds << "M_AREA_NOT_READY";
    break;
  case EVENT::M_AREA_READY:
    ds << "M_AREA_READY";
    break;
  case EVENT::M_AREA_PATH_CHANGED:
    ds << "M_AREA_PATH_CHANGED";
    break;
  case EVENT::S_AREA_PATH_CHANGED:
    ds << "S_AREA_PATH_CHANGED";
    break;
  case EVENT::CORRIDOR_PATH_CHANGED:
    ds << "CORRIDOR_PATH_CHANGED";
    break;
  case EVENT::M_AREA_TILES_CHANGED:
    ds << "M_AREA_TILES_CHANGED";
    break;
  case EVENT::M_AREA_PROGRESS_CHANGED:
    ds << "M_AREA_PROGRESS_CHANGED";
    break;
  case EVENT::J_AREA_UPDATED:
    ds << "J_AREA_UPDATED";
    break;
  case EVENT::DEPOT_CHANGED:
    ds << "DEPOT_CHANGED";
    break;
  case EVENT::SURVEY_DESTROYED:
    ds << "SURVEY_DESTROYED";
    break;
  case EVENT::MISSION_ITEMS_DESTROYED:
    ds << "MISSION_ITEMS_DESTROYED";
    break;
  case EVENT::SURVEY_UPDATE_TRIGGERED:
    ds << "SURVEY_UPDATE_TRIGGERED";
    break;
  case EVENT::SURVEY_UPDATED:
    ds << "SURVEY_UPDATED";
    break;
  case EVENT::PATH_CHANGED:
    ds << "PATH_CHANGED";
    break;
  case EVENT::PATH_UPDATED:
    ds << "PATH_UPDATED";
    break;
  }
  return ds;
}

} // namespace wima_planer_detail
