#ifndef MISSION_DEVICES_DEVICEDEFINITIONS_PLOCSVPDEFINITIONS_H_
#define MISSION_DEVICES_DEVICEDEFINITIONS_PLOCSVPDEFINITIONS_H_

#include <fsfw/datapoollocal/StaticLocalDataSet.h>
#include <fsfw/devicehandlers/DeviceHandlerIF.h>
#include <fsfw/globalfunctions/CRC.h>
#include <fsfw/serialize/SerializeAdapter.h>
#include <fsfw/timemanager/Clock.h>
#include <fsfw/tmtcpacket/SpacePacket.h>

namespace PLOC_SPV {

/** Command IDs */
static const DeviceCommandId_t NONE = 0;
static const DeviceCommandId_t GET_HK_REPORT = 1;
static const DeviceCommandId_t RESTART_MPSOC = 2;
static const DeviceCommandId_t START_MPSOC = 3;
static const DeviceCommandId_t SHUTDOWN_MPSOC = 4;
static const DeviceCommandId_t SEL_MPSOC_BOOT_IMAGE = 5;
static const DeviceCommandId_t SET_BOOT_TIMEOUT = 6;
static const DeviceCommandId_t SET_MAX_RESTART_TRIES = 7;
static const DeviceCommandId_t RESET_MPSOC = 8;
static const DeviceCommandId_t SET_TIME_REF = 9;
static const DeviceCommandId_t DISABLE_PERIOIC_HK_TRANSMISSION = 10;
static const DeviceCommandId_t GET_BOOT_STATUS_REPORT = 11;
/** Notifies the supervisor that a new update is available for the MPSoC */
static const DeviceCommandId_t UPDATE_AVAILABLE = 12;
static const DeviceCommandId_t WATCHDOGS_ENABLE = 13;
static const DeviceCommandId_t WATCHDOGS_CONFIG_TIMEOUT = 14;
static const DeviceCommandId_t ENABLE_LATCHUP_ALERT = 15;
static const DeviceCommandId_t DISABLE_LATCHUP_ALERT = 16;
static const DeviceCommandId_t AUTO_CALIBRATE_ALERT = 17;
static const DeviceCommandId_t SET_ALERT_LIMIT = 18;
static const DeviceCommandId_t SET_ALERT_IRQ_FILTER = 19;
static const DeviceCommandId_t SET_ADC_SWEEP_PERIOD = 20;
static const DeviceCommandId_t SET_ADC_ENABLED_CHANNELS = 21;
static const DeviceCommandId_t SET_ADC_WINDOW_AND_STRIDE = 22;
static const DeviceCommandId_t SET_ADC_THRESHOLD = 23;
static const DeviceCommandId_t GET_LATCHUP_STATUS_REPORT = 24;
static const DeviceCommandId_t COPY_ADC_DATA_TO_MRAM = 25;
static const DeviceCommandId_t ENABLE_NVMS = 26;
static const DeviceCommandId_t SELECT_NVM = 27;
static const DeviceCommandId_t RUN_AUTO_EM_TESTS = 28;
static const DeviceCommandId_t WIPE_MRAM = 29;
static const DeviceCommandId_t FIRST_MRAM_DUMP = 30;
static const DeviceCommandId_t SET_DBG_VERBOSITY = 31;
static const DeviceCommandId_t CAN_LOOPBACK_TEST = 32;
static const DeviceCommandId_t PRINT_CPU_STATS = 33;
static const DeviceCommandId_t SET_GPIO = 34;
static const DeviceCommandId_t READ_GPIO = 35;
static const DeviceCommandId_t RESTART_SUPERVISOR = 36;
static const DeviceCommandId_t FACTORY_RESET_CLEAR_ALL = 37;
static const DeviceCommandId_t REQUEST_LOGGING_DATA = 38;
static const DeviceCommandId_t UPDATE_IMAGE_DATA = 39;
static const DeviceCommandId_t FACTORY_RESET_CLEAR_MIRROR = 40;
static const DeviceCommandId_t FACTORY_RESET_CLEAR_CIRCULAR = 41;
static const DeviceCommandId_t UPDATE_VERIFY = 42;
static const DeviceCommandId_t CONSECUTIVE_MRAM_DUMP = 43;

/** Reply IDs */
static const DeviceCommandId_t ACK_REPORT = 50;
static const DeviceCommandId_t EXE_REPORT = 51;
static const DeviceCommandId_t HK_REPORT = 52;
static const DeviceCommandId_t BOOT_STATUS_REPORT = 53;
static const DeviceCommandId_t LATCHUP_REPORT = 54;

static const uint16_t SIZE_ACK_REPORT = 14;
static const uint16_t SIZE_EXE_REPORT = 14;
static const uint16_t SIZE_HK_REPORT = 48;
static const uint16_t SIZE_BOOT_STATUS_REPORT = 22;
static const uint16_t SIZE_LATCHUP_STATUS_REPORT = 55;

/**
 * SpacePacket apids of telemetry packets
 */
static const uint16_t APID_ACK_SUCCESS = 0x200;
static const uint16_t APID_ACK_FAILURE = 0x201;
static const uint16_t APID_EXE_SUCCESS = 0x202;
static const uint16_t APID_EXE_FAILURE = 0x203;
static const uint16_t APID_HK_REPORT = 0x204;
static const uint16_t APID_BOOT_STATUS_REPORT = 0x205;
static const uint16_t APID_UPDATE_STATUS_REPORT = 0x206;
static const uint16_t APID_WDG_STATUS_REPORT = 0x207;
static const uint16_t APID_LATCHUP_STATUS_REPORT = 0x208;
static const uint16_t APID_SOC_SYSMON = 0x209;
static const uint16_t APID_MRAM_DUMP_TM = 0x20A;
static const uint16_t APID_SRAM = 0x20B;
static const uint16_t APID_NOR_DATA = 0x20C;
static const uint16_t APID_DATA_LOGGER_DATA = 0x20D;

/**
 * APIDs of telecommand packets
 */
static const uint16_t APID_RESTART_MPSOC = 0xA0;
static const uint16_t APID_START_MPSOC = 0xA1;
static const uint16_t APID_SHUTWOWN_MPSOC = 0xA2;
static const uint16_t APID_SEL_MPSOC_BOOT_IMAGE = 0xA3;
static const uint16_t APID_SET_BOOT_TIMEOUT = 0xA4;
static const uint16_t APID_SET_MAX_RESTART_TRIES = 0xA5;
static const uint16_t APID_RESET_MPSOC = 0xA6;
static const uint16_t APID_GET_BOOT_STATUS_RPT = 0xA8;
static const uint16_t APID_UPDATE_AVAILABLE = 0xB0;
static const uint16_t APID_UPDATE_IMAGE_DATA = 0xB1;
static const uint16_t APID_UPDATE_VERIFY = 0xB2;
static const uint16_t APID_WTD_ENABLE = 0xC0;
static const uint16_t APID_WTD_CONFIG_TIMEOUT = 0xC1;
static const uint16_t APID_SET_TIME_REF = 0xC2;
static const uint16_t APID_DISABLE_HK = 0xC3;
static const uint16_t APID_ENABLE_LATCHUP_ALERT = 0xD0;
static const uint16_t APID_DISABLE_LATCHUP_ALERT = 0xD1;
static const uint16_t APID_AUTO_CALIBRATE_ALERT = 0xD2;
static const uint16_t APID_SET_ALERT_LIMIT = 0xD3;
static const uint16_t APID_SET_ALERT_IRQ_FILTER = 0xD4;
static const uint16_t APID_SET_ADC_SWEEP_PERIOD = 0xD5;
static const uint16_t APID_SET_ADC_ENABLED_CHANNELS = 0xD6;
static const uint16_t APID_SET_ADC_WINDOW_AND_STRIDE = 0xD7;
static const uint16_t APID_SET_ADC_THRESHOLD = 0xD8;
static const uint16_t APID_GET_LATCHUP_STATUS_REPORT = 0xD9;
static const uint16_t APID_COPY_ADC_DATA_TO_MRAM = 0xDA;
static const uint16_t APID_ENABLE_NVMS = 0xF0;
static const uint16_t APID_SELECT_NVM = 0xF1;
static const uint16_t APID_RUN_AUTO_EM_TESTS = 0xF2;
static const uint16_t APID_WIPE_MRAM = 0xF3;
static const uint16_t APID_DUMP_MRAM = 0xF4;
static const uint16_t APID_SET_DBG_VERBOSITY = 0xF5;
static const uint16_t APID_CAN_LOOPBACK_TEST = 0xF6;
static const uint16_t APID_PRINT_CPU_STATS = 0xF8;
static const uint16_t APID_SET_GPIO = 0xF9;
static const uint16_t APID_READ_GPIO = 0xFA;
static const uint16_t APID_RESTART_SUPERVISOR = 0xFB;
static const uint16_t APID_FACTORY_RESET = 0xFC;
static const uint16_t APID_REQUEST_LOGGING_DATA = 0xFD;

static const uint16_t APID_GET_HK_REPORT = 0xC6;

static const uint16_t APID_MASK = 0x3FF;
static const uint16_t SEQUENCE_COUNT_MASK = 0xFFF;

/** Offset from first byte in Space packet to first byte of data field */
static const uint8_t DATA_FIELD_OFFSET = 6;

/**
 * Space packet length for fixed size packets. This is the size of the whole packet data
 * field. For the length field in the space packet this size will be substracted by one.
 */
static const uint16_t LENGTH_EMPTY_TC = 2;  // Only CRC will be transported with the data field

/** This is the maximum length of a space packet as defined by the TAS ICD */
static const size_t MAX_COMMAND_SIZE = 1024;
static const size_t MAX_DATA_CAPACITY = 1016;
/** This is the maximum size of a space packet for the supervisor */
static const size_t MAX_PACKET_SIZE = 1024;

static const uint8_t SPACE_PACKET_HEADER_LENGTH = 6;

enum class SequenceFlags : uint8_t {
  CONTINUED_PKT = 0b00,
  FIRST_PKT = 0b01,
  LAST_PKT = 0b10,
  STANDALONE_PKT = 0b11
};

enum PoolIds : lp_id_t {
  NUM_TMS,
  TEMP_PS,
  TEMP_PL,
  SOC_STATE,
  NVM0_1_STATE,
  NVM3_STATE,
  MISSION_IO_STATE,
  FMC_STATE,
  NUM_TCS,
  TEMP_SUP,
  UPTIME,
  CPULOAD,
  AVAILABLEHEAP,
  BOOT_SIGNAL,
  RESET_COUNTER,
  BOOT_AFTER_MS,
  BOOT_TIMEOUT_MS,
  ACTIVE_NVM,
  BP0_STATE,
  BP1_STATE,
  BP2_STATE,

  LATCHUP_ID,
  CNT0,
  CNT1,
  CNT2,
  CNT3,
  CNT4,
  CNT5,
  CNT6,
  LATCHUP_RPT_TIME_SEC,
  LATCHUP_RPT_TIME_MIN,
  LATCHUP_RPT_TIME_HOUR,
  LATCHUP_RPT_TIME_DAY,
  LATCHUP_RPT_TIME_MON,
  LATCHUP_RPT_TIME_YEAR,
  LATCHUP_RPT_TIME_MSEC,
  LATCHUP_RPT_TIME_USEC,
  LATCHUP_RPT_TIME_IS_SET,
};

static const uint8_t HK_SET_ENTRIES = 13;
static const uint8_t BOOT_REPORT_SET_ENTRIES = 8;
static const uint8_t LATCHUP_RPT_SET_ENTRIES = 16;

static const uint32_t HK_SET_ID = HK_REPORT;
static const uint32_t BOOT_REPORT_SET_ID = BOOT_STATUS_REPORT;
static const uint32_t LATCHUP_RPT_ID = LATCHUP_REPORT;

/**
 * @brief   With this class a space packet can be created which does not contain any data.
 */
class EmptyPacket : public SpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param apid  The APID to set in the space packet.
   *
   * @note Sequence count of empty packet is always 1.
   */
  EmptyPacket(uint16_t apid) : SpacePacket(LENGTH_EMPTY_TC - 1, true, apid, 1) { calcCrc(); }

 private:
  /**
   * @brief   CRC calculation which involves only the header in an empty packet
   */
  void calcCrc() {
    /* Calculate crc */
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream, sizeof(CCSDSPrimaryHeader));

    /* Add crc to packet data field of space packet */
    size_t serializedSize = 0;
    uint8_t* crcPos = this->localData.fields.buffer;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPos, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   This class can be used to generate the space packet selecting the boot image of
 *          of the MPSoC.
 */
class MPSoCBootSelect : public SpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param mem   The memory to boot from: NVM0 (0), NVM1 (1)
   * @param bp0   Partition pin 0
   * @param bp1   Partition pin 1
   * @param bp2   Partition pin 2
   */
  MPSoCBootSelect(uint8_t mem, uint8_t bp0, uint8_t bp1, uint8_t bp2)
      : SpacePacket(DATA_FIELD_LENGTH - 1, true, APID_SEL_MPSOC_BOOT_IMAGE, DEFAULT_SEQUENCE_COUNT),
        mem(mem),
        bp0(bp0),
        bp1(bp1),
        bp2(bp2) {
    initPacket();
  }

 private:
  static const uint16_t DATA_FIELD_LENGTH = 6;
  static const uint16_t DEFAULT_SEQUENCE_COUNT = 1;

  static const uint8_t MEM_OFFSET = 0;
  static const uint8_t BP0_OFFSET = 1;
  static const uint8_t BP1_OFFSET = 2;
  static const uint8_t BP2_OFFSET = 3;
  static const uint16_t CRC_OFFSET = DATA_FIELD_LENGTH - 2;

  uint8_t mem = 0;
  uint8_t bp0 = 0;
  uint8_t bp1 = 0;
  uint8_t bp2 = 0;

  void initPacket() {
    uint8_t* data_field_start = this->localData.fields.buffer;
    std::memcpy(data_field_start + MEM_OFFSET, &mem, sizeof(mem));
    std::memcpy(data_field_start + BP0_OFFSET, &bp0, sizeof(bp0));
    std::memcpy(data_field_start + BP1_OFFSET, &bp1, sizeof(bp1));
    std::memcpy(data_field_start + BP2_OFFSET, &bp2, sizeof(bp2));
    /* Calculate crc */
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);

    /* Add crc to packet data field of space packet */
    size_t serializedSize = 0;
    uint8_t* crcPos = this->localData.fields.buffer + CRC_OFFSET;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPos, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   This class generates the space packet to update the time of the PLOC supervisor.
 */
class SetTimeRef : public SpacePacket {
 public:
  SetTimeRef(Clock::TimeOfDay_t* time)
      : SpacePacket(DATA_FIELD_LENGTH - 1, true, APID_SET_TIME_REF, DEFAULT_SEQUENCE_COUNT) {
    initPacket(time);
  }

 private:
  static const uint16_t DATA_FIELD_LENGTH = 34;
  static const uint16_t DEFAULT_SEQUENCE_COUNT = 1;
  static const uint16_t CRC_OFFSET = DATA_FIELD_LENGTH - 2;

  void initPacket(Clock::TimeOfDay_t* time) {
    size_t serializedSize = 0;
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    SerializeAdapter::serialize<uint32_t>(&time->second, &data_field_ptr, &serializedSize,
                                          sizeof(time->second), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint32_t>(&time->minute, &data_field_ptr, &serializedSize,
                                          sizeof(time->minute), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint32_t>(&time->hour, &data_field_ptr, &serializedSize,
                                          sizeof(time->hour), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint32_t>(&time->day, &data_field_ptr, &serializedSize,
                                          sizeof(time->day), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint32_t>(&time->month, &data_field_ptr, &serializedSize,
                                          sizeof(time->month), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint32_t>(&time->year, &data_field_ptr, &serializedSize,
                                          sizeof(time->year), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    uint32_t milliseconds = time->usecond / 1000;
    SerializeAdapter::serialize<uint32_t>(&milliseconds, &data_field_ptr, &serializedSize,
                                          sizeof(milliseconds), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    uint32_t isSet = 0xFFFFFFFF;
    SerializeAdapter::serialize<uint32_t>(&isSet, &data_field_ptr, &serializedSize, sizeof(isSet),
                                          SerializeIF::Endianness::BIG);
    serializedSize = 0;
    /* Calculate crc */
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);
    SerializeAdapter::serialize<uint16_t>(&crc, &data_field_ptr, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   This class can be used to generate the set boot timout command.
 */
class SetBootTimeout : public SpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param timeout  The boot timeout in milliseconds.
   */
  SetBootTimeout(uint32_t timeout)
      : SpacePacket(DATA_FIELD_LENGTH - 1, true, APID_SET_BOOT_TIMEOUT, 1), timeout(timeout) {
    initPacket();
  }

 private:
  uint32_t timeout = 0;

  /** boot timeout value (uint32_t) and crc (uint16_t) */
  static const uint16_t DATA_FIELD_LENGTH = 6;

  void initPacket() {
    size_t serializedSize = 0;
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    SerializeAdapter::serialize<uint32_t>(&timeout, &data_field_ptr, &serializedSize,
                                          sizeof(timeout), SerializeIF::Endianness::BIG);
    /* Calculate crc */
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);
    /* Add crc to packet data field of space packet */
    serializedSize = 0;
    SerializeAdapter::serialize<uint16_t>(&crc, &data_field_ptr, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   This class can be used to generate the space packet to set the maximum boot tries.
 */
class SetRestartTries : public SpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param restartTries  Maximum restart tries to set.
   */
  SetRestartTries(uint8_t restartTries)
      : SpacePacket(DATA_FIELD_LENGTH - 1, true, APID_SET_MAX_RESTART_TRIES, 1),
        restartTries(restartTries) {
    initPacket();
  }

 private:
  uint8_t restartTries = 0;

  /** Restart tries value (uint8_t) and crc (uint16_t) */
  static const uint16_t DATA_FIELD_LENGTH = 3;

  void initPacket() {
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    *data_field_ptr = restartTries;
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);
    size_t serializedSize = 0;
    uint8_t* crcPtr = data_field_ptr + 1;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPtr, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   This class packages the command to notify the supervisor that a new update for the
 *          MPSoC is available.
 */
class UpdateAvailable : public SpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param imageSelect
   * @param imagePartition
   * @param imageSize
   * @param imageCrc
   * @param numberOfPackets
   */
  UpdateAvailable(uint8_t imageSelect, uint8_t imagePartition, uint32_t imageSize,
                  uint32_t imageCrc, uint32_t numberOfPackets)
      : SpacePacket(DATA_FIELD_LENGTH - 1, true, APID_UPDATE_AVAILABLE, DEFAULT_SEQUENCE_COUNT),
        imageSelect(imageSelect),
        imagePartition(imagePartition),
        imageSize(imageSize),
        imageCrc(imageCrc),
        numberOfPackets(numberOfPackets) {
    initPacket();
  }

 private:
  static const uint16_t DATA_FIELD_LENGTH = 16;
  static const uint16_t DEFAULT_SEQUENCE_COUNT = 1;

  static const uint16_t CRC_OFFSET = DATA_FIELD_LENGTH - 2;

  uint8_t imageSelect = 0;
  uint8_t imagePartition = 0;
  uint32_t imageSize = 0;
  uint32_t imageCrc = 0;
  uint32_t numberOfPackets = 0;

  void initPacket() {
    size_t serializedSize = 0;
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    SerializeAdapter::serialize<uint8_t>(&imageSelect, &data_field_ptr, &serializedSize,
                                         sizeof(imageSelect), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint8_t>(&imagePartition, &data_field_ptr, &serializedSize,
                                         sizeof(imagePartition), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint32_t>(&imageSize, &data_field_ptr, &serializedSize,
                                          sizeof(imageSize), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint32_t>(&imageCrc, &data_field_ptr, &serializedSize,
                                          sizeof(imageCrc), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint32_t>(&numberOfPackets, &data_field_ptr, &serializedSize,
                                          sizeof(numberOfPackets), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);
    uint8_t* crcPos = this->localData.fields.buffer + CRC_OFFSET;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPos, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   With this class the space packet can be generated to disable to periodic transmission
 *          of housekeeping data. Normally, this will be disabled by default. However, adding this
 *          command can be useful for debugging.
 */
class DisablePeriodicHkTransmission : public SpacePacket {
 public:
  /**
   * @brief   Constructor
   */
  DisablePeriodicHkTransmission() : SpacePacket(DATA_FIELD_LENGTH - 1, true, APID_DISABLE_HK, 1) {
    initPacket();
  }

 private:
  uint8_t disableHk = 0;

  /** Restart tries value (uint8_t) and crc (uint16_t) */
  static const uint16_t DATA_FIELD_LENGTH = 3;

  void initPacket() {
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    *data_field_ptr = disableHk;
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);
    size_t serializedSize = 0;
    uint8_t* crcPtr = data_field_ptr + 1;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPtr, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   This class packages the command to enable the watchdogs of the PLOC.
 */
class WatchdogsEnable : public SpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param watchdogPs    Enables processing system watchdog
   * @param watchdogPl    Enables programmable logic wathdog
   * @param watchdogInt
   */
  WatchdogsEnable(uint8_t watchdogPs, uint8_t watchdogPl, uint8_t watchdogInt)
      : SpacePacket(DATA_FIELD_LENGTH - 1, true, APID_WTD_ENABLE, DEFAULT_SEQUENCE_COUNT),
        watchdogPs(watchdogPs),
        watchdogPl(watchdogPl),
        watchdogInt(watchdogInt) {
    initPacket();
  }

 private:
  static const uint16_t DATA_FIELD_LENGTH = 5;
  static const uint16_t DEFAULT_SEQUENCE_COUNT = 1;

  static const uint16_t CRC_OFFSET = DATA_FIELD_LENGTH - 2;

  uint8_t watchdogPs = 0;
  uint8_t watchdogPl = 0;
  uint8_t watchdogInt = 0;

  void initPacket() {
    size_t serializedSize = 0;
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    SerializeAdapter::serialize<uint8_t>(&watchdogPs, &data_field_ptr, &serializedSize,
                                         sizeof(watchdogPs), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint8_t>(&watchdogPl, &data_field_ptr, &serializedSize,
                                         sizeof(watchdogPl), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint8_t>(&watchdogInt, &data_field_ptr, &serializedSize,
                                         sizeof(watchdogInt), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);
    uint8_t* crcPos = this->localData.fields.buffer + CRC_OFFSET;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPos, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   This class packages the command to set the timeout of one of the three watchdogs (PS,
 *          PL, INT)
 */
class WatchdogsConfigTimeout : public SpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param watchdogPs    Selects the watchdog to configure (0 - PS, 1 - PL, 2 - INT)
   * @param timeout       The timeout to set
   */
  WatchdogsConfigTimeout(uint8_t watchdog, uint32_t timeout)
      : SpacePacket(DATA_FIELD_LENGTH - 1, true, APID_WTD_CONFIG_TIMEOUT, DEFAULT_SEQUENCE_COUNT),
        watchdog(watchdog),
        timeout(timeout) {
    initPacket();
  }

 private:
  static const uint16_t DATA_FIELD_LENGTH = 7;
  static const uint16_t DEFAULT_SEQUENCE_COUNT = 1;

  static const uint16_t CRC_OFFSET = DATA_FIELD_LENGTH - 2;

  uint8_t watchdog = 0;
  uint32_t timeout = 0;

  void initPacket() {
    size_t serializedSize = 0;
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    SerializeAdapter::serialize<uint8_t>(&watchdog, &data_field_ptr, &serializedSize,
                                         sizeof(watchdog), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint32_t>(&timeout, &data_field_ptr, &serializedSize,
                                          sizeof(timeout), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);
    uint8_t* crcPos = this->localData.fields.buffer + CRC_OFFSET;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPos, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   This class packages the command to enable of disable the latchup alert.
 *
 * @details There are 7 different latchup alerts.
 */
class LatchupAlert : public SpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param state    true - enable, false - disable
   * @param latchupId Identifies the latchup to enable/disable (0 - 0.85V, 1 - 1.8V, 2 - MISC,
   *        3 - 3.3V, 4 - NVM_4XO, 5 - MISSION, 6 - SAFECOTS)
   */
  LatchupAlert(bool state, uint8_t latchupId)
      : SpacePacket(DATA_FIELD_LENGTH - 1, true), latchupId(latchupId) {
    if (state) {
      this->setAPID(APID_ENABLE_LATCHUP_ALERT);
    } else {
      this->setAPID(APID_DISABLE_LATCHUP_ALERT);
    }
    this->setPacketSequenceCount(DEFAULT_SEQUENCE_COUNT);
    initPacket();
  }

 private:
  static const uint16_t DATA_FIELD_LENGTH = 3;
  static const uint16_t DEFAULT_SEQUENCE_COUNT = 1;

  static const uint16_t CRC_OFFSET = DATA_FIELD_LENGTH - 2;

  uint8_t latchupId = 0;

  void initPacket() {
    size_t serializedSize = 0;
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    SerializeAdapter::serialize<uint8_t>(&latchupId, &data_field_ptr, &serializedSize,
                                         sizeof(latchupId), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);
    uint8_t* crcPos = this->localData.fields.buffer + CRC_OFFSET;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPos, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   This class packages the command to calibrate a certain latchup alert.
 */
class AutoCalibrateAlert : public SpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param latchupId Identifies the latchup alert to calibrate (0 - 0.85V, 1 - 1.8V, 2 - MISC,
   *        3 - 3.3V, 4 - NVM_4XO, 5 - MISSION, 6 - SAFECOTS)
   * @param mg
   */
  AutoCalibrateAlert(uint8_t latchupId, uint32_t mg)
      : SpacePacket(DATA_FIELD_LENGTH - 1, true, APID_AUTO_CALIBRATE_ALERT, DEFAULT_SEQUENCE_COUNT),
        latchupId(latchupId),
        mg(mg) {
    initPacket();
  }

 private:
  static const uint16_t DATA_FIELD_LENGTH = 7;
  static const uint16_t DEFAULT_SEQUENCE_COUNT = 1;

  static const uint16_t CRC_OFFSET = DATA_FIELD_LENGTH - 2;

  uint8_t latchupId = 0;
  uint32_t mg = 0;

  void initPacket() {
    size_t serializedSize = 0;
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    SerializeAdapter::serialize<uint8_t>(&latchupId, &data_field_ptr, &serializedSize,
                                         sizeof(latchupId), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint32_t>(&mg, &data_field_ptr, &serializedSize, sizeof(mg),
                                          SerializeIF::Endianness::BIG);
    serializedSize = 0;
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);
    uint8_t* crcPos = this->localData.fields.buffer + CRC_OFFSET;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPos, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

class SetAlertlimit : public SpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param latchupId Identifies the latchup alert to calibrate (0 - 0.85V, 1 - 1.8V, 2 - MISC,
   *        3 - 3.3V, 4 - NVM_4XO, 5 - MISSION, 6 - SAFECOTS)
   * @param dutycycle
   */
  SetAlertlimit(uint8_t latchupId, uint32_t dutycycle)
      : SpacePacket(DATA_FIELD_LENGTH - 1, true, APID_SET_ALERT_LIMIT, DEFAULT_SEQUENCE_COUNT),
        latchupId(latchupId),
        dutycycle(dutycycle) {
    initPacket();
  }

 private:
  static const uint16_t DATA_FIELD_LENGTH = 7;
  static const uint16_t DEFAULT_SEQUENCE_COUNT = 1;

  static const uint16_t CRC_OFFSET = DATA_FIELD_LENGTH - 2;

  uint8_t latchupId = 0;
  uint32_t dutycycle = 0;

  void initPacket() {
    size_t serializedSize = 0;
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    SerializeAdapter::serialize<uint8_t>(&latchupId, &data_field_ptr, &serializedSize,
                                         sizeof(latchupId), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint32_t>(&dutycycle, &data_field_ptr, &serializedSize,
                                          sizeof(dutycycle), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);
    uint8_t* crcPos = this->localData.fields.buffer + CRC_OFFSET;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPos, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

class SetAlertIrqFilter : public SpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param latchupId Identifies the latchup alert to calibrate (0 - 0.85V, 1 - 1.8V, 2 - MISC,
   *        3 - 3.3V, 4 - NVM_4XO, 5 - MISSION, 6 - SAFECOTS)
   * @param tp
   * @param div
   */
  SetAlertIrqFilter(uint8_t latchupId, uint8_t tp, uint8_t div)
      : SpacePacket(DATA_FIELD_LENGTH - 1, true, APID_SET_ALERT_IRQ_FILTER, DEFAULT_SEQUENCE_COUNT),
        tp(tp),
        div(div) {
    initPacket();
  }

 private:
  static const uint16_t DATA_FIELD_LENGTH = 5;
  static const uint16_t DEFAULT_SEQUENCE_COUNT = 1;

  static const uint16_t CRC_OFFSET = DATA_FIELD_LENGTH - 2;

  uint8_t latchupId = 0;
  uint8_t tp = 0;
  uint8_t div = 0;

  void initPacket() {
    size_t serializedSize = 0;
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    SerializeAdapter::serialize<uint8_t>(&latchupId, &data_field_ptr, &serializedSize,
                                         sizeof(latchupId), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint8_t>(&tp, &data_field_ptr, &serializedSize, sizeof(tp),
                                         SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint8_t>(&div, &data_field_ptr, &serializedSize, sizeof(div),
                                         SerializeIF::Endianness::BIG);
    serializedSize = 0;
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);
    uint8_t* crcPos = this->localData.fields.buffer + CRC_OFFSET;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPos, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   This class packages the space packet to set the sweep period of the ADC.
 */
class SetAdcSweepPeriod : public SpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param sweepPeriod   Sweep period in us. minimum is 21 us
   */
  SetAdcSweepPeriod(uint32_t sweepPeriod)
      : SpacePacket(DATA_FIELD_LENGTH - 1, true, APID_SET_ADC_SWEEP_PERIOD, DEFAULT_SEQUENCE_COUNT),
        sweepPeriod(sweepPeriod) {
    initPacket();
  }

 private:
  static const uint16_t DATA_FIELD_LENGTH = 6;
  static const uint16_t DEFAULT_SEQUENCE_COUNT = 1;

  static const uint16_t CRC_OFFSET = DATA_FIELD_LENGTH - 2;

  uint32_t sweepPeriod = 0;

  void initPacket() {
    size_t serializedSize = 0;
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    SerializeAdapter::serialize<uint32_t>(&sweepPeriod, &data_field_ptr, &serializedSize,
                                          sizeof(sweepPeriod), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);
    uint8_t* crcPos = this->localData.fields.buffer + CRC_OFFSET;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPos, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   This class packages the space packet to enable or disable ADC channels.
 */
class SetAdcEnabledChannels : public SpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param ch    Defines channels to be enabled or disabled.
   */
  SetAdcEnabledChannels(uint16_t ch)
      : SpacePacket(DATA_FIELD_LENGTH - 1, true, APID_SET_ADC_ENABLED_CHANNELS,
                    DEFAULT_SEQUENCE_COUNT),
        ch(ch) {
    initPacket();
  }

 private:
  static const uint16_t DATA_FIELD_LENGTH = 4;
  static const uint16_t DEFAULT_SEQUENCE_COUNT = 1;

  static const uint16_t CRC_OFFSET = DATA_FIELD_LENGTH - 2;

  uint16_t ch = 0;

  void initPacket() {
    size_t serializedSize = 0;
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    SerializeAdapter::serialize<uint16_t>(&ch, &data_field_ptr, &serializedSize, sizeof(ch),
                                          SerializeIF::Endianness::BIG);
    serializedSize = 0;
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);
    uint8_t* crcPos = this->localData.fields.buffer + CRC_OFFSET;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPos, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   This class packages the space packet to configures the window size and striding step of
 *          the moving average filter applied to the ADC readings.
 */
class SetAdcWindowAndStride : public SpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param windowSize
   * @param stridingStepSize
   */
  SetAdcWindowAndStride(uint16_t windowSize, uint16_t stridingStepSize)
      : SpacePacket(DATA_FIELD_LENGTH - 1, true, APID_SET_ADC_WINDOW_AND_STRIDE,
                    DEFAULT_SEQUENCE_COUNT),
        windowSize(windowSize),
        stridingStepSize(stridingStepSize) {
    initPacket();
  }

 private:
  static const uint16_t DATA_FIELD_LENGTH = 6;
  static const uint16_t DEFAULT_SEQUENCE_COUNT = 1;

  static const uint16_t CRC_OFFSET = DATA_FIELD_LENGTH - 2;

  uint16_t windowSize = 0;
  uint16_t stridingStepSize = 0;

  void initPacket() {
    size_t serializedSize = 0;
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    SerializeAdapter::serialize<uint16_t>(&windowSize, &data_field_ptr, &serializedSize,
                                          sizeof(windowSize), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint16_t>(&stridingStepSize, &data_field_ptr, &serializedSize,
                                          sizeof(stridingStepSize), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);
    uint8_t* crcPos = this->localData.fields.buffer + CRC_OFFSET;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPos, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   This class packages the space packet to set the ADC trigger threshold.
 */
class SetAdcThreshold : public SpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param threshold
   */
  SetAdcThreshold(uint32_t threshold)
      : SpacePacket(DATA_FIELD_LENGTH - 1, true, APID_SET_ADC_THRESHOLD, DEFAULT_SEQUENCE_COUNT),
        threshold(threshold) {
    initPacket();
  }

 private:
  static const uint16_t DATA_FIELD_LENGTH = 6;
  static const uint16_t DEFAULT_SEQUENCE_COUNT = 1;

  static const uint16_t CRC_OFFSET = DATA_FIELD_LENGTH - 2;

  uint32_t threshold = 0;

  void initPacket() {
    size_t serializedSize = 0;
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    SerializeAdapter::serialize<uint32_t>(&threshold, &data_field_ptr, &serializedSize,
                                          sizeof(threshold), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);
    uint8_t* crcPos = this->localData.fields.buffer + CRC_OFFSET;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPos, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   This class packages the space packet to select between NVM 0 and NVM 1.
 */
class SelectNvm : public SpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param mem   0 - select NVM0, 1 - select NVM1.
   */
  SelectNvm(uint8_t mem)
      : SpacePacket(DATA_FIELD_LENGTH - 1, true, APID_SELECT_NVM, DEFAULT_SEQUENCE_COUNT),
        mem(mem) {
    initPacket();
  }

 private:
  static const uint16_t DATA_FIELD_LENGTH = 3;
  static const uint16_t DEFAULT_SEQUENCE_COUNT = 1;

  static const uint16_t CRC_OFFSET = DATA_FIELD_LENGTH - 2;

  uint8_t mem = 0;

  void initPacket() {
    size_t serializedSize = 0;
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    SerializeAdapter::serialize<uint8_t>(&mem, &data_field_ptr, &serializedSize, sizeof(mem),
                                         SerializeIF::Endianness::BIG);
    serializedSize = 0;
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);
    uint8_t* crcPos = this->localData.fields.buffer + CRC_OFFSET;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPos, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   This class packages the space packet to power the NVMs on or off.
 */
class EnableNvms : public SpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param n01   Set to one to power NVM0 and NVM1 on. 0 powers off memory.
   * @param n3    Set to one to power NVM3 on. 0 powers off the memory.
   */
  EnableNvms(uint8_t n01, uint8_t n3)
      : SpacePacket(DATA_FIELD_LENGTH - 1, true, APID_ENABLE_NVMS, DEFAULT_SEQUENCE_COUNT),
        n01(n01),
        n3(n3) {
    initPacket();
  }

 private:
  static const uint16_t DATA_FIELD_LENGTH = 4;
  static const uint16_t DEFAULT_SEQUENCE_COUNT = 1;

  static const uint16_t CRC_OFFSET = DATA_FIELD_LENGTH - 2;

  uint8_t n01 = 0;
  uint8_t n3 = 0;

  void initPacket() {
    size_t serializedSize = 0;
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    SerializeAdapter::serialize<uint8_t>(&n01, &data_field_ptr, &serializedSize, sizeof(n01),
                                         SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint8_t>(&n3, &data_field_ptr, &serializedSize, sizeof(n3),
                                         SerializeIF::Endianness::BIG);
    serializedSize = 0;
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);
    uint8_t* crcPos = this->localData.fields.buffer + CRC_OFFSET;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPos, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   This class packages the space packet to run auto EM tests.
 */
class RunAutoEmTests : public SpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param test  1 - complete EM test, 2 - Short test (only memory readback NVM0,1,3)
   */
  RunAutoEmTests(uint8_t test)
      : SpacePacket(DATA_FIELD_LENGTH - 1, true, APID_RUN_AUTO_EM_TESTS, DEFAULT_SEQUENCE_COUNT),
        test(test) {
    initPacket();
  }

 private:
  static const uint16_t DATA_FIELD_LENGTH = 3;
  static const uint16_t DEFAULT_SEQUENCE_COUNT = 1;

  static const uint16_t CRC_OFFSET = DATA_FIELD_LENGTH - 2;

  uint8_t test = 0;

  void initPacket() {
    size_t serializedSize = 0;
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    SerializeAdapter::serialize<uint8_t>(&test, &data_field_ptr, &serializedSize, sizeof(test),
                                         SerializeIF::Endianness::BIG);
    serializedSize = 0;
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);
    uint8_t* crcPos = this->localData.fields.buffer + CRC_OFFSET;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPos, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   This class packages the space packet causing the supervisor to print the CPU load to
 *          the debug output.
 */
class PrintCpuStats : public SpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param en    Print is enabled if en != 0
   */
  PrintCpuStats(uint8_t en)
      : SpacePacket(DATA_FIELD_LENGTH - 1, true, APID_PRINT_CPU_STATS, DEFAULT_SEQUENCE_COUNT),
        en(en) {
    initPacket();
  }

 private:
  static const uint16_t DATA_FIELD_LENGTH = 3;
  static const uint16_t DEFAULT_SEQUENCE_COUNT = 1;

  static const uint16_t CRC_OFFSET = DATA_FIELD_LENGTH - 2;

  uint8_t en = 0;

  void initPacket() {
    size_t serializedSize = 0;
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    SerializeAdapter::serialize<uint8_t>(&en, &data_field_ptr, &serializedSize, sizeof(en),
                                         SerializeIF::Endianness::BIG);
    serializedSize = 0;
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);
    uint8_t* crcPos = this->localData.fields.buffer + CRC_OFFSET;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPos, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   This class packages the space packet to set the print verbosity in the supervisor
 *          software.
 */
class SetDbgVerbosity : public SpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param vb    0: None, 1: Error, 2: Warn, 3: Info
   */
  SetDbgVerbosity(uint8_t vb)
      : SpacePacket(DATA_FIELD_LENGTH - 1, true, APID_SET_DBG_VERBOSITY, DEFAULT_SEQUENCE_COUNT),
        vb(vb) {
    initPacket();
  }

 private:
  static const uint16_t DATA_FIELD_LENGTH = 3;
  static const uint16_t DEFAULT_SEQUENCE_COUNT = 1;

  static const uint16_t CRC_OFFSET = DATA_FIELD_LENGTH - 2;

  uint8_t vb = 0;

  void initPacket() {
    size_t serializedSize = 0;
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    SerializeAdapter::serialize<uint8_t>(&vb, &data_field_ptr, &serializedSize, sizeof(vb),
                                         SerializeIF::Endianness::BIG);
    serializedSize = 0;
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);
    uint8_t* crcPos = this->localData.fields.buffer + CRC_OFFSET;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPos, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   This class packages the space packet to wipe or dump parts of the MRAM.
 */
class MramCmd : public SpacePacket {
 public:
  enum class MramAction { WIPE, DUMP };

  /**
   * @brief   Constructor
   *
   * @param start Start address of the MRAM section to wipe or dump
   * @param stop   End address of the MRAM section to wipe or dump
   * @param action    Dump or wipe MRAM
   *
   * @note    The content at the stop address is excluded from the dump or wipe operation.
   */
  MramCmd(uint32_t start, uint32_t stop, MramAction action)
      : SpacePacket(DATA_FIELD_LENGTH - 1, true, APID_IDLE_PACKET, DEFAULT_SEQUENCE_COUNT),
        start(start),
        stop(stop) {
    if (action == MramAction::WIPE) {
      this->setAPID(APID_WIPE_MRAM);
    } else if (action == MramAction::DUMP) {
      this->setAPID(APID_DUMP_MRAM);
    } else {
      sif::debug << "WipeMram: Invalid action specified";
    }
    initPacket();
  }

 private:
  static const uint16_t DATA_FIELD_LENGTH = 8;
  static const uint16_t DEFAULT_SEQUENCE_COUNT = 1;

  static const uint16_t CRC_OFFSET = DATA_FIELD_LENGTH - 2;

  uint32_t start = 0;
  uint32_t stop = 0;

  void initPacket() {
    uint8_t concatBuffer[6];
    concatBuffer[0] = static_cast<uint8_t>(start >> 16);
    concatBuffer[1] = static_cast<uint8_t>(start >> 8);
    concatBuffer[2] = static_cast<uint8_t>(start);
    concatBuffer[3] = static_cast<uint8_t>(stop >> 16);
    concatBuffer[4] = static_cast<uint8_t>(stop >> 8);
    concatBuffer[5] = static_cast<uint8_t>(stop);
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    std::memcpy(data_field_ptr, concatBuffer, sizeof(concatBuffer));
    size_t serializedSize = 0;
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);
    uint8_t* crcPos = this->localData.fields.buffer + CRC_OFFSET;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPos, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   This class packages the space packet change the state of a GPIO. This command is only
 *          required for ground testing.
 */
class SetGpio : public SpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param port
   * @param pin
   * @param val
   */
  SetGpio(uint8_t port, uint8_t pin, uint8_t val)
      : SpacePacket(DATA_FIELD_LENGTH - 1, true, APID_SET_GPIO, DEFAULT_SEQUENCE_COUNT),
        port(port),
        pin(pin),
        val(val) {
    initPacket();
  }

 private:
  static const uint16_t DATA_FIELD_LENGTH = 5;
  static const uint16_t DEFAULT_SEQUENCE_COUNT = 1;

  static const uint16_t CRC_OFFSET = DATA_FIELD_LENGTH - 2;

  uint8_t port = 0;
  uint8_t pin = 0;
  uint8_t val = 0;

  void initPacket() {
    size_t serializedSize = 0;
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    SerializeAdapter::serialize<uint8_t>(&port, &data_field_ptr, &serializedSize, sizeof(port),
                                         SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint8_t>(&pin, &data_field_ptr, &serializedSize, sizeof(pin),
                                         SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint8_t>(&val, &data_field_ptr, &serializedSize, sizeof(val),
                                         SerializeIF::Endianness::BIG);
    serializedSize = 0;
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);
    uint8_t* crcPos = this->localData.fields.buffer + CRC_OFFSET;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPos, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   This class packages the space packet causing the supervisor print the state of a GPIO
 *          to the debug output.
 */
class ReadGpio : public SpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param port
   * @param pin
   */
  ReadGpio(uint8_t port, uint8_t pin)
      : SpacePacket(DATA_FIELD_LENGTH - 1, true, APID_READ_GPIO, DEFAULT_SEQUENCE_COUNT),
        port(port),
        pin(pin) {
    initPacket();
  }

 private:
  static const uint16_t DATA_FIELD_LENGTH = 4;
  static const uint16_t DEFAULT_SEQUENCE_COUNT = 1;

  static const uint16_t CRC_OFFSET = DATA_FIELD_LENGTH - 2;

  uint8_t port = 0;
  uint8_t pin = 0;

  void initPacket() {
    size_t serializedSize = 0;
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    SerializeAdapter::serialize<uint8_t>(&port, &data_field_ptr, &serializedSize, sizeof(port),
                                         SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint8_t>(&pin, &data_field_ptr, &serializedSize, sizeof(pin),
                                         SerializeIF::Endianness::BIG);
    serializedSize = 0;
    uint16_t crc = CRC::crc16ccitt(this->localData.byteStream,
                                   sizeof(CCSDSPrimaryHeader) + DATA_FIELD_LENGTH - 2);
    uint8_t* crcPos = this->localData.fields.buffer + CRC_OFFSET;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPos, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   This class packages the space packet to perform the factory reset. The op parameter is
 *          optional.
 *
 * @details: Without OP: All MRAM entries will be cleared.
 *           OP = 0x01: Only the mirror entries will be wiped.
 *           OP = 0x02: Only the circular entries will be wiped.
 */
class FactoryReset : public SpacePacket {
 public:
  enum class Op { CLEAR_ALL, MIRROR_ENTRIES, CIRCULAR_ENTRIES };

  /**
   * @brief   Constructor
   *
   * @param op
   */
  FactoryReset(Op op) : SpacePacket(0, true, APID_FACTORY_RESET, DEFAULT_SEQUENCE_COUNT), op(op) {
    initPacket();
  }

 private:
  uint16_t packetLen = 1;  // only CRC in data field
  static const uint16_t DEFAULT_SEQUENCE_COUNT = 1;

  uint8_t crcOffset = 0;

  Op op = Op::CLEAR_ALL;

  void initPacket() {
    uint8_t* data_field_ptr = this->localData.fields.buffer;

    switch (op) {
      case Op::MIRROR_ENTRIES:
        *data_field_ptr = 1;
        packetLen = 2;
        crcOffset = 1;
        break;
      case Op::CIRCULAR_ENTRIES:
        *data_field_ptr = 2;
        packetLen = 2;
        crcOffset = 1;
        break;
      default:
        break;
    }
    this->setPacketDataLength(packetLen);
    size_t serializedSize = 0;
    uint16_t crc =
        CRC::crc16ccitt(this->localData.byteStream, sizeof(CCSDSPrimaryHeader) + packetLen - 1);
    uint8_t* crcPos = this->localData.fields.buffer + crcOffset;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPos, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }
};

class SupvTcSpacePacket : public SpacePacket {
 public:
  SupvTcSpacePacket(size_t payloadDataLen, uint16_t apid)
      : SpacePacket(payloadDataLen + 1, true, apid, DEFAULT_SEQUENCE_COUNT),
        payloadDataLen(payloadDataLen) {}

  void makeCrc() {
    size_t serializedSize = 0;
    uint16_t crc =
        CRC::crc16ccitt(this->localData.byteStream, sizeof(CCSDSPrimaryHeader) + payloadDataLen);
    uint8_t* crcPos = this->localData.fields.buffer + payloadDataLen;
    SerializeAdapter::serialize<uint16_t>(&crc, &crcPos, &serializedSize, sizeof(crc),
                                          SerializeIF::Endianness::BIG);
  }

 private:
  // The sequence count of most of the TC packets for the supervisor is 1.
  static const uint16_t DEFAULT_SEQUENCE_COUNT = 1;

  // The size of the payload data (data field without crc size)
  size_t payloadDataLen = 0;
};

/**
 * @brief   This class can be used to package the update available or update verify command.
 */
class UpdateInfo : public SupvTcSpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param apid      Packet can be used to generate the update available and the update verify
   *                  packet. Thus the APID must be specified here.
   * @param image     The image to update on a NVM (A - 0, B - 1)
   * @param partition  The partition to update. uboot - 1, bitstream - 2, linux - 3,
   *                  application - 4
   * @param imageSize The size of the update image
   * param numPackets The number of space packets required to transfer all data.
   */
  UpdateInfo(uint16_t apid, uint8_t image, uint8_t partition, uint32_t imageSize, uint32_t imageCrc,
             uint32_t numPackets)
      : SupvTcSpacePacket(PAYLOAD_LENGTH, apid),
        image(image),
        partition(partition),
        imageSize(imageSize),
        imageCrc(imageCrc),
        numPackets(numPackets) {
    initPacket();
    makeCrc();
  }

 private:
  static const uint16_t PAYLOAD_LENGTH = 14;  // length without CRC field

  uint8_t image = 0;
  uint8_t partition = 0;
  uint32_t imageSize = 0;
  uint32_t imageCrc = 0;
  uint32_t numPackets = 0;

  void initPacket() {
    size_t serializedSize = 0;
    uint8_t* data_field_ptr = this->localData.fields.buffer;
    SerializeAdapter::serialize<uint8_t>(&image, &data_field_ptr, &serializedSize, sizeof(image),
                                         SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint8_t>(&partition, &data_field_ptr, &serializedSize,
                                         sizeof(partition), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint32_t>(&imageSize, &data_field_ptr, &serializedSize,
                                          sizeof(imageSize), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint32_t>(&imageCrc, &data_field_ptr, &serializedSize,
                                          sizeof(imageCrc), SerializeIF::Endianness::BIG);
    serializedSize = 0;
    SerializeAdapter::serialize<uint32_t>(&numPackets, &data_field_ptr, &serializedSize,
                                          sizeof(numPackets), SerializeIF::Endianness::BIG);
  }
};

/**
 * @brief   This class packages the space packet transporting a part of an MPSoC update.
 */
class UpdatePacket : public SupvTcSpacePacket {
 public:
  /**
   * @brief   Constructor
   *
   * @param payloadLength Update data length (data field length without CRC)
   */
  UpdatePacket(uint16_t payloadLength) : SupvTcSpacePacket(payloadLength, APID_UPDATE_IMAGE_DATA) {}

  /**
   * @brief   Returns the pointer to the beginning of the data field.
   */
  uint8_t* getDataFieldPointer() { return this->localData.fields.buffer; }
};

/**
 * @brief   This dataset stores the boot status report of the supervisor.
 */
class BootStatusReport : public StaticLocalDataSet<BOOT_REPORT_SET_ENTRIES> {
 public:
  BootStatusReport(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, BOOT_REPORT_SET_ID) {}

  BootStatusReport(object_id_t objectId)
      : StaticLocalDataSet(sid_t(objectId, BOOT_REPORT_SET_ID)) {}

  /** Information about boot status of MPSoC */
  lp_var_t<uint8_t> bootSignal = lp_var_t<uint8_t>(sid.objectId, PoolIds::BOOT_SIGNAL, this);
  lp_var_t<uint8_t> resetCounter = lp_var_t<uint8_t>(sid.objectId, PoolIds::RESET_COUNTER, this);
  /** Time the MPSoC needs for last boot */
  lp_var_t<uint32_t> bootAfterMs = lp_var_t<uint32_t>(sid.objectId, PoolIds::BOOT_AFTER_MS, this);
  /** The currently set boot timeout */
  lp_var_t<uint32_t> bootTimeoutMs =
      lp_var_t<uint32_t>(sid.objectId, PoolIds::BOOT_TIMEOUT_MS, this);
  lp_var_t<uint8_t> activeNvm = lp_var_t<uint8_t>(sid.objectId, PoolIds::ACTIVE_NVM, this);
  /** States of the boot partition pins */
  lp_var_t<uint8_t> bp0State = lp_var_t<uint8_t>(sid.objectId, PoolIds::BP0_STATE, this);
  lp_var_t<uint8_t> bp1State = lp_var_t<uint8_t>(sid.objectId, PoolIds::BP1_STATE, this);
  lp_var_t<uint8_t> bp2State = lp_var_t<uint8_t>(sid.objectId, PoolIds::BP2_STATE, this);
};

/**
 * @brief   This dataset stores the housekeeping data of the supervisor.
 */
class HkSet : public StaticLocalDataSet<HK_SET_ENTRIES> {
 public:
  HkSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, HK_SET_ID) {}

  HkSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, HK_SET_ID)) {}

  lp_var_t<uint32_t> tempPs = lp_var_t<uint32_t>(sid.objectId, PoolIds::TEMP_PS, this);
  lp_var_t<uint32_t> tempPl = lp_var_t<uint32_t>(sid.objectId, PoolIds::TEMP_PS, this);
  lp_var_t<uint32_t> tempSup = lp_var_t<uint32_t>(sid.objectId, PoolIds::TEMP_SUP, this);
  lp_var_t<uint32_t> uptime = lp_var_t<uint32_t>(sid.objectId, PoolIds::UPTIME, this);
  lp_var_t<uint32_t> cpuLoad = lp_var_t<uint32_t>(sid.objectId, PoolIds::CPULOAD, this);
  lp_var_t<uint32_t> availableHeap = lp_var_t<uint32_t>(sid.objectId, PoolIds::AVAILABLEHEAP, this);
  lp_var_t<uint32_t> numTcs = lp_var_t<uint32_t>(sid.objectId, PoolIds::NUM_TCS, this);
  lp_var_t<uint32_t> numTms = lp_var_t<uint32_t>(sid.objectId, PoolIds::NUM_TMS, this);
  lp_var_t<uint32_t> socState = lp_var_t<uint32_t>(sid.objectId, PoolIds::SOC_STATE, this);
  lp_var_t<uint8_t> nvm0_1_state = lp_var_t<uint8_t>(sid.objectId, PoolIds::NVM0_1_STATE, this);
  lp_var_t<uint8_t> nvm3_state = lp_var_t<uint8_t>(sid.objectId, PoolIds::NVM3_STATE, this);
  lp_var_t<uint8_t> missionIoState =
      lp_var_t<uint8_t>(sid.objectId, PoolIds::MISSION_IO_STATE, this);
  lp_var_t<uint8_t> fmcState = lp_var_t<uint8_t>(sid.objectId, PoolIds::FMC_STATE, this);
};

/**
 * @brief   This dataset stores the last requested latchup status report.
 */
class LatchupStatusReport : public StaticLocalDataSet<LATCHUP_RPT_SET_ENTRIES> {
 public:
  LatchupStatusReport(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, LATCHUP_RPT_ID) {}

  LatchupStatusReport(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, LATCHUP_RPT_ID)) {}

  lp_var_t<uint8_t> id = lp_var_t<uint8_t>(sid.objectId, PoolIds::LATCHUP_ID, this);
  lp_var_t<uint16_t> cnt0 = lp_var_t<uint16_t>(sid.objectId, PoolIds::CNT0, this);
  lp_var_t<uint16_t> cnt1 = lp_var_t<uint16_t>(sid.objectId, PoolIds::CNT1, this);
  lp_var_t<uint16_t> cnt2 = lp_var_t<uint16_t>(sid.objectId, PoolIds::CNT2, this);
  lp_var_t<uint16_t> cnt3 = lp_var_t<uint16_t>(sid.objectId, PoolIds::CNT3, this);
  lp_var_t<uint16_t> cnt4 = lp_var_t<uint16_t>(sid.objectId, PoolIds::CNT4, this);
  lp_var_t<uint16_t> cnt5 = lp_var_t<uint16_t>(sid.objectId, PoolIds::CNT5, this);
  lp_var_t<uint16_t> cnt6 = lp_var_t<uint16_t>(sid.objectId, PoolIds::CNT6, this);
  lp_var_t<uint32_t> timeSec =
      lp_var_t<uint32_t>(sid.objectId, PoolIds::LATCHUP_RPT_TIME_SEC, this);
  lp_var_t<uint32_t> timeMin =
      lp_var_t<uint32_t>(sid.objectId, PoolIds::LATCHUP_RPT_TIME_MIN, this);
  lp_var_t<uint32_t> timeHour =
      lp_var_t<uint32_t>(sid.objectId, PoolIds::LATCHUP_RPT_TIME_HOUR, this);
  lp_var_t<uint32_t> timeDay =
      lp_var_t<uint32_t>(sid.objectId, PoolIds::LATCHUP_RPT_TIME_DAY, this);
  lp_var_t<uint32_t> timeMon =
      lp_var_t<uint32_t>(sid.objectId, PoolIds::LATCHUP_RPT_TIME_MON, this);
  lp_var_t<uint32_t> timeYear =
      lp_var_t<uint32_t>(sid.objectId, PoolIds::LATCHUP_RPT_TIME_YEAR, this);
  lp_var_t<uint32_t> timeMsec =
      lp_var_t<uint32_t>(sid.objectId, PoolIds::LATCHUP_RPT_TIME_MSEC, this);
  lp_var_t<uint32_t> isSet =
      lp_var_t<uint32_t>(sid.objectId, PoolIds::LATCHUP_RPT_TIME_IS_SET, this);
};
}  // namespace PLOC_SPV

#endif /* MISSION_DEVICES_DEVICEDEFINITIONS_PLOCSVPDEFINITIONS_H_ */