#include "FileSystemHandler.h"

#include <cstring>
#include <filesystem>
#include <fstream>

#include "bsp_q7s/core/CoreController.h"
#include "fsfw/ipc/QueueFactory.h"
#include "fsfw/memory/GenericFileSystemMessage.h"
#include "fsfw/tasks/TaskFactory.h"

FileSystemHandler::FileSystemHandler(object_id_t fileSystemHandler)
    : SystemObject(fileSystemHandler) {
  auto mqArgs = MqArgs(this->getObjectId());
  mq = QueueFactory::instance()->createMessageQueue(FS_MAX_QUEUE_SIZE,
                                                    MessageQueueMessage::MAX_MESSAGE_SIZE, &mqArgs);
}

FileSystemHandler::~FileSystemHandler() { QueueFactory::instance()->deleteMessageQueue(mq); }

ReturnValue_t FileSystemHandler::performOperation(uint8_t unsignedChar) {
  while (true) {
    try {
      fileSystemHandlerLoop();
    } catch (std::bad_alloc& e) {
      // Restart OBSW, hints at a memory leak
      sif::error << "Allocation error in FileSystemHandler::performOperation" << e.what()
                 << std::endl;
      // Set up an error file or a special flag in the scratch buffer for these cases
      triggerEvent(CoreController::ALLOC_FAILURE, 0, 0);
      CoreController::incrementAllocationFailureCount();
    }
  }
}

void FileSystemHandler::fileSystemHandlerLoop() {
  CommandMessage filemsg;
  ReturnValue_t result = HasReturnvaluesIF::RETURN_OK;
  while (true) {
    if (opCounter % 5 == 0) {
      if (coreCtrl->sdInitFinished()) {
        fileSystemCheckup();
      }
    }
    result = mq->receiveMessage(&filemsg);
    if (result == MessageQueueIF::EMPTY) {
      break;
    } else if (result != HasReturnvaluesIF::RETURN_FAILED) {
      sif::warning << "FileSystemHandler::performOperation: Message reception failed!" << std::endl;
      break;
    }
    Command_t command = filemsg.getCommand();
    switch (command) {
      case (GenericFileSystemMessage::CMD_CREATE_DIRECTORY): {
        break;
      }
      case (GenericFileSystemMessage::CMD_CREATE_FILE): {
        break;
      }
    }
    opCounter++;
  }

  // This task will have a low priority and will run permanently in the background
  // so we will just run in a permanent loop here and check file system
  // messages permanently
  opCounter++;
  TaskFactory::instance()->delayTask(1000);
}

void FileSystemHandler::fileSystemCheckup() {
  SdCardManager::SdStatePair statusPair;
  sdcMan->getSdCardsStatus(statusPair);
  sd::SdCard preferredSdCard = sdcMan->getPreferredSdCard();
  if ((preferredSdCard == sd::SdCard::SLOT_0) and (statusPair.first == sd::SdState::MOUNTED)) {
    currentMountPrefix = SdCardManager::SD_0_MOUNT_POINT;
  } else if ((preferredSdCard == sd::SdCard::SLOT_1) and
             (statusPair.second == sd::SdState::MOUNTED)) {
    currentMountPrefix = SdCardManager::SD_1_MOUNT_POINT;
  } else {
    std::string sdString;
    if (preferredSdCard == sd::SdCard::SLOT_0) {
      sdString = "0";
    } else {
      sdString = "1";
    }
    sif::warning << "FileSystemHandler::performOperation: "
                    "Inconsistent state detected"
                 << std::endl;
    sif::warning << "Preferred SD card is " << sdString
                 << " but does not appear to be mounted. Attempting fix.." << std::endl;
    // This function will appear to fix the inconsistent state
    ReturnValue_t result = sdcMan->sanitizeState(&statusPair, preferredSdCard);
    if (result != HasReturnvaluesIF::RETURN_OK) {
      // Oh no.
      triggerEvent(SdCardManager::SANITIZATION_FAILED, 0, 0);
      sif::error << "FileSystemHandler::fileSystemCheckup: Sanitization failed" << std::endl;
    }
  }
}

MessageQueueId_t FileSystemHandler::getCommandQueue() const { return mq->getId(); }

ReturnValue_t FileSystemHandler::initialize() {
  coreCtrl = ObjectManager::instance()->get<CoreController>(objects::CORE_CONTROLLER);
  if (coreCtrl == nullptr) {
    sif::error << "FileSystemHandler::initialize: Could not retrieve core controller handle"
               << std::endl;
  }
  sdcMan = SdCardManager::instance();
  sd::SdCard preferredSdCard = sdcMan->getPreferredSdCard();
  if (preferredSdCard == sd::SdCard::SLOT_0) {
    currentMountPrefix = SdCardManager::SD_0_MOUNT_POINT;
  } else if (preferredSdCard == sd::SdCard::SLOT_1) {
    currentMountPrefix = SdCardManager::SD_1_MOUNT_POINT;
  }
  return HasReturnvaluesIF::RETURN_OK;
}

ReturnValue_t FileSystemHandler::appendToFile(const char* repositoryPath, const char* filename,
                                              const uint8_t* data, size_t size,
                                              uint16_t packetNumber, FileSystemArgsIF* args) {
  auto path = getInitPath(args) / repositoryPath / filename;
  if (not std::filesystem::exists(path)) {
    return FILE_DOES_NOT_EXIST;
  }
  std::ofstream file(path, std::ios_base::app | std::ios_base::out);
  file.write(reinterpret_cast<const char*>(data), size);
  if (not file.good()) {
    return GENERIC_FILE_ERROR;
  }
  return HasReturnvaluesIF::RETURN_OK;
}

ReturnValue_t FileSystemHandler::createFile(const char* repositoryPath, const char* filename,
                                            const uint8_t* data, size_t size,
                                            FileSystemArgsIF* args) {
  auto path = getInitPath(args) / filename;
  if (std::filesystem::exists(path)) {
    return FILE_ALREADY_EXISTS;
  }
  std::ofstream file(path);
  file.write(reinterpret_cast<const char*>(data), size);
  if (not file.good()) {
    return GENERIC_FILE_ERROR;
  }
  return HasReturnvaluesIF::RETURN_OK;
}

ReturnValue_t FileSystemHandler::removeFile(const char* repositoryPath, const char* filename,
                                            FileSystemArgsIF* args) {
  auto path = getInitPath(args) / repositoryPath / filename;
  if (not std::filesystem::exists(path)) {
    return FILE_DOES_NOT_EXIST;
  }
  int result = std::remove(path.c_str());
  if (result != 0) {
    sif::warning << "FileSystemHandler::deleteFile: Failed with code " << result << std::endl;
    return GENERIC_FILE_ERROR;
  }
  return HasReturnvaluesIF::RETURN_OK;
}

ReturnValue_t FileSystemHandler::createDirectory(const char* repositoryPath, const char* dirname,
                                                 bool createParentDirs, FileSystemArgsIF* args) {
  auto path = getInitPath(args) / repositoryPath / dirname;
  if (std::filesystem::exists(path)) {
    return DIRECTORY_ALREADY_EXISTS;
  }
  if (std::filesystem::create_directory(path)) {
    return HasReturnvaluesIF::RETURN_OK;
  }
  sif::warning << "Creating directory " << path << " failed" << std::endl;
  return GENERIC_FILE_ERROR;
}

ReturnValue_t FileSystemHandler::removeDirectory(const char* repositoryPath, const char* dirname,
                                                 bool deleteRecurively, FileSystemArgsIF* args) {
  auto path = getInitPath(args) / repositoryPath / dirname;
  if (not std::filesystem::exists(path)) {
    return DIRECTORY_DOES_NOT_EXIST;
  }
  std::error_code err;
  if (not deleteRecurively) {
    if (std::filesystem::remove(path, err)) {
      return HasReturnvaluesIF::RETURN_OK;
    } else {
      // Check error code. Most probably denied permissions because folder is not empty
      sif::warning << "FileSystemHandler::removeDirectory: Deleting directory failed with "
                      "code "
                   << err.value() << ": " << strerror(err.value()) << std::endl;
      if (err.value() == ENOTEMPTY) {
        return DIRECTORY_NOT_EMPTY;
      } else {
        return GENERIC_FILE_ERROR;
      }
    }
  } else {
    if (std::filesystem::remove_all(path, err)) {
      return HasReturnvaluesIF::RETURN_OK;
    } else {
      sif::warning << "FileSystemHandler::removeDirectory: Deleting directory failed with "
                      "code "
                   << err.value() << ": " << strerror(err.value()) << std::endl;
      // Check error code
      if (err.value() == ENOTEMPTY) {
        return DIRECTORY_NOT_EMPTY;
      } else {
        return GENERIC_FILE_ERROR;
      }
    }
  }

  return HasReturnvaluesIF::RETURN_OK;
}

ReturnValue_t FileSystemHandler::renameFile(const char* repositoryPath, const char* oldFilename,
                                            const char* newFilename, FileSystemArgsIF* args) {
  auto basepath = getInitPath(args) / repositoryPath;
  std::filesystem::rename(basepath / oldFilename, basepath / newFilename);
  return HasReturnvaluesIF::RETURN_OK;
}

void FileSystemHandler::parseCfg(FsCommandCfg* cfg, bool& useMountPrefix) {
  if (cfg != nullptr) {
    useMountPrefix = cfg->useMountPrefix;
  }
}

std::filesystem::path FileSystemHandler::getInitPath(FileSystemArgsIF* args) {
  bool useMountPrefix = true;
  parseCfg(reinterpret_cast<FsCommandCfg*>(args), useMountPrefix);
  std::string path;
  if (useMountPrefix) {
    path = currentMountPrefix;
  }
  return std::filesystem::path(path);
}