#include <fsfw/globalfunctions/arrayprinter.h>
#include <linux/payload/PlocMpsocHandler.h>
#include <linux/payload/plocSupvDefs.h>

#include <sstream>

#include "OBSWConfig.h"
#include "fsfw/datapool/PoolReadGuard.h"
#include "fsfw/globalfunctions/CRC.h"

PlocMpsocHandler::PlocMpsocHandler(object_id_t objectId, object_id_t uartComIFid,
                                   CookieIF* comCookie, PlocMpsocSpecialComHelper* plocMPSoCHelper,
                                   Gpio uartIsolatorSwitch, object_id_t supervisorHandler)
    : DeviceHandlerBase(objectId, uartComIFid, comCookie),
      hkReport(this),
      specialComHelper(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);
  spParams.maxSize = sizeof(commandBuffer);
  spParams.buf = commandBuffer;
}

PlocMpsocHandler::~PlocMpsocHandler() {}

ReturnValue_t PlocMpsocHandler::initialize() {
  ReturnValue_t result = returnvalue::OK;
  result = DeviceHandlerBase::initialize();
  if (result != returnvalue::OK) {
    return result;
  }
  uartComIf = dynamic_cast<SerialComIF*>(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 != returnvalue::OK) {
    return result;
  }
  result = manager->subscribeToEvent(
      eventQueue->getId(), event::getEventId(PlocMpsocSpecialComHelper::MPSOC_FLASH_WRITE_FAILED));
  if (result != returnvalue::OK) {
    return ObjectManagerIF::CHILD_INIT_FAILED;
  }
  result = manager->subscribeToEvent(
      eventQueue->getId(),
      event::getEventId(PlocMpsocSpecialComHelper::MPSOC_FLASH_WRITE_SUCCESSFUL));
  if (result != returnvalue::OK) {
    return ObjectManagerIF::CHILD_INIT_FAILED;
  }
  result = manager->subscribeToEvent(
      eventQueue->getId(),
      event::getEventId(PlocMpsocSpecialComHelper::MPSOC_FLASH_READ_SUCCESSFUL));
  if (result != returnvalue::OK) {
    return ObjectManagerIF::CHILD_INIT_FAILED;
  }
  result = manager->subscribeToEvent(
      eventQueue->getId(), event::getEventId(PlocMpsocSpecialComHelper::MPSOC_FLASH_READ_FAILED));
  if (result != returnvalue::OK) {
    return ObjectManagerIF::CHILD_INIT_FAILED;
  }

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

void PlocMpsocHandler::performOperationHook() {
  if (commandIsPending and cmdCountdown.hasTimedOut()) {
    sif::warning << "PlocMpsocHandler: Command " << getPendingCommand() << " has timed out"
                 << std::endl;
    commandIsPending = false;
    // TODO: Better returnvalue?
    cmdDoneHandler(false, returnvalue::FAILED);
  }
  EventMessage event;
  for (ReturnValue_t result = eventQueue->receiveMessage(&event); result == returnvalue::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 == returnvalue::OK; result = commandActionHelperQueue->receiveMessage(&message)) {
    result = commandActionHelper.handleReply(&message);
    if (result == returnvalue::OK) {
      continue;
    }
  }
}

ReturnValue_t PlocMpsocHandler::executeAction(ActionId_t actionId, MessageQueueId_t commandedBy,
                                              const uint8_t* data, size_t size) {
  ReturnValue_t result = returnvalue::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 (specialComHelperExecuting) {
    return MPSoCReturnValuesIF::MPSOC_HELPER_EXECUTING;
  }

  switch (actionId) {
    case mpsoc::TC_FLASH_WRITE_FULL_FILE: {
      mpsoc::FlashBasePusCmd flashWritePusCmd;
      result = flashWritePusCmd.extractFields(data, size);
      if (result != returnvalue::OK) {
        return result;
      }
      result = specialComHelper->startFlashWrite(flashWritePusCmd.getObcFile(),
                                                 flashWritePusCmd.getMPSoCFile());
      if (result != returnvalue::OK) {
        return result;
      }
      specialComHelperExecuting = true;
      return EXECUTION_FINISHED;
    }
    case mpsoc::TC_FLASH_READ_FULL_FILE: {
      mpsoc::FlashReadPusCmd flashReadPusCmd;
      result = flashReadPusCmd.extractFields(data, size);
      if (result != returnvalue::OK) {
        return result;
      }
      result = specialComHelper->startFlashRead(flashReadPusCmd.getObcFile(),
                                                flashReadPusCmd.getMPSoCFile(),
                                                flashReadPusCmd.getReadSize());
      if (result != returnvalue::OK) {
        return result;
      }
      specialComHelperExecuting = true;
      return EXECUTION_FINISHED;
    }
    case (mpsoc::OBSW_RESET_SEQ_COUNT): {
      sequenceCount = 0;
      return EXECUTION_FINISHED;
    }
    default:
      break;
  }
  // For longer commands, do not set these.
  commandIsPending = true;
  cmdCountdown.resetTimer();
  return DeviceHandlerBase::executeAction(actionId, commandedBy, data, size);
}

void PlocMpsocHandler::doStartUp() {
  if (startupState == StartupState::IDLE) {
    startupState = StartupState::HW_INIT;
  }
  if (startupState == StartupState::HW_INIT) {
    if (handleHwStartup()) {
      startupState = StartupState::DONE;
    }
  }
  if (startupState == StartupState::DONE) {
    setMode(_MODE_TO_ON);
    hkReport.setReportingEnabled(true);
    powerState = PowerState::IDLE;
    startupState = StartupState::IDLE;
  }
}

void PlocMpsocHandler::doShutDown() {
  if (handleHwShutdown()) {
    hkReport.setReportingEnabled(false);
    setMode(_MODE_POWER_DOWN);
    commandIsPending = false;
    sequenceCount = 0;
    powerState = PowerState::IDLE;
    startupState = StartupState::IDLE;
  }
}

ReturnValue_t PlocMpsocHandler::buildNormalDeviceCommand(DeviceCommandId_t* id) {
  if (not commandIsPending and not specialComHelperExecuting) {
    *id = mpsoc::TC_GET_HK_REPORT;
    commandIsPending = true;
    return buildCommandFromCommand(*id, nullptr, 0);
  }
  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) {
  spParams.buf = commandBuffer;
  ReturnValue_t result = returnvalue::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_GET_HK_REPORT): {
      result = prepareTcGetHkReport();
      break;
    }
    case (mpsoc::TC_FLASH_GET_DIRECTORY_CONTENT): {
      result = prepareTcGetDirContent(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;
    }
    case (mpsoc::TC_CAM_TAKE_PIC): {
      result = prepareTcCamTakePic(commandData, commandDataLen);
      break;
    }
    case (mpsoc::TC_SIMPLEX_SEND_FILE): {
      result = prepareTcSimplexSendFile(commandData, commandDataLen);
      break;
    }
    case (mpsoc::TC_DOWNLINK_DATA_MODULATE): {
      result = prepareTcDownlinkDataModulate(commandData, commandDataLen);
      break;
    }
    case (mpsoc::TC_MODE_SNAPSHOT): {
      result = prepareTcModeSnapshot();
      break;
    }
    default:
      sif::debug << "PlocMPSoCHandler::buildCommandFromCommand: Command not implemented"
                 << std::endl;
      result = DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED;
      break;
  }

  if (result == returnvalue::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);
  insertInCommandMap(mpsoc::TC_FLASH_WRITE_FULL_FILE);
  insertInCommandMap(mpsoc::TC_FLASH_READ_FULL_FILE);
  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::TC_GET_HK_REPORT);
  this->insertInCommandMap(mpsoc::RELEASE_UART_TX);
  this->insertInCommandMap(mpsoc::SET_UART_TX_TRISTATE);
  this->insertInCommandMap(mpsoc::TC_CAM_TAKE_PIC);
  this->insertInCommandMap(mpsoc::TC_FLASH_GET_DIRECTORY_CONTENT);
  this->insertInCommandMap(mpsoc::TC_SIMPLEX_SEND_FILE);
  this->insertInCommandMap(mpsoc::TC_DOWNLINK_DATA_MODULATE);
  this->insertInCommandMap(mpsoc::TC_MODE_SNAPSHOT);
  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_GET_HK_REPORT, 5, nullptr, mpsoc::SIZE_TM_HK_REPORT);
  this->insertInReplyMap(mpsoc::TM_CAM_CMD_RPT, 2, nullptr, mpsoc::SP_MAX_SIZE);
  this->insertInReplyMap(mpsoc::TM_FLASH_DIRECTORY_CONTENT, 2, nullptr, mpsoc::SP_MAX_SIZE);
}

ReturnValue_t PlocMpsocHandler::scanForReply(const uint8_t* start, size_t remainingSize,
                                             DeviceCommandId_t* foundId, size_t* foundLen) {
  ReturnValue_t result = returnvalue::OK;

  SpacePacketReader spacePacket;
  spacePacket.setReadOnlyData(start, remainingSize);
  if (DEBUG_MPSOC_COMMUNICATION) {
    sif::debug << "RECV MPSOC packet. APID 0x" << std::hex << std::setw(3) << spacePacket.getApid()
               << std::dec << " Size " << spacePacket.getFullPacketLen() << " SSC "
               << spacePacket.getSequenceCount() << std::endl;
  }
  if (spacePacket.isNull()) {
    return returnvalue::FAILED;
  }
  auto res = spacePacket.checkSize();
  if (res != returnvalue::OK) {
    return res;
  }
  uint16_t apid = spacePacket.getApid();

  auto handleDedicatedReply = [&](DeviceCommandId_t replyId) {
    *foundLen = spacePacket.getFullPacketLen();
    foundPacketLen = *foundLen;
    *foundId = replyId;
  };
  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):
      handleDedicatedReply(mpsoc::TM_CAM_CMD_RPT);
      break;
    case (mpsoc::apid::TM_HK_GET_REPORT): {
      handleDedicatedReply(mpsoc::TM_GET_HK_REPORT);
      break;
    }
    case (mpsoc::apid::TM_FLASH_DIRECTORY_CONTENT): {
      handleDedicatedReply(mpsoc::TM_FLASH_DIRECTORY_CONTENT);
      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 0x" << std::hex
                 << std::setfill('0') << std::setw(2) << apid << std::dec << std::endl;
      *foundLen = remainingSize;
      return MPSoCReturnValuesIF::INVALID_APID;
    }
  }

  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;
  }
  // This sequence count ping pong does not make any sense but it is how the MPSoC expects it.
  sequenceCount++;

  return result;
}

ReturnValue_t PlocMpsocHandler::interpretDeviceReply(DeviceCommandId_t id, const uint8_t* packet) {
  ReturnValue_t result = returnvalue::OK;

  switch (id) {
    case mpsoc::ACK_REPORT: {
      result = handleAckReport(packet);
      break;
    }
    case (mpsoc::TM_MEMORY_READ_REPORT): {
      result = handleMemoryReadReport(packet);
      break;
    }
    case (mpsoc::TM_GET_HK_REPORT): {
      result = handleGetHkReport(packet);
      break;
    }
    case (mpsoc::TM_CAM_CMD_RPT): {
      result = handleCamCmdRpt(packet);
      break;
    }
    case (mpsoc::TM_FLASH_DIRECTORY_CONTENT): {
      result = verifyPacket(packet, foundPacketLen);
      if (result == MPSoCReturnValuesIF::CRC_FAILURE) {
        sif::warning << "PLOC MPSoC: Flash directory content reply invalid CRC" << std::endl;
      }
      /** Send data to commanding queue */
      handleDeviceTm(packet + mpsoc::DATA_FIELD_OFFSET,
                     foundPacketLen - mpsoc::DATA_FIELD_OFFSET - mpsoc::CRC_SIZE,
                     mpsoc::TM_FLASH_DIRECTORY_CONTENT);
      nextReplyId = mpsoc::EXE_REPORT;
      return result;
    }
    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() {
  PoolReadGuard pg(&hkReport);
  hkReport.setValidity(false, true);
}

uint32_t PlocMpsocHandler::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 15000; }

ReturnValue_t PlocMpsocHandler::initializeLocalDataPool(localpool::DataPool& localDataPoolMap,
                                                        LocalDataPoolManager& poolManager) {
  localDataPoolMap.emplace(mpsoc::poolid::STATUS, &peStatus);
  localDataPoolMap.emplace(mpsoc::poolid::MODE, &peMode);
  localDataPoolMap.emplace(mpsoc::poolid::DOWNLINK_PWR_ON, &peDownlinkPwrOn);
  localDataPoolMap.emplace(mpsoc::poolid::DOWNLINK_REPLY_ACTIIVE, &peDownlinkReplyActive);
  localDataPoolMap.emplace(mpsoc::poolid::DOWNLINK_JESD_SYNC_STATUS, &peDownlinkJesdSyncStatus);
  localDataPoolMap.emplace(mpsoc::poolid::DOWNLINK_DAC_STATUS, &peDownlinkDacStatus);
  localDataPoolMap.emplace(mpsoc::poolid::CAM_STATUS, &peCameraStatus);
  localDataPoolMap.emplace(mpsoc::poolid::CAM_SDI_STATUS, &peCameraSdiStatus);
  localDataPoolMap.emplace(mpsoc::poolid::CAM_FPGA_TEMP, &peCameraFpgaTemp);
  localDataPoolMap.emplace(mpsoc::poolid::CAM_SOC_TEMP, &peCameraSocTemp);
  localDataPoolMap.emplace(mpsoc::poolid::SYSMON_TEMP, &peSysmonTemp);
  localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCCINT, &peSysmonVccInt);
  localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCCAUX, &peSysmonVccAux);
  localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCCBRAM, &peSysmonVccBram);
  localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCCPAUX, &peSysmonVccPaux);
  localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCCPINT, &peSysmonVccPint);
  localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCCPDRO, &peSysmonVccPdro);
  localDataPoolMap.emplace(mpsoc::poolid::SYSMON_MB12V, &peSysmonMb12V);
  localDataPoolMap.emplace(mpsoc::poolid::SYSMON_MB3V3, &peSysmonMb3V3);
  localDataPoolMap.emplace(mpsoc::poolid::SYSMON_MB1V8, &peSysmonMb1V8);
  localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC12V, &peSysmonVcc12V);
  localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC5V, &peSysmonVcc5V);
  localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC3V3, &peSysmonVcc3V3);
  localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC3V3VA, &peSysmonVcc3V3VA);
  localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC2V5DDR, &peSysmonVcc2V5DDR);
  localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC1V2DDR, &peSysmonVcc1V2DDR);
  localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC0V9, &peSysmonVcc0V9);
  localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC0V6VTT, &peSysmonVcc0V6VTT);
  localDataPoolMap.emplace(mpsoc::poolid::SYSMON_SAFE_COTS_CUR, &peSysmonSafeCotsCur);
  localDataPoolMap.emplace(mpsoc::poolid::SYSMON_NVM4_XO_CUR, &peSysmonNvm4XoCur);
  localDataPoolMap.emplace(mpsoc::poolid::SEM_UNCORRECTABLE_ERRS, &peSemUncorrectableErrs);
  localDataPoolMap.emplace(mpsoc::poolid::SEM_CORRECTABLE_ERRS, &peSemCorrectableErrs);
  localDataPoolMap.emplace(mpsoc::poolid::SEM_STATUS, &peSemStatus);
  localDataPoolMap.emplace(mpsoc::poolid::REBOOT_MPSOC_REQUIRED, &peRebootMpsocRequired);
  poolManager.subscribeForRegularPeriodicPacket(
      subdp::RegularHkPeriodicParams(hkReport.getSid(), false, 10.0));
  return returnvalue::OK;
}

void PlocMpsocHandler::handleEvent(EventMessage* eventMessage) {
  object_id_t objectId = eventMessage->getReporter();
  switch (objectId) {
    case objects::PLOC_MPSOC_HELPER: {
      specialComHelperExecuting = 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 = returnvalue::OK;
  mpsoc::TcMemWrite tcMemWrite(spParams, sequenceCount);
  result = tcMemWrite.setPayload(commandData, commandDataLen);
  if (result != returnvalue::OK) {
    return result;
  }
  finishTcPrep(tcMemWrite);
  return returnvalue::OK;
}

ReturnValue_t PlocMpsocHandler::prepareTcMemRead(const uint8_t* commandData,
                                                 size_t commandDataLen) {
  ReturnValue_t result = returnvalue::OK;
  mpsoc::TcMemRead tcMemRead(spParams, sequenceCount);
  result = tcMemRead.setPayload(commandData, commandDataLen);
  if (result != returnvalue::OK) {
    return result;
  }
  finishTcPrep(tcMemRead);
  tmMemReadReport.rememberRequestedSize = tcMemRead.getMemLen() * 4 + TmMemReadReport::FIX_SIZE;
  return returnvalue::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 = returnvalue::OK;
  mpsoc::TcFlashDelete tcFlashDelete(spParams, sequenceCount);
  std::string filename = std::string(reinterpret_cast<const char*>(commandData), commandDataLen);
  result = tcFlashDelete.setPayload(filename);
  if (result != returnvalue::OK) {
    return result;
  }
  finishTcPrep(tcFlashDelete);
  return returnvalue::OK;
}

ReturnValue_t PlocMpsocHandler::prepareTcReplayStart(const uint8_t* commandData,
                                                     size_t commandDataLen) {
  ReturnValue_t result = returnvalue::OK;
  mpsoc::TcReplayStart tcReplayStart(spParams, sequenceCount);
  result = tcReplayStart.setPayload(commandData, commandDataLen);
  if (result != returnvalue::OK) {
    return result;
  }
  finishTcPrep(tcReplayStart);
  return returnvalue::OK;
}

ReturnValue_t PlocMpsocHandler::prepareTcReplayStop() {
  mpsoc::TcReplayStop tcReplayStop(spParams, sequenceCount);
  finishTcPrep(tcReplayStop);
  return returnvalue::OK;
}

ReturnValue_t PlocMpsocHandler::prepareTcDownlinkPwrOn(const uint8_t* commandData,
                                                       size_t commandDataLen) {
  ReturnValue_t result = returnvalue::OK;
  mpsoc::TcDownlinkPwrOn tcDownlinkPwrOn(spParams, sequenceCount);
  result = tcDownlinkPwrOn.setPayload(commandData, commandDataLen);
  if (result != returnvalue::OK) {
    return result;
  }
  finishTcPrep(tcDownlinkPwrOn);
  return returnvalue::OK;
}

ReturnValue_t PlocMpsocHandler::prepareTcDownlinkPwrOff() {
  mpsoc::TcDownlinkPwrOff tcDownlinkPwrOff(spParams, sequenceCount);
  finishTcPrep(tcDownlinkPwrOff);
  return returnvalue::OK;
}

ReturnValue_t PlocMpsocHandler::prepareTcGetHkReport() {
  mpsoc::TcGetHkReport tcGetHkReport(spParams, sequenceCount);
  finishTcPrep(tcGetHkReport);
  return returnvalue::OK;
}

ReturnValue_t PlocMpsocHandler::prepareTcReplayWriteSequence(const uint8_t* commandData,
                                                             size_t commandDataLen) {
  mpsoc::TcReplayWriteSeq tcReplayWriteSeq(spParams, sequenceCount);
  ReturnValue_t result = tcReplayWriteSeq.setPayload(commandData, commandDataLen);
  if (result != returnvalue::OK) {
    return result;
  }
  finishTcPrep(tcReplayWriteSeq);
  return returnvalue::OK;
}

ReturnValue_t PlocMpsocHandler::prepareTcModeReplay() {
  mpsoc::TcModeReplay tcModeReplay(spParams, sequenceCount);
  finishTcPrep(tcModeReplay);
  return returnvalue::OK;
}

ReturnValue_t PlocMpsocHandler::prepareTcModeIdle() {
  mpsoc::TcModeIdle tcModeIdle(spParams, sequenceCount);
  finishTcPrep(tcModeIdle);
  return returnvalue::OK;
}

ReturnValue_t PlocMpsocHandler::prepareTcCamCmdSend(const uint8_t* commandData,
                                                    size_t commandDataLen) {
  mpsoc::TcCamcmdSend tcCamCmdSend(spParams, sequenceCount);
  ReturnValue_t result = tcCamCmdSend.setPayload(commandData, commandDataLen);
  if (result != returnvalue::OK) {
    return result;
  }
  finishTcPrep(tcCamCmdSend);
  nextReplyId = mpsoc::TM_CAM_CMD_RPT;
  return returnvalue::OK;
}

ReturnValue_t PlocMpsocHandler::prepareTcCamTakePic(const uint8_t* commandData,
                                                    size_t commandDataLen) {
  mpsoc::TcCamTakePic tcCamTakePic(spParams, sequenceCount);
  ReturnValue_t result = tcCamTakePic.setPayload(commandData, commandDataLen);
  if (result != returnvalue::OK) {
    return result;
  }
  finishTcPrep(tcCamTakePic);
  return returnvalue::OK;
}

ReturnValue_t PlocMpsocHandler::prepareTcSimplexSendFile(const uint8_t* commandData,
                                                         size_t commandDataLen) {
  mpsoc::TcSimplexSendFile tcSimplexSendFile(spParams, sequenceCount);
  ReturnValue_t result = tcSimplexSendFile.setPayload(commandData, commandDataLen);
  if (result != returnvalue::OK) {
    return result;
  }
  finishTcPrep(tcSimplexSendFile);
  return returnvalue::OK;
}

ReturnValue_t PlocMpsocHandler::prepareTcGetDirContent(const uint8_t* commandData,
                                                       size_t commandDataLen) {
  mpsoc::TcGetDirContent tcGetDirContent(spParams, sequenceCount);
  ReturnValue_t result = tcGetDirContent.setPayload(commandData, commandDataLen);
  if (result != returnvalue::OK) {
    return result;
  }
  finishTcPrep(tcGetDirContent);
  return returnvalue::OK;
}

ReturnValue_t PlocMpsocHandler::prepareTcDownlinkDataModulate(const uint8_t* commandData,
                                                              size_t commandDataLen) {
  mpsoc::TcDownlinkDataModulate tcDownlinkDataModulate(spParams, sequenceCount);
  ReturnValue_t result = tcDownlinkDataModulate.setPayload(commandData, commandDataLen);
  if (result != returnvalue::OK) {
    return result;
  }
  finishTcPrep(tcDownlinkDataModulate);
  return returnvalue::OK;
}

ReturnValue_t PlocMpsocHandler::prepareTcModeSnapshot() {
  mpsoc::TcModeSnapshot tcModeSnapshot(spParams, sequenceCount);
  finishTcPrep(tcModeSnapshot);
  return returnvalue::OK;
}

ReturnValue_t PlocMpsocHandler::finishTcPrep(mpsoc::TcBase& tcBase) {
  nextReplyId = mpsoc::ACK_REPORT;
  ReturnValue_t result = tcBase.finishPacket();
  if (result != returnvalue::OK) {
    return result;
  }
  rawPacket = commandBuffer;
  rawPacketLen = tcBase.getFullPacketLen();
  sequenceCount++;

  if (DEBUG_MPSOC_COMMUNICATION) {
    sif::debug << "SEND MPSOC packet. APID 0x" << std::hex << std::setw(3) << tcBase.getApid()
               << " Size " << std::dec << tcBase.getFullPacketLen() << " SSC "
               << tcBase.getSeqCount() << std::endl;
  }
  cmdCountdown.resetTimer();
  return returnvalue::OK;
}

ReturnValue_t PlocMpsocHandler::verifyPacket(const uint8_t* start, size_t foundLen) {
  if (CRC::crc16ccitt(start, foundLen) != 0) {
    return MPSoCReturnValuesIF::CRC_FAILURE;
  }
  return returnvalue::OK;
}

ReturnValue_t PlocMpsocHandler::handleAckReport(const uint8_t* data) {
  ReturnValue_t result = returnvalue::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: {
      DeviceCommandId_t commandId = getPendingCommand();
      uint16_t status = mpsoc::getStatusFromRawData(data);
      sif::warning << "MPSoC ACK Failure: " << mpsoc::getStatusString(status) << std::endl;
      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 = returnvalue::FAILED;
      break;
    }
  }

  return result;
}

ReturnValue_t PlocMpsocHandler::handleExecutionReport(const uint8_t* data) {
  ReturnValue_t result = returnvalue::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): {
      cmdDoneHandler(true, result);
      break;
    }
    case (mpsoc::apid::EXE_FAILURE): {
      DeviceCommandId_t commandId = getPendingCommand();
      if (commandId == DeviceHandlerIF::NO_COMMAND_ID) {
        sif::debug << "PlocMPSoCHandler::handleExecutionReport: Unknown command id" << std::endl;
      }
      uint16_t status = mpsoc::getStatusFromRawData(data);
      sif::warning << "MPSoC EXE Failure: " << mpsoc::getStatusString(status) << std::endl;
      triggerEvent(EXE_FAILURE, commandId, status);
      sendFailureReport(mpsoc::EXE_REPORT, MPSoCReturnValuesIF::RECEIVED_EXE_FAILURE);
      result = IGNORE_REPLY_DATA;
      cmdDoneHandler(false, MPSoCReturnValuesIF::RECEIVED_EXE_FAILURE);
      break;
    }
    default: {
      sif::warning << "PlocMPSoCHandler::handleExecutionReport: Unknown APID" << std::endl;
      result = returnvalue::FAILED;
      break;
    }
  }
  nextReplyId = mpsoc::NONE;
  return result;
}

ReturnValue_t PlocMpsocHandler::handleMemoryReadReport(const uint8_t* data) {
  ReturnValue_t result = returnvalue::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::handleGetHkReport(const uint8_t* data) {
  ReturnValue_t result = verifyPacket(data, foundPacketLen);
  if (result != returnvalue::OK) {
    return result;
  }
  SpacePacketReader packetReader(data, foundPacketLen);
  const uint8_t* dataStart = data + 6;
  PoolReadGuard pg(&hkReport);
  size_t deserLen = mpsoc::SIZE_TM_HK_REPORT;
  SerializeIF::Endianness endianness = SerializeIF::Endianness::NETWORK;
  result = SerializeAdapter::deSerialize(&hkReport.status.value, &dataStart, &deserLen, endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result = SerializeAdapter::deSerialize(&hkReport.mode.value, &dataStart, &deserLen, endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result = SerializeAdapter::deSerialize(&hkReport.downlinkPwrOn.value, &dataStart, &deserLen,
                                         endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result = SerializeAdapter::deSerialize(&hkReport.downlinkReplyActive.value, &dataStart, &deserLen,
                                         endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result = SerializeAdapter::deSerialize(&hkReport.downlinkJesdSyncStatus.value, &dataStart,
                                         &deserLen, endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result = SerializeAdapter::deSerialize(&hkReport.downlinkDacStatus.value, &dataStart, &deserLen,
                                         endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result =
      SerializeAdapter::deSerialize(&hkReport.camStatus.value, &dataStart, &deserLen, endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result = SerializeAdapter::deSerialize(&hkReport.camSdiStatus.value, &dataStart, &deserLen,
                                         endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result =
      SerializeAdapter::deSerialize(&hkReport.camFpgaTemp.value, &dataStart, &deserLen, endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result =
      SerializeAdapter::deSerialize(&hkReport.sysmonTemp.value, &dataStart, &deserLen, endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result = SerializeAdapter::deSerialize(&hkReport.sysmonVccInt.value, &dataStart, &deserLen,
                                         endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result = SerializeAdapter::deSerialize(&hkReport.sysmonVccAux.value, &dataStart, &deserLen,
                                         endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result = SerializeAdapter::deSerialize(&hkReport.sysmonVccBram.value, &dataStart, &deserLen,
                                         endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result = SerializeAdapter::deSerialize(&hkReport.sysmonVccPaux.value, &dataStart, &deserLen,
                                         endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result = SerializeAdapter::deSerialize(&hkReport.sysmonVccPint.value, &dataStart, &deserLen,
                                         endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result = SerializeAdapter::deSerialize(&hkReport.sysmonVccPdro.value, &dataStart, &deserLen,
                                         endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result =
      SerializeAdapter::deSerialize(&hkReport.sysmonMb12V.value, &dataStart, &deserLen, endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result =
      SerializeAdapter::deSerialize(&hkReport.sysmonMb3V3.value, &dataStart, &deserLen, endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result =
      SerializeAdapter::deSerialize(&hkReport.sysmonMb1V8.value, &dataStart, &deserLen, endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result = SerializeAdapter::deSerialize(&hkReport.sysmonVcc12V.value, &dataStart, &deserLen,
                                         endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result =
      SerializeAdapter::deSerialize(&hkReport.sysmonVcc5V.value, &dataStart, &deserLen, endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result = SerializeAdapter::deSerialize(&hkReport.sysmonVcc3V3.value, &dataStart, &deserLen,
                                         endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result = SerializeAdapter::deSerialize(&hkReport.sysmonVcc3V3VA.value, &dataStart, &deserLen,
                                         endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result = SerializeAdapter::deSerialize(&hkReport.sysmonVcc2V5DDR.value, &dataStart, &deserLen,
                                         endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result = SerializeAdapter::deSerialize(&hkReport.sysmonVcc1V2DDR.value, &dataStart, &deserLen,
                                         endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result = SerializeAdapter::deSerialize(&hkReport.sysmonVcc0V9.value, &dataStart, &deserLen,
                                         endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result = SerializeAdapter::deSerialize(&hkReport.sysmonVcc0V6VTT.value, &dataStart, &deserLen,
                                         endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result = SerializeAdapter::deSerialize(&hkReport.sysmonSafeCotsCur.value, &dataStart, &deserLen,
                                         endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result = SerializeAdapter::deSerialize(&hkReport.sysmonNvm4XoCur.value, &dataStart, &deserLen,
                                         endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result = SerializeAdapter::deSerialize(&hkReport.semUncorrectableErrs.value, &dataStart,
                                         &deserLen, endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result = SerializeAdapter::deSerialize(&hkReport.semCorrectableErrs.value, &dataStart, &deserLen,
                                         endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  result =
      SerializeAdapter::deSerialize(&hkReport.semStatus.value, &dataStart, &deserLen, endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  // Skip the weird filename
  dataStart += 256;
  result = SerializeAdapter::deSerialize(&hkReport.rebootMpsocRequired, &dataStart, &deserLen,
                                         endianness);
  if (result != returnvalue::OK) {
    return result;
  }
  return returnvalue::OK;
}

ReturnValue_t PlocMpsocHandler::handleCamCmdRpt(const uint8_t* data) {
  ReturnValue_t result = verifyPacket(data, foundPacketLen);
  if (result == MPSoCReturnValuesIF::CRC_FAILURE) {
    sif::warning << "PlocMPSoCHandler::handleCamCmdRpt: CRC failure" << std::endl;
  }
  SpacePacketReader packetReader(data, foundPacketLen);
  const uint8_t* dataFieldPtr = data + mpsoc::SPACE_PACKET_HEADER_SIZE + sizeof(uint16_t);
  std::string camCmdRptMsg(reinterpret_cast<const char*>(dataFieldPtr),
                           foundPacketLen - mpsoc::SPACE_PACKET_HEADER_SIZE - sizeof(uint16_t) - 3);
#if OBSW_DEBUG_PLOC_MPSOC == 1
  uint8_t ackValue = *(packetReader.getFullData() + packetReader.getFullPacketLen() - 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(packetReader.getPacketData() + sizeof(uint16_t),
                 packetReader.getPacketDataLen() - 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 = returnvalue::OK;

  uint8_t enabledReplies = 0;

  auto enableThreeReplies = [&](DeviceCommandId_t replyId) {
    enabledReplies = 3;
    result = DeviceHandlerBase::enableReplyInReplyMap(command, enabledReplies, true, replyId);
    if (result != returnvalue::OK) {
      sif::debug << "PlocMPSoCHandler::enableReplyInReplyMap: Reply with id "
                 << mpsoc::TM_MEMORY_READ_REPORT << " not in replyMap" << std::endl;
      return result;
    }
    return returnvalue::OK;
  };
  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:
    case mpsoc::TC_CAM_TAKE_PIC:
    case mpsoc::TC_SIMPLEX_SEND_FILE:
    case mpsoc::TC_DOWNLINK_DATA_MODULATE:
    case mpsoc::TC_MODE_SNAPSHOT:
      enabledReplies = 2;
      break;
    case mpsoc::TC_GET_HK_REPORT: {
      result = enableThreeReplies(mpsoc::TM_GET_HK_REPORT);
      if (result != returnvalue::OK) {
        return result;
      }
      break;
    }
    case mpsoc::TC_MEM_READ: {
      result = enableThreeReplies(mpsoc::TM_MEMORY_READ_REPORT);
      if (result != returnvalue::OK) {
        return result;
      }
      break;
    }
    case mpsoc::TC_CAM_CMD_SEND: {
      result = enableThreeReplies(mpsoc::TM_CAM_CMD_RPT);
      if (result != returnvalue::OK) {
        return result;
      }
      break;
    }
    case mpsoc::TC_FLASH_GET_DIRECTORY_CONTENT: {
      result = enableThreeReplies(mpsoc::TM_FLASH_DIRECTORY_CONTENT);
      if (result != returnvalue::OK) {
        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 != returnvalue::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 != returnvalue::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_EXECUTION_DELAY;
      break;
    }
    case mpsoc::TC_CAM_TAKE_PIC: {
      DeviceReplyIter iter = deviceReplyMap.find(mpsoc::EXE_REPORT);
      iter->second.delayCycles = mpsoc::TC_CAM_TAKE_PIC_EXECUTION_DELAY;
      break;
    }
    case mpsoc::TC_SIMPLEX_SEND_FILE: {
      DeviceReplyIter iter = deviceReplyMap.find(mpsoc::EXE_REPORT);
      iter->second.delayCycles = mpsoc::TC_SIMPLEX_SEND_FILE_DELAY;
      break;
    }
    default:
      break;
  }

  return returnvalue::OK;
}

void PlocMpsocHandler::setNextReplyId() {
  switch (getPendingCommand()) {
    case mpsoc::TC_MEM_READ:
      nextReplyId = mpsoc::TM_MEMORY_READ_REPORT;
      break;
    case mpsoc::TC_FLASH_GET_DIRECTORY_CONTENT: {
      nextReplyId = mpsoc::TM_FLASH_DIRECTORY_CONTENT;
      break;
    }
    case mpsoc::TC_GET_HK_REPORT: {
      nextReplyId = mpsoc::TM_GET_HK_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 = mpsoc::SP_MAX_SIZE;
        break;
      case mpsoc::TM_FLASH_DIRECTORY_CONTENT:
        // I think the reply size is not fixed either.
        replyLen = mpsoc::SP_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 (specialComHelperExecuting) {
    return returnvalue::FAILED;
  }
  return returnvalue::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;
      break;
    }
    case supv::SHUTDOWN_MPSOC: {
      triggerEvent(MPSOC_SHUTDOWN_FAILED);
      sif::warning << "PlocMPSoCHandler::stepFailedReceived: Failed to shutdown MPSoC" << std::endl;
      break;
    }
    default:
      sif::debug << "PlocMPSoCHandler::stepFailedReceived: Received unexpected action reply"
                 << std::endl;
      break;
  }
  powerState = PowerState::SUPV_FAILED;
}

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

void PlocMpsocHandler::completionSuccessfulReceived(ActionId_t actionId) {
  if (actionId == supv::ACK_REPORT) {
    // I seriously don't know why this happens..
    //    sif::warning
    //        << "PlocMpsocHandler::completionSuccessfulReceived: Only received ACK report. Consider
    //        "
    //           "increasing the MPSoC boot timer."
    //        << std::endl;
  } else if (actionId != supv::EXE_REPORT) {
    sif::warning << "PlocMpsocHandler::completionSuccessfulReceived: Did not expect the action "
                 << "ID " << actionId << std::endl;
    return;
  }
  switch (powerState) {
    case PowerState::PENDING_STARTUP: {
      mpsocBootTransitionCd.resetTimer();
      powerState = PowerState::DONE;
      break;
    }
    case PowerState::PENDING_SHUTDOWN: {
      powerState = PowerState::DONE;
      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 = returnvalue::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 != returnvalue::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();

  auto disableCommandWithReply = [&](DeviceCommandId_t replyId) {
    iter = deviceReplyMap.find(replyId);
    info = &(iter->second);
    info->delayCycles = 0;
    info->active = false;
    info->command = deviceCommandMap.end();
  };
  /* If the command expects a telemetry packet the appropriate tm reply will be disabled here */
  switch (commandId) {
    case TC_MEM_WRITE:
    case TC_FLASHDELETE:
    case TC_REPLAY_START:
    case TC_REPLAY_STOP:
    case TC_DOWNLINK_PWR_ON:
    case TC_DOWNLINK_PWR_OFF:
    case TC_REPLAY_WRITE_SEQUENCE:
    case TC_MODE_REPLAY:
    case TC_MODE_IDLE:
    case TC_CAM_TAKE_PIC:
    case TC_SIMPLEX_SEND_FILE:
    case TC_DOWNLINK_DATA_MODULATE:
    case TC_MODE_SNAPSHOT:
      break;
    case TC_MEM_READ: {
      disableCommandWithReply(TM_MEMORY_READ_REPORT);
      break;
    }
    case TC_GET_HK_REPORT: {
      disableCommandWithReply(TM_GET_HK_REPORT);
      break;
    }
    case TC_FLASH_GET_DIRECTORY_CONTENT: {
      disableCommandWithReply(TM_FLASH_DIRECTORY_CONTENT);
      break;
    }
    case TC_CAM_CMD_SEND: {
      disableCommandWithReply(TM_CAM_CMD_RPT);
      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::stopSpecialComHelper() {
  if (specialComHelper != nullptr) {
    specialComHelper->stopProcess();
  }
  specialComHelperExecuting = false;
}

bool PlocMpsocHandler::handleHwStartup() {
#if OBSW_MPSOC_JTAG_BOOT == 1
  uartIsolatorSwitch.pullHigh();
  startupState = StartupState::WAIT_CYCLES;
  return true;
#endif
  if (powerState == PowerState::IDLE) {
    if (supv::SUPV_ON) {
      commandActionHelper.commandAction(supervisorHandler, supv::START_MPSOC);
      supvTransitionCd.resetTimer();
      powerState = PowerState::PENDING_STARTUP;
    } else {
      triggerEvent(SUPV_NOT_ON, 1);
      // Set back to OFF for now, failing the transition.
      setMode(MODE_OFF);
    }
  }
  if (powerState == PowerState::SUPV_FAILED) {
    setMode(MODE_OFF);
    powerState = PowerState::IDLE;
    return false;
  }
  if (powerState == PowerState::PENDING_STARTUP) {
    if (supvTransitionCd.hasTimedOut()) {
      // Process with transition nonetheless..
      triggerEvent(SUPV_REPLY_TIMEOUT);
      powerState = PowerState::DONE;
    } else {
      return false;
    }
  }
  if (powerState == PowerState::DONE) {
    if (mpsocBootTransitionCd.hasTimedOut()) {
      // Wait a bit for the MPSoC to fully boot.
      uartIsolatorSwitch.pullHigh();
      powerState = PowerState::IDLE;
    } else {
      return false;
    }
  }
  return true;
}

bool PlocMpsocHandler::handleHwShutdown() {
  stopSpecialComHelper();
  uartIsolatorSwitch.pullLow();
#if OBSW_MPSOC_JTAG_BOOT == 1
  powerState = PowerState::DONE;
  return true;
#endif

  if (powerState == PowerState::IDLE) {
    if (supv::SUPV_ON) {
      commandActionHelper.commandAction(supervisorHandler, supv::SHUTDOWN_MPSOC);
      supvTransitionCd.resetTimer();
      powerState = PowerState::PENDING_SHUTDOWN;
    } else {
      triggerEvent(SUPV_NOT_ON, 0);
      powerState = PowerState::DONE;
    }
  }
  if (powerState == PowerState::PENDING_SHUTDOWN) {
    if (supvTransitionCd.hasTimedOut()) {
      powerState = PowerState::DONE;
      // Process with transition nonetheless..
      triggerEvent(SUPV_REPLY_TIMEOUT);
      return true;
    } else {
      // Wait till power state is OFF.
      return false;
    }
  }
  return true;
}

void PlocMpsocHandler::handleActionCommandFailure(ActionId_t actionId) {
  switch (actionId) {
    case supv::ACK_REPORT:
    case supv::EXE_REPORT:
      break;
    default:
      sif::warning << "PlocMPSoCHandler::handleActionCommandFailure: Did not expect the action ID "
                   << actionId << std::endl;
      return;
  }
  switch (powerState) {
    case PowerState::PENDING_STARTUP: {
      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
      break;
    }
    case PowerState::PENDING_SHUTDOWN: {
      // FDIR will intercept event and switch PLOC power off
      triggerEvent(MPSOC_SHUTDOWN_FAILED);
      sif::warning << "PlocMPSoCHandler::handleActionCommandFailure: Failed to shutdown MPSoC"
                   << std::endl;
      break;
    }
    default:
      break;
  }
  powerState = PowerState::SUPV_FAILED;
  return;
}

LocalPoolDataSetBase* PlocMpsocHandler::getDataSetHandle(sid_t sid) {
  if (sid == hkReport.getSid()) {
    return &hkReport;
  }
  return nullptr;
}

bool PlocMpsocHandler::dontCheckQueue() {
  // The TC and TMs need to be handled strictly sequentially, so while a command is pending,
  // more specifically while replies are still expected, do not check the queue.
  return commandIsPending;
}

void PlocMpsocHandler::cmdDoneHandler(bool success, ReturnValue_t result) {
  commandIsPending = false;
  auto commandIter = deviceCommandMap.find(getPendingCommand());
  if (commandIter != deviceCommandMap.end()) {
    commandIter->second.isExecuting = false;
    if (commandIter->second.sendReplyTo != MessageQueueIF::NO_QUEUE) {
      actionHelper.finish(success, commandIter->second.sendReplyTo, getPendingCommand(), result);
    }
  }
  disableAllReplies();
}

ReturnValue_t PlocMpsocHandler::checkModeCommand(Mode_t commandedMode, Submode_t commandedSubmode,
                                                 uint32_t* msToReachTheMode) {
  if (commandedMode != MODE_OFF) {
    PoolReadGuard pg(&enablePl);
    if (pg.getReadResult() == returnvalue::OK) {
      if (enablePl.plUseAllowed.isValid() and not enablePl.plUseAllowed.value) {
        return NON_OP_STATE_OF_CHARGE;
      }
    }
  }
  return DeviceHandlerBase::checkModeCommand(commandedMode, commandedSubmode, msToReachTheMode);
}