#include "TmStoreTaskBase.h"

#include <fsfw/ipc/CommandMessageIF.h>
#include <fsfw/ipc/QueueFactory.h>
#include <fsfw/subsystem/helper.h>
#include <fsfw/tasks/TaskFactory.h>
#include <fsfw/timemanager/Stopwatch.h>
#include <fsfw/tmstorage/TmStoreMessage.h>

#include "mission/persistentTmStoreDefs.h"

TmStoreTaskBase::TmStoreTaskBase(object_id_t objectId, StorageManagerIF& ipcStore,
                                 VirtualChannel& channel, SdCardMountedIF& sdcMan,
                                 const std::atomic_bool& ptmeLocked)
    : SystemObject(objectId),
      modeHelper(this),
      ipcStore(ipcStore),
      tmReader(&timeReader),
      channel(channel),
      sdcMan(sdcMan),
      ptmeLocked(ptmeLocked) {
  requestQueue = QueueFactory::instance()->createMessageQueue(10);
}

bool TmStoreTaskBase::handleOneStore(PersistentTmStoreWithTmQueue& store,
                                     DumpContext& dumpContext) {
  ReturnValue_t result;
  bool tmToStoreReceived = false;
  bool tcRequestReceived = false;
  bool dumpPerformed = false;
  fileHasSwapped = false;
  dumpContext.packetWasDumped = false;
  dumpContext.vcBusyDuringDump = false;

  // Store TM persistently
  result = store.handleNextTm();
  if (result == returnvalue::OK) {
    tmToStoreReceived = true;
  }
  // Dump TMs
  if (store.getState() == PersistentTmStore::State::DUMPING) {
    if (handleOneDump(store, dumpContext, dumpPerformed) != returnvalue::OK) {
      return result;
    }
  } else {
    Command_t execCmd;
    // Handle TC requests, for example deletion or retrieval requests.
    result = store.handleCommandQueue(ipcStore, execCmd);
    if (result == returnvalue::OK) {
      if (execCmd == TmStoreMessage::DOWNLINK_STORE_CONTENT_TIME) {
        cancelDumpCd.resetTimer();
        tmSinkBusyCd.resetTimer();
        dumpContext.reset();
      }
      tcRequestReceived = true;
    }
  }
  if (tcRequestReceived or tmToStoreReceived or dumpPerformed) {
    return true;
  }
  return false;
}

bool TmStoreTaskBase::cyclicStoreCheck() {
  if (not storesInitialized) {
    storesInitialized = initStoresIfPossible();
    if (not storesInitialized) {
      TaskFactory::delayTask(400);
      return false;
    }
  } else if (sdCardCheckCd.hasTimedOut()) {
    if (not sdcMan.isSdCardUsable(std::nullopt)) {
      // Might be due to imminent shutdown or SD card switch.
      storesInitialized = false;
      TaskFactory::delayTask(100);
      return false;
    }
    sdCardCheckCd.resetTimer();
  }
  return true;
}

void TmStoreTaskBase::cancelDump(DumpContext& ctx, PersistentTmStore& store, bool isTxOn) {
  ctx.reset();
  if (store.getState() == PersistentTmStore::State::DUMPING) {
    triggerEvent(ctx.eventIfCancelled, ctx.numberOfDumpedPackets, ctx.dumpedBytes);
  }
  store.cancelDump();
  if (isTxOn) {
    channel.cancelTransfer();
  }
}

ReturnValue_t TmStoreTaskBase::handleOneDump(PersistentTmStoreWithTmQueue& store,
                                             DumpContext& dumpContext, bool& dumpPerformed) {
  ReturnValue_t result = returnvalue::OK;
  // The PTME might have been reset an transmitter state change, so there is no point in continuing
  // the dump.
  // TODO: Will be solved in a cleaner way, this is kind of a hack.
  if (not channel.isTxOn()) {
    cancelDump(dumpContext, store, false);
    return returnvalue::FAILED;
  }
  // It is assumed that the PTME will only be locked for a short period (e.g. to change datarate).
  if (not channel.isBusy() and not ptmeLocked) {
    performDump(store, dumpContext, dumpPerformed);
  } else {
    // The PTME might be at full load, so it might sense to delay for a bit to let it do
    // its work until some more bandwidth is available. Set a flag here so the upper layer can
    // do ths.
    dumpContext.vcBusyDuringDump = true;
    dumpContext.ptmeBusyCounter++;
    if (dumpContext.ptmeBusyCounter == 100) {
      // If this happens, something is probably wrong.
      sif::warning << "PTME busy for longer period. Cancelling dump" << std::endl;
      cancelDump(dumpContext, store, channel.isTxOn());
    }
  }
  if (cancelDumpCd.hasTimedOut() or tmSinkBusyCd.hasTimedOut()) {
    cancelDump(dumpContext, store, channel.isTxOn());
  }
  return result;
}

ReturnValue_t TmStoreTaskBase::performDump(PersistentTmStoreWithTmQueue& store,
                                           DumpContext& dumpContext, bool& dumpPerformed) {
  size_t dumpedLen = 0;

  auto dumpDoneHandler = [&]() {
    uint32_t startTime;
    uint32_t endTime;
    store.getStartAndEndTimeCurrentOrLastDump(startTime, endTime);
    triggerEvent(dumpContext.eventIfDone, dumpContext.numberOfDumpedPackets,
                 dumpContext.dumpedBytes);
    dumpContext.reset();
  };
  // Dump the next packet into the PTME.
  dumpContext.ptmeBusyCounter = 0;
  tmSinkBusyCd.resetTimer();
  ReturnValue_t result = store.getNextDumpPacket(tmReader, fileHasSwapped);
  if (result != returnvalue::OK) {
    sif::error << "PersistentTmStore: Getting next dump packet failed" << std::endl;
  } else if (fileHasSwapped or result == PersistentTmStore::DUMP_DONE) {
    // This can happen if a file is corrupted and the next file swap completes the dump.
    dumpDoneHandler();
    return returnvalue::OK;
  }
  dumpedLen = tmReader.getFullPacketLen();
  // Only write to VC if mode is on, but always confirm the dump.
  // If the mode is OFF, it is assumed the PTME is not usable and is not allowed to be written
  // (e.g. to confirm a reset or the transmitter is off anyway).
  if (mode == MODE_ON) {
    result = channel.write(tmReader.getFullData(), dumpedLen);
    if (result == DirectTmSinkIF::IS_BUSY) {
      sif::warning << "PersistentTmStore: Unexpected VC channel busy" << std::endl;
    } else if (result != returnvalue::OK) {
      sif::warning << "PersistentTmStore: Unexpected VC channel write failure" << std::endl;
    }
  }
  result = store.confirmDump(tmReader, fileHasSwapped);
  if ((result == PersistentTmStore::DUMP_DONE or result == returnvalue::OK)) {
    dumpPerformed = true;
    if (dumpedLen > 0) {
      dumpContext.dumpedBytes += dumpedLen;
      dumpContext.numberOfDumpedPackets += 1;
      dumpContext.packetWasDumped = true;
    }
  }
  if (result == PersistentTmStore::DUMP_DONE) {
    dumpDoneHandler();
  }
  return returnvalue::OK;
}

ReturnValue_t TmStoreTaskBase::initialize() {
  modeHelper.initialize();
  return returnvalue::OK;
}

void TmStoreTaskBase::getMode(Mode_t* mode, Submode_t* submode) {
  if (mode != nullptr) {
    *mode = this->mode;
  }
  if (submode != nullptr) {
    *submode = SUBMODE_NONE;
  }
}

ReturnValue_t TmStoreTaskBase::checkModeCommand(Mode_t mode, Submode_t submode,
                                                uint32_t* msToReachTheMode) {
  if (mode == MODE_ON or mode == MODE_OFF) {
    return returnvalue::OK;
  }
  return returnvalue::FAILED;
}

MessageQueueId_t TmStoreTaskBase::getCommandQueue() const { return requestQueue->getId(); }

void TmStoreTaskBase::announceMode(bool recursive) { triggerEvent(MODE_INFO, mode, SUBMODE_NONE); }

object_id_t TmStoreTaskBase::getObjectId() const { return SystemObject::getObjectId(); }

const HasHealthIF* TmStoreTaskBase::getOptHealthIF() const { return nullptr; }

const HasModesIF& TmStoreTaskBase::getModeIF() const { return *this; }

ReturnValue_t TmStoreTaskBase::connectModeTreeParent(HasModeTreeChildrenIF& parent) {
  return modetree::connectModeTreeParent(parent, *this, nullptr, modeHelper);
}

ModeTreeChildIF& TmStoreTaskBase::getModeTreeChildIF() { return *this; }

void TmStoreTaskBase::readCommandQueue(void) {
  CommandMessage commandMessage;
  ReturnValue_t result = returnvalue::FAILED;

  result = requestQueue->receiveMessage(&commandMessage);
  if (result == returnvalue::OK) {
    result = modeHelper.handleModeCommand(&commandMessage);
    if (result == returnvalue::OK) {
      return;
    }
    CommandMessage reply;
    reply.setReplyRejected(CommandMessage::UNKNOWN_COMMAND, commandMessage.getCommand());
    requestQueue->reply(&reply);
    return;
  }
}