#ifndef MISSION_DEVICES_DEVICEDEFINITIONS_PLOCMPSOCDEFINITIONS_H_
#define MISSION_DEVICES_DEVICEDEFINITIONS_PLOCMPSOCDEFINITIONS_H_

#include "MPSoCReturnValuesIF.h"
#include "OBSWConfig.h"
#include "eive/definitions.h"
#include "fsfw/globalfunctions/CRC.h"
#include "fsfw/serialize/SerializeAdapter.h"
#include "fsfw/tmtcpacket/SpacePacket.h"

namespace mpsoc {

static const DeviceCommandId_t NONE = 0;
static const DeviceCommandId_t TC_MEM_WRITE = 1;
static const DeviceCommandId_t TC_MEM_READ = 2;
static const DeviceCommandId_t ACK_REPORT = 3;
static const DeviceCommandId_t EXE_REPORT = 5;
static const DeviceCommandId_t TM_MEMORY_READ_REPORT = 6;
static const DeviceCommandId_t TC_FLASHFOPEN = 7;
static const DeviceCommandId_t TC_FLASHFCLOSE = 8;
static const DeviceCommandId_t TC_FLASHWRITE = 9;
static const DeviceCommandId_t TC_FLASHDELETE = 10;
static const DeviceCommandId_t TC_REPLAY_START = 11;
static const DeviceCommandId_t TC_REPLAY_STOP = 12;
static const DeviceCommandId_t TC_REPLAY_WRITE_SEQUENCE = 13;
static const DeviceCommandId_t TC_DOWNLINK_PWR_ON = 14;
static const DeviceCommandId_t TC_DOWNLINK_PWR_OFF = 15;
static const DeviceCommandId_t TC_MODE_REPLAY = 16;

// Will reset the sequence count of the OBSW
static const DeviceCommandId_t OBSW_RESET_SEQ_COUNT = 50;

static const uint16_t SIZE_ACK_REPORT = 14;
static const uint16_t SIZE_EXE_REPORT = 14;
static const uint16_t SIZE_TM_MEM_READ_REPORT = 18;

/**
 * SpacePacket apids of PLOC telecommands and telemetry.
 */
namespace apid {
static const uint16_t TC_REPLAY_START = 0x110;
static const uint16_t TC_REPLAY_STOP = 0x111;
static const uint16_t TC_REPLAY_WRITE_SEQUENCE = 0x112;
static const uint16_t TC_DOWNLINK_PWR_ON = 0x113;
static const uint16_t TC_MEM_WRITE = 0x114;
static const uint16_t TC_MEM_READ = 0x115;
static const uint16_t TC_FLASHWRITE = 0x117;
static const uint16_t TC_FLASHFOPEN = 0x119;
static const uint16_t TC_FLASHFCLOSE = 0x11A;
static const uint16_t TC_FLASHDELETE = 0x11C;
static const uint16_t TC_MODE_REPLAY = 0x11F;
static const uint16_t TC_DOWNLINK_PWR_OFF = 0x124;
static const uint16_t TM_MEMORY_READ_REPORT = 0x404;
static const uint16_t ACK_SUCCESS = 0x400;
static const uint16_t ACK_FAILURE = 0x401;
static const uint16_t EXE_SUCCESS = 0x402;
static const uint16_t EXE_FAILURE = 0x403;
}  // namespace apid

/** Offset from first byte in space packet to first byte of data field */
static const uint8_t DATA_FIELD_OFFSET = 6;
static const size_t MEM_READ_RPT_LEN_OFFSET = 10;
static const char NULL_TERMINATOR = '\0';

/**
 * The size of payload data which will be forwarded to the requesting object. e.g. PUS Service
 * 8.
 */
static const uint8_t SIZE_MEM_READ_RPT_FIX = 6;

static const size_t MAX_FILENAME_SIZE = 256;

/**
 * PLOC 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_TC_MEM_WRITE = 12;
static const uint16_t LENGTH_TC_MEM_READ = 8;

static const size_t MAX_REPLY_SIZE = SpacePacket::PACKET_MAX_SIZE * 3;
static const size_t MAX_COMMAND_SIZE = SpacePacket::PACKET_MAX_SIZE;
static const size_t MAX_DATA_SIZE = 1016;

/**
 * The replay write sequence command has a maximum delay for the execution report which amounts to
 * 30 seconds. (80 cycles * 0.5 seconds = 40 seconds).
 */
static const uint16_t TC_WRITE_SEQ_EXECUTION_DELAY = 80;
// Requires approx. 2 seconds for execution. 8 => 4 seconds
static const uint16_t TC_DOWNLINK_PWR_ON_EXECUTION_DELAY = 8;

/**
 * @brief   Abstract base class for TC space packet of MPSoC.
 */
class TcBase : public SpacePacket, public MPSoCReturnValuesIF {
 public:
  // Initial length field of space packet. Will always be updated when packet is created.
  static const uint16_t INIT_LENGTH = 1;

  /**
   * @brief    Constructor
   *
   * @param sequenceCount Sequence count of space packet which will be incremented with each
   *        sent and received packets.
   */
  TcBase(uint16_t apid, uint16_t sequenceCount)
      : SpacePacket(INIT_LENGTH, true, apid, sequenceCount) {}

  /**
   * @brief   Function to initialize the space packet
   *
   * @param commandData       Pointer to command specific data
   * @param commandDataLen    Length of command data
   *
   * @return  RETURN_OK if packet creation was successful, otherwise error return value
   */
  virtual ReturnValue_t createPacket(const uint8_t* commandData, size_t commandDataLen) {
    ReturnValue_t result = HasReturnvaluesIF::RETURN_OK;
    result = initPacket(commandData, commandDataLen);
    if (result != HasReturnvaluesIF::HasReturnvaluesIF::RETURN_OK) {
      return result;
    }
    result = addCrc();
    if (result != HasReturnvaluesIF::HasReturnvaluesIF::RETURN_OK) {
      return result;
    }
    return result;
  }

 protected:
  /**
   * @brief   Must be overwritten by the child class to define the command specific parameters
   *
   * @param commandData       Pointer to received command data
   * @param commandDataLen    Length of received command data
   */
  virtual ReturnValue_t initPacket(const uint8_t* commandData, size_t commandDataLen) {
    return HasReturnvaluesIF::RETURN_OK;
  }

  /**
   * @brief   Calculates and adds the CRC
   */
  ReturnValue_t addCrc() {
    ReturnValue_t result = HasReturnvaluesIF::RETURN_OK;
    size_t serializedSize = 0;
    uint32_t full_size = getFullSize();
    uint16_t crc = CRC::crc16ccitt(getWholeData(), full_size - CRC_SIZE);
    result = SerializeAdapter::serialize<uint16_t>(
        &crc, this->localData.byteStream + full_size - CRC_SIZE, &serializedSize, sizeof(crc),
        SerializeIF::Endianness::BIG);
    if (result != HasReturnvaluesIF::RETURN_OK) {
      sif::debug << "TcBase::addCrc: Failed to serialize crc field" << std::endl;
    }
    return result;
  }
};

/**
 * @brief   Class for handling tm replies of the PLOC MPSoC.
 */
class TmPacket : public SpacePacket, public MPSoCReturnValuesIF {
 public:
  /**
   * @brief   Constructor creates idle packet and sets length field to maximum allowed size.
   */
  TmPacket() : SpacePacket(PACKET_MAX_SIZE) {}

  ReturnValue_t checkCrc() {
    uint8_t* crcPtr = this->getPacketData() + this->getPacketDataLength() - 1;
    uint16_t receivedCrc = *(crcPtr) << 8 | *(crcPtr + 1);
    uint16_t recalculatedCrc =
        CRC::crc16ccitt(this->localData.byteStream, this->getFullSize() - CRC_SIZE);
    if (recalculatedCrc != receivedCrc) {
      return CRC_FAILURE;
    }
    return HasReturnvaluesIF::RETURN_OK;
  }
};

/**
 * @brief   This class helps to build the memory read command for the PLOC.
 */
class TcMemRead : public TcBase {
 public:
  /**
   * @brief   Constructor
   */
  TcMemRead(uint16_t sequenceCount) : TcBase(apid::TC_MEM_READ, sequenceCount) {
    this->setPacketDataLength(PACKET_LENGTH);
  }

  uint16_t getMemLen() const { return memLen; }

 protected:
  ReturnValue_t initPacket(const uint8_t* commandData, size_t commandDataLen) {
    ReturnValue_t result = HasReturnvaluesIF::RETURN_OK;
    result = lengthCheck(commandDataLen);
    if (result != HasReturnvaluesIF::RETURN_OK) {
      return result;
    }
    std::memcpy(this->localData.fields.buffer, commandData, MEM_ADDRESS_SIZE);
    std::memcpy(this->localData.fields.buffer + MEM_ADDRESS_SIZE, commandData + MEM_ADDRESS_SIZE,
                MEM_LEN_SIZE);
    size_t size = sizeof(memLen);
    const uint8_t* memLenPtr = commandData + MEM_ADDRESS_SIZE;
    result =
        SerializeAdapter::deSerialize(&memLen, &memLenPtr, &size, SerializeIF::Endianness::BIG);
    if (result != HasReturnvaluesIF::RETURN_OK) {
      return result;
    }
    return result;
  }

 private:
  static const size_t COMMAND_LENGTH = 6;
  static const size_t MEM_ADDRESS_SIZE = 4;
  static const size_t MEM_LEN_SIZE = 2;
  static const uint16_t PACKET_LENGTH = 7;

  uint16_t memLen = 0;

  ReturnValue_t lengthCheck(size_t commandDataLen) {
    if (commandDataLen != COMMAND_LENGTH) {
      return INVALID_LENGTH;
    }
    return HasReturnvaluesIF::RETURN_OK;
  }
};

/**
 * @brief   This class helps to generate the space packet to write data to a memory address within
 *          the PLOC.
 */
class TcMemWrite : public TcBase {
 public:
  /**
   * @brief   Constructor
   */
  TcMemWrite(uint16_t sequenceCount) : TcBase(apid::TC_MEM_WRITE, sequenceCount) {}

 protected:
  ReturnValue_t initPacket(const uint8_t* commandData, size_t commandDataLen) {
    ReturnValue_t result = HasReturnvaluesIF::RETURN_OK;
    result = lengthCheck(commandDataLen);
    if (result != HasReturnvaluesIF::RETURN_OK) {
      return result;
    }
    std::memcpy(this->localData.fields.buffer, commandData, commandDataLen);
    uint16_t memLen =
        *(commandData + MEM_ADDRESS_SIZE) << 8 | *(commandData + MEM_ADDRESS_SIZE + 1);
    this->setPacketDataLength(memLen * 4 + FIX_LENGTH - 1);
    return result;
  }

 private:
  // Min length consists of 4 byte address, 2 byte mem length field, 4 byte data (1 word)
  static const size_t MIN_COMMAND_DATA_LENGTH = 10;
  static const size_t MEM_ADDRESS_SIZE = 4;
  static const size_t FIX_LENGTH = 8;

  ReturnValue_t lengthCheck(size_t commandDataLen) {
    if (commandDataLen < MIN_COMMAND_DATA_LENGTH) {
      sif::warning << "TcMemWrite: Command has invalid length " << commandDataLen << std::endl;
      return INVALID_LENGTH;
    }
    return HasReturnvaluesIF::RETURN_OK;
  }
};

/**
 * @brief   Class to help creation of flash fopen command.
 */
class FlashFopen : public TcBase {
 public:
  FlashFopen(uint16_t sequenceCount) : TcBase(apid::TC_FLASHFOPEN, sequenceCount) {}

  static const char APPEND = 'a';
  static const char WRITE = 'w';
  static const char READ = 'r';

  ReturnValue_t createPacket(std::string filename, char accessMode_) {
    accessMode = accessMode_;
    ReturnValue_t result = HasReturnvaluesIF::RETURN_OK;
    size_t nameSize = filename.size();
    std::memcpy(this->getPacketData(), filename.c_str(), nameSize);
    *(this->getPacketData() + nameSize) = NULL_TERMINATOR;
    std::memcpy(this->getPacketData() + nameSize + sizeof(NULL_TERMINATOR), &accessMode,
                sizeof(accessMode));
    this->setPacketDataLength(nameSize + sizeof(NULL_TERMINATOR) + sizeof(accessMode) + CRC_SIZE -
                              1);
    result = addCrc();
    if (result != HasReturnvaluesIF::RETURN_OK) {
      return result;
    }
    return result;
  }

 private:
  char accessMode = APPEND;
};

/**
 * @brief   Class to help creation of flash fclose command.
 */
class FlashFclose : public TcBase {
 public:
  FlashFclose(uint16_t sequenceCount) : TcBase(apid::TC_FLASHFCLOSE, sequenceCount) {}

  ReturnValue_t createPacket(std::string filename) {
    ReturnValue_t result = HasReturnvaluesIF::RETURN_OK;
    size_t nameSize = filename.size();
    std::memcpy(this->getPacketData(), filename.c_str(), nameSize);
    *(this->getPacketData() + nameSize) = NULL_TERMINATOR;
    this->setPacketDataLength(nameSize + sizeof(NULL_TERMINATOR) + CRC_SIZE - 1);
    result = addCrc();
    if (result != HasReturnvaluesIF::RETURN_OK) {
      return result;
    }
    return result;
  }
};

/**
 * @brief   Class to build flash write space packet.
 */
class TcFlashWrite : public TcBase {
 public:
  TcFlashWrite(uint16_t sequenceCount) : TcBase(apid::TC_FLASHWRITE, sequenceCount) {}

  ReturnValue_t createPacket(const uint8_t* writeData, uint32_t writeLen_) {
    ReturnValue_t result = HasReturnvaluesIF::RETURN_OK;
    writeLen = writeLen_;
    if (writeLen > MAX_DATA_SIZE) {
      sif::debug << "FlashWrite::createPacket: Command data too big" << std::endl;
      return HasReturnvaluesIF::RETURN_FAILED;
    }
    size_t serializedSize = 0;
    result =
        SerializeAdapter::serialize<uint32_t>(&writeLen, this->getPacketData(), &serializedSize,
                                              sizeof(writeLen), SerializeIF::Endianness::BIG);
    if (result != HasReturnvaluesIF::RETURN_OK) {
      return result;
    }
    std::memcpy(this->getPacketData() + sizeof(writeLen), writeData, writeLen);
    this->setPacketDataLength(static_cast<uint16_t>(writeLen + CRC_SIZE - 1));
    result = addCrc();
    if (result != HasReturnvaluesIF::RETURN_OK) {
      return result;
    }
    return HasReturnvaluesIF::RETURN_OK;
  }

 private:
  uint32_t writeLen = 0;
};

/**
 * @brief   Class to help creation of flash delete command.
 */
class TcFlashDelete : public TcBase {
 public:
  TcFlashDelete(uint16_t sequenceCount) : TcBase(apid::TC_FLASHDELETE, sequenceCount) {}

  ReturnValue_t createPacket(std::string filename) {
    ReturnValue_t result = HasReturnvaluesIF::RETURN_OK;
    size_t nameSize = filename.size();
    std::memcpy(this->getPacketData(), filename.c_str(), nameSize);
    *(this->getPacketData() + nameSize) = NULL_TERMINATOR;
    this->setPacketDataLength(nameSize + sizeof(NULL_TERMINATOR) + CRC_SIZE - 1);
    result = addCrc();
    if (result != HasReturnvaluesIF::RETURN_OK) {
      return result;
    }
    return result;
  }
};

/**
 * @brief   Class to build replay stop space packet.
 */
class TcReplayStop : public TcBase {
 public:
  TcReplayStop(uint16_t sequenceCount) : TcBase(apid::TC_REPLAY_STOP, sequenceCount) {}

  ReturnValue_t createPacket() {
    ReturnValue_t result = HasReturnvaluesIF::RETURN_OK;
    result = addCrc();
    if (result != HasReturnvaluesIF::RETURN_OK) {
      return result;
    }
    this->setPacketDataLength(static_cast<uint16_t>(CRC_SIZE - 1));
    return HasReturnvaluesIF::RETURN_OK;
  }
};

/**
 * @brief   This class helps to build the replay start command.
 */
class TcReplayStart : public TcBase {
 public:
  /**
   * @brief   Constructor
   */
  TcReplayStart(uint16_t sequenceCount) : TcBase(apid::TC_REPLAY_START, sequenceCount) {}

 protected:
  ReturnValue_t initPacket(const uint8_t* commandData, size_t commandDataLen) {
    ReturnValue_t result = HasReturnvaluesIF::RETURN_OK;
    result = lengthCheck(commandDataLen);
    if (result != HasReturnvaluesIF::RETURN_OK) {
      return result;
    }
    result = checkData(*commandData);
    if (result != HasReturnvaluesIF::RETURN_OK) {
      return result;
    }
    std::memcpy(this->localData.fields.buffer, commandData, commandDataLen);
    this->setPacketDataLength(commandDataLen + CRC_SIZE - 1);
    return result;
  }

 private:
  static const size_t COMMAND_DATA_LENGTH = 1;
  static const uint8_t REPEATING = 0;
  static const uint8_t ONCE = 1;

  ReturnValue_t lengthCheck(size_t commandDataLen) {
    if (commandDataLen != COMMAND_DATA_LENGTH) {
      sif::warning << "TcReplayStart: Command has invalid length " << commandDataLen << std::endl;
      return INVALID_LENGTH;
    }
    return HasReturnvaluesIF::RETURN_OK;
  }

  ReturnValue_t checkData(uint8_t replay) {
    if (replay != REPEATING && replay != ONCE) {
      sif::warning << "TcReplayStart::checkData: Invalid replay value" << std::endl;
      return INVALID_PARAMETER;
    }
    return HasReturnvaluesIF::RETURN_OK;
  }
};

/**
 * @brief   This class helps to build downlink power on command.
 */
class TcDownlinkPwrOn : public TcBase {
 public:
  /**
   * @brief   Constructor
   */
  TcDownlinkPwrOn(uint16_t sequenceCount) : TcBase(apid::TC_DOWNLINK_PWR_ON, sequenceCount) {}

 protected:
  ReturnValue_t initPacket(const uint8_t* commandData, size_t commandDataLen) {
    ReturnValue_t result = HasReturnvaluesIF::RETURN_OK;
    result = lengthCheck(commandDataLen);
    if (result != HasReturnvaluesIF::RETURN_OK) {
      return result;
    }
    result = modeCheck(*commandData);
    if (result != HasReturnvaluesIF::RETURN_OK) {
      return result;
    }
    result = laneRateCheck(*(commandData + 1));
    if (result != HasReturnvaluesIF::RETURN_OK) {
      return result;
    }
    std::memcpy(this->localData.fields.buffer, commandData, commandDataLen);
    std::memcpy(this->localData.fields.buffer + commandDataLen, &MAX_AMPLITUDE,
                sizeof(MAX_AMPLITUDE));
    this->setPacketDataLength(commandDataLen + sizeof(MAX_AMPLITUDE) + CRC_SIZE - 1);
    return result;
  }

 private:
  static const uint8_t INTERFACE_ID = CLASS_ID::DWLPWRON_CMD;

  //! [EXPORT] : [COMMENT] Received command has invalid JESD mode (valid modes are 0 - 5)
  static const ReturnValue_t INVALID_MODE = MAKE_RETURN_CODE(0xE0);
  //! [EXPORT] : [COMMENT] Received command has invalid lane rate (valid lane rate are 0 - 9)
  static const ReturnValue_t INVALID_LANE_RATE = MAKE_RETURN_CODE(0xE1);

  static const size_t COMMAND_DATA_LENGTH = 2;
  static const uint8_t MAX_MODE = 5;
  static const uint8_t MAX_LANE_RATE = 9;
  static const uint16_t MAX_AMPLITUDE = 0;

  ReturnValue_t lengthCheck(size_t commandDataLen) {
    if (commandDataLen != COMMAND_DATA_LENGTH) {
      sif::warning << "TcDownlinkPwrOn: Command has invalid length " << commandDataLen << std::endl;
      return INVALID_LENGTH;
    }
    return HasReturnvaluesIF::RETURN_OK;
  }

  ReturnValue_t modeCheck(uint8_t mode) {
    if (mode > MAX_MODE) {
      sif::warning << "TcDwonlinkPwrOn::modeCheck: Invalid JESD mode" << std::endl;
      return INVALID_MODE;
    }
    return HasReturnvaluesIF::RETURN_OK;
  }

  ReturnValue_t laneRateCheck(uint8_t laneRate) {
    if (laneRate > MAX_LANE_RATE) {
      sif::warning << "TcReplayStart::laneRateCheck: Invalid lane rate" << std::endl;
      return INVALID_LANE_RATE;
    }
    return HasReturnvaluesIF::RETURN_OK;
  }
};

/**
 * @brief   Class to build replay stop space packet.
 */
class TcDownlinkPwrOff : public TcBase {
 public:
  TcDownlinkPwrOff(uint16_t sequenceCount) : TcBase(apid::TC_DOWNLINK_PWR_OFF, sequenceCount) {}

  ReturnValue_t createPacket() {
    ReturnValue_t result = HasReturnvaluesIF::RETURN_OK;
    result = addCrc();
    if (result != HasReturnvaluesIF::RETURN_OK) {
      return result;
    }
    this->setPacketDataLength(static_cast<uint16_t>(CRC_SIZE - 1));
    return HasReturnvaluesIF::RETURN_OK;
  }
};

/**
 * @brief   This class helps to build the replay start command.
 */
class TcReplayWriteSeq : public TcBase {
 public:
  /**
   * @brief   Constructor
   */
  TcReplayWriteSeq(uint16_t sequenceCount)
      : TcBase(apid::TC_REPLAY_WRITE_SEQUENCE, sequenceCount) {}

 protected:
  ReturnValue_t initPacket(const uint8_t* commandData, size_t commandDataLen) {
    ReturnValue_t result = HasReturnvaluesIF::RETURN_OK;
    result = lengthCheck(commandDataLen);
    if (result != HasReturnvaluesIF::RETURN_OK) {
      return result;
    }
    std::memcpy(this->localData.fields.buffer, commandData, commandDataLen);
    *(this->localData.fields.buffer + commandDataLen) = NULL_TERMINATOR;
    this->setPacketDataLength(commandDataLen + sizeof(NULL_TERMINATOR) + CRC_SIZE - 1);
    return result;
  }

 private:
  static const size_t USE_DECODING_LENGTH = 1;

  ReturnValue_t lengthCheck(size_t commandDataLen) {
    if (commandDataLen > USE_DECODING_LENGTH + MAX_FILENAME_SIZE) {
      sif::warning << "TcReplayWriteSeq: Command has invalid length " << commandDataLen
                   << std::endl;
      return INVALID_LENGTH;
    }
    return HasReturnvaluesIF::RETURN_OK;
  }
};

class FlashWritePusCmd : public MPSoCReturnValuesIF {
 public:
  FlashWritePusCmd(){};

  ReturnValue_t extractFields(const uint8_t* commandData, size_t commandDataLen) {
    if (commandDataLen > (config::MAX_PATH_SIZE + config::MAX_FILENAME_SIZE + MAX_FILENAME_SIZE)) {
      return INVALID_LENGTH;
    }
    obcFile = std::string(reinterpret_cast<const char*>(commandData));
    if (obcFile.size() > (config::MAX_PATH_SIZE + config::MAX_FILENAME_SIZE)) {
      return FILENAME_TOO_LONG;
    }
    mpsocFile = std::string(
        reinterpret_cast<const char*>(commandData + obcFile.size() + SIZE_NULL_TERMINATOR));
    if (mpsocFile.size() > MAX_FILENAME_SIZE) {
      return MPSOC_FILENAME_TOO_LONG;
    }
    return HasReturnvaluesIF::RETURN_OK;
  }

  std::string getObcFile() { return obcFile; }

  std::string getMPSoCFile() { return mpsocFile; }

 private:
  static const size_t SIZE_NULL_TERMINATOR = 1;
  std::string obcFile = "";
  std::string mpsocFile = "";
};

/**
 * @brief   Class to build replay stop space packet.
 */
class TcModeReplay : public TcBase {
 public:
  TcModeReplay(uint16_t sequenceCount) : TcBase(apid::TC_MODE_REPLAY, sequenceCount) {}

  ReturnValue_t createPacket() {
    ReturnValue_t result = HasReturnvaluesIF::RETURN_OK;
    result = addCrc();
    if (result != HasReturnvaluesIF::RETURN_OK) {
      return result;
    }
    this->setPacketDataLength(static_cast<uint16_t>(CRC_SIZE - 1));
    return HasReturnvaluesIF::RETURN_OK;
  }
};

}  // namespace mpsoc

#endif /* MISSION_DEVICES_DEVICEDEFINITIONS_PLOCMPSOCDEFINITIONS_H_ */