#ifndef MISSION_SYSDEFS_H_
#define MISSION_SYSDEFS_H_

#include <fsfw/action/ActionMessage.h>
#include <fsfw/action/HasActionsIF.h>
#include <fsfw/modes/ModeMessage.h>
#include <fsfw/serialize/SerializeIF.h>

#include <atomic>
#include <cstring>

extern std::atomic_uint16_t I2C_FATAL_ERRORS;

namespace satsystem {

enum Mode : Mode_t {
  BOOT = 5,
  // DO NOT CHANGE THE ORDER starting from here, breaks ACS mode checks.
  SAFE = 10,
  PTG_IDLE = 11,
  PTG_NADIR = 12,
  PTG_TARGET = 13,
  PTG_TARGET_GS = 14,
  PTG_INERTIAL = 15,
};
}

namespace xsc {

enum Chip : int { CHIP_0, CHIP_1, NO_CHIP, SELF_CHIP, ALL_CHIP };
enum Copy : int { COPY_0, COPY_1, NO_COPY, SELF_COPY, ALL_COPY };

}  // namespace xsc

namespace core {

extern std::atomic_bool SAVE_PUS_SEQUENCE_COUNT;
extern std::atomic_bool SAVE_CFDP_SEQUENCE_COUNT;

// TODO: Support for status? Or maybe some command to quickly get information whether a unit
// is running.
enum SystemctlCmd : uint8_t { START = 0, STOP = 1, RESTART = 2, NUM_CMDS = 3 };

static constexpr char CONF_FOLDER[] = "conf";

static constexpr char VERSION_FILE_NAME[] = "version.txt";
static constexpr char LEGACY_REBOOT_WATCHDOG_FILE_NAME[] = "reboot.txt";
static constexpr char REBOOT_WATCHDOG_FILE_NAME[] = "reboot_watchdog.txt";
static constexpr char REBOOT_COUNTER_FILE_NAME[] = "reboot_counters.txt";
static constexpr char TIME_FILE_NAME[] = "time_backup.txt";

static constexpr uint32_t SYS_ROM_BASE_ADDR = 0x80000000;

static constexpr ActionId_t ANNOUNCE_VERSION = 1;
static constexpr ActionId_t ANNOUNCE_CURRENT_IMAGE = 2;
static constexpr ActionId_t ANNOUNCE_BOOT_COUNTS = 3;
static constexpr ActionId_t SWITCH_REBOOT_FILE_HANDLING = 5;
static constexpr ActionId_t RESET_REBOOT_COUNTERS = 6;
static constexpr ActionId_t SWITCH_IMG_LOCK = 7;
static constexpr ActionId_t SET_MAX_REBOOT_CNT = 8;
static constexpr ActionId_t READ_REBOOT_MECHANISM_INFO = 9;

static constexpr ActionId_t OBSW_UPDATE_FROM_SD_0 = 10;
static constexpr ActionId_t OBSW_UPDATE_FROM_SD_1 = 11;
static constexpr ActionId_t OBSW_UPDATE_FROM_TMP = 12;

static constexpr ActionId_t SWITCH_TO_SD_0 = 16;
static constexpr ActionId_t SWITCH_TO_SD_1 = 17;
static constexpr ActionId_t SWITCH_TO_BOTH_SD_CARDS = 18;

//! Reboot using the xsc_boot_copy command
static constexpr ActionId_t XSC_REBOOT_OBC = 32;
static constexpr ActionId_t MOUNT_OTHER_COPY = 33;
//! Reboot using the reboot command
static constexpr ActionId_t REBOOT_OBC = 34;

static constexpr ActionId_t EXECUTE_SHELL_CMD_BLOCKING = 40;
static constexpr ActionId_t EXECUTE_SHELL_CMD_NON_BLOCKING = 41;
static constexpr ActionId_t SYSTEMCTL_CMD_EXECUTOR = 42;

static constexpr ActionId_t LIST_DIRECTORY_INTO_FILE = 50;
static constexpr ActionId_t LIST_DIRECTORY_DUMP_DIRECTLY = 51;
static constexpr ActionId_t CP_HELPER = 52;
static constexpr ActionId_t MV_HELPER = 53;
static constexpr ActionId_t RM_HELPER = 54;
static constexpr ActionId_t MKDIR_HELPER = 55;

static constexpr uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::CORE;

static constexpr Event ALLOC_FAILURE = event::makeEvent(SUBSYSTEM_ID, 0, severity::MEDIUM);
//! [EXPORT] : [COMMENT]  Software reboot occurred. Can also be a systemd reboot.
//! P1: Current Chip, P2: Current Copy
static constexpr Event REBOOT_SW = event::makeEvent(SUBSYSTEM_ID, 1, severity::LOW);
//! [EXPORT] : [COMMENT] The reboot mechanism was triggered.
//! P1: First 16 bits: Last Chip, Last 16 bits: Last Copy,
//! P2: Each byte is the respective reboot count for the slots
static constexpr Event REBOOT_MECHANISM_TRIGGERED =
    event::makeEvent(SUBSYSTEM_ID, 2, severity::MEDIUM);
//! Trying to find a way how to determine that the reboot came from ProASIC3 or PCDU..
static constexpr Event REBOOT_HW = event::makeEvent(SUBSYSTEM_ID, 3, severity::MEDIUM);
//! [EXPORT] : [COMMENT] No SD card was active. Core controller will attempt to re-initialize
//! a SD card.
static constexpr Event NO_SD_CARD_ACTIVE = event::makeEvent(SUBSYSTEM_ID, 4, severity::HIGH);
//! [EXPORT] : [COMMENT]
//! P1: Byte 0: Major, Byte 1: Minor, Byte 2: Patch, Byte 3: Has Git Hash
//! P2: First four letters of Git SHA is the last byte of P1 is set.
static constexpr Event VERSION_INFO = event::makeEvent(SUBSYSTEM_ID, 5, severity::INFO);
//! [EXPORT] : [COMMENT] P1: Current Chip, P2: Current Copy
static constexpr Event CURRENT_IMAGE_INFO = event::makeEvent(SUBSYSTEM_ID, 6, severity::INFO);
//! [EXPORT] : [COMMENT] Total reboot counter, which is the sum of the boot count of all
//! individual images.
static constexpr Event REBOOT_COUNTER = event::makeEvent(SUBSYSTEM_ID, 7, severity::INFO);
//! [EXPORT] : [COMMENT] Get the boot count of the individual images.
//! P1: First 16 bits boot count of image 0 0, last 16 bits boot count of image 0 1.
//! P2: First 16 bits boot count of image 1 0, last 16 bits boot count of image 1 1.
static constexpr Event INDIVIDUAL_BOOT_COUNTS = event::makeEvent(SUBSYSTEM_ID, 8, severity::INFO);
//! [EXPORT] : [COMMENT] I2C is unavailable. Trying recovery of I2C bus by power cycling all I2C
//! devices.
static constexpr Event TRYING_I2C_RECOVERY = event::makeEvent(SUBSYSTEM_ID, 10, severity::HIGH);
//! [EXPORT] : [COMMENT] I2C is unavailable. Recovery did not work, performing full reboot.
static constexpr Event I2C_REBOOT = event::makeEvent(SUBSYSTEM_ID, 11, severity::HIGH);
//! [EXPORT] : [COMMENT] PDEC recovery through reset was not possible, performing full reboot.
static constexpr Event PDEC_REBOOT = event::makeEvent(SUBSYSTEM_ID, 12, severity::HIGH);
//! [EXPORT] : [COMMENT] Version information of the firmware (not OBSW).
//! P1: Byte 0: Major, Byte 1: Minor, Byte 2: Patch, Byte 3: Has Git Hash
//! P2: First four letters of Git SHA is the last byte of P1 is set.
static constexpr Event FIRMWARE_INFO = event::makeEvent(SUBSYSTEM_ID, 13, severity::INFO);
//! [EXPORT] : [COMMENT] Active SD card info. SD States: 0: OFF, 1: ON, 2: MOUNTED.
//! P1: Active SD Card Index, 0 if none is active
//! P2: First two bytes: SD state of SD card 0, last two bytes SD state of SD card 1
static constexpr Event ACTIVE_SD_INFO = event::makeEvent(SUBSYSTEM_ID, 14, severity::INFO);

class ListDirectoryCmdBase {
 public:  // TODO: Packet definition for clean deserialization
  // 3 bytes for a and R flag, at least 5 bytes for minimum valid path /tmp with
  // null termination
  static constexpr size_t MIN_DATA_LEN = 8;

  ListDirectoryCmdBase(const uint8_t* data, size_t maxSize) : data(data), maxSize(maxSize) {}
  virtual ~ListDirectoryCmdBase() = default;

  virtual ReturnValue_t parse() {
    if (maxSize < MIN_DATA_LEN) {
      return SerializeIF::STREAM_TOO_SHORT;
    }
    aFlag = data[0];
    rFlag = data[1];
    compressOption = data[2];

    repoNameLen = strnlen(reinterpret_cast<const char*>(data + 3), maxSize - 3);
    // Last byte MUST be null terminated!
    if (repoNameLen >= maxSize - 3) {
      return HasActionsIF::INVALID_PARAMETERS;
    }
    repoName = reinterpret_cast<const char*>(data + 3);
    return returnvalue::OK;
  }

  bool aFlagSet() const { return this->aFlag; }
  bool rFlagSet() const { return this->rFlag; }

  bool compressionOptionSet() const { return this->compressOption; }

  const char* getRepoName(size_t& repoNameLen) const {
    repoNameLen = this->repoNameLen;
    return this->repoName;
  }

  size_t getBaseSize() {
    // Include NULL termination
    if (repoName != nullptr) {
      return 3 + repoNameLen + 1;
    }
    return 0;
  }

 protected:
  const uint8_t* data;
  size_t maxSize;

  bool aFlag = false;
  bool rFlag = false;
  bool compressOption = false;
  const char* repoName = nullptr;
  size_t repoNameLen = 0;
};

class ListDirectoryIntoFile : public ListDirectoryCmdBase {
 public:
  // TODO: Packet definition for clean deserialization
  // 3 bytes for a and R flag, at least 5 bytes for minimum valid path /tmp with
  // null termination, at least 7 bytes for minimum target file name /tmp/a with
  // null termination.
  static constexpr size_t MIN_DATA_LEN = 15;

  ListDirectoryIntoFile(const uint8_t* data, size_t maxSize)
      : ListDirectoryCmdBase(data, maxSize) {}
  ReturnValue_t parse() override {
    if (maxSize < MIN_DATA_LEN) {
      return SerializeIF::STREAM_TOO_SHORT;
    }
    ReturnValue_t result = ListDirectoryCmdBase::parse();
    if (result != returnvalue::OK) {
      return result;
    }

    targetNameLen =
        strnlen(reinterpret_cast<const char*>(data + getBaseSize()), maxSize - getBaseSize());
    if (targetNameLen >= maxSize - getBaseSize()) {
      // Again: String MUST be null terminated.
      return HasActionsIF::INVALID_PARAMETERS;
    }
    targetName = reinterpret_cast<const char*>(data + getBaseSize());
    return result;
  }
  const char* getTargetName(size_t& targetNameLen) const {
    targetNameLen = this->targetNameLen;
    return this->targetName;
  }

 private:
  const char* targetName = nullptr;
  size_t targetNameLen = 0;
};

struct SourceTargetPair {
  const char* sourceName = nullptr;
  size_t sourceNameSize = 0;
  const char* targetName = nullptr;
  size_t targetNameSize = 0;
};

static ReturnValue_t parseDestTargetString(const uint8_t* data, size_t maxLen,
                                           SourceTargetPair& destTgt) {
  if (maxLen < 4) {
    return SerializeIF::STREAM_TOO_SHORT;
  }
  destTgt.sourceNameSize = strnlen(reinterpret_cast<const char*>(data), maxLen);
  if (destTgt.sourceNameSize >= maxLen) {
    return HasActionsIF::INVALID_PARAMETERS;
  }
  destTgt.sourceName = reinterpret_cast<const char*>(data);
  size_t remainingLen = maxLen - destTgt.sourceNameSize - 1;
  if (remainingLen == 0) {
    return HasActionsIF::INVALID_PARAMETERS;
  }
  destTgt.targetNameSize =
      strnlen(reinterpret_cast<const char*>(data + destTgt.sourceNameSize + 1), remainingLen);
  if (destTgt.targetNameSize >= remainingLen) {
    return HasActionsIF::INVALID_PARAMETERS;
  }
  destTgt.targetName = reinterpret_cast<const char*>(data + destTgt.sourceNameSize + 1);
  return returnvalue::OK;
}

class CpHelperParser {
 public:
  CpHelperParser(const uint8_t* data, size_t maxLen) : data(data), maxLen(maxLen) {}

  ReturnValue_t parse() {
    if (maxLen < 2) {
      return SerializeIF::STREAM_TOO_SHORT;
    }
    recursiveOpt = data[0];
    forceOpt = data[1];
    return parseDestTargetString(data + 2, maxLen - 2, destTgt);
  }
  const SourceTargetPair& destTgtPair() const { return destTgt; }
  bool isRecursiveOptSet() const { return recursiveOpt; }
  bool isForceOptSet() const { return forceOpt; }

 private:
  const uint8_t* data;
  size_t maxLen;
  bool recursiveOpt = false;
  bool forceOpt = false;
  SourceTargetPair destTgt;
};

class MvHelperParser {
 public:
  MvHelperParser(const uint8_t* data, size_t maxLen) : data(data), maxLen(maxLen) {}

  ReturnValue_t parse() { return parseDestTargetString(data, maxLen, destTgt); }
  const SourceTargetPair& destTgtPair() const { return destTgt; }

 private:
  const uint8_t* data;
  size_t maxLen;
  SourceTargetPair destTgt;
};

class RmHelperParser {
 public:
  RmHelperParser(const uint8_t* data, size_t maxLen) : data(data), maxLen(maxLen) {}

  ReturnValue_t parse() {
    if (maxLen < 2) {
      return SerializeIF::STREAM_TOO_SHORT;
    }
    recursiveOpt = data[0];
    forceOpt = data[1];
    removeTargetSize = strnlen(reinterpret_cast<const char*>(data + 2), maxLen - 2);
    // Must be null-terminated
    if (removeTargetSize >= maxLen - 2) {
      return HasActionsIF::INVALID_PARAMETERS;
    }
    removeTarget = reinterpret_cast<const char*>(data + 2);
    return returnvalue::OK;
  }
  bool isRecursiveOptSet() const { return recursiveOpt; }
  bool isForceOptSet() const { return forceOpt; }

  const char* getRemoveTarget(size_t& removeTargetSize) {
    removeTargetSize = this->removeTargetSize;
    return removeTarget;
  }

 private:
  const uint8_t* data;
  size_t maxLen;
  bool recursiveOpt = false;
  bool forceOpt = false;
  const char* removeTarget = nullptr;
  size_t removeTargetSize = 0;
};

}  // namespace core

#endif /* MISSION_SYSDEFS_H_ */