#include "FileSystemHandler.h"

#include "bsp_q7s/core/CoreController.h"

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

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

FileSystemHandler::FileSystemHandler(object_id_t fileSystemHandler):
        SystemObject(fileSystemHandler) {
    mq = QueueFactory::instance()->createMessageQueue(FS_MAX_QUEUE_SIZE);
}

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->getSdCardActiveStatus(statusPair);
    sd::SdCard preferredSdCard;
    sdcMan->getPreferredSdCard(preferredSdCard);
    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;
    ReturnValue_t result = sdcMan->getPreferredSdCard(preferredSdCard);
    if(result != HasReturnvaluesIF::RETURN_OK) {
        return result;
    }
    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, void *args) {
    // A double slash between repo and filename should not be an issue, so add it in any case
    std::string fullPath = currentMountPrefix + std::string(repositoryPath) + "/" +
            std::string(filename);
    if(not std::filesystem::exists(fullPath)) {
        return FILE_DOES_NOT_EXIST;
    }
    std::ofstream file(fullPath, 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, void *args) {
    std::string fullPath;
    bool useMountPrefix = true;
    parseCfg(reinterpret_cast<FsCommandCfg*>(args), useMountPrefix);
    if(useMountPrefix) {
        fullPath += currentMountPrefix;
    }

    // A double slash between repo and filename should not be an issue, so add it in any case
    fullPath += std::string(repositoryPath) + "/" + std::string(filename);
    if(std::filesystem::exists(fullPath)) {
        return FILE_ALREADY_EXISTS;
    }
    std::ofstream file(fullPath);
    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,
        void *args) {
    std::string fullPath;
    bool useMountPrefix = true;
    parseCfg(reinterpret_cast<FsCommandCfg*>(args), useMountPrefix);
    if(useMountPrefix) {
        fullPath += currentMountPrefix;
    }

    // A double slash between repo and filename should not be an issue, so add it in any case
    fullPath += std::string(repositoryPath) + "/" + std::string(filename);
    if(not std::filesystem::exists(fullPath)) {
        return FILE_DOES_NOT_EXIST;
    }
    int result = std::remove(fullPath.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, void *args) {
    std::string fullPath;
    bool useMountPrefix = true;
    parseCfg(reinterpret_cast<FsCommandCfg*>(args), useMountPrefix);
    if(useMountPrefix) {
        fullPath += currentMountPrefix;
    }

    fullPath += std::string(repositoryPath);
    if(std::filesystem::exists(fullPath)) {
        return DIRECTORY_ALREADY_EXISTS;
    }
    if(std::filesystem::create_directory(fullPath)) {
        return HasReturnvaluesIF::RETURN_OK;
    }
    sif::warning << "Creating directory " << fullPath << " failed" << std::endl;
    return GENERIC_FILE_ERROR;
}

ReturnValue_t FileSystemHandler::removeDirectory(const char *repositoryPath,
        bool deleteRecurively, void *args) {
    std::string fullPath;
    bool useMountPrefix = true;
    parseCfg(reinterpret_cast<FsCommandCfg*>(args), useMountPrefix);
    if(useMountPrefix) {
        fullPath += currentMountPrefix;
    }

    fullPath += std::string(repositoryPath);
    if(not std::filesystem::exists(fullPath)) {
        return DIRECTORY_DOES_NOT_EXIST;
    }
    std::error_code err;
    if(not deleteRecurively) {
        if(std::filesystem::remove(fullPath, 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(fullPath, 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;
}

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