#include "PlocMPSoCHandler.h"

#include "OBSWConfig.h"
#include "fsfw/datapool/PoolReadGuard.h"
#include "fsfw/globalfunctions/CRC.h"
#include "linux/devices/devicedefinitions/PlocSupervisorDefinitions.h"

PlocMPSoCHandler::PlocMPSoCHandler(object_id_t objectId, object_id_t uartComIFid,
                                   CookieIF* comCookie, PlocMPSoCHelper* plocMPSoCHelper,
                                   Gpio uartIsolatorSwitch, object_id_t supervisorHandler)
    : DeviceHandlerBase(objectId, uartComIFid, comCookie),
      plocMPSoCHelper(plocMPSoCHelper),
      uartIsolatorSwitch(uartIsolatorSwitch),
      supervisorHandler(supervisorHandler),
      commandActionHelper(this) {
  if (comCookie == nullptr) {
    sif::error << "PlocMPSoCHandler: Invalid communication cookie" << std::endl;
  }
  eventQueue = QueueFactory::instance()->createMessageQueue(EventMessage::EVENT_MESSAGE_SIZE * 5);
  commandActionHelperQueue =
      QueueFactory::instance()->createMessageQueue(EventMessage::EVENT_MESSAGE_SIZE * 5);
}

PlocMPSoCHandler::~PlocMPSoCHandler() {}

ReturnValue_t PlocMPSoCHandler::initialize() {
  ReturnValue_t result = RETURN_OK;
  result = DeviceHandlerBase::initialize();
  if (result != RETURN_OK) {
    return result;
  }
  uartComIf = dynamic_cast<UartComIF*>(communicationInterface);
  if (uartComIf == nullptr) {
    sif::warning << "PlocMPSoCHandler::initialize: Invalid uart com if" << std::endl;
    return ObjectManagerIF::CHILD_INIT_FAILED;
  }

  EventManagerIF* manager = ObjectManager::instance()->get<EventManagerIF>(objects::EVENT_MANAGER);
  if (manager == nullptr) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
    sif::error << "PlocMPSoCHandler::initialize: Invalid event manager" << std::endl;
#endif
    return ObjectManagerIF::CHILD_INIT_FAILED;
    ;
  }
  result = manager->registerListener(eventQueue->getId());
  if (result != RETURN_OK) {
    return result;
  }
  result = manager->subscribeToEventRange(
      eventQueue->getId(), event::getEventId(PlocMPSoCHelper::MPSOC_FLASH_WRITE_FAILED),
      event::getEventId(PlocMPSoCHelper::MPSOC_FLASH_WRITE_SUCCESSFUL));
  if (result != RETURN_OK) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
    sif::warning << "PlocMPSoCHandler::initialize: Failed to subscribe to events from "
                    " ploc mpsoc helper"
                 << std::endl;
#endif
    return ObjectManagerIF::CHILD_INIT_FAILED;
  }

  result = plocMPSoCHelper->setComIF(communicationInterface);
  if (result != RETURN_OK) {
    return ObjectManagerIF::CHILD_INIT_FAILED;
  }
  plocMPSoCHelper->setComCookie(comCookie);
  plocMPSoCHelper->setSequenceCount(&sequenceCount);
  result = commandActionHelper.initialize();
  if (result != HasReturnvaluesIF::RETURN_OK) {
    return ObjectManagerIF::CHILD_INIT_FAILED;
  }
  return result;
}

void PlocMPSoCHandler::performOperationHook() {
  EventMessage event;
  for (ReturnValue_t result = eventQueue->receiveMessage(&event); result == RETURN_OK;
       result = eventQueue->receiveMessage(&event)) {
    switch (event.getMessageId()) {
      case EventMessage::EVENT_MESSAGE:
        handleEvent(&event);
        break;
      default:
        sif::debug << "PlocMPSoCHandler::performOperationHook: Did not subscribe to this event"
                   << " message" << std::endl;
        break;
    }
  }
  CommandMessage message;
  for (ReturnValue_t result = commandActionHelperQueue->receiveMessage(&message);
       result == RETURN_OK; result = commandActionHelperQueue->receiveMessage(&message)) {
    result = commandActionHelper.handleReply(&message);
    if (result == RETURN_OK) {
      continue;
    }
  }
}

ReturnValue_t PlocMPSoCHandler::executeAction(ActionId_t actionId, MessageQueueId_t commandedBy,
                                              const uint8_t* data, size_t size) {
  ReturnValue_t result = RETURN_OK;
  switch (actionId) {
    case mpsoc::SET_UART_TX_TRISTATE: {
      uartIsolatorSwitch.pullLow();
      return EXECUTION_FINISHED;
      break;
    }
    case mpsoc::RELEASE_UART_TX: {
      uartIsolatorSwitch.pullHigh();
      return EXECUTION_FINISHED;
      break;
      default:
        break;
    }
  }

  if (plocMPSoCHelperExecuting) {
    return MPSoCReturnValuesIF::MPSOC_HELPER_EXECUTING;
  }

  switch (actionId) {
    case mpsoc::TC_FLASHWRITE: {
      if (size > config::MAX_PATH_SIZE + config::MAX_FILENAME_SIZE) {
        return MPSoCReturnValuesIF::FILENAME_TOO_LONG;
      }
      mpsoc::FlashWritePusCmd flashWritePusCmd;
      result = flashWritePusCmd.extractFields(data, size);
      if (result != RETURN_OK) {
        return result;
      }
      result = plocMPSoCHelper->startFlashWrite(flashWritePusCmd.getObcFile(),
                                                flashWritePusCmd.getMPSoCFile());
      if (result != RETURN_OK) {
        return result;
      }
      plocMPSoCHelperExecuting = true;
      return EXECUTION_FINISHED;
    }
    case (mpsoc::OBSW_RESET_SEQ_COUNT): {
      sequenceCount = 0;
      return EXECUTION_FINISHED;
    }
    default:
      break;
  }
  return DeviceHandlerBase::executeAction(actionId, commandedBy, data, size);
}

void PlocMPSoCHandler::doStartUp() {
#ifdef XIPHOS_Q7S
#if not OBSW_MPSOC_JTAG_BOOT == 1
  switch (powerState) {
    case PowerState::OFF:
      commandActionHelper.commandAction(supervisorHandler, supv::START_MPSOC);
      powerState = PowerState::BOOTING;
      break;
    case PowerState::ON:
      setMode(_MODE_TO_ON);
      uartIsolatorSwitch.pullHigh();
      break;
    default:
      break;
  }
#else
  powerState = PowerState::ON;
  setMode(_MODE_TO_ON);
  uartIsolatorSwitch.pullHigh();
#endif /* not MSPOC_JTAG_BOOT == 1 */
#else
  powerState = PowerState::ON;
  setMode(_MODE_TO_ON);
#endif /* XIPHOS_Q7S */
}

void PlocMPSoCHandler::doShutDown() {
#ifdef XIPHOS_Q7S
#if not OBSW_MPSOC_JTAG_BOOT == 1
  switch (powerState) {
    case PowerState::ON:
      uartIsolatorSwitch.pullLow();
      commandActionHelper.commandAction(supervisorHandler, supv::SHUTDOWN_MPSOC);
      powerState = PowerState::SHUTDOWN;
      break;
    case PowerState::OFF:
      setMode(_MODE_POWER_DOWN);
      break;
    default:
      break;
  }
#else
  uartIsolatorSwitch.pullLow();
  setMode(_MODE_POWER_DOWN);
  powerState = PowerState::OFF;
#endif
#endif
}

ReturnValue_t PlocMPSoCHandler::buildNormalDeviceCommand(DeviceCommandId_t* id) {
  return NOTHING_TO_SEND;
}

ReturnValue_t PlocMPSoCHandler::buildTransitionDeviceCommand(DeviceCommandId_t* id) {
  return NOTHING_TO_SEND;
}

ReturnValue_t PlocMPSoCHandler::buildCommandFromCommand(DeviceCommandId_t deviceCommand,
                                                        const uint8_t* commandData,
                                                        size_t commandDataLen) {
  ReturnValue_t result = RETURN_OK;
  switch (deviceCommand) {
    case (mpsoc::TC_MEM_WRITE): {
      result = prepareTcMemWrite(commandData, commandDataLen);
      break;
    }
    case (mpsoc::TC_MEM_READ): {
      result = prepareTcMemRead(commandData, commandDataLen);
      break;
    }
    case (mpsoc::TC_FLASHDELETE): {
      result = prepareTcFlashDelete(commandData, commandDataLen);
      break;
    }
    case (mpsoc::TC_REPLAY_START): {
      result = prepareTcReplayStart(commandData, commandDataLen);
      break;
    }
    case (mpsoc::TC_REPLAY_STOP): {
      result = prepareTcReplayStop();
      break;
    }
    case (mpsoc::TC_DOWNLINK_PWR_ON): {
      result = prepareTcDownlinkPwrOn(commandData, commandDataLen);
      break;
    }
    case (mpsoc::TC_DOWNLINK_PWR_OFF): {
      result = prepareTcDownlinkPwrOff();
      break;
    }
    case (mpsoc::TC_REPLAY_WRITE_SEQUENCE): {
      result = prepareTcReplayWriteSequence(commandData, commandDataLen);
      break;
    }
    case (mpsoc::TC_MODE_REPLAY): {
      result = prepareTcModeReplay();
      break;
    }
    case (mpsoc::TC_MODE_IDLE): {
      result = prepareTcModeIdle();
      break;
    }
    case (mpsoc::TC_CAM_CMD_SEND): {
      result = prepareTcCamCmdSend(commandData, commandDataLen);
      break;
    }
    default:
      sif::debug << "PlocMPSoCHandler::buildCommandFromCommand: Command not implemented"
                 << std::endl;
      result = DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED;
      break;
  }

  if (result == RETURN_OK) {
    /**
     * Flushing the receive buffer to make sure there are no data left from a faulty reply.
     */
    uartComIf->flushUartRxBuffer(comCookie);
  }

  return result;
}

void PlocMPSoCHandler::fillCommandAndReplyMap() {
  this->insertInCommandMap(mpsoc::TC_MEM_WRITE);
  this->insertInCommandMap(mpsoc::TC_MEM_READ);
  this->insertInCommandMap(mpsoc::TC_FLASHDELETE);
  this->insertInCommandMap(mpsoc::TC_REPLAY_START);
  this->insertInCommandMap(mpsoc::TC_REPLAY_STOP);
  this->insertInCommandMap(mpsoc::TC_DOWNLINK_PWR_ON);
  this->insertInCommandMap(mpsoc::TC_DOWNLINK_PWR_OFF);
  this->insertInCommandMap(mpsoc::TC_REPLAY_WRITE_SEQUENCE);
  this->insertInCommandMap(mpsoc::TC_MODE_REPLAY);
  this->insertInCommandMap(mpsoc::TC_MODE_IDLE);
  this->insertInCommandMap(mpsoc::TC_CAM_CMD_SEND);
  this->insertInCommandMap(mpsoc::RELEASE_UART_TX);
  this->insertInCommandMap(mpsoc::SET_UART_TX_TRISTATE);
  this->insertInReplyMap(mpsoc::ACK_REPORT, 3, nullptr, mpsoc::SIZE_ACK_REPORT);
  this->insertInReplyMap(mpsoc::EXE_REPORT, 3, nullptr, mpsoc::SIZE_EXE_REPORT);
  this->insertInReplyMap(mpsoc::TM_MEMORY_READ_REPORT, 2, nullptr, mpsoc::SIZE_TM_MEM_READ_REPORT);
  this->insertInReplyMap(mpsoc::TM_CAM_CMD_RPT, 2, nullptr, SpacePacket::PACKET_MAX_SIZE);
}

ReturnValue_t PlocMPSoCHandler::scanForReply(const uint8_t* start, size_t remainingSize,
                                             DeviceCommandId_t* foundId, size_t* foundLen) {
  ReturnValue_t result = RETURN_OK;

  SpacePacket spacePacket;
  std::memcpy(spacePacket.getWholeData(), start, remainingSize);
  uint16_t apid = spacePacket.getAPID();

  switch (apid) {
    case (mpsoc::apid::ACK_SUCCESS):
      *foundLen = mpsoc::SIZE_ACK_REPORT;
      *foundId = mpsoc::ACK_REPORT;
      break;
    case (mpsoc::apid::ACK_FAILURE):
      *foundLen = mpsoc::SIZE_ACK_REPORT;
      *foundId = mpsoc::ACK_REPORT;
      break;
    case (mpsoc::apid::TM_MEMORY_READ_REPORT):
      *foundLen = tmMemReadReport.rememberRequestedSize;
      *foundId = mpsoc::TM_MEMORY_READ_REPORT;
      break;
    case (mpsoc::apid::TM_CAM_CMD_RPT):
      *foundLen = spacePacket.getFullSize();
      tmCamCmdRpt.rememberSpacePacketSize = *foundLen;
      *foundId = mpsoc::TM_CAM_CMD_RPT;
      break;
    case (mpsoc::apid::EXE_SUCCESS):
      *foundLen = mpsoc::SIZE_EXE_REPORT;
      *foundId = mpsoc::EXE_REPORT;
      break;
    case (mpsoc::apid::EXE_FAILURE):
      *foundLen = mpsoc::SIZE_EXE_REPORT;
      *foundId = mpsoc::EXE_REPORT;
      break;
    default: {
      sif::debug << "PlocMPSoCHandler::scanForReply: Reply has invalid apid" << std::endl;
      *foundLen = remainingSize;
      return MPSoCReturnValuesIF::INVALID_APID;
    }
  }

  sequenceCount++;
  uint16_t recvSeqCnt = (*(start + 2) << 8 | *(start + 3)) & PACKET_SEQUENCE_COUNT_MASK;
  if (recvSeqCnt != sequenceCount) {
    triggerEvent(MPSOC_HANDLER_SEQUENCE_COUNT_MISMATCH, sequenceCount, recvSeqCnt);
    sequenceCount = recvSeqCnt;
  }
  return result;
}

ReturnValue_t PlocMPSoCHandler::interpretDeviceReply(DeviceCommandId_t id, const uint8_t* packet) {
  ReturnValue_t result = RETURN_OK;

  switch (id) {
    case mpsoc::ACK_REPORT: {
      result = handleAckReport(packet);
      break;
    }
    case (mpsoc::TM_MEMORY_READ_REPORT): {
      result = handleMemoryReadReport(packet);
      break;
    }
    case (mpsoc::TM_CAM_CMD_RPT): {
      result = handleCamCmdRpt(packet);
      break;
    }
    case (mpsoc::EXE_REPORT): {
      result = handleExecutionReport(packet);
      break;
    }
    default: {
      sif::debug << "PlocMPSoCHandler::interpretDeviceReply: Unknown device reply id" << std::endl;
      return DeviceHandlerIF::UNKNOWN_DEVICE_REPLY;
    }
  }

  return result;
}

void PlocMPSoCHandler::setNormalDatapoolEntriesInvalid() {}

uint32_t PlocMPSoCHandler::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 5000; }

ReturnValue_t PlocMPSoCHandler::initializeLocalDataPool(localpool::DataPool& localDataPoolMap,
                                                        LocalDataPoolManager& poolManager) {
  return HasReturnvaluesIF::RETURN_OK;
}

void PlocMPSoCHandler::handleEvent(EventMessage* eventMessage) {
  object_id_t objectId = eventMessage->getReporter();
  switch (objectId) {
    case objects::PLOC_MPSOC_HELPER: {
      plocMPSoCHelperExecuting = false;
      break;
    }
    default:
      sif::debug << "PlocMPSoCHandler::handleEvent: Did not subscribe to this event" << std::endl;
      break;
  }
}

ReturnValue_t PlocMPSoCHandler::prepareTcMemWrite(const uint8_t* commandData,
                                                  size_t commandDataLen) {
  ReturnValue_t result = RETURN_OK;
  sequenceCount++;
  mpsoc::TcMemWrite tcMemWrite(sequenceCount);
  result = tcMemWrite.createPacket(commandData, commandDataLen);
  if (result != RETURN_OK) {
    sequenceCount--;
    return result;
  }
  copyToCommandBuffer(&tcMemWrite);
  return RETURN_OK;
}

ReturnValue_t PlocMPSoCHandler::prepareTcMemRead(const uint8_t* commandData,
                                                 size_t commandDataLen) {
  ReturnValue_t result = RETURN_OK;
  sequenceCount++;
  mpsoc::TcMemRead tcMemRead(sequenceCount);
  result = tcMemRead.createPacket(commandData, commandDataLen);
  if (result != RETURN_OK) {
    sequenceCount--;
    return result;
  }
  copyToCommandBuffer(&tcMemRead);
  tmMemReadReport.rememberRequestedSize = tcMemRead.getMemLen() * 4 + TmMemReadReport::FIX_SIZE;
  return RETURN_OK;
}

ReturnValue_t PlocMPSoCHandler::prepareTcFlashDelete(const uint8_t* commandData,
                                                     size_t commandDataLen) {
  if (commandDataLen > config::MAX_PATH_SIZE + config::MAX_FILENAME_SIZE) {
    return MPSoCReturnValuesIF::NAME_TOO_LONG;
  }
  ReturnValue_t result = RETURN_OK;
  sequenceCount++;
  mpsoc::TcFlashDelete tcFlashDelete(sequenceCount);
  result = tcFlashDelete.createPacket(
      std::string(reinterpret_cast<const char*>(commandData), commandDataLen));
  if (result != RETURN_OK) {
    sequenceCount--;
    return result;
  }
  copyToCommandBuffer(&tcFlashDelete);
  return RETURN_OK;
}

ReturnValue_t PlocMPSoCHandler::prepareTcReplayStart(const uint8_t* commandData,
                                                     size_t commandDataLen) {
  ReturnValue_t result = RETURN_OK;
  sequenceCount++;
  mpsoc::TcReplayStart tcReplayStart(sequenceCount);
  result = tcReplayStart.createPacket(commandData, commandDataLen);
  if (result != RETURN_OK) {
    sequenceCount--;
    return result;
  }
  copyToCommandBuffer(&tcReplayStart);
  return RETURN_OK;
}

ReturnValue_t PlocMPSoCHandler::prepareTcReplayStop() {
  ReturnValue_t result = RETURN_OK;
  sequenceCount++;
  mpsoc::TcReplayStop tcReplayStop(sequenceCount);
  result = tcReplayStop.createPacket();
  if (result != RETURN_OK) {
    sequenceCount--;
    return result;
  }
  copyToCommandBuffer(&tcReplayStop);
  return RETURN_OK;
}

ReturnValue_t PlocMPSoCHandler::prepareTcDownlinkPwrOn(const uint8_t* commandData,
                                                       size_t commandDataLen) {
  ReturnValue_t result = RETURN_OK;
  sequenceCount++;
  mpsoc::TcDownlinkPwrOn tcDownlinkPwrOn(sequenceCount);
  result = tcDownlinkPwrOn.createPacket(commandData, commandDataLen);
  if (result != RETURN_OK) {
    sequenceCount--;
    return result;
  }
  copyToCommandBuffer(&tcDownlinkPwrOn);
  return RETURN_OK;
}

ReturnValue_t PlocMPSoCHandler::prepareTcDownlinkPwrOff() {
  ReturnValue_t result = RETURN_OK;
  sequenceCount++;
  mpsoc::TcDownlinkPwrOff tcDownlinkPwrOff(sequenceCount);
  result = tcDownlinkPwrOff.createPacket();
  if (result != RETURN_OK) {
    sequenceCount--;
    return result;
  }
  copyToCommandBuffer(&tcDownlinkPwrOff);
  return RETURN_OK;
}

ReturnValue_t PlocMPSoCHandler::prepareTcReplayWriteSequence(const uint8_t* commandData,
                                                             size_t commandDataLen) {
  ReturnValue_t result = RETURN_OK;
  sequenceCount++;
  mpsoc::TcReplayWriteSeq tcReplayWriteSeq(sequenceCount);
  result = tcReplayWriteSeq.createPacket(commandData, commandDataLen);
  if (result != RETURN_OK) {
    sequenceCount--;
    return result;
  }
  copyToCommandBuffer(&tcReplayWriteSeq);
  return RETURN_OK;
}

ReturnValue_t PlocMPSoCHandler::prepareTcModeReplay() {
  ReturnValue_t result = RETURN_OK;
  sequenceCount++;
  mpsoc::TcModeReplay tcModeReplay(sequenceCount);
  result = tcModeReplay.createPacket();
  if (result != RETURN_OK) {
    sequenceCount--;
    return result;
  }
  memcpy(commandBuffer, tcModeReplay.getWholeData(), tcModeReplay.getFullSize());
  rawPacket = commandBuffer;
  rawPacketLen = tcModeReplay.getFullSize();
  nextReplyId = mpsoc::ACK_REPORT;
  return RETURN_OK;
}

ReturnValue_t PlocMPSoCHandler::prepareTcModeIdle() {
  ReturnValue_t result = RETURN_OK;
  sequenceCount++;
  mpsoc::TcModeIdle tcModeIdle(sequenceCount);
  result = tcModeIdle.createPacket();
  if (result != RETURN_OK) {
    sequenceCount--;
    return result;
  }
  memcpy(commandBuffer, tcModeIdle.getWholeData(), tcModeIdle.getFullSize());
  rawPacket = commandBuffer;
  rawPacketLen = tcModeIdle.getFullSize();
  nextReplyId = mpsoc::ACK_REPORT;
  return RETURN_OK;
}

ReturnValue_t PlocMPSoCHandler::prepareTcCamCmdSend(const uint8_t* commandData,
                                                    size_t commandDataLen) {
  ReturnValue_t result = RETURN_OK;
  sequenceCount++;
  mpsoc::TcCamcmdSend tcCamCmdSend(sequenceCount);
  result = tcCamCmdSend.createPacket(commandData, commandDataLen);
  if (result != RETURN_OK) {
    sequenceCount--;
    return result;
  }
  copyToCommandBuffer(&tcCamCmdSend);
  nextReplyId = mpsoc::TM_CAM_CMD_RPT;
  return RETURN_OK;
}

void PlocMPSoCHandler::copyToCommandBuffer(mpsoc::TcBase* tc) {
  if (tc == nullptr) {
    sif::debug << "PlocMPSoCHandler::copyToCommandBuffer: Invalid TC" << std::endl;
  }
  memcpy(commandBuffer, tc->getWholeData(), tc->getFullSize());
  rawPacket = commandBuffer;
  rawPacketLen = tc->getFullSize();
  nextReplyId = mpsoc::ACK_REPORT;
}

ReturnValue_t PlocMPSoCHandler::verifyPacket(const uint8_t* start, size_t foundLen) {
  uint16_t receivedCrc = *(start + foundLen - 2) << 8 | *(start + foundLen - 1);
  uint16_t recalculatedCrc = CRC::crc16ccitt(start, foundLen - 2);
  if (receivedCrc != recalculatedCrc) {
    return MPSoCReturnValuesIF::CRC_FAILURE;
  }
  return RETURN_OK;
}

ReturnValue_t PlocMPSoCHandler::handleAckReport(const uint8_t* data) {
  ReturnValue_t result = RETURN_OK;

  result = verifyPacket(data, mpsoc::SIZE_ACK_REPORT);
  if (result == MPSoCReturnValuesIF::CRC_FAILURE) {
    sif::warning << "PlocMPSoCHandler::handleAckReport: CRC failure" << std::endl;
    nextReplyId = mpsoc::NONE;
    replyRawReplyIfnotWiretapped(data, mpsoc::SIZE_ACK_REPORT);
    triggerEvent(MPSOC_HANDLER_CRC_FAILURE);
    sendFailureReport(mpsoc::ACK_REPORT, MPSoCReturnValuesIF::CRC_FAILURE);
    disableAllReplies();
    return IGNORE_REPLY_DATA;
  }

  uint16_t apid = (*(data) << 8 | *(data + 1)) & APID_MASK;

  switch (apid) {
    case mpsoc::apid::ACK_FAILURE: {
      sif::debug << "PlocMPSoCHandler::handleAckReport: Received Ack failure report" << std::endl;
      DeviceCommandId_t commandId = getPendingCommand();
      uint16_t status = getStatus(data);
      printStatus(data);
      if (commandId != DeviceHandlerIF::NO_COMMAND_ID) {
        triggerEvent(ACK_FAILURE, commandId, status);
      }
      sendFailureReport(mpsoc::ACK_REPORT, status);
      disableAllReplies();
      nextReplyId = mpsoc::NONE;
      result = IGNORE_REPLY_DATA;
      break;
    }
    case mpsoc::apid::ACK_SUCCESS: {
      setNextReplyId();
      break;
    }
    default: {
      sif::debug << "PlocMPSoCHandler::handleAckReport: Invalid APID in Ack report" << std::endl;
      result = RETURN_FAILED;
      break;
    }
  }

  return result;
}

ReturnValue_t PlocMPSoCHandler::handleExecutionReport(const uint8_t* data) {
  ReturnValue_t result = RETURN_OK;

  result = verifyPacket(data, mpsoc::SIZE_EXE_REPORT);
  if (result == MPSoCReturnValuesIF::CRC_FAILURE) {
    sif::warning << "PlocMPSoCHandler::handleExecutionReport: CRC failure" << std::endl;
    nextReplyId = mpsoc::NONE;
    return result;
  }

  uint16_t apid = (*(data) << 8 | *(data + 1)) & APID_MASK;

  switch (apid) {
    case (mpsoc::apid::EXE_SUCCESS): {
      break;
    }
    case (mpsoc::apid::EXE_FAILURE): {
      // TODO: Interpretation of status field in execution report
      sif::warning << "PlocMPSoCHandler::handleExecutionReport: Received execution failure report"
                   << std::endl;
      DeviceCommandId_t commandId = getPendingCommand();
      if (commandId != DeviceHandlerIF::NO_COMMAND_ID) {
        uint16_t status = getStatus(data);
        triggerEvent(EXE_FAILURE, commandId, status);
      } else {
        sif::debug << "PlocMPSoCHandler::handleExecutionReport: Unknown command id" << std::endl;
      }
      printStatus(data);
      sendFailureReport(mpsoc::EXE_REPORT, MPSoCReturnValuesIF::RECEIVED_EXE_FAILURE);
      disableExeReportReply();
      result = IGNORE_REPLY_DATA;
      break;
    }
    default: {
      sif::warning << "PlocMPSoCHandler::handleExecutionReport: Unknown APID" << std::endl;
      result = RETURN_FAILED;
      break;
    }
  }
  nextReplyId = mpsoc::NONE;
  return result;
}

ReturnValue_t PlocMPSoCHandler::handleMemoryReadReport(const uint8_t* data) {
  ReturnValue_t result = RETURN_OK;
  result = verifyPacket(data, tmMemReadReport.rememberRequestedSize);
  if (result == MPSoCReturnValuesIF::CRC_FAILURE) {
    sif::warning << "PlocMPSoCHandler::handleMemoryReadReport: Memory read report has invalid crc"
                 << std::endl;
  }
  uint16_t memLen =
      *(data + mpsoc::MEM_READ_RPT_LEN_OFFSET) << 8 | *(data + mpsoc::MEM_READ_RPT_LEN_OFFSET + 1);
  /** Send data to commanding queue */
  handleDeviceTM(data + mpsoc::DATA_FIELD_OFFSET, mpsoc::SIZE_MEM_READ_RPT_FIX + memLen * 4,
                 mpsoc::TM_MEMORY_READ_REPORT);
  nextReplyId = mpsoc::EXE_REPORT;
  return result;
}

ReturnValue_t PlocMPSoCHandler::handleCamCmdRpt(const uint8_t* data) {
  ReturnValue_t result = RETURN_OK;
  SpacePacket packet;
  std::memcpy(packet.getWholeData(), data, tmCamCmdRpt.rememberSpacePacketSize);
  result = verifyPacket(data, tmCamCmdRpt.rememberSpacePacketSize);
  if (result == MPSoCReturnValuesIF::CRC_FAILURE) {
    sif::warning << "PlocMPSoCHandler::handleCamCmdRpt: CRC failure" << std::endl;
  }
  const uint8_t* dataFieldPtr = data + mpsoc::SPACE_PACKET_HEADER_SIZE + sizeof(uint16_t);
  std::string camCmdRptMsg(
      reinterpret_cast<const char*>(dataFieldPtr),
      tmCamCmdRpt.rememberSpacePacketSize - mpsoc::SPACE_PACKET_HEADER_SIZE - sizeof(uint16_t) - 3);
#if OBSW_DEBUG_PLOC_MPSOC == 1
  uint8_t ackValue = *(packet.getPacketData() + packet.getPacketDataLength() - 2);
  sif::info << "PlocMPSoCHandler: CamCmdRpt message: " << camCmdRptMsg << std::endl;
  sif::info << "PlocMPSoCHandler: CamCmdRpt Ack value: 0x" << std::hex
            << static_cast<unsigned int>(ackValue) << std::endl;
#endif /* OBSW_DEBUG_PLOC_MPSOC == 1 */
  handleDeviceTM(packet.getPacketData() + sizeof(uint16_t), packet.getPacketDataLength() - 1,
                 mpsoc::TM_CAM_CMD_RPT);
  return result;
}

ReturnValue_t PlocMPSoCHandler::enableReplyInReplyMap(DeviceCommandMap::iterator command,
                                                      uint8_t expectedReplies, bool useAlternateId,
                                                      DeviceCommandId_t alternateReplyID) {
  ReturnValue_t result = RETURN_OK;

  uint8_t enabledReplies = 0;

  switch (command->first) {
    case mpsoc::TC_MEM_WRITE:
    case mpsoc::TC_FLASHDELETE:
    case mpsoc::TC_REPLAY_START:
    case mpsoc::TC_REPLAY_STOP:
    case mpsoc::TC_DOWNLINK_PWR_ON:
    case mpsoc::TC_DOWNLINK_PWR_OFF:
    case mpsoc::TC_REPLAY_WRITE_SEQUENCE:
    case mpsoc::TC_MODE_REPLAY:
    case mpsoc::TC_MODE_IDLE:
      enabledReplies = 2;
      break;
    case mpsoc::TC_MEM_READ: {
      enabledReplies = 3;
      result = DeviceHandlerBase::enableReplyInReplyMap(command, enabledReplies, true,
                                                        mpsoc::TM_MEMORY_READ_REPORT);
      if (result != RETURN_OK) {
        sif::debug << "PlocMPSoCHandler::enableReplyInReplyMap: Reply with id "
                   << mpsoc::TM_MEMORY_READ_REPORT << " not in replyMap" << std::endl;
        return result;
      }
      break;
    }
    case mpsoc::TC_CAM_CMD_SEND: {
      enabledReplies = 3;
      result = DeviceHandlerBase::enableReplyInReplyMap(command, enabledReplies, true,
                                                        mpsoc::TM_CAM_CMD_RPT);
      if (result != RETURN_OK) {
        sif::debug << "PlocMPSoCHandler::enableReplyInReplyMap: Reply with id "
                   << mpsoc::TM_CAM_CMD_RPT << " not in replyMap" << std::endl;
        return result;
      }
      break;
    }
    case mpsoc::OBSW_RESET_SEQ_COUNT:
      break;
    default:
      sif::debug << "PlocMPSoCHandler::enableReplyInReplyMap: Unknown command id" << std::endl;
      break;
  }

  /**
   * Every command causes at least one acknowledgment and one execution report. Therefore both
   * replies will be enabled here.
   */
  result =
      DeviceHandlerBase::enableReplyInReplyMap(command, enabledReplies, true, mpsoc::ACK_REPORT);
  if (result != RETURN_OK) {
    sif::debug << "PlocMPSoCHandler::enableReplyInReplyMap: Reply with id " << mpsoc::ACK_REPORT
               << " not in replyMap" << std::endl;
  }

  result =
      DeviceHandlerBase::enableReplyInReplyMap(command, enabledReplies, true, mpsoc::EXE_REPORT);
  if (result != RETURN_OK) {
    sif::debug << "PlocMPSoCHandler::enableReplyInReplyMap: Reply with id " << mpsoc::EXE_REPORT
               << " not in replyMap" << std::endl;
  }

  switch (command->first) {
    case mpsoc::TC_REPLAY_WRITE_SEQUENCE: {
      DeviceReplyIter iter = deviceReplyMap.find(mpsoc::EXE_REPORT);
      // Overwrite delay cycles because replay write sequence command can required up to
      // 30 seconds for execution
      iter->second.delayCycles = mpsoc::TC_WRITE_SEQ_EXECUTION_DELAY;
      break;
    }
    case mpsoc::TC_DOWNLINK_PWR_ON: {
      DeviceReplyIter iter = deviceReplyMap.find(mpsoc::EXE_REPORT);
      //
      iter->second.delayCycles = mpsoc::TC_DOWNLINK_PWR_ON;
      break;
    }
    default:
      break;
  }

  return RETURN_OK;
}

void PlocMPSoCHandler::setNextReplyId() {
  switch (getPendingCommand()) {
    case mpsoc::TC_MEM_READ:
      nextReplyId = mpsoc::TM_MEMORY_READ_REPORT;
      break;
    default:
      /* If no telemetry is expected the next reply is always the execution report */
      nextReplyId = mpsoc::EXE_REPORT;
      break;
  }
}
size_t PlocMPSoCHandler::getNextReplyLength(DeviceCommandId_t commandId) {
  size_t replyLen = 0;

  if (nextReplyId == mpsoc::NONE) {
    return replyLen;
  }

  DeviceReplyIter iter = deviceReplyMap.find(nextReplyId);

  if (iter != deviceReplyMap.end()) {
    if (iter->second.delayCycles == 0) {
      /* Reply inactive */
      return replyLen;
    }
    switch (nextReplyId) {
      case mpsoc::TM_MEMORY_READ_REPORT: {
        replyLen = tmMemReadReport.rememberRequestedSize;
        break;
      }
      case mpsoc::TM_CAM_CMD_RPT:
        // Read acknowledgment, camera and execution report in one go because length of camera
        // report is not fixed
        replyLen = SpacePacket::PACKET_MAX_SIZE;
        break;
      default: {
        replyLen = iter->second.replyLen;
        break;
      }
    }
  } else {
    sif::debug << "PlocMPSoCHandler::getNextReplyLength: No entry for reply with reply id "
               << std::hex << nextReplyId << " in deviceReplyMap" << std::endl;
  }

  return replyLen;
}

ReturnValue_t PlocMPSoCHandler::doSendReadHook() {
  // Prevent DHB from polling UART during commands executed by the mpsoc helper task
  if (plocMPSoCHelperExecuting) {
    return RETURN_FAILED;
  }
  return RETURN_OK;
}

MessageQueueIF* PlocMPSoCHandler::getCommandQueuePtr() { return commandActionHelperQueue; }

void PlocMPSoCHandler::stepSuccessfulReceived(ActionId_t actionId, uint8_t step) { return; }

void PlocMPSoCHandler::stepFailedReceived(ActionId_t actionId, uint8_t step,
                                          ReturnValue_t returnCode) {
  switch (actionId) {
    case supv::START_MPSOC: {
      sif::warning << "PlocMPSoCHandler::stepFailedReceived: Failed to start MPSoC" << std::endl;
      // This usually happens when the supervisor handler is in off mode
      powerState = PowerState::OFF;
      setMode(MODE_OFF);
      break;
    }
    case supv::SHUTDOWN_MPSOC: {
      triggerEvent(MPSOC_SHUTDOWN_FAILED);
      sif::warning << "PlocMPSoCHandler::stepFailedReceived: Failed to shutdown MPSoC" << std::endl;
      powerState = PowerState::OFF;
      break;
    }
    default:
      sif::debug << "PlocMPSoCHandler::stepFailedReceived: Received unexpected action reply"
                 << std::endl;
      break;
  }
}

void PlocMPSoCHandler::dataReceived(ActionId_t actionId, const uint8_t* data, uint32_t size) {
  return;
}

void PlocMPSoCHandler::completionSuccessfulReceived(ActionId_t actionId) {
  if (actionId != supv::EXE_REPORT) {
    sif::debug << "PlocMPSoCHandler::completionSuccessfulReceived: Did not expect this action "
               << "ID" << std::endl;
    return;
  }
  switch (powerState) {
    case PowerState::BOOTING: {
      powerState = PowerState::ON;
      break;
    }
    case PowerState::SHUTDOWN: {
      powerState = PowerState::OFF;
      break;
    }
    default: {
      break;
    }
  }
}

void PlocMPSoCHandler::completionFailedReceived(ActionId_t actionId, ReturnValue_t returnCode) {
  handleActionCommandFailure(actionId);
}

void PlocMPSoCHandler::handleDeviceTM(const uint8_t* data, size_t dataSize,
                                      DeviceCommandId_t replyId) {
  ReturnValue_t result = RETURN_OK;

  if (wiretappingMode == RAW) {
    /* Data already sent in doGetRead() */
    return;
  }

  DeviceReplyMap::iterator iter = deviceReplyMap.find(replyId);
  if (iter == deviceReplyMap.end()) {
    sif::debug << "PlocMPSoCHandler::handleDeviceTM: Unknown reply id" << std::endl;
    return;
  }
  MessageQueueId_t queueId = iter->second.command->second.sendReplyTo;

  if (queueId == NO_COMMANDER) {
    return;
  }

  result = actionHelper.reportData(queueId, replyId, data, dataSize);
  if (result != RETURN_OK) {
    sif::debug << "PlocMPSoCHandler::handleDeviceTM: Failed to report data" << std::endl;
  }
}

void PlocMPSoCHandler::disableAllReplies() {
  using namespace mpsoc;
  DeviceReplyMap::iterator iter;

  /* Disable ack reply */
  iter = deviceReplyMap.find(ACK_REPORT);
  DeviceReplyInfo* info = &(iter->second);
  info->delayCycles = 0;
  info->command = deviceCommandMap.end();

  DeviceCommandId_t commandId = getPendingCommand();

  /* If the command expects a telemetry packet the appropriate tm reply will be disabled here */
  switch (commandId) {
    case TC_MEM_WRITE:
      break;
    case TC_MEM_READ: {
      iter = deviceReplyMap.find(TM_MEMORY_READ_REPORT);
      info = &(iter->second);
      info->delayCycles = 0;
      info->active = false;
      info->command = deviceCommandMap.end();
      break;
    }
    case TC_CAM_CMD_SEND: {
      iter = deviceReplyMap.find(TM_CAM_CMD_RPT);
      info = &(iter->second);
      info->delayCycles = 0;
      info->active = false;
      info->command = deviceCommandMap.end();
      break;
    }
    default: {
      sif::debug << "PlocMPSoCHandler::disableAllReplies: Unknown command id: " << commandId
                 << std::endl;
      break;
    }
  }

  /* We always need to disable the execution report reply here */
  disableExeReportReply();
  nextReplyId = mpsoc::NONE;
}

void PlocMPSoCHandler::sendFailureReport(DeviceCommandId_t replyId, ReturnValue_t status) {
  DeviceReplyIter iter = deviceReplyMap.find(replyId);
  if (iter == deviceReplyMap.end()) {
    sif::debug << "PlocMPSoCHandler::sendFailureReport: Reply not in reply map" << std::endl;
    return;
  }
  DeviceCommandInfo* info = &(iter->second.command->second);
  if (info == nullptr) {
    sif::debug << "PlocMPSoCHandler::sendFailureReport: Reply has no active command" << std::endl;
    return;
  }
  if (info->sendReplyTo != NO_COMMANDER) {
    actionHelper.finish(false, info->sendReplyTo, iter->first, status);
  }
  info->isExecuting = false;
}

void PlocMPSoCHandler::disableExeReportReply() {
  DeviceReplyIter iter = deviceReplyMap.find(mpsoc::EXE_REPORT);
  DeviceReplyInfo* info = &(iter->second);
  info->delayCycles = 0;
  info->command = deviceCommandMap.end();
  /* Expected replies is set to one here. The value will be set to 0 in replyToReply() */
  info->command->second.expectedReplies = 0;
}

void PlocMPSoCHandler::printStatus(const uint8_t* data) {
  uint16_t status = *(data + STATUS_OFFSET) << 8 | *(data + STATUS_OFFSET + 1);
  sif::info << "Verification report status: " << getStatusString(status) << std::endl;
}

uint16_t PlocMPSoCHandler::getStatus(const uint8_t* data) {
  return *(data + STATUS_OFFSET) << 8 | *(data + STATUS_OFFSET + 1);
}

void PlocMPSoCHandler::handleActionCommandFailure(ActionId_t actionId) {
  switch (actionId) {
    case supv::ACK_REPORT:
    case supv::EXE_REPORT:
      break;
    default:
      sif::debug << "PlocMPSoCHandler::handleActionCommandFailure: Did not expect this action ID "
                 << std::endl;
      return;
  }
  switch (powerState) {
    case PowerState::BOOTING: {
      sif::info << "PlocMPSoCHandler::handleActionCommandFailure: MPSoC boot command failed"
                << std::endl;
      // This is commonly the case when the MPSoC is already operational. Thus the power state is
      // set to on here
      powerState = PowerState::ON;
      break;
    }
    case PowerState::SHUTDOWN: {
      // FDIR will intercept event and switch PLOC power off
      triggerEvent(MPSOC_SHUTDOWN_FAILED);
      sif::warning << "PlocMPSoCHandler::handleActionCommandFailure: Failed to shutdown MPSoC"
                   << std::endl;
      powerState = PowerState::OFF;
      break;
    }
    default:
      break;
  }
  return;
}

std::string PlocMPSoCHandler::getStatusString(uint16_t status) {
  switch (status) {
    case (mpsoc::status_code::UNKNOWN_APID): {
      return "Unknown APID";
      break;
    }
    case (mpsoc::status_code::INCORRECT_LENGTH): {
      return "Incorrect length";
      break;
    }
    case (mpsoc::status_code::INCORRECT_CRC): {
      return "Incorrect crc";
      break;
    }
    case (mpsoc::status_code::INCORRECT_PKT_SEQ_CNT): {
      return "Incorrect packet sequence count";
      break;
    }
    case (mpsoc::status_code::TC_NOT_ALLOWED_IN_MODE): {
      return "TC not allowed in this mode";
      break;
    }
    case (mpsoc::status_code::TC_EXEUTION_DISABLED): {
      return "TC execution disabled";
      break;
    }
    case (mpsoc::status_code::FLASH_MOUNT_FAILED): {
      return "Flash mount failed";
      break;
    }
    case (mpsoc::status_code::FLASH_FILE_ALREADY_CLOSED): {
      return "Flash file already closed";
      break;
    }
    case (mpsoc::status_code::FLASH_FILE_NOT_OPEN): {
      return "Flash file not open";
      break;
    }
    case (mpsoc::status_code::FLASH_UNMOUNT_FAILED): {
      return "Flash unmount failed";
      break;
    }
    case (mpsoc::status_code::HEAP_ALLOCATION_FAILED): {
      return "Heap allocation failed";
      break;
    }
    case (mpsoc::status_code::INVALID_PARAMETER): {
      return "Invalid parameter";
      break;
    }
    case (mpsoc::status_code::NOT_INITIALIZED): {
      return "Not initialized";
      break;
    }
    case (mpsoc::status_code::REBOOT_IMMINENT): {
      return "Reboot imminent";
      break;
    }
    case (mpsoc::status_code::CORRUPT_DATA): {
      return "Corrupt data";
      break;
    }
    case (mpsoc::status_code::FLASH_CORRECTABLE_MISMATCH): {
      return "Flash correctable mismatch";
      break;
    }
    case (mpsoc::status_code::FLASH_UNCORRECTABLE_MISMATCH): {
      return "Flash uncorrectable mismatch";
      break;
    }
    default:
      break;
  }
  return "";
}