#include "CoreController.h"

#include <fsfw/events/EventManager.h>
#include <fsfw/filesystem/HasFileSystemIF.h>
#include <fsfw/ipc/QueueFactory.h>
#include <fsfw/tasks/TaskFactory.h>
#include <fsfw_hal/linux/uio/UioMapper.h>

#include "commonConfig.h"
#include "fsfw/serviceinterface/ServiceInterface.h"
#include "fsfw/timemanager/Stopwatch.h"
#include "fsfw/version.h"
#include "watchdog/definitions.h"
#if OBSW_ADD_TMTC_UDP_SERVER == 1
#include "fsfw/osal/common/UdpTmTcBridge.h"
#endif
#if OBSW_ADD_TMTC_TCP_SERVER == 1
#include "fsfw/osal/common/TcpTmTcServer.h"
#endif
#include <fcntl.h>
#include <unistd.h>

#include <algorithm>
#include <filesystem>

#include "bsp_q7s/boardconfig/busConf.h"
#include "bsp_q7s/fs/SdCardManager.h"
#include "bsp_q7s/memory/scratchApi.h"
#include "bsp_q7s/xadc/Xadc.h"
#include "eive/definitions.h"
#include "linux/utility/utility.h"

xsc::Chip CoreController::CURRENT_CHIP = xsc::Chip::NO_CHIP;
xsc::Copy CoreController::CURRENT_COPY = xsc::Copy::NO_COPY;

CoreController::CoreController(object_id_t objectId, bool enableHkSet)
    : ExtendedControllerBase(objectId, 5),
      enableHkSet(enableHkSet),
      cmdExecutor(4096),
      cmdReplyBuf(4096, true),
      cmdRepliesSizes(128),
      opDivider5(5),
      opDivider10(10),
      hkSet(this),
      paramHelper(this) {
  cmdExecutor.setRingBuffer(&cmdReplyBuf, &cmdRepliesSizes);
  try {
    sdcMan = SdCardManager::instance();
    if (sdcMan == nullptr) {
      sif::error << "CoreController::CoreController: SD card manager invalid!" << std::endl;
    }

    if (not BLOCKING_SD_INIT) {
      sdcMan->setBlocking(false);
    }
    // Set up state of SD card manager and own initial state.
    // Stopwatch watch;
    sdcMan->updateSdCardStateFile();
    SdCardManager::SdStatePair sdStates;
    sdcMan->getSdCardsStatus(sdStates);
    auto sdCard = sdcMan->getPreferredSdCard();
    if (not sdCard.has_value()) {
      sif::error << "CoreController::initializeAfterTaskCreation: "
                    "Issues getting preferred SD card, setting to 0"
                 << std::endl;
      sdCard = sd::SdCard::SLOT_0;
    }
    sdInfo.active = sdCard.value();
    if (sdStates.first == sd::SdState::MOUNTED) {
      sdcMan->setActiveSdCard(sd::SdCard::SLOT_0);
    } else if (sdStates.second == sd::SdState::MOUNTED) {
      sdcMan->setActiveSdCard(sd::SdCard::SLOT_1);
    }
    currMntPrefix = sdcMan->getCurrentMountPrefix();

    getCurrentBootCopy(CURRENT_CHIP, CURRENT_COPY);

    initClockFromTimeFile();
  } catch (const std::filesystem::filesystem_error &e) {
    sif::error << "CoreController::CoreController: Failed with exception " << e.what() << std::endl;
  }
  // Add script folder to path
  char *currentEnvPath = getenv("PATH");
  std::string updatedEnvPath = std::string(currentEnvPath) + ":/home/root/scripts:/usr/local/bin";
  setenv("PATH", updatedEnvPath.c_str(), true);
  sdCardCheckCd.timeOut();
  eventQueue = QueueFactory::instance()->createMessageQueue(5, EventMessage::MAX_MESSAGE_SIZE);
}

CoreController::~CoreController() {}

ReturnValue_t CoreController::handleCommandMessage(CommandMessage *message) {
  ReturnValue_t result = paramHelper.handleParameterMessage(message);
  if (result == returnvalue::OK) {
    return result;
  }
  return ExtendedControllerBase::handleCommandMessage(message);
}

void CoreController::performControlOperation() {
#if OBSW_THREAD_TRACING == 1
  trace::threadTrace(opCounter, "CORE CTRL");
#endif
  EventMessage event;
  for (ReturnValue_t result = eventQueue->receiveMessage(&event); result == returnvalue::OK;
       result = eventQueue->receiveMessage(&event)) {
    switch (event.getEvent()) {
      case (GpsHyperion::GPS_FIX_CHANGE): {
        gpsFix = static_cast<GpsHyperion::FixMode>(event.getParameter2());
        break;
      }
    }
  }
  sdStateMachine();
  performMountedSdCardOperations();
  readHkData();
  if (dumpContext.active) {
    dirListingDumpHandler();
  }

  if (shellCmdIsExecuting) {
    bool replyReceived = false;
    // TODO: We could read the data in the ring buffer and send it as an action data reply.
    if (cmdExecutor.check(replyReceived) == CommandExecutor::EXECUTION_FINISHED) {
      actionHelper.finish(true, successRecipient, core::EXECUTE_SHELL_CMD_BLOCKING);
      shellCmdIsExecuting = false;
      cmdReplyBuf.clear();
      while (not cmdRepliesSizes.empty()) {
        cmdRepliesSizes.pop();
      }
      successRecipient = MessageQueueIF::NO_QUEUE;
    }
  }
  opDivider5.checkAndIncrement();
  opDivider10.checkAndIncrement();
}

ReturnValue_t CoreController::initializeLocalDataPool(localpool::DataPool &localDataPoolMap,
                                                      LocalDataPoolManager &poolManager) {
  localDataPoolMap.emplace(core::TEMPERATURE, &tempPoolEntry);
  localDataPoolMap.emplace(core::PS_VOLTAGE, &psVoltageEntry);
  localDataPoolMap.emplace(core::PL_VOLTAGE, &plVoltageEntry);
  poolManager.subscribeForRegularPeriodicPacket({hkSet.getSid(), enableHkSet, 60.0});
  return returnvalue::OK;
}

LocalPoolDataSetBase *CoreController::getDataSetHandle(sid_t sid) {
  if (sid.ownerSetId == core::HK_SET_ID) {
    return &hkSet;
  }
  return nullptr;
}

ReturnValue_t CoreController::initialize() {
  ReturnValue_t result = ExtendedControllerBase::initialize();
  if (result != returnvalue::OK) {
    sif::warning << "CoreController::initialize: Base init failed" << std::endl;
  }

  result = paramHelper.initialize();
  if (result != returnvalue::OK) {
    return result;
  }

  EventManagerIF *eventManager =
      ObjectManager::instance()->get<EventManagerIF>(objects::EVENT_MANAGER);
  if (eventManager == nullptr or eventQueue == nullptr) {
    sif::warning << "CoreController::initialize: No valid event manager found or "
                    "queue invalid"
                 << std::endl;
  }
  result = eventManager->registerListener(eventQueue->getId());
  if (result != returnvalue::OK) {
    sif::warning << "CoreController::initialize: Registering as event listener failed" << std::endl;
  }
  result = eventManager->subscribeToEvent(eventQueue->getId(),
                                          event::getEventId(GpsHyperion::GPS_FIX_CHANGE));
  if (result != returnvalue::OK) {
    sif::warning << "Subscribing for GPS GPS_FIX_CHANGE event failed" << std::endl;
  }
  triggerEvent(core::REBOOT_SW, CURRENT_CHIP, CURRENT_COPY);
  announceCurrentImageInfo();
  announceVersionInfo();
  SdCardManager::SdStatePair sdStates;
  sdcMan->getSdCardsStatus(sdStates);
  announceSdInfo(sdStates);
  sdStateMachine();
  result = scratch::writeNumber(scratch::ALLOC_FAILURE_COUNT, 0);
  if (result != returnvalue::OK) {
    sif::warning << "CoreController::initialize: Setting up alloc failure "
                    "count failed"
                 << std::endl;
  }
  return result;
}

ReturnValue_t CoreController::initializeAfterTaskCreation() {
  ReturnValue_t result = returnvalue::OK;
  if (BLOCKING_SD_INIT) {
    result = initSdCardBlocking();
    if (result != returnvalue::OK and result != SdCardManager::ALREADY_MOUNTED) {
      sif::warning << "CoreController::CoreController: SD card init failed" << std::endl;
    }
  }
  sdStateMachine();
  performMountedSdCardOperations();
  if (result != returnvalue::OK) {
    sif::warning << "CoreController::initialize: Version initialization failed" << std::endl;
  }
  updateProtInfo();
  return ExtendedControllerBase::initializeAfterTaskCreation();
}

ReturnValue_t CoreController::executeAction(ActionId_t actionId, MessageQueueId_t commandedBy,
                                            const uint8_t *data, size_t size) {
  using namespace core;
  switch (actionId) {
    case (ANNOUNCE_VERSION): {
      announceVersionInfo();
      return HasActionsIF::EXECUTION_FINISHED;
    }
    case (ANNOUNCE_BOOT_COUNTS): {
      announceBootCounts();
      return HasActionsIF::EXECUTION_FINISHED;
    }
    case (ANNOUNCE_CURRENT_IMAGE): {
      announceCurrentImageInfo();
      return HasActionsIF::EXECUTION_FINISHED;
    }
    case (LIST_DIRECTORY_INTO_FILE): {
      return actionListDirectoryIntoFile(actionId, commandedBy, data, size);
    }
    case (LIST_DIRECTORY_DUMP_DIRECTLY): {
      return actionListDirectoryDumpDirectly(actionId, commandedBy, data, size);
    }
    case (CP_HELPER): {
      CpHelperParser parser(data, size);
      ReturnValue_t result = parser.parse();
      if (result != returnvalue::OK) {
        return result;
      }
      std::ostringstream oss("cp ", std::ostringstream::ate);
      if (parser.isForceOptSet()) {
        oss << "-f ";
      }
      if (parser.isRecursiveOptSet()) {
        oss << "-r ";
      }
      auto &sourceTgt = parser.destTgtPair();
      oss << sourceTgt.sourceName << " " << sourceTgt.targetName;
      sif::info << "CoreController: Performing copy command: " << oss.str() << std::endl;
      int ret = std::system(oss.str().c_str());
      if (ret != 0) {
        return returnvalue::FAILED;
      }
      return EXECUTION_FINISHED;
    }
    case (MV_HELPER): {
      MvHelperParser parser(data, size);
      ReturnValue_t result = parser.parse();
      if (result != returnvalue::OK) {
        return result;
      }
      std::ostringstream oss("mv ", std::ostringstream::ate);
      auto &sourceTgt = parser.destTgtPair();
      oss << sourceTgt.sourceName << " " << sourceTgt.targetName;
      sif::info << "CoreController: Performing move command: " << oss.str() << std::endl;
      int ret = std::system(oss.str().c_str());
      if (ret != 0) {
        return returnvalue::FAILED;
      }
      return EXECUTION_FINISHED;
    }
    case (RM_HELPER): {
      RmHelperParser parser(data, size);
      ReturnValue_t result = parser.parse();
      if (result != returnvalue::OK) {
        return result;
      }
      std::ostringstream oss("rm ", std::ostringstream::ate);
      if (parser.isRecursiveOptSet() or parser.isForceOptSet()) {
        oss << "-";
      }
      if (parser.isRecursiveOptSet()) {
        oss << "r";
      }
      if (parser.isForceOptSet()) {
        oss << "f";
      }
      size_t removeTargetSize = 0;
      const char *removeTgt = parser.getRemoveTarget(removeTargetSize);
      oss << " " << removeTgt;
      sif::info << "CoreController: Performing remove command: " << oss.str() << std::endl;
      int ret = std::system(oss.str().c_str());
      if (ret != 0) {
        return returnvalue::FAILED;
      }
      return EXECUTION_FINISHED;
    }
    case (MKDIR_HELPER): {
      if (size < 1) {
        return HasActionsIF::INVALID_PARAMETERS;
      }
      std::string createdDir = std::string(reinterpret_cast<const char *>(data), size);
      std::ostringstream oss("mkdir ", std::ostringstream::ate);
      oss << createdDir;
      sif::info << "CoreController: Performing directory creation: " << oss.str() << std::endl;
      int ret = std::system(oss.str().c_str());
      if (ret != 0) {
        return returnvalue::FAILED;
      }
      return EXECUTION_FINISHED;
    }
    case (SWITCH_REBOOT_FILE_HANDLING): {
      if (size < 1) {
        return HasActionsIF::INVALID_PARAMETERS;
      }
      std::string path = sdcMan->getCurrentMountPrefix() + REBOOT_WATCHDOG_FILE;
      parseRebootWatchdogFile(path, rebootWatchdogFile);
      if (data[0] == 0) {
        rebootWatchdogFile.enabled = false;
        rewriteRebootWatchdogFile(rebootWatchdogFile);
      } else if (data[0] == 1) {
        rebootWatchdogFile.enabled = true;
        rewriteRebootWatchdogFile(rebootWatchdogFile);
      } else {
        return HasActionsIF::INVALID_PARAMETERS;
      }
      return HasActionsIF::EXECUTION_FINISHED;
    }
    case (READ_REBOOT_MECHANISM_INFO): {
      std::string path = sdcMan->getCurrentMountPrefix() + REBOOT_WATCHDOG_FILE;
      parseRebootWatchdogFile(path, rebootWatchdogFile);
      RebootWatchdogPacket packet(rebootWatchdogFile);
      ReturnValue_t result = actionHelper.reportData(commandedBy, actionId, &packet);
      if (result != returnvalue::OK) {
        return result;
      }
      return HasActionsIF::EXECUTION_FINISHED;
    }
    case (RESET_REBOOT_COUNTERS): {
      if (size == 0) {
        resetRebootWatchdogCounters(xsc::ALL_CHIP, xsc::ALL_COPY);
      } else if (size == 2) {
        if (data[0] > 1 or data[1] > 1) {
          return HasActionsIF::INVALID_PARAMETERS;
        }
        resetRebootWatchdogCounters(static_cast<xsc::Chip>(data[0]),
                                    static_cast<xsc::Copy>(data[1]));
      }
      return HasActionsIF::EXECUTION_FINISHED;
    }
    case (OBSW_UPDATE_FROM_SD_0): {
      return executeSwUpdate(SwUpdateSources::SD_0, data, size);
    }
    case (OBSW_UPDATE_FROM_SD_1): {
      return executeSwUpdate(SwUpdateSources::SD_1, data, size);
    }
    case (OBSW_UPDATE_FROM_TMP): {
      return executeSwUpdate(SwUpdateSources::TMP_DIR, data, size);
    }
    case (SWITCH_TO_SD_0): {
      if (not startSdStateMachine(sd::SdCard::SLOT_0, SdCfgMode::COLD_REDUNDANT, commandedBy,
                                  actionId)) {
        return HasActionsIF::IS_BUSY;
      }
      // Completion will be reported by SD card state machine
      return returnvalue::OK;
    }
    case (SWITCH_TO_SD_1): {
      if (not startSdStateMachine(sd::SdCard::SLOT_1, SdCfgMode::COLD_REDUNDANT, commandedBy,
                                  actionId)) {
        return HasActionsIF::IS_BUSY;
      }
      // Completion will be reported by SD card state machine
      return returnvalue::OK;
    }
    case (SWITCH_TO_BOTH_SD_CARDS): {
      // An active SD still needs to be specified because the system needs to know which SD
      // card to use for regular operations like telemetry storage.
      if (size != 1) {
        return HasActionsIF::INVALID_PARAMETERS;
      }
      if (data[0] != 0 and data[0] != 1) {
        return HasActionsIF::INVALID_PARAMETERS;
      }
      auto active = static_cast<sd::SdCard>(data[0]);
      if (not startSdStateMachine(active, SdCfgMode::HOT_REDUNDANT, commandedBy, actionId)) {
        return HasActionsIF::IS_BUSY;
      }
      // Completion will be reported by SD card state machine
      return returnvalue::OK;
    }
    case (SYSTEMCTL_CMD_EXECUTOR): {
      // Expect one byte systemctl command type and a unit name with at least one byte as minimum.
      if (size < 2) {
        return HasActionsIF::INVALID_PARAMETERS;
      }
      if (data[0] >= core::SystemctlCmd::NUM_CMDS) {
        return HasActionsIF::INVALID_PARAMETERS;
      }
      core::SystemctlCmd cmdType = static_cast<core::SystemctlCmd>(data[0]);
      std::string unitName = std::string(reinterpret_cast<const char *>(data + 1), size - 1);
      std::ostringstream oss("systemctl ", std::ostringstream::ate);
      switch (cmdType) {
        case (core::SystemctlCmd::START): {
          oss << "start ";
          break;
        }
        case (core::SystemctlCmd::STOP): {
          oss << "stop ";
          break;
        }
        case (core::SystemctlCmd::RESTART): {
          oss << "restart ";
          break;
        }
        default: {
          return HasActionsIF::INVALID_PARAMETERS;
        }
      }
      oss << unitName;
      int result = std::system(oss.str().c_str());
      if (result != 0) {
        return returnvalue::FAILED;
      }
      return EXECUTION_FINISHED;
    }
    case (SWITCH_IMG_LOCK): {
      if (size != 3) {
        return HasActionsIF::INVALID_PARAMETERS;
      }
      if (data[1] > 1 or data[2] > 1) {
        return HasActionsIF::INVALID_PARAMETERS;
      }
      setRebootMechanismLock(data[0], static_cast<xsc::Chip>(data[1]),
                             static_cast<xsc::Copy>(data[2]));
      return HasActionsIF::EXECUTION_FINISHED;
    }
    case (SET_MAX_REBOOT_CNT): {
      if (size < 1) {
        return HasActionsIF::INVALID_PARAMETERS;
      }
      std::string path = sdcMan->getCurrentMountPrefix() + REBOOT_WATCHDOG_FILE;
      // Disable the reboot file mechanism
      parseRebootWatchdogFile(path, rebootWatchdogFile);
      rebootWatchdogFile.maxCount = data[0];
      rewriteRebootWatchdogFile(rebootWatchdogFile);
      return HasActionsIF::EXECUTION_FINISHED;
    }
    case (XSC_REBOOT_OBC): {
      // Warning: This function will never return, because it reboots the system
      return actionXscReboot(data, size);
    }
    case (REBOOT_OBC): {
      // Warning: This function will never return, because it reboots the system
      return actionReboot(data, size);
    }
    case (EXECUTE_SHELL_CMD_BLOCKING): {
      std::string cmdToExecute = std::string(reinterpret_cast<const char *>(data), size);
      int result = std::system(cmdToExecute.c_str());
      if (result != 0) {
        // TODO: Data reply with returnalue maybe?
        return returnvalue::FAILED;
      }
      return EXECUTION_FINISHED;
    }
    case (EXECUTE_SHELL_CMD_NON_BLOCKING): {
      std::string cmdToExecute = std::string(reinterpret_cast<const char *>(data), size);
      if (cmdExecutor.getCurrentState() == CommandExecutor::States::PENDING or
          shellCmdIsExecuting) {
        return HasActionsIF::IS_BUSY;
      }
      cmdExecutor.load(cmdToExecute, false, false);
      ReturnValue_t result = cmdExecutor.execute();
      if (result != returnvalue::OK) {
        return result;
      }
      shellCmdIsExecuting = true;
      successRecipient = commandedBy;
      return returnvalue::OK;
    }
    default: {
      return HasActionsIF::INVALID_ACTION_ID;
    }
  }
}

ReturnValue_t CoreController::checkModeCommand(Mode_t mode, Submode_t submode,
                                               uint32_t *msToReachTheMode) {
  return returnvalue::OK;
}

ReturnValue_t CoreController::initSdCardBlocking() {
  // Create update status file
  ReturnValue_t result = sdcMan->updateSdCardStateFile();
  if (result != returnvalue::OK) {
    sif::warning << "CoreController::initialize: Updating SD card state file failed" << std::endl;
  }
  if (sdInfo.cfgMode == SdCfgMode::PASSIVE) {
    sif::info << "No SD card initialization will be performed" << std::endl;
    return returnvalue::OK;
  }

  result = sdcMan->getSdCardsStatus(sdInfo.currentState);
  if (result != returnvalue::OK) {
    sif::warning << "Getting SD card activity status failed" << std::endl;
  }

  if (sdInfo.cfgMode == SdCfgMode::COLD_REDUNDANT) {
    updateInternalSdInfo();
    sif::info << "Cold redundant SD card configuration, preferred SD card: "
              << static_cast<int>(sdInfo.active) << std::endl;
    result = sdColdRedundantBlockingInit();
    // Update status file
    sdcMan->updateSdCardStateFile();
    return result;
  }
  if (sdInfo.cfgMode == SdCfgMode::HOT_REDUNDANT) {
    sif::info << "Hot redundant SD card configuration" << std::endl;
    sdCardSetup(sd::SdCard::SLOT_0, sd::SdState::MOUNTED, "0", false);
    sdCardSetup(sd::SdCard::SLOT_1, sd::SdState::MOUNTED, "1", false);
    // Update status file
    sdcMan->updateSdCardStateFile();
  }
  return returnvalue::OK;
}

ReturnValue_t CoreController::sdStateMachine() {
  ReturnValue_t result = returnvalue::OK;
  SdCardManager::Operations operation;

  if (sdFsmState == SdStates::IDLE) {
    // Nothing to do
    return result;
  }

  if (sdFsmState == SdStates::START) {
    // Init will be performed by separate function
    if (BLOCKING_SD_INIT) {
      sdFsmState = SdStates::IDLE;
      sdInfo.initFinished = true;
      return result;
    } else {
      // Still update SD state file
      if (sdInfo.cfgMode == SdCfgMode::PASSIVE) {
        sdFsmState = SdStates::UPDATE_SD_INFO_END;
      } else {
        sdInfo.cycleCount = 0;
        sdInfo.commandPending = false;
        sdFsmState = SdStates::UPDATE_SD_INFO_START;
      }
    }
  }

  // This lambda checks the non-blocking operation of the SD card manager and assigns the new
  // state on success. It returns 0 for an operation success, -1 for failed operations, and 1
  // for pending operations
  auto nonBlockingSdcOpChecking = [&](SdStates newStateOnSuccess, uint16_t maxCycleCount,
                                      std::string opPrintout) {
    SdCardManager::OpStatus status = sdcMan->checkCurrentOp(operation);
    if (status == SdCardManager::OpStatus::SUCCESS or sdInfo.cycleCount > maxCycleCount) {
      sdFsmState = newStateOnSuccess;
      sdInfo.commandPending = false;
      if (sdInfo.cycleCount > maxCycleCount) {
        sif::warning << "CoreController::sdStateMachine: " << opPrintout << " takes too long"
                     << std::endl;
        sdInfo.cycleCount = 0;
        return -1;
      }
      sdInfo.cycleCount = 0;
      return 0;
    };
    return 1;
  };

  if (sdFsmState == SdStates::UPDATE_SD_INFO_START) {
    if (not sdInfo.commandPending) {
      // Create updated status file
      result = sdcMan->updateSdCardStateFile();
      if (result != returnvalue::OK) {
        sif::warning << "CoreController::sdStateMachine: Updating SD card state file failed"
                     << std::endl;
      }
      result = sdcMan->getSdCardsStatus(sdInfo.currentState);
      updateInternalSdInfo();
      auto currentlyActiveSdc = sdcMan->getActiveSdCard();
      // Used/active SD card switches, so mark SD card unusable so other tasks have some time
      // registering the unavailable SD card.
      if (not currentlyActiveSdc.has_value() or
          ((currentlyActiveSdc.value() == sd::SdCard::SLOT_0) and
           (sdInfo.active == sd::SdCard::SLOT_1)) or
          ((currentlyActiveSdc.value() == sd::SdCard::SLOT_1) and
           (sdInfo.active == sd::SdCard::SLOT_0))) {
        sdInfo.lockSdCardUsage = true;
      }
      if (sdInfo.lockSdCardUsage) {
        sdcMan->markUnusable();
      }
      if (sdInfo.active != sd::SdCard::SLOT_0 and sdInfo.active != sd::SdCard::SLOT_1) {
        sif::warning << "Preferred SD card invalid. Setting to card 0.." << std::endl;
        sdInfo.active = sd::SdCard::SLOT_0;
      }
      if (result != returnvalue::OK) {
        sif::warning << "Getting SD card activity status failed" << std::endl;
      }
      if (sdInfo.cfgMode == SdCfgMode::COLD_REDUNDANT) {
        sif::info << "Cold redundant SD card configuration, target SD card: "
                  << static_cast<int>(sdInfo.active) << std::endl;
      }
      SdStates tgtState = SdStates::IDLE;
      bool skipCycles = sdInfo.lockSdCardUsage;
      // Need to do different things depending on state of SD card which will be active.
      if (sdInfo.activeState == sd::SdState::MOUNTED) {
        // Already mounted, so we can perform handling of the other side.
#if OBSW_VERBOSE_LEVEL >= 1
        std::string mountString;
        if (sdInfo.active == sd::SdCard::SLOT_0) {
          mountString = config::SD_0_MOUNT_POINT;
        } else {
          mountString = config::SD_1_MOUNT_POINT;
        }
        sif::info << "SD card " << sdInfo.activeChar << " already on and mounted at " << mountString
                  << std::endl;
#endif
        sdcMan->setActiveSdCard(sdInfo.active);
        currMntPrefix = sdcMan->getCurrentMountPrefix();
        tgtState = SdStates::DETERMINE_OTHER;
      } else if (sdInfo.activeState == sd::SdState::OFF) {
        // It's okay to do the delay after swichting active SD on, no one can use it anyway..
        sdCardSetup(sdInfo.active, sd::SdState::ON, sdInfo.activeChar, false);
        sdInfo.commandPending = true;
        // Do not skip cycles here, would mess up the state machine. We skip the cycles after
        // the SD card was switched on.
        skipCycles = false;
        // Remain on the current state.
        tgtState = sdFsmState;
      } else if (sdInfo.activeState == sd::SdState::ON) {
        // We can do the delay before mounting where applicable.
        tgtState = SdStates::MOUNT_SELF;
      }
      if (skipCycles) {
        sdFsmState = SdStates::SKIP_TWO_CYCLES_IF_SD_LOCKED;
        fsmStateAfterDelay = tgtState;
        sdInfo.skippedCyclesCount = 0;
      } else {
        sdFsmState = tgtState;
      }
    } else {
      if (nonBlockingSdcOpChecking(SdStates::MOUNT_SELF, 10, "Setting SDC state") <= 0) {
        sdInfo.activeState = sd::SdState::ON;
        currentStateSetter(sdInfo.active, sd::SdState::ON);
        // Skip the two cycles now.
        if (sdInfo.lockSdCardUsage) {
          sdFsmState = SdStates::SKIP_TWO_CYCLES_IF_SD_LOCKED;
          fsmStateAfterDelay = SdStates::MOUNT_SELF;
          sdInfo.skippedCyclesCount = 0;
        }
      }
    }
  }

  if (sdFsmState == SdStates::SKIP_TWO_CYCLES_IF_SD_LOCKED) {
    sdInfo.skippedCyclesCount++;
    // Count to three because this branch will run in the same FSM cycle.
    if (sdInfo.skippedCyclesCount == 3) {
      sdFsmState = fsmStateAfterDelay;
      fsmStateAfterDelay = SdStates::IDLE;
      sdInfo.skippedCyclesCount = 0;
    }
  }

  if (sdFsmState == SdStates::MOUNT_SELF) {
    if (not sdInfo.commandPending) {
      result = sdCardSetup(sdInfo.active, sd::SdState::MOUNTED, sdInfo.activeChar);
      sdInfo.commandPending = true;
    } else {
      if (nonBlockingSdcOpChecking(SdStates::DETERMINE_OTHER, 5, "Mounting SD card") <= 0) {
        sdcMan->setActiveSdCard(sdInfo.active);
        currMntPrefix = sdcMan->getCurrentMountPrefix();
        sdInfo.activeState = sd::SdState::MOUNTED;
        currentStateSetter(sdInfo.active, sd::SdState::MOUNTED);
      }
    }
  }

  if (sdFsmState == SdStates::DETERMINE_OTHER) {
    // Determine whether any additional operations have to be done for the other SD card
    // 1. Cold redundant case: Other SD card needs to be unmounted and switched off
    // 2. Hot redundant case: Other SD card needs to be mounted and switched on
    if (sdInfo.cfgMode == SdCfgMode::COLD_REDUNDANT) {
      if (sdInfo.otherState == sd::SdState::ON) {
        sdFsmState = SdStates::SET_STATE_OTHER;
      } else if (sdInfo.otherState == sd::SdState::MOUNTED) {
        sdFsmState = SdStates::MOUNT_UNMOUNT_OTHER;
      } else {
        // Is already off, update info, but with a small delay
        sdFsmState = SdStates::SKIP_CYCLE_BEFORE_INFO_UPDATE;
      }
    } else if (sdInfo.cfgMode == SdCfgMode::HOT_REDUNDANT) {
      if (sdInfo.otherState == sd::SdState::OFF) {
        sdFsmState = SdStates::SET_STATE_OTHER;
      } else if (sdInfo.otherState == sd::SdState::ON) {
        sdFsmState = SdStates::MOUNT_UNMOUNT_OTHER;
      } else {
        // Is already on and mounted, update info
        sdFsmState = SdStates::SKIP_CYCLE_BEFORE_INFO_UPDATE;
      }
    }
  }

  if (sdFsmState == SdStates::SET_STATE_OTHER) {
    // Set state of other SD card to ON or OFF, depending on redundancy mode
    if (sdInfo.cfgMode == SdCfgMode::COLD_REDUNDANT) {
      if (not sdInfo.commandPending) {
        result = sdCardSetup(sdInfo.other, sd::SdState::OFF, sdInfo.otherChar, false);
        sdInfo.commandPending = true;
      } else {
        if (nonBlockingSdcOpChecking(SdStates::SKIP_CYCLE_BEFORE_INFO_UPDATE, 10,
                                     "Switching off other SD card") <= 0) {
          sdInfo.otherState = sd::SdState::OFF;
          currentStateSetter(sdInfo.other, sd::SdState::OFF);
        }
      }
    } else if (sdInfo.cfgMode == SdCfgMode::HOT_REDUNDANT) {
      if (not sdInfo.commandPending) {
        result = sdCardSetup(sdInfo.other, sd::SdState::ON, sdInfo.otherChar, false);
        sdInfo.commandPending = true;
      } else {
        if (nonBlockingSdcOpChecking(SdStates::MOUNT_UNMOUNT_OTHER, 10,
                                     "Switching on other SD card") <= 0) {
          sdInfo.otherState = sd::SdState::ON;
          currentStateSetter(sdInfo.other, sd::SdState::ON);
        }
      }
    }
  }

  if (sdFsmState == SdStates::MOUNT_UNMOUNT_OTHER) {
    // Mount or unmount other SD card, depending on redundancy mode
    if (sdInfo.cfgMode == SdCfgMode::COLD_REDUNDANT) {
      if (not sdInfo.commandPending) {
        result = sdCardSetup(sdInfo.other, sd::SdState::ON, sdInfo.otherChar);
        sdInfo.commandPending = true;
      } else {
        if (nonBlockingSdcOpChecking(SdStates::SET_STATE_OTHER, 10, "Unmounting other SD card") <=
            0) {
          sdInfo.otherState = sd::SdState::ON;
          currentStateSetter(sdInfo.other, sd::SdState::ON);
        } else {
          sdInfo.otherState = sd::SdState::ON;
          currentStateSetter(sdInfo.other, sd::SdState::ON);
          sdFsmState = SdStates::SET_STATE_OTHER;
        }
      }
    } else if (sdInfo.cfgMode == SdCfgMode::HOT_REDUNDANT) {
      if (not sdInfo.commandPending) {
        result = sdCardSetup(sdInfo.other, sd::SdState::MOUNTED, sdInfo.otherChar);
        sdInfo.commandPending = true;
      } else {
        if (nonBlockingSdcOpChecking(SdStates::UPDATE_SD_INFO_END, 4, "Mounting other SD card") <=
            0) {
          sdInfo.otherState = sd::SdState::MOUNTED;
          currentStateSetter(sdInfo.other, sd::SdState::MOUNTED);
        }
      }
    }
  }

  if (sdFsmState == SdStates::SKIP_CYCLE_BEFORE_INFO_UPDATE) {
    sdFsmState = SdStates::UPDATE_SD_INFO_END;
  } else if (sdFsmState == SdStates::UPDATE_SD_INFO_END) {
    // Update status file
    result = sdcMan->updateSdCardStateFile();
    if (result != returnvalue::OK) {
      sif::warning << "CoreController: Updating SD card state file failed" << std::endl;
    }
    updateInternalSdInfo();
    // Mark usable again in any case.
    sdcMan->markUsable();
    sdInfo.commandPending = false;
    sdFsmState = SdStates::IDLE;
    sdInfo.cycleCount = 0;
    sdcMan->setBlocking(false);
    sdcMan->getSdCardsStatus(sdInfo.currentState);
    if (sdCommandingInfo.cmdPending) {
      sdCommandingInfo.cmdPending = false;
      actionHelper.finish(true, sdCommandingInfo.commander, sdCommandingInfo.actionId,
                          returnvalue::OK);
    }
    const char *modeStr = "UNKNOWN";
    if (sdInfo.cfgMode == SdCfgMode::COLD_REDUNDANT) {
      modeStr = "COLD REDUNDANT";
    } else if (sdInfo.cfgMode == SdCfgMode::HOT_REDUNDANT) {
      modeStr = "HOT REDUNDANT";
    }
    sif::info << "SD card update into " << modeStr
              << " mode finished. Active SD: " << sdInfo.activeChar << std::endl;
    announceSdInfo(sdInfo.currentState);
    if (not sdInfo.initFinished) {
      updateInternalSdInfo();
      sdInfo.initFinished = true;
      sif::info << "SD card initialization finished" << std::endl;
    }
  }

  sdInfo.cycleCount++;
  return returnvalue::OK;
}

void CoreController::currentStateSetter(sd::SdCard sdCard, sd::SdState newState) {
  if (sdCard == sd::SdCard::SLOT_0) {
    sdInfo.currentState.first = newState;
  } else {
    sdInfo.currentState.second = newState;
  }
}

ReturnValue_t CoreController::sdCardSetup(sd::SdCard sdCard, sd::SdState targetState,
                                          std::string sdChar, bool printOutput) {
  std::string mountString;
  sdcMan->setPrintCommandOutput(printOutput);
  if (sdCard == sd::SdCard::SLOT_0) {
    mountString = config::SD_0_MOUNT_POINT;
  } else {
    mountString = config::SD_1_MOUNT_POINT;
  }

  sd::SdState state = sd::SdState::OFF;
  if (sdCard == sd::SdCard::SLOT_0) {
    state = sdInfo.currentState.first;
  } else {
    state = sdInfo.currentState.second;
  }
  if (state == sd::SdState::MOUNTED) {
    if (targetState == sd::SdState::OFF) {
      sif::info << "Switching off SD card " << sdChar << std::endl;
      return sdcMan->switchOffSdCard(sdCard, sdInfo.currentState, true);
    } else if (targetState == sd::SdState::ON) {
      sif::info << "Unmounting SD card " << sdChar << std::endl;
      return sdcMan->unmountSdCard(sdCard);
    } else {
      std::error_code e;
      if (std::filesystem::exists(mountString, e)) {
        sif::info << "SD card " << sdChar << " already on and mounted at " << mountString
                  << std::endl;
        return SdCardManager::ALREADY_MOUNTED;
      }
      sif::error << "SD card mounted but expected mount point " << mountString << " not found!"
                 << std::endl;
      return SdCardManager::MOUNT_ERROR;
    }
  }

  if (state == sd::SdState::OFF) {
    if (targetState == sd::SdState::MOUNTED) {
      sif::info << "Switching on and mounting SD card " << sdChar << " at " << mountString
                << std::endl;
      return sdcMan->switchOnSdCard(sdCard, true, &sdInfo.currentState);
    } else if (targetState == sd::SdState::ON) {
      sif::info << "Switching on SD card " << sdChar << std::endl;
      return sdcMan->switchOnSdCard(sdCard, false, &sdInfo.currentState);
    }
  }

  else if (state == sd::SdState::ON) {
    if (targetState == sd::SdState::MOUNTED) {
      sif::info << "Mounting SD card " << sdChar << " at " << mountString << std::endl;
      return sdcMan->mountSdCard(sdCard);
    } else if (targetState == sd::SdState::OFF) {
      sif::info << "Switching off SD card " << sdChar << std::endl;
      return sdcMan->switchOffSdCard(sdCard, sdInfo.currentState, false);
    }
  } else {
    sif::warning << "CoreController::sdCardSetup: Invalid state for this call" << std::endl;
  }
  return returnvalue::OK;
}

ReturnValue_t CoreController::sdColdRedundantBlockingInit() {
  ReturnValue_t result = returnvalue::OK;

  result = sdCardSetup(sdInfo.active, sd::SdState::MOUNTED, sdInfo.activeChar);
  if (result != SdCardManager::ALREADY_MOUNTED and result != returnvalue::OK) {
    sif::warning << "Setting up preferred card " << sdInfo.otherChar
                 << " in cold redundant mode failed" << std::endl;
    // Try other SD card and mark set up operation as failed
    sdCardSetup(sdInfo.active, sd::SdState::MOUNTED, sdInfo.activeChar);
    result = returnvalue::FAILED;
  }

  if (result != returnvalue::FAILED and sdInfo.otherState != sd::SdState::OFF) {
    sif::info << "Switching off secondary SD card " << sdInfo.otherChar << std::endl;
    // Switch off other SD card in cold redundant mode if setting up preferred one worked
    // without issues
    ReturnValue_t result2 = sdcMan->switchOffSdCard(sdInfo.other, sdInfo.currentState, true);
    if (result2 != returnvalue::OK and result2 != SdCardManager::ALREADY_OFF) {
      sif::warning << "Switching off secondary SD card " << sdInfo.otherChar
                   << " in cold redundant mode failed" << std::endl;
    }
  }
  return result;
}

ReturnValue_t CoreController::incrementAllocationFailureCount() {
  uint32_t count = 0;
  ReturnValue_t result = scratch::readNumber(scratch::ALLOC_FAILURE_COUNT, count);
  if (result != returnvalue::OK) {
    return result;
  }
  count++;
  return scratch::writeNumber(scratch::ALLOC_FAILURE_COUNT, count);
}

ReturnValue_t CoreController::initVersionFile() {
  using namespace fsfw;
  std::string unameFileName = "/tmp/uname_version.txt";
  // TODO: No -v flag for now. If the kernel version is used, need to cut off first few letters
  std::string unameCmd = "uname -mnrso > " + unameFileName;
  int result = std::system(unameCmd.c_str());
  if (result != 0) {
    utility::handleSystemError(result, "CoreController::versionFileInit");
  }
  std::ifstream unameFile(unameFileName);
  std::string unameLine;
  if (not std::getline(unameFile, unameLine)) {
    sif::warning << "CoreController::versionFileInit: Retrieving uname line failed" << std::endl;
  }

  std::string fullObswVersionString = "OBSW: v" + std::to_string(common::OBSW_VERSION_MAJOR) + "." +
                                      std::to_string(common::OBSW_VERSION_MINOR) + "." +
                                      std::to_string(common::OBSW_VERSION_REVISION);
  char versionString[16] = {};
  fsfw::FSFW_VERSION.getVersion(versionString, sizeof(versionString));
  std::string fullFsfwVersionString = "FSFW: v" + std::string(versionString);
  std::string systemString = "System: " + unameLine;
  std::string versionFilePath = currMntPrefix + VERSION_FILE;
  std::fstream versionFile;

  std::error_code e;
  if (not std::filesystem::exists(versionFilePath, e)) {
    sif::info << "Writing version file " << versionFilePath << ".." << std::endl;
    versionFile.open(versionFilePath, std::ios_base::out);
    versionFile << fullObswVersionString << std::endl;
    versionFile << fullFsfwVersionString << std::endl;
    versionFile << systemString << std::endl;
    return returnvalue::OK;
  }

  // Check whether any version has changed
  bool createNewFile = false;
  versionFile.open(versionFilePath);
  std::string currentVersionString;
  uint8_t idx = 0;
  while (std::getline(versionFile, currentVersionString)) {
    if (idx == 0) {
      if (currentVersionString != fullObswVersionString) {
        sif::info << "OBSW version changed" << std::endl;
        sif::info << "From " << currentVersionString << " to " << fullObswVersionString
                  << std::endl;
        createNewFile = true;
      }
    } else if (idx == 1) {
      if (currentVersionString != fullFsfwVersionString) {
        sif::info << "FSFW version changed" << std::endl;
        sif::info << "From " << currentVersionString << " to " << fullFsfwVersionString
                  << std::endl;
        createNewFile = true;
      }
    } else if (idx == 2) {
      if (currentVersionString != systemString) {
        sif::info << "System version changed" << std::endl;
        sif::info << "Old: " << currentVersionString << std::endl;
        sif::info << "New: " << systemString << std::endl;
        createNewFile = true;
      }
    } else {
      sif::warning << "Invalid version file! Rewriting it.." << std::endl;
      createNewFile = true;
    }
    idx++;
  }

  // Overwrite file if necessary
  if (createNewFile) {
    sif::info << "Rewriting version.txt file with updated versions.." << std::endl;
    versionFile.close();
    versionFile.open(versionFilePath, std::ios_base::out | std::ios_base::trunc);
    versionFile << fullObswVersionString << std::endl;
    versionFile << fullFsfwVersionString << std::endl;
    versionFile << systemString << std::endl;
  }

  return returnvalue::OK;
}

ReturnValue_t CoreController::actionListDirectoryDumpDirectly(ActionId_t actionId,
                                                              MessageQueueId_t commandedBy,
                                                              const uint8_t *data, size_t size) {
  core::ListDirectoryCmdBase parser(data, size);
  ReturnValue_t result = parser.parse();
  if (result != returnvalue::OK) {
    return result;
  }

  std::ostringstream oss("ls -l", std::ostringstream::ate);
  if (parser.aFlagSet()) {
    oss << "a";
  }
  if (parser.rFlagSet()) {
    oss << "R";
  }

  size_t repoNameLen = 0;
  const char *repoName = parser.getRepoName(repoNameLen);

  oss << " " << repoName << " > " << LIST_DIR_DUMP_WORK_FILE;
  sif::info << "Executing " << oss.str() << " for direct dump";
  if (parser.compressionOptionSet()) {
    sif::info << " with compression";
  }
  sif::info << std::endl;
  int ret = std::system(oss.str().c_str());
  if (ret != 0) {
    utility::handleSystemError(result, "CoreController::actionListDirectoryDumpDirectly");
    return returnvalue::FAILED;
  }
  if (parser.compressionOptionSet()) {
    std::string compressedName = LIST_DIR_DUMP_WORK_FILE + std::string(".gz");
    oss.str("");
    oss << "gzip " << LIST_DIR_DUMP_WORK_FILE;
    ret = std::system(oss.str().c_str());
    if (ret != 0) {
      utility::handleSystemError(result, "CoreController::actionListDirectoryDumpDirectly");
      return returnvalue::FAILED;
    }
    oss.str("");
    // Overwrite the work file with the compressed archive.
    oss << "mv " << compressedName << " " << LIST_DIR_DUMP_WORK_FILE;
    ret = std::system(oss.str().c_str());
    if (ret != 0) {
      utility::handleSystemError(result, "CoreController::actionListDirectoryDumpDirectly");
      return returnvalue::FAILED;
    }
  }
  dirListingBuf[8] = parser.compressionOptionSet();
  // First four bytes reserved for segment index. One byte for compression option information
  std::strcpy(reinterpret_cast<char *>(dirListingBuf.data() + 2 * sizeof(uint32_t) + 1), repoName);
  std::ifstream ifile(LIST_DIR_DUMP_WORK_FILE, std::ios::binary);
  if (ifile.bad()) {
    return returnvalue::FAILED;
  }
  std::error_code e;
  dumpContext.totalFileSize = std::filesystem::file_size(LIST_DIR_DUMP_WORK_FILE, e);
  dumpContext.segmentIdx = 0;
  dumpContext.dumpedBytes = 0;
  size_t nextDumpLen = 0;
  size_t dummy = 0;
  dumpContext.maxDumpLen = dirListingBuf.size() - 2 * sizeof(uint32_t) - 1 - repoNameLen - 1;
  dumpContext.listingDataOffset = 2 * sizeof(uint32_t) + 1 + repoNameLen + 1;
  uint32_t chunks = dumpContext.totalFileSize / dumpContext.maxDumpLen;
  if (dumpContext.totalFileSize % dumpContext.maxDumpLen != 0) {
    chunks++;
  }
  SerializeAdapter::serialize(&chunks, dirListingBuf.data() + sizeof(uint32_t), &dummy,
                              dirListingBuf.size() - sizeof(uint32_t),
                              SerializeIF::Endianness::NETWORK);
  while (dumpContext.dumpedBytes < dumpContext.totalFileSize) {
    ifile.seekg(dumpContext.dumpedBytes, std::ios::beg);
    nextDumpLen = dumpContext.maxDumpLen;
    if (dumpContext.totalFileSize - dumpContext.dumpedBytes < dumpContext.maxDumpLen) {
      nextDumpLen = dumpContext.totalFileSize - dumpContext.dumpedBytes;
    }
    SerializeAdapter::serialize(&dumpContext.segmentIdx, dirListingBuf.data(), &dummy,
                                dirListingBuf.size(), SerializeIF::Endianness::NETWORK);
    ifile.read(reinterpret_cast<char *>(dirListingBuf.data() + dumpContext.listingDataOffset),
               nextDumpLen);
    result = actionHelper.reportData(commandedBy, actionId, dirListingBuf.data(),
                                     dumpContext.listingDataOffset + nextDumpLen);
    if (result != returnvalue::OK) {
      // Remove work file when we are done
      std::filesystem::remove(LIST_DIR_DUMP_WORK_FILE, e);
      return result;
    }
    dumpContext.segmentIdx++;
    dumpContext.dumpedBytes += nextDumpLen;
    // Dump takes multiple task cycles, so cache the dump state and continue dump the next cycles.
    if (dumpContext.segmentIdx == 10) {
      dumpContext.active = true;
      dumpContext.firstDump = true;
      dumpContext.commander = commandedBy;
      dumpContext.actionId = actionId;
      return returnvalue::OK;
    }
  }
  // Remove work file when we are done
  std::filesystem::remove(LIST_DIR_DUMP_WORK_FILE, e);
  return EXECUTION_FINISHED;
}

ReturnValue_t CoreController::actionListDirectoryIntoFile(ActionId_t actionId,
                                                          MessageQueueId_t commandedBy,
                                                          const uint8_t *data, size_t size) {
  core::ListDirectoryIntoFile parser(data, size);
  ReturnValue_t result = parser.parse();
  if (result != returnvalue::OK) {
    return result;
  }

  std::ostringstream oss("ls -l", std::ostringstream::ate);
  if (parser.aFlagSet()) {
    oss << "a";
  }
  if (parser.rFlagSet()) {
    oss << "R";
  }

  size_t repoNameLen = 0;
  const char *repoName = parser.getRepoName(repoNameLen);
  size_t targetFileNameLen = 0;
  const char *targetFileName = parser.getTargetName(targetFileNameLen);
  oss << " " << repoName << " > " << targetFileName;
  sif::info << "Executing list directory request, command: " << oss.str() << std::endl;
  int ret = std::system(oss.str().c_str());
  if (ret != 0) {
    utility::handleSystemError(result, "CoreController::actionListDirectoryIntoFile");
    return returnvalue::FAILED;
  }

  // Compression will add a .gz ending. I don't have any issue with this, it makes it explicit
  // that this is a compressed file.
  if (parser.compressionOptionSet()) {
    oss.str("");
    oss << "gzip " << targetFileName;
    sif::info << "Compressing directory listing: " << oss.str() << std::endl;
    ret = std::system(oss.str().c_str());
    if (ret != 0) {
      utility::handleSystemError(result, "CoreController::actionListDirectoryIntoFile");
      return returnvalue::FAILED;
    }
  }
  return EXECUTION_FINISHED;
}

ReturnValue_t CoreController::initBootCopyFile() {
  std::error_code e;
  if (not std::filesystem::exists(CURR_COPY_FILE, e)) {
    // This file is created by the systemd service eive-early-config so this should
    // not happen normally
    std::string cmd = "xsc_boot_copy > " + std::string(CURR_COPY_FILE);
    int result = std::system(cmd.c_str());
    if (result != 0) {
      utility::handleSystemError(result, "CoreController::initBootCopy");
    }
  }
  return returnvalue::OK;
}

void CoreController::getCurrentBootCopy(xsc::Chip &chip, xsc::Copy &copy) {
  xsc_libnor_chip_t xscChip;
  xsc_libnor_copy_t xscCopy;
  xsc_boot_get_chip_copy(&xscChip, &xscCopy);
  // Not really thread-safe but it does not need to be
  chip = static_cast<xsc::Chip>(xscChip);
  copy = static_cast<xsc::Copy>(xscCopy);
}

ReturnValue_t CoreController::actionXscReboot(const uint8_t *data, size_t size) {
  if (size < 1) {
    return HasActionsIF::INVALID_PARAMETERS;
  }
  bool rebootSameBootCopy = data[0];
  bool protOpPerformed = false;
  SdCardManager::instance()->setBlocking(true);
  if (rebootSameBootCopy) {
#if OBSW_VERBOSE_LEVEL >= 1
    sif::info << "CoreController::actionPerformReboot: Rebooting on current image" << std::endl;
#endif
    gracefulShutdownTasks(xsc::Chip::SELF_CHIP, xsc::Copy::SELF_COPY, protOpPerformed);
    int result = std::system("xsc_boot_copy -r");
    if (result != 0) {
      utility::handleSystemError(result, "CoreController::executeAction");
      return returnvalue::FAILED;
    }
    return HasActionsIF::EXECUTION_FINISHED;
  }
  if (size < 3 or (data[1] > 1 or data[2] > 1)) {
    return HasActionsIF::INVALID_PARAMETERS;
  }
#if OBSW_VERBOSE_LEVEL >= 1
  sif::info << "CoreController::actionPerformReboot: Rebooting on " << static_cast<int>(data[1])
            << " " << static_cast<int>(data[2]) << std::endl;
#endif

  // Check that the target chip and copy is writeprotected first
  generateChipStateFile();
  // If any boot copies are unprotected, protect them here
  auto tgtChip = static_cast<xsc::Chip>(data[1]);
  auto tgtCopy = static_cast<xsc::Copy>(data[2]);

  performGracefulShutdown(tgtChip, tgtCopy);
  return returnvalue::FAILED;
}

ReturnValue_t CoreController::actionReboot(const uint8_t *data, size_t size) {
  bool protOpPerformed = false;
  gracefulShutdownTasks(xsc::Chip::CHIP_0, xsc::Copy::COPY_0, protOpPerformed);
  std::system("reboot");
  return returnvalue::OK;
}

ReturnValue_t CoreController::gracefulShutdownTasks(xsc::Chip chip, xsc::Copy copy,
                                                    bool &protOpPerformed) {
  // Store both sequence counters persistently.
  core::SAVE_CFDP_SEQUENCE_COUNT = true;
  core::SAVE_PUS_SEQUENCE_COUNT = true;

  sdcMan->setBlocking(true);
  sdcMan->markUnusable();
  // Wait two seconds to ensure no one uses the SD cards
  TaskFactory::delayTask(2000);

  // Ensure that all writes/reads do finish.
  sync();

  // Unmount and switch off SD cards. This could possibly fix issues with the SD card and is
  // the more graceful way to reboot the system. This function takes around 400 ms.
  ReturnValue_t result = handleSwitchingSdCardsOffNonBlocking();
  if (result != returnvalue::OK) {
    sif::error
        << "CoreController::gracefulShutdownTasks: Issues unmounting or switching SD cards off"
        << std::endl;
  }

  // Ensure that the target chip is writeprotected in any case.
  bool wasProtected = handleBootCopyProt(chip, copy, true);
  if (wasProtected) {
    // TODO: Would be nice to notify operator. But we can't use the filesystem anymore
    // and a reboot is imminent. Use scratch buffer?
    sif::info << "Running slot was writeprotected before reboot" << std::endl;
  }
  sif::info << "Graceful shutdown handling done" << std::endl;
  // Ensure that all diagnostic prinouts arrive.
  TaskFactory::delayTask(50);
  return result;
}

void CoreController::updateInternalSdInfo() {
  if (sdInfo.active == sd::SdCard::SLOT_0) {
    sdInfo.activeChar = "0";
    sdInfo.otherChar = "1";
    sdInfo.otherState = sdInfo.currentState.second;
    sdInfo.activeState = sdInfo.currentState.first;
    sdInfo.other = sd::SdCard::SLOT_1;

  } else if (sdInfo.active == sd::SdCard::SLOT_1) {
    sdInfo.activeChar = "1";
    sdInfo.otherChar = "0";
    sdInfo.otherState = sdInfo.currentState.first;
    sdInfo.activeState = sdInfo.currentState.second;
    sdInfo.other = sd::SdCard::SLOT_0;
  } else {
    sif::warning << "CoreController::updateSdInfoOther: Invalid SD card passed" << std::endl;
  }
}

bool CoreController::sdInitFinished() const { return sdInfo.initFinished; }

ReturnValue_t CoreController::generateChipStateFile() {
  int result = std::system(CHIP_PROT_SCRIPT);
  if (result != 0) {
    utility::handleSystemError(result, "CoreController::generateChipStateFile");
    return returnvalue::FAILED;
  }
  return returnvalue::OK;
}

ReturnValue_t CoreController::setBootCopyProtectionAndUpdateFile(xsc::Chip targetChip,
                                                                 xsc::Copy targetCopy,
                                                                 bool protect) {
  if (targetChip == xsc::Chip::ALL_CHIP or targetCopy == xsc::Copy::ALL_COPY) {
    return returnvalue::FAILED;
  }

  bool protOperationPerformed = handleBootCopyProt(targetChip, targetCopy, protect);
  if (protOperationPerformed) {
    updateProtInfo();
  }
  return returnvalue::OK;
}

bool CoreController::handleBootCopyProt(xsc::Chip targetChip, xsc::Copy targetCopy, bool protect) {
  std::ostringstream oss;
  oss << "writeprotect ";
  if (targetChip == xsc::Chip::SELF_CHIP) {
    targetChip = CURRENT_CHIP;
  }
  if (targetCopy == xsc::Copy::SELF_COPY) {
    targetCopy = CURRENT_COPY;
  }
  if (targetChip == xsc::Chip::CHIP_0) {
    oss << "0 ";
  } else if (targetChip == xsc::Chip::CHIP_1) {
    oss << "1 ";
  }
  if (targetCopy == xsc::Copy::COPY_0) {
    oss << "0 ";
  } else if (targetCopy == xsc::Copy::COPY_1) {
    oss << "1 ";
  }
  if (protect) {
    oss << "1";
  } else {
    oss << "0";
  }
  sif::info << "Executing command: " << oss.str() << std::endl;
  int result = std::system(oss.str().c_str());
  if (result == 0) {
    return true;
  }
  return false;
}

ReturnValue_t CoreController::updateProtInfo(bool regenerateChipStateFile) {
  using namespace std;
  ReturnValue_t result = returnvalue::OK;
  if (regenerateChipStateFile) {
    result = generateChipStateFile();
    if (result != returnvalue::OK) {
      sif::warning << "CoreController::updateProtInfo: Generating chip state file failed"
                   << std::endl;
      return result;
    }
  }
  std::error_code e;
  if (not filesystem::exists(CHIP_STATE_FILE, e)) {
    return returnvalue::FAILED;
  }
  ifstream chipStateFile(CHIP_STATE_FILE);
  if (not chipStateFile.good()) {
    return returnvalue::FAILED;
  }
  string nextLine;
  uint8_t lineCounter = 0;
  string word;
  while (getline(chipStateFile, nextLine)) {
    result = handleProtInfoUpdateLine(nextLine);
    if (result != returnvalue::OK) {
      sif::warning << "CoreController::updateProtInfo: Protection info update failed!" << std::endl;
      return result;
    }
    ++lineCounter;
    if (lineCounter > 4) {
      sif::warning << "CoreController::checkAndProtectBootCopy: "
                      "Line counter larger than 4"
                   << std::endl;
    }
  }
  return returnvalue::OK;
}

ReturnValue_t CoreController::handleProtInfoUpdateLine(std::string nextLine) {
  using namespace std;
  string word;
  uint8_t wordIdx = 0;
  istringstream iss(nextLine);
  xsc::Chip currentChip = xsc::Chip::CHIP_0;
  xsc::Copy currentCopy = xsc::Copy::COPY_0;
  while (iss >> word) {
    if (wordIdx == 1) {
      currentChip = static_cast<xsc::Chip>(stoi(word));
    }
    if (wordIdx == 3) {
      currentCopy = static_cast<xsc::Copy>(stoi(word));
    }

    if (wordIdx == 5) {
      if (word == "unlocked.") {
        protArray[currentChip][currentCopy] = false;
      } else {
        protArray[currentChip][currentCopy] = true;
      }
    }
    wordIdx++;
    if (wordIdx >= 10) {
      break;
    }
  }
  return returnvalue::OK;
}

void CoreController::performMountedSdCardOperations() {
  auto mountedSdCardOp = [&](sd::SdCard sdCard, std::string mntPoint) {
    if (not performOneShotSdCardOpsSwitch) {
      std::ostringstream path;
      path << mntPoint << "/" << core::CONF_FOLDER;
      std::error_code e;
      if (not std::filesystem::exists(path.str()), e) {
        bool created = std::filesystem::create_directory(path.str(), e);
        if (not created) {
          sif::error << "Could not create CONF folder at " << path.str() << ": " << e.message()
                     << std::endl;
          return;
        }
      }
      initVersionFile();
      ReturnValue_t result = initBootCopyFile();
      if (result != returnvalue::OK) {
        sif::warning << "CoreController::CoreController: Boot copy init" << std::endl;
      }
      if (not timeFileInitDone) {
        initClockFromTimeFile();
      }
      performRebootWatchdogHandling(false);
      performRebootCountersHandling(false);
    }
    backupTimeFileHandler();
  };
  bool someSdCardActive = false;
  if (sdInfo.active == sd::SdCard::SLOT_0 and sdcMan->isSdCardUsable(sd::SdCard::SLOT_0)) {
    mountedSdCardOp(sd::SdCard::SLOT_0, config::SD_0_MOUNT_POINT);
    someSdCardActive = true;
  }
  if (sdInfo.active == sd::SdCard::SLOT_1 and sdcMan->isSdCardUsable(sd::SdCard::SLOT_1)) {
    mountedSdCardOp(sd::SdCard::SLOT_1, config::SD_1_MOUNT_POINT);
    someSdCardActive = true;
  }
  if (someSdCardActive) {
    performOneShotSdCardOpsSwitch = true;
  }
}

ReturnValue_t CoreController::performSdCardCheck() {
  bool mountedReadOnly = false;
  SdCardManager::SdStatePair active;
  sdcMan->getSdCardsStatus(active);
  if (sdFsmState != SdStates::IDLE) {
    return returnvalue::OK;
  }
  auto sdCardCheck = [&](sd::SdCard sdCard) {
    ReturnValue_t result = sdcMan->isSdCardMountedReadOnly(sdCard, mountedReadOnly);
    if (result != returnvalue::OK) {
      sif::error << "CoreController::performSdCardCheck: Could not check "
                    "read-only mount state"
                 << std::endl;
    }
    if (mountedReadOnly) {
      int linuxErrno = 0;
      result = sdcMan->performFsck(sdCard, true, linuxErrno);
      if (result != returnvalue::OK) {
        sif::error << "CoreController::performSdCardCheck: fsck command on SD Card "
                   << static_cast<uint8_t>(sdCard) << " failed with code " << linuxErrno << " | "
                   << strerror(linuxErrno);
      }
      result = sdcMan->remountReadWrite(sdCard);
      if (result == returnvalue::OK) {
        sif::warning << "CoreController::performSdCardCheck: Remounted SD Card "
                     << static_cast<uint8_t>(sdCard) << " read-write";
      } else {
        sif::error << "CoreController::performSdCardCheck: Remounting SD Card "
                   << static_cast<uint8_t>(sdCard) << " read-write failed";
      }
    }
  };
  if (active.first == sd::SdState::MOUNTED) {
    sdCardCheck(sd::SdCard::SLOT_0);
  }
  if (active.second == sd::SdState::MOUNTED) {
    sdCardCheck(sd::SdCard::SLOT_1);
  }
#if OBSW_SD_CARD_MUST_BE_ON == 1
  // This is FDIR. The core controller will attempt once to get some SD card working
  bool someSdCardActive = false;
  if ((sdInfo.active == sd::SdCard::SLOT_0 and sdcMan->isSdCardUsable(sd::SdCard::SLOT_0)) or
      (sdInfo.active == sd::SdCard::SLOT_1 and sdcMan->isSdCardUsable(sd::SdCard::SLOT_1))) {
    someSdCardActive = true;
  }
  if (not someSdCardActive and remountAttemptFlag) {
    triggerEvent(core::NO_SD_CARD_ACTIVE);
    initSdCardBlocking();
    remountAttemptFlag = false;
  }
#endif
  return returnvalue::OK;
}

void CoreController::performRebootWatchdogHandling(bool recreateFile) {
  using namespace std;
  std::string path = currMntPrefix + REBOOT_WATCHDOG_FILE;
  std::string legacyPath = currMntPrefix + LEGACY_REBOOT_WATCHDOG_FILE;
  std::error_code e;
  // TODO: Remove at some point in the future.
  if (std::filesystem::exists(legacyPath, e)) {
    // Old file might still exist, so copy it to new path
    std::filesystem::copy(legacyPath, path, std::filesystem::copy_options::overwrite_existing, e);
    if (e) {
      sif::error << "File copy has failed: " << e.message() << std::endl;
    }
  }
  if (not std::filesystem::exists(path, e) or recreateFile) {
#if OBSW_VERBOSE_LEVEL >= 1
    sif::info << "CoreController::performRebootFileHandling: Recreating reboot watchdog file"
              << std::endl;
#endif
    rebootWatchdogFile.enabled = false;
    rebootWatchdogFile.img00Cnt = 0;
    rebootWatchdogFile.img01Cnt = 0;
    rebootWatchdogFile.img10Cnt = 0;
    rebootWatchdogFile.img11Cnt = 0;
    rebootWatchdogFile.lastChip = xsc::Chip::CHIP_0;
    rebootWatchdogFile.lastCopy = xsc::Copy::COPY_0;
    rebootWatchdogFile.img00Lock = false;
    rebootWatchdogFile.img01Lock = false;
    rebootWatchdogFile.img10Lock = false;
    rebootWatchdogFile.img11Lock = false;
    rebootWatchdogFile.mechanismNextChip = xsc::Chip::NO_CHIP;
    rebootWatchdogFile.mechanismNextCopy = xsc::Copy::NO_COPY;
    rebootWatchdogFile.bootFlag = false;
    rewriteRebootWatchdogFile(rebootWatchdogFile);
  } else {
    if (not parseRebootWatchdogFile(path, rebootWatchdogFile)) {
      performRebootWatchdogHandling(true);
      return;
    }
  }

  if (CURRENT_CHIP == xsc::CHIP_0) {
    if (CURRENT_COPY == xsc::COPY_0) {
      rebootWatchdogFile.img00Cnt++;
    } else {
      rebootWatchdogFile.img01Cnt++;
    }
  } else {
    if (CURRENT_COPY == xsc::COPY_0) {
      rebootWatchdogFile.img10Cnt++;
    } else {
      rebootWatchdogFile.img11Cnt++;
    }
  }

  if (rebootWatchdogFile.bootFlag) {
    // Trigger event to inform ground that a reboot was triggered
    uint32_t p1 = rebootWatchdogFile.lastChip << 16 | rebootWatchdogFile.lastCopy;
    triggerEvent(core::REBOOT_MECHANISM_TRIGGERED, p1, 0);
    // Clear the boot flag
    rebootWatchdogFile.bootFlag = false;
  }

  if (rebootWatchdogFile.mechanismNextChip != xsc::NO_CHIP and
      rebootWatchdogFile.mechanismNextCopy != xsc::NO_COPY) {
    if (CURRENT_CHIP != rebootWatchdogFile.mechanismNextChip or
        CURRENT_COPY != rebootWatchdogFile.mechanismNextCopy) {
      std::string infoString = std::to_string(rebootWatchdogFile.mechanismNextChip) + " " +
                               std::to_string(rebootWatchdogFile.mechanismNextCopy);
      sif::warning << "CoreController::performRebootFileHandling: Expected to be on image "
                   << infoString << " but currently on other image. Locking " << infoString
                   << std::endl;
      // Firmware or other component might be corrupt and we are on another image then the target
      // image specified by the mechanism. We can't really trust the target image anymore.
      // Lock it for now
      if (rebootWatchdogFile.mechanismNextChip == xsc::CHIP_0) {
        if (rebootWatchdogFile.mechanismNextCopy == xsc::COPY_0) {
          rebootWatchdogFile.img00Lock = true;
        } else {
          rebootWatchdogFile.img01Lock = true;
        }
      } else {
        if (rebootWatchdogFile.mechanismNextCopy == xsc::COPY_0) {
          rebootWatchdogFile.img10Lock = true;
        } else {
          rebootWatchdogFile.img11Lock = true;
        }
      }
    }
  }

  rebootWatchdogFile.lastChip = CURRENT_CHIP;
  rebootWatchdogFile.lastCopy = CURRENT_COPY;
  // Only reboot if the reboot functionality is enabled.
  // The handler will still increment the boot counts
  if (rebootWatchdogFile.enabled and
      (*rebootWatchdogFile.relevantBootCnt >= rebootWatchdogFile.maxCount)) {
    // Reboot to other image
    bool doReboot = false;
    xsc::Chip tgtChip = xsc::NO_CHIP;
    xsc::Copy tgtCopy = xsc::NO_COPY;
    rebootWatchdogAlgorithm(rebootWatchdogFile, doReboot, tgtChip, tgtCopy);
    if (doReboot) {
      rebootWatchdogFile.bootFlag = true;
#if OBSW_VERBOSE_LEVEL >= 1
      sif::info << "Boot counter for image " << CURRENT_CHIP << " " << CURRENT_COPY
                << " too high. Rebooting to " << tgtChip << " " << tgtCopy << std::endl;
#endif
      rebootWatchdogFile.mechanismNextChip = tgtChip;
      rebootWatchdogFile.mechanismNextCopy = tgtCopy;
      rewriteRebootWatchdogFile(rebootWatchdogFile);
      performGracefulShutdown(tgtChip, tgtCopy);
    }
  } else {
    rebootWatchdogFile.mechanismNextChip = xsc::NO_CHIP;
    rebootWatchdogFile.mechanismNextCopy = xsc::NO_COPY;
  }
  rewriteRebootWatchdogFile(rebootWatchdogFile);
}

void CoreController::rebootWatchdogAlgorithm(RebootWatchdogFile &rf, bool &needsReboot,
                                             xsc::Chip &tgtChip, xsc::Copy &tgtCopy) {
  tgtChip = xsc::CHIP_0;
  tgtCopy = xsc::COPY_0;
  needsReboot = false;
  if ((CURRENT_CHIP == xsc::CHIP_0) and (CURRENT_COPY == xsc::COPY_0) and
      (rf.img00Cnt >= rf.maxCount)) {
    needsReboot = true;
    if (rf.img01Cnt < rf.maxCount and not rf.img01Lock) {
      tgtCopy = xsc::COPY_1;
      return;
    }
    if (rf.img10Cnt < rf.maxCount and not rf.img10Lock) {
      tgtChip = xsc::CHIP_1;
      return;
    }
    if (rf.img11Cnt < rf.maxCount and not rf.img11Lock) {
      tgtChip = xsc::CHIP_1;
      tgtCopy = xsc::COPY_1;
      return;
    }
    // Can't really do much here. Stay on image
    sif::warning
        << "All reboot counts too high or all fallback images locked, already on fallback image"
        << std::endl;
    needsReboot = false;
    return;
  }
  if ((CURRENT_CHIP == xsc::CHIP_0) and (CURRENT_COPY == xsc::COPY_1) and
      (rf.img01Cnt >= rf.maxCount)) {
    needsReboot = true;
    if (rf.img00Cnt < rf.maxCount and not rf.img00Lock) {
      // Reboot on fallback image
      return;
    }
    if (rf.img10Cnt < rf.maxCount and not rf.img10Lock) {
      tgtChip = xsc::CHIP_1;
      return;
    }
    if (rf.img11Cnt < rf.maxCount and not rf.img11Lock) {
      tgtChip = xsc::CHIP_1;
      tgtCopy = xsc::COPY_1;
    }
    if (rf.img00Lock) {
      needsReboot = false;
    }
    // Reboot to fallback image
  }
  if ((CURRENT_CHIP == xsc::CHIP_1) and (CURRENT_COPY == xsc::COPY_0) and
      (rf.img10Cnt >= rf.maxCount)) {
    needsReboot = true;
    if (rf.img11Cnt < rf.maxCount and not rf.img11Lock) {
      tgtChip = xsc::CHIP_1;
      tgtCopy = xsc::COPY_1;
      return;
    }
    if (rf.img00Cnt < rf.maxCount and not rf.img00Lock) {
      return;
    }
    if (rf.img01Cnt < rf.maxCount and not rf.img01Lock) {
      tgtCopy = xsc::COPY_1;
      return;
    }
    if (rf.img00Lock) {
      needsReboot = false;
    }
    // Reboot to fallback image
  }
  if ((CURRENT_CHIP == xsc::CHIP_1) and (CURRENT_COPY == xsc::COPY_1) and
      (rf.img11Cnt >= rf.maxCount)) {
    needsReboot = true;
    if (rf.img10Cnt < rf.maxCount and not rf.img10Lock) {
      tgtChip = xsc::CHIP_1;
      return;
    }
    if (rf.img00Cnt < rf.maxCount and not rf.img00Lock) {
      return;
    }
    if (rf.img01Cnt < rf.maxCount and not rf.img01Lock) {
      tgtCopy = xsc::COPY_1;
      return;
    }
    if (rf.img00Lock) {
      needsReboot = false;
    }
    // Reboot to fallback image
  }
}

bool CoreController::parseRebootWatchdogFile(std::string path, RebootWatchdogFile &rf) {
  using namespace std;
  std::string selfMatch;
  if (CURRENT_CHIP == xsc::CHIP_0) {
    if (CURRENT_COPY == xsc::COPY_0) {
      selfMatch = "00";
    } else {
      selfMatch = "01";
    }
  } else {
    if (CURRENT_COPY == xsc::COPY_0) {
      selfMatch = "10";
    } else {
      selfMatch = "11";
    }
  }
  ifstream file(path);
  string word;
  string line;
  uint8_t lineIdx = 0;
  while (std::getline(file, line)) {
    istringstream iss(line);
    switch (lineIdx) {
      case 0: {
        iss >> word;
        if (word.find("on:") == string::npos) {
          // invalid file
          return false;
        }
        iss >> rf.enabled;
        break;
      }
      case 1: {
        iss >> word;
        if (word.find("maxcnt:") == string::npos) {
          return false;
        }
        iss >> rf.maxCount;
        break;
      }
      case 2: {
        iss >> word;
        if (word.find("img00:") == string::npos) {
          return false;
        }
        iss >> rf.img00Cnt;
        if (word.find(selfMatch) != string::npos) {
          rf.relevantBootCnt = &rf.img00Cnt;
        }
        break;
      }
      case 3: {
        iss >> word;
        if (word.find("img01:") == string::npos) {
          return false;
        }
        iss >> rf.img01Cnt;
        if (word.find(selfMatch) != string::npos) {
          rf.relevantBootCnt = &rf.img01Cnt;
        }
        break;
      }
      case 4: {
        iss >> word;
        if (word.find("img10:") == string::npos) {
          return false;
        }
        iss >> rf.img10Cnt;
        if (word.find(selfMatch) != string::npos) {
          rf.relevantBootCnt = &rf.img10Cnt;
        }
        break;
      }
      case 5: {
        iss >> word;
        if (word.find("img11:") == string::npos) {
          return false;
        }
        iss >> rf.img11Cnt;
        if (word.find(selfMatch) != string::npos) {
          rf.relevantBootCnt = &rf.img11Cnt;
        }
        break;
      }
      case 6: {
        iss >> word;
        if (word.find("img00lock:") == string::npos) {
          return false;
        }
        iss >> rf.img00Lock;
        break;
      }
      case 7: {
        iss >> word;
        if (word.find("img01lock:") == string::npos) {
          return false;
        }
        iss >> rf.img01Lock;
        break;
      }
      case 8: {
        iss >> word;
        if (word.find("img10lock:") == string::npos) {
          return false;
        }
        iss >> rf.img10Lock;
        break;
      }
      case 9: {
        iss >> word;
        if (word.find("img11lock:") == string::npos) {
          return false;
        }
        iss >> rf.img11Lock;
        break;
      }
      case 10: {
        iss >> word;
        if (word.find("bootflag:") == string::npos) {
          return false;
        }
        iss >> rf.bootFlag;
        break;
      }
      case 11: {
        iss >> word;
        int copyRaw = 0;
        int chipRaw = 0;
        if (word.find("last:") == string::npos) {
          return false;
        }
        iss >> chipRaw;
        if (iss.fail()) {
          return false;
        }
        iss >> copyRaw;
        if (iss.fail()) {
          return false;
        }

        if (chipRaw > 1 or copyRaw > 1) {
          return false;
        }
        rf.lastChip = static_cast<xsc::Chip>(chipRaw);
        rf.lastCopy = static_cast<xsc::Copy>(copyRaw);
        break;
      }
      case 12: {
        iss >> word;
        int copyRaw = 0;
        int chipRaw = 0;
        if (word.find("next:") == string::npos) {
          return false;
        }
        iss >> chipRaw;
        if (iss.fail()) {
          return false;
        }
        iss >> copyRaw;
        if (iss.fail()) {
          return false;
        }

        if (chipRaw > 2 or copyRaw > 2) {
          return false;
        }
        rf.mechanismNextChip = static_cast<xsc::Chip>(chipRaw);
        rf.mechanismNextCopy = static_cast<xsc::Copy>(copyRaw);
        break;
      }
    }
    if (iss.fail()) {
      return false;
    }
    lineIdx++;
  }
  if (lineIdx < 12) {
    return false;
  }
  return true;
}

bool CoreController::parseRebootCountersFile(std::string path, RebootCountersFile &rf) {
  using namespace std;
  ifstream file(path);
  string word;
  string line;
  uint8_t lineIdx = 0;
  while (std::getline(file, line)) {
    istringstream iss(line);
    switch (lineIdx) {
      case 0: {
        iss >> word;
        if (word.find("img00:") == string::npos) {
          return false;
        }
        iss >> rf.img00Cnt;

        break;
      }
      case 1: {
        iss >> word;
        if (word.find("img01:") == string::npos) {
          return false;
        }
        iss >> rf.img01Cnt;

        break;
      }
      case 2: {
        iss >> word;
        if (word.find("img10:") == string::npos) {
          return false;
        }
        iss >> rf.img10Cnt;

        break;
      }
      case 3: {
        iss >> word;
        if (word.find("img11:") == string::npos) {
          return false;
        }
        iss >> rf.img11Cnt;
        break;
      }
    }
    lineIdx++;
  }
  return true;
}

void CoreController::resetRebootWatchdogCounters(xsc::Chip tgtChip, xsc::Copy tgtCopy) {
  std::string path = currMntPrefix + REBOOT_WATCHDOG_FILE;
  parseRebootWatchdogFile(path, rebootWatchdogFile);
  if (tgtChip == xsc::ALL_CHIP and tgtCopy == xsc::ALL_COPY) {
    rebootWatchdogFile.img00Cnt = 0;
    rebootWatchdogFile.img01Cnt = 0;
    rebootWatchdogFile.img10Cnt = 0;
    rebootWatchdogFile.img11Cnt = 0;
  } else {
    if (tgtChip == xsc::CHIP_0) {
      if (tgtCopy == xsc::COPY_0) {
        rebootWatchdogFile.img00Cnt = 0;
      } else {
        rebootWatchdogFile.img01Cnt = 0;
      }
    } else {
      if (tgtCopy == xsc::COPY_0) {
        rebootWatchdogFile.img10Cnt = 0;
      } else {
        rebootWatchdogFile.img11Cnt = 0;
      }
    }
  }
  rewriteRebootWatchdogFile(rebootWatchdogFile);
}

void CoreController::performRebootCountersHandling(bool recreateFile) {
  std::string path = currMntPrefix + REBOOT_COUNTERS_FILE;
  std::error_code e;
  if (not std::filesystem::exists(path, e) or recreateFile) {
#if OBSW_VERBOSE_LEVEL >= 1
    sif::info << "CoreController::performRebootFileHandling: Recreating reboot counters file"
              << std::endl;
#endif
    rebootCountersFile.img00Cnt = 0;
    rebootCountersFile.img01Cnt = 0;
    rebootCountersFile.img10Cnt = 0;
    rebootCountersFile.img11Cnt = 0;
    rewriteRebootCountersFile(rebootCountersFile);
  } else {
    if (not parseRebootCountersFile(path, rebootCountersFile)) {
      performRebootCountersHandling(true);
      return;
    }
  }

  if (CURRENT_CHIP == xsc::CHIP_0) {
    if (CURRENT_COPY == xsc::COPY_0) {
      rebootCountersFile.img00Cnt++;
    } else {
      rebootCountersFile.img01Cnt++;
    }
  } else {
    if (CURRENT_COPY == xsc::COPY_0) {
      rebootCountersFile.img10Cnt++;
    } else {
      rebootCountersFile.img11Cnt++;
    }
  }
  announceBootCounts();
  rewriteRebootCountersFile(rebootCountersFile);
}
void CoreController::rewriteRebootWatchdogFile(RebootWatchdogFile file) {
  using namespace std::filesystem;
  std::string path = currMntPrefix + REBOOT_WATCHDOG_FILE;
  std::string legacyPath = currMntPrefix + LEGACY_REBOOT_WATCHDOG_FILE;
  {
    std::ofstream rebootFile(path);
    if (rebootFile.is_open()) {
      // Initiate reboot file first. Reboot handling will be on on initialization
      rebootFile << "on: " << file.enabled << "\nmaxcnt: " << file.maxCount
                 << "\nimg00: " << file.img00Cnt << "\nimg01: " << file.img01Cnt
                 << "\nimg10: " << file.img10Cnt << "\nimg11: " << file.img11Cnt
                 << "\nimg00lock: " << file.img00Lock << "\nimg01lock: " << file.img01Lock
                 << "\nimg10lock: " << file.img10Lock << "\nimg11lock: " << file.img11Lock
                 << "\nbootflag: " << file.bootFlag << "\nlast: " << static_cast<int>(file.lastChip)
                 << " " << static_cast<int>(file.lastCopy)
                 << "\nnext: " << static_cast<int>(file.mechanismNextChip) << " "
                 << static_cast<int>(file.mechanismNextCopy) << "\n";
    }
  }
  std::error_code e;
  // TODO: Remove at some point in the future when all images have been updated.
  if (std::filesystem::exists(legacyPath)) {
    // Keep those two files in sync
    std::filesystem::copy(path, legacyPath, std::filesystem::copy_options::overwrite_existing, e);
    if (e) {
      sif::error << "File copy has failed: " << e.message() << std::endl;
    }
  }
}

void CoreController::rewriteRebootCountersFile(RebootCountersFile file) {
  std::string path = currMntPrefix + REBOOT_COUNTERS_FILE;
  std::ofstream rebootFile(path);
  if (rebootFile.is_open()) {
    rebootFile << "img00: " << file.img00Cnt << "\nimg01: " << file.img01Cnt
               << "\nimg10: " << file.img10Cnt << "\nimg11: " << file.img11Cnt << "\n";
  }
}

void CoreController::setRebootMechanismLock(bool lock, xsc::Chip tgtChip, xsc::Copy tgtCopy) {
  std::string path = currMntPrefix + REBOOT_WATCHDOG_FILE;
  parseRebootWatchdogFile(path, rebootWatchdogFile);
  if (tgtChip == xsc::CHIP_0) {
    if (tgtCopy == xsc::COPY_0) {
      rebootWatchdogFile.img00Lock = lock;
    } else {
      rebootWatchdogFile.img01Lock = lock;
    }
  } else {
    if (tgtCopy == xsc::COPY_0) {
      rebootWatchdogFile.img10Lock = lock;
    } else {
      rebootWatchdogFile.img11Lock = lock;
    }
  }
  rewriteRebootWatchdogFile(rebootWatchdogFile);
}

ReturnValue_t CoreController::backupTimeFileHandler() {
  // Always set time. We could only set it if it is updated by GPS, but then the backup time would
  // become obsolete on GPS problems.
  if (opDivider10.check()) {
    // It is assumed that the system time is set from the GPS time
    timeval currentTime = {};
    ReturnValue_t result = Clock::getClock_timeval(&currentTime);
    if (result != returnvalue::OK) {
      return result;
    }
    std::string fileName = currMntPrefix + BACKUP_TIME_FILE;
    std::ofstream timeFile(fileName);
    if (not timeFile.good()) {
      sif::error << "CoreController::timeFileHandler: Error opening time file: " << strerror(errno)
                 << std::endl;
      return returnvalue::FAILED;
    }
    timeFile << "UNIX SECONDS: " << currentTime.tv_sec + BOOT_OFFSET_SECONDS << std::endl;
  }
  return returnvalue::OK;
}

ReturnValue_t CoreController::initClockFromTimeFile() {
  using namespace GpsHyperion;
  using namespace std;
  std::string fileName = currMntPrefix + BACKUP_TIME_FILE;
  std::error_code e;
  if (sdcMan->isSdCardUsable(std::nullopt) and std::filesystem::exists(fileName, e) and
      ((gpsFix == FixMode::UNKNOWN or gpsFix == FixMode::NOT_SEEN) or
       not utility::timeSanityCheck())) {
    ifstream timeFile(fileName);
    string nextWord;
    getline(timeFile, nextWord);
    istringstream iss(nextWord);
    iss >> nextWord;
    if (iss.bad() or nextWord != "UNIX") {
      return returnvalue::FAILED;
    }
    iss >> nextWord;
    if (iss.bad() or nextWord != "SECONDS:") {
      return returnvalue::FAILED;
    }
    iss >> nextWord;
    timeval currentTime = {};
    char *checkPtr;
    currentTime.tv_sec = strtol(nextWord.c_str(), &checkPtr, 10);
    if (iss.bad() or *checkPtr) {
      return returnvalue::FAILED;
    }
#if OBSW_VERBOSE_LEVEL >= 1
    time_t timeRaw = currentTime.tv_sec;
    std::tm *time = std::gmtime(&timeRaw);
    sif::info << "Setting system time from time files: " << std::put_time(time, "%c %Z")
              << std::endl;
#endif
    timeFileInitDone = true;
    return Clock::setClock(&currentTime);
  }
  return returnvalue::OK;
}

void CoreController::readHkData() {
  ReturnValue_t result = returnvalue::OK;
  result = hkSet.read(TIMEOUT_TYPE, MUTEX_TIMEOUT);
  if (result != returnvalue::OK) {
    return;
  }
  Xadc xadc;
  result = xadc.getTemperature(hkSet.temperature.value);
  if (result != returnvalue::OK) {
    hkSet.temperature.setValid(false);
  } else {
    hkSet.temperature.setValid(true);
  }
  result = xadc.getVccPint(hkSet.psVoltage.value);
  if (result != returnvalue::OK) {
    hkSet.psVoltage.setValid(false);
  } else {
    hkSet.psVoltage.setValid(true);
  }
  result = xadc.getVccInt(hkSet.plVoltage.value);
  if (result != returnvalue::OK) {
    hkSet.plVoltage.setValid(false);
  } else {
    hkSet.plVoltage.setValid(true);
  }
#if OBSW_PRINT_CORE_HK == 1
  hkSet.printSet();
#endif /* OBSW_PRINT_CORE_HK == 1 */
  result = hkSet.commit(TIMEOUT_TYPE, MUTEX_TIMEOUT);
  if (result != returnvalue::OK) {
    return;
  }
}

const char *CoreController::getXscMountDir(xsc::Chip chip, xsc::Copy copy) {
  if (chip == xsc::Chip::CHIP_0) {
    if (copy == xsc::Copy::COPY_0) {
      return CHIP_0_COPY_0_MOUNT_DIR;
    } else if (copy == xsc::Copy::COPY_1) {
      return CHIP_0_COPY_1_MOUNT_DIR;
    }
  } else if (chip == xsc::Chip::CHIP_1) {
    if (copy == xsc::Copy::COPY_0) {
      return CHIP_1_COPY_0_MOUNT_DIR;
    } else if (copy == xsc::Copy::COPY_1) {
      return CHIP_1_COPY_1_MOUNT_DIR;
    }
  }
  sif::error << "Invalid chip or copy passed to CoreController::getXscMountDir" << std::endl;
  return CHIP_0_COPY_0_MOUNT_DIR;
}

ReturnValue_t CoreController::executeSwUpdate(SwUpdateSources sourceDir, const uint8_t *data,
                                              size_t size) {
  using namespace std;
  using namespace std::filesystem;
  // At the very least, chip and copy ID need to be included in the command
  if (size < 2) {
    return HasActionsIF::INVALID_PARAMETERS;
  }
  if (data[0] > 1 or data[1] > 1) {
    return HasActionsIF::INVALID_PARAMETERS;
  }
  auto chip = static_cast<xsc::Chip>(data[0]);
  auto copy = static_cast<xsc::Copy>(data[1]);
  const char *sourceStr = "unknown";
  if (sourceDir == SwUpdateSources::SD_0) {
    sourceStr = "SD 0";
  } else if (sourceDir == SwUpdateSources::SD_1) {
    sourceStr = "SD 1";
  } else {
    sourceStr = "tmp directory";
  }
  bool sameChipAndCopy = false;
  if (chip == CURRENT_CHIP and copy == CURRENT_COPY) {
    // This is problematic if the OBSW is running as a systemd service.
    // Do not allow for now.
    return HasActionsIF::INVALID_PARAMETERS;
    // sameChipAndCopy = true;
  }
  sif::info << "Executing SW update for Chip " << static_cast<int>(data[0]) << " Copy "
            << static_cast<int>(data[1]) << " from " << sourceStr << std::endl;
  path prefixPath;
  if (sourceDir == SwUpdateSources::SD_0) {
    prefixPath = path(config::SD_0_MOUNT_POINT);
  } else if (sourceDir == SwUpdateSources::SD_1) {
    prefixPath = path(config::SD_1_MOUNT_POINT);
  } else if (sourceDir == SwUpdateSources::TMP_DIR) {
    prefixPath = path("/tmp");
  }
  path archivePath;
  // It is optionally possible to supply the source file path
  if (size > 2) {
    archivePath = prefixPath / std::string(reinterpret_cast<const char *>(data + 2), size - 2);
  } else {
    archivePath = prefixPath / path(config::OBSW_UPDATE_ARCHIVE_FILE_NAME);
  }
  sif::info << "Updating with archive path " << archivePath << std::endl;
  std::error_code e;
  if (not exists(archivePath, e)) {
    return HasFileSystemIF::FILE_DOES_NOT_EXIST;
  }
  // TODO: Decompressing without limiting memory usage with xz is actually a bit risky..
  // But has not been an issue so far.
  ostringstream cmd("tar -xJf", ios::app);
  cmd << " " << archivePath << " -C " << prefixPath;
  int result = system(cmd.str().c_str());
  if (result != 0) {
    utility::handleSystemError(result, "CoreController::executeAction: SW Update Decompression");
  }
  path strippedImagePath = prefixPath / path(config::STRIPPED_OBSW_BINARY_FILE_NAME);
  if (!exists(strippedImagePath, e)) {
    // TODO: Custom returnvalue?
    return returnvalue::FAILED;
  }
  path obswVersionFilePath = prefixPath / path(config::OBSW_VERSION_FILE_NAME);
  if (!exists(obswVersionFilePath, e)) {
    // TODO: Custom returnvalue?
    return returnvalue::FAILED;
  }
  cmd.str("");
  cmd.clear();
  path obswDestPath;
  path obswVersionDestPath;
  if (not sameChipAndCopy) {
    cmd << "xsc_mount_copy " << std::to_string(data[0]) << " " << std::to_string(data[1]);
    result = system(cmd.str().c_str());
    if (result != 0) {
      std::string contextString = "CoreController::executeAction: SW Update Mounting " +
                                  std::to_string(data[0]) + " " + std::to_string(data[1]);
      utility::handleSystemError(result, contextString);
    }
    cmd.str("");
    cmd.clear();
    path xscMountDest(getXscMountDir(chip, copy));
    obswDestPath = xscMountDest / path(relative(config::OBSW_PATH, "/"));
    obswVersionDestPath = xscMountDest / path(relative(config::OBSW_VERSION_FILE_PATH, "/"));
  } else {
    obswDestPath = path(config::OBSW_PATH);
    obswVersionDestPath = path(config::OBSW_VERSION_FILE_PATH);
    cmd << "writeprotect " << std::to_string(CURRENT_CHIP) << " " << std::to_string(CURRENT_COPY)
        << " 0";
    result = system(cmd.str().c_str());
    if (result != 0) {
      std::string contextString = "CoreController::executeAction: Unlocking current chip";
      utility::handleSystemError(result, contextString);
    }
    cmd.str("");
    cmd.clear();
  }

  cmd << "cp " << strippedImagePath << " " << obswDestPath;
  result = system(cmd.str().c_str());
  if (result != 0) {
    utility::handleSystemError(result, "CoreController::executeAction: Copying SW update");
  }
  cmd.str("");
  cmd.clear();

  cmd << "cp " << obswVersionFilePath << " " << obswVersionDestPath;
  result = system(cmd.str().c_str());
  if (result != 0) {
    utility::handleSystemError(result, "CoreController::executeAction: Copying SW version file");
  }
  cmd.str("");
  cmd.clear();

  // Set correct permission for both files
  cmd << "chmod 0755 " << obswDestPath;
  result = system(cmd.str().c_str());
  if (result != 0) {
    utility::handleSystemError(result,
                               "CoreController::executeAction: Setting SW permissions 0755");
  }
  cmd.str("");
  cmd.clear();

  cmd << "chmod 0644 " << obswVersionDestPath;
  result = system(cmd.str().c_str());
  if (result != 0) {
    utility::handleSystemError(
        result, "CoreController::executeAction: Setting version file permission 0644");
  }
  cmd.str("");
  cmd.clear();

  // Remove the extracted files to keep directories clean.
  std::filesystem::remove(strippedImagePath, e);
  std::filesystem::remove(obswVersionFilePath, e);

  // TODO: This takes a long time and will block the core controller.. Maybe use command executor?
  //       For now dont care..
  cmd << "writeprotect " << std::to_string(data[0]) << " " << std::to_string(data[1]) << " 1";
  result = system(cmd.str().c_str());
  if (result != 0) {
    std::string contextString = "CoreController::executeAction: Writeprotecting " +
                                std::to_string(data[0]) + " " + std::to_string(data[1]);
    utility::handleSystemError(result, contextString);
  }
  sif::info << "SW update complete" << std::endl;
  return HasActionsIF::EXECUTION_FINISHED;
}

bool CoreController::startSdStateMachine(sd::SdCard targetActiveSd, SdCfgMode mode,
                                         MessageQueueId_t commander, DeviceCommandId_t actionId) {
  if (sdFsmState != SdStates::IDLE or sdCommandingInfo.cmdPending) {
    return false;
  }
  sdFsmState = SdStates::START;
  sdInfo.active = targetActiveSd;
  // If we are going from 2 SD cards to one, lock SD card usage in any case because 1 SD card is
  // going off.
  if (sdInfo.cfgMode == SdCfgMode::HOT_REDUNDANT and mode == SdCfgMode::COLD_REDUNDANT) {
    sdInfo.lockSdCardUsage = true;
  }
  sdInfo.cfgMode = mode;
  sdCommandingInfo.actionId = actionId;
  sdCommandingInfo.commander = commander;
  sdCommandingInfo.cmdPending = true;
  return true;
}

void CoreController::announceBootCounts() {
  uint64_t totalBootCount = rebootCountersFile.img00Cnt + rebootCountersFile.img01Cnt +
                            rebootCountersFile.img10Cnt + rebootCountersFile.img11Cnt;
  uint32_t individualBootCountsP1 =
      (rebootCountersFile.img00Cnt << 16) | rebootCountersFile.img01Cnt;
  uint32_t individualBootCountsP2 =
      (rebootCountersFile.img10Cnt << 16) | rebootCountersFile.img11Cnt;
  triggerEvent(core::INDIVIDUAL_BOOT_COUNTS, individualBootCountsP1, individualBootCountsP2);
  triggerEvent(core::REBOOT_COUNTER, (totalBootCount >> 32) & 0xffffffff,
               totalBootCount & 0xffffffff);
}

MessageQueueId_t CoreController::getCommandQueue() const {
  return ExtendedControllerBase::getCommandQueue();
}

void CoreController::dirListingDumpHandler() {
  if (dumpContext.firstDump) {
    dumpContext.firstDump = false;
    return;
  }
  size_t nextDumpLen = 0;
  size_t dummy = 0;
  ReturnValue_t result;
  std::error_code e;
  std::ifstream ifile(LIST_DIR_DUMP_WORK_FILE, std::ios::binary);
  if (ifile.bad()) {
    return;
  }
  while (dumpContext.dumpedBytes < dumpContext.totalFileSize) {
    ifile.seekg(dumpContext.dumpedBytes, std::ios::beg);
    nextDumpLen = dumpContext.maxDumpLen;
    if (dumpContext.totalFileSize - dumpContext.dumpedBytes < dumpContext.maxDumpLen) {
      nextDumpLen = dumpContext.totalFileSize - dumpContext.dumpedBytes;
    }
    SerializeAdapter::serialize(&dumpContext.segmentIdx, dirListingBuf.data(), &dummy,
                                dirListingBuf.size(), SerializeIF::Endianness::NETWORK);
    ifile.read(reinterpret_cast<char *>(dirListingBuf.data() + dumpContext.listingDataOffset),
               nextDumpLen);
    result =
        actionHelper.reportData(dumpContext.commander, dumpContext.actionId, dirListingBuf.data(),
                                dumpContext.listingDataOffset + nextDumpLen);
    if (result != returnvalue::OK) {
      // Remove work file when we are done
      std::filesystem::remove(LIST_DIR_DUMP_WORK_FILE, e);
      dumpContext.active = false;
      actionHelper.finish(false, dumpContext.commander, dumpContext.actionId, result);
      return;
    }
    dumpContext.segmentIdx++;
    dumpContext.dumpedBytes += nextDumpLen;
    // Dump takes multiple task cycles, so cache the dump state and continue dump the next cycles.
    if (dumpContext.segmentIdx == 10) {
      break;
    }
  }
  if (dumpContext.dumpedBytes >= dumpContext.totalFileSize) {
    actionHelper.finish(true, dumpContext.commander, dumpContext.actionId, result);
    dumpContext.active = false;
    // Remove work file when we are done
    std::filesystem::remove(LIST_DIR_DUMP_WORK_FILE, e);
  }
}

void CoreController::announceVersionInfo() {
  using namespace core;
  uint32_t p1 = (common::OBSW_VERSION_MAJOR << 24) | (common::OBSW_VERSION_MINOR << 16) |
                (common::OBSW_VERSION_REVISION << 8);
  uint32_t p2 = 0;
  if (strcmp("", common::OBSW_VERSION_CST_GIT_SHA1) != 0) {
    p1 |= 1;
    auto shaAsStr = std::string(common::OBSW_VERSION_CST_GIT_SHA1);
    size_t posDash = shaAsStr.find("-");
    auto gitHash = shaAsStr.substr(posDash + 2, 4);
    // Only copy first 4 letters of git hash
    memcpy(&p2, gitHash.c_str(), 4);
  }

  triggerEvent(VERSION_INFO, p1, p2);
  p1 = (core::FW_VERSION_MAJOR << 24) | (core::FW_VERSION_MINOR << 16) |
       (core::FW_VERSION_REVISION << 8) | (core::FW_VERSION_HAS_SHA);
  std::memcpy(&p2, core::FW_VERSION_GIT_SHA, 4);
  triggerEvent(FIRMWARE_INFO, p1, p2);
}

void CoreController::announceCurrentImageInfo() {
  using namespace core;
  triggerEvent(CURRENT_IMAGE_INFO, CURRENT_CHIP, CURRENT_COPY);
}

ReturnValue_t CoreController::performGracefulShutdown(xsc::Chip tgtChip, xsc::Copy tgtCopy) {
  bool protOpPerformed = false;
  // This function can not really fail
  gracefulShutdownTasks(tgtChip, tgtCopy, protOpPerformed);

  switch (tgtChip) {
    case (xsc::Chip::CHIP_0): {
      switch (tgtCopy) {
        case (xsc::Copy::COPY_0): {
          xsc_boot_copy(XSC_LIBNOR_CHIP_0, XSC_LIBNOR_COPY_NOMINAL);
          break;
        }
        case (xsc::Copy::COPY_1): {
          xsc_boot_copy(XSC_LIBNOR_CHIP_0, XSC_LIBNOR_COPY_GOLD);
          break;
        }
        default: {
          break;
        }
      }
      break;
    }
    case (xsc::Chip::CHIP_1): {
      switch (tgtCopy) {
        case (xsc::Copy::COPY_0): {
          xsc_boot_copy(XSC_LIBNOR_CHIP_1, XSC_LIBNOR_COPY_NOMINAL);
          break;
        }
        case (xsc::Copy::COPY_1): {
          xsc_boot_copy(XSC_LIBNOR_CHIP_1, XSC_LIBNOR_COPY_GOLD);
          break;
        }
        default: {
          break;
        }
      }
      break;
    }
    default:
      break;
  }
  return returnvalue::OK;
}

void CoreController::announceSdInfo(SdCardManager::SdStatePair sdStates) {
  auto activeSdCard = sdcMan->getActiveSdCard();
  uint32_t p1 = sd::SdCard::NONE;
  if (activeSdCard.has_value()) {
    p1 = static_cast<uint32_t>(activeSdCard.value());
  }
  uint32_t p2 =
      (static_cast<uint32_t>(sdStates.first) << 16) | static_cast<uint32_t>(sdStates.second);
  triggerEvent(core::ACTIVE_SD_INFO, p1, p2);
}

ReturnValue_t CoreController::handleSwitchingSdCardsOffNonBlocking() {
  sdcMan->setBlocking(false);
  SdCardManager::Operations op;
  std::pair<sd::SdState, sd::SdState> sdStatus;
  ReturnValue_t result = sdcMan->getSdCardsStatus(sdStatus);
  if (result != returnvalue::OK) {
    return result;
  }
  Countdown maxWaitTimeCd(10000);
  // Stopwatch watch;
  auto waitingForFinish = [&]() {
    auto currentState = sdcMan->checkCurrentOp(op);
    if (currentState == SdCardManager::OpStatus::IDLE) {
      return returnvalue::OK;
    }
    while (currentState == SdCardManager::OpStatus::ONGOING) {
      if (maxWaitTimeCd.hasTimedOut()) {
        return returnvalue::FAILED;
      }
      TaskFactory::delayTask(50);
      currentState = sdcMan->checkCurrentOp(op);
    }
    return returnvalue::OK;
  };
  if (sdStatus.first != sd::SdState::OFF) {
    sdcMan->unmountSdCard(sd::SdCard::SLOT_0);
    result = waitingForFinish();
    if (result != returnvalue::OK) {
      return result;
    }
    sdcMan->switchOffSdCard(sd::SdCard::SLOT_0, sdStatus, false);
    result = waitingForFinish();
    if (result != returnvalue::OK) {
      return result;
    }
  }
  if (sdStatus.second != sd::SdState::OFF) {
    sdcMan->unmountSdCard(sd::SdCard::SLOT_1);
    result = waitingForFinish();
    if (result != returnvalue::OK) {
      return result;
    }
    sdcMan->switchOffSdCard(sd::SdCard::SLOT_1, sdStatus, false);
    result = waitingForFinish();
    if (result != returnvalue::OK) {
      return result;
    }
  }
  return result;
}

bool CoreController::isNumber(const std::string &s) {
  return !s.empty() && std::find_if(s.begin(), s.end(),
                                    [](unsigned char c) { return !std::isdigit(c); }) == s.end();
}

ReturnValue_t CoreController::getParameter(uint8_t domainId, uint8_t uniqueIdentifier,
                                           ParameterWrapper *parameterWrapper,
                                           const ParameterWrapper *newValues,
                                           uint16_t startAtIndex) {
  if (domainId != 0) {
    return HasParametersIF::INVALID_DOMAIN_ID;
  }
  if (uniqueIdentifier >= ParamId::NUM_IDS) {
    return HasParametersIF::INVALID_IDENTIFIER_ID;
  }
  uint8_t newPrefSd;
  ReturnValue_t result = newValues->getElement(&newPrefSd);
  if (result != returnvalue::OK) {
    return result;
  }
  // Only SD card 0 (0) and 1 (1) are allowed values.
  if (newPrefSd > 1) {
    return HasParametersIF::INVALID_VALUE;
  }
  result = sdcMan->setPreferredSdCard(static_cast<sd::SdCard>(newPrefSd));
  if (result != returnvalue::OK) {
    return returnvalue::FAILED;
  }
  parameterWrapper->set(prefSdRaw);
  return returnvalue::OK;
}