#include "CoreController.h"
#include "OBSWConfig.h"
#include "OBSWVersion.h"

#include "fsfw/FSFWVersion.h"
#include "fsfw/serviceinterface/ServiceInterface.h"
#if OBSW_USE_TMTC_TCP_BRIDGE == 0
#include "fsfw/osal/common/UdpTmTcBridge.h"
#else
#include "fsfw/osal/common/TcpTmTcBridge.h"
#endif

#include "bsp_q7s/memory/scratchApi.h"
#include "bsp_q7s/memory/SdCardManager.h"

#include <filesystem>

CoreController::CoreController(object_id_t objectId):
        ExtendedControllerBase(objectId, objects::NO_OBJECT, 5) {
}

ReturnValue_t CoreController::handleCommandMessage(CommandMessage *message) {
    return HasReturnvaluesIF::RETURN_OK;
}

void CoreController::performControlOperation() {
}

ReturnValue_t CoreController::initializeLocalDataPool(localpool::DataPool &localDataPoolMap,
        LocalDataPoolManager &poolManager) {
    return HasReturnvaluesIF::RETURN_OK;
}

LocalPoolDataSetBase* CoreController::getDataSetHandle(sid_t sid) {
    return nullptr;
}

ReturnValue_t CoreController::initialize() {
    ReturnValue_t result = HasReturnvaluesIF::RETURN_OK;
    try {
        result = sdCardInit();
        if(result != HasReturnvaluesIF::RETURN_OK) {
            sif::warning << "CoreController::initialize: SD card init failed" << std::endl;
        }
    }
    catch(const std::filesystem::filesystem_error& e) {
        sif::error << "CoreController::initialize: sdCardInit failed with exception " << e.what()
                << std::endl;
    }

    result = scratch::writeNumber(scratch::ALLOC_FAILURE_COUNT, 0);
    if(result != HasReturnvaluesIF::RETURN_OK) {
        sif::warning << "CoreController::initialize: Setting up alloc failure "
                "count failed" << std::endl;
    }

    return HasReturnvaluesIF::RETURN_OK;
}

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

ReturnValue_t CoreController::sdCardInit() {
#if Q7S_SD_CARD_CONFIG == Q7S_SD_NONE
    sif::info << "No SD card initialization will be performed" << std::endl;
    return HasReturnvaluesIF::RETURN_OK;
#else
    SdCardManager* sdcMan = SdCardManager::instance();
    if(sdcMan == nullptr) {
        return HasReturnvaluesIF::RETURN_FAILED;
    }
    // Create update status file
    ReturnValue_t result = sdcMan->updateSdCardStateFile();
    if(result != HasReturnvaluesIF::RETURN_OK) {
        sif::warning << "CoreController::initialize: Updating SD card state file failed"
                << std::endl;
    }

    auto statusPair = SdCardManager::SdStatusPair(sd::SdStatus::OFF, sd::SdStatus::OFF);
    result = sdcMan->getSdCardActiveStatus(statusPair);
    if(result != HasReturnvaluesIF::RETURN_OK) {
        sif::warning << "Getting SD card activity status failed" << std::endl;
    }

#if Q7S_SD_CARD_CONFIG == Q7S_SD_COLD_REDUNDANT
    return sdCardColdRedundantInit(sdcMan, statusPair);
#elif Q7S_SD_CARD_CONFIG == Q7S_SD_HOT_REDUNDANT
    sif::info << "Hot redundant SD card configuration" << std::endl;

    setUpSdCard(sd::SdCard::SLOT_0, sdStatus.first, "0");
    setUpSdCard(sd::SdCard::SLOT_1, sdStatus.second, "1");
    // Update status file
    sdcMan->updateSdCardStateFile();
    return HasReturnvaluesIF::RETURN_OK;
#endif

#endif /* Q7S_SD_CARD_CONFIG != Q7S_SD_NONE */

}

ReturnValue_t CoreController::sdCardSetup(SdCardManager& sdcMan,
        SdCardManager::SdStatusPair& statusPair,sd::SdCard sdCard, sd::SdStatus status,
        std::string sdString) {
    std::string mountString;
    if(sdCard == sd::SdCard::SLOT_0) {
        mountString = SdCardManager::SD_0_MOUNT_POINT;
    }
    else {
        mountString = SdCardManager::SD_1_MOUNT_POINT;
    }

    if(status == sd::SdStatus::OFF) {
        sif::info << "Switching on and mounting SD card " << sdString << " at " <<
                mountString << std::endl;
        return sdcMan.switchOnSdCard(sdCard, true, &statusPair);
    }
    else if(status == sd::SdStatus::ON) {
        sif::info << "Mounting SD card " << sdString << " at " << mountString << std::endl;
        return sdcMan.mountSdCard(sdCard);
    }
    else {
        if(std::filesystem::exists(mountString)) {
            sif::info << "SD card " << sdString << " 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;
    }
}

ReturnValue_t CoreController::executeAction(ActionId_t actionId, MessageQueueId_t commandedBy,
        const uint8_t *data, size_t size) {
    switch(actionId) {
    case(LIST_DIRECTORY_INTO_FILE): {
        // TODO: Packet definition for clean deserialization
        // 2 bytes for a and R flag, at least 5 bytes for minimum valid path /tmp with
        // null termination, at least 7 bytes for minimum target file name /tmp/a with
        // null termination.
        if(size < 14) {
            return HasActionsIF::INVALID_PARAMETERS;
        }
        // We could also make -l optional, but I can't think of a reason why to not use -l..

        // This flag specifies to run ls with -a
        bool aFlag = data[0];
        data += 1;
        // This flag specifies to run ls with -R
        bool RFlag = data[1];
        data += 1;

        size_t remainingSize = size - 2;
        // One larger for null termination, which prevents undefined behaviour if the sent
        // strings are not 0 terminated properly
        std::vector<uint8_t> repoAndTargetFileBuffer(remainingSize + 1, 0);
        std::memcpy(repoAndTargetFileBuffer.data(), data, remainingSize);
        const char* currentCharPtr = reinterpret_cast<const char*>(repoAndTargetFileBuffer.data());
        // Full target file name
        std::string repoName(currentCharPtr);
        size_t repoLength = repoName.length();
        // The other string needs to be at least one letter plus NULL termination to be valid at all
        // The first string also needs to be NULL terminated, but the termination is not included
        // in the string length, so this is subtracted from the remaining size as well
        if(repoLength > remainingSize - 3) {
            return HasActionsIF::INVALID_PARAMETERS;
        }
        // The file length will not include the NULL termination, so we skip it
        currentCharPtr += repoLength + 1;
        std::string targetFileName(currentCharPtr);
        std::ostringstream oss;
        oss << "ls -l";
        if(aFlag) {
            oss << "a";
        }
        if(RFlag) {
            oss << "R";
        }
        oss << " " << repoName << " > " << targetFileName;
        std::system(oss.str().c_str());
        return HasReturnvaluesIF::RETURN_OK;
    }
    default: {
        return HasActionsIF::INVALID_ACTION_ID;
    }
    }
}

ReturnValue_t CoreController::initializeAfterTaskCreation() {
    ReturnValue_t result = versionFileInit();
    if(result != HasReturnvaluesIF::RETURN_OK) {
        sif::warning << "CoreController::initialize: Version initialization failed" << std::endl;
    }
    initPrint();
    return result;
}

ReturnValue_t CoreController::sdCardColdRedundantInit(SdCardManager* sdcMan,
        SdCardManager::SdStatusPair& statusPair) {
    sd::SdCard preferredSdCard = sd::SdCard::SLOT_0;
    ReturnValue_t result = sdcMan->getPreferredSdCard(preferredSdCard);
    if(result != HasReturnvaluesIF::RETURN_OK) {
        if(result == scratch::KEY_NOT_FOUND) {
            sif::warning << "CoreController::sdCardInit: "
                    "Preferred SD card not set. Setting to 0" << std::endl;
            sdcMan->setPreferredSdCard(preferredSdCard);
        }
        else {
            sif::warning << "CoreController::sdCardInit: Could not get preferred SD card"
                    "information from the scratch buffer" << std::endl;
        }
    }
    std::string preferredString;
    sd::SdStatus preferredStatus = sd::SdStatus::OFF;

    sd::SdStatus otherStatus = sd::SdStatus::OFF;
    std::string otherString;
    sd::SdCard otherSdc = sd::SdCard::SLOT_0;

    if(preferredSdCard == sd::SdCard::SLOT_0) {
        preferredStatus = statusPair.first;
        preferredString = "0";
        otherSdc = sd::SdCard::SLOT_1;
        otherStatus = statusPair.second;
        otherString = "1";
    }
    else {
        preferredString = "1";
        preferredStatus = statusPair.second;
        otherStatus = statusPair.first;
        otherSdc = sd::SdCard::SLOT_0;
        otherString = "0";
    }

    sif::info << "Cold redundant SD card configuration, preferred SD card " <<
            preferredString << std::endl;

    result = sdCardSetup(*sdcMan, statusPair, preferredSdCard, preferredStatus, preferredString);
    if(result != SdCardManager::ALREADY_MOUNTED and result != HasReturnvaluesIF::RETURN_OK) {
        sif::warning << "Setting up preferred card " << otherString <<
                " in cold redundant mode failed" << std::endl;
        // Try other SD card and mark set up operation as failed
        sdCardSetup(*sdcMan, statusPair, preferredSdCard, preferredStatus, preferredString);
        result = HasReturnvaluesIF::RETURN_FAILED;
    }

    if(result != HasReturnvaluesIF::RETURN_FAILED and otherStatus != sd::SdStatus::OFF) {
        sif::info << "Switching off secondary SD card " << otherString << std::endl;
        // Switch off other SD card in cold redundant mode if setting up preferred one walked
        // without issues
        result = sdcMan->switchOffSdCard(otherSdc, otherStatus, &statusPair);
        if(result != HasReturnvaluesIF::RETURN_OK and result != SdCardManager::ALREADY_OFF) {
            sif::warning << "Switching off secondary SD card " << otherString <<
                    " in cold redundant mode failed" << std::endl;
        }
    }

    // Update status file
    sdcMan->updateSdCardStateFile();
    return HasReturnvaluesIF::RETURN_OK;
}

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

ReturnValue_t CoreController::versionFileInit() {

    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(SW_VERSION) + "." +
            std::to_string(SW_SUBVERSION) + "." + std::to_string(SW_REVISION);
    std::string fullFsfwVersionString = "FSFW: v" + std::to_string(FSFW_VERSION) + "." +
            std::to_string(FSFW_SUBVERSION) + "." + std::to_string(FSFW_REVISION);
    std::string systemString = "System: " + unameLine;
    std::string mountPrefix = SdCardManager::instance()->getCurrentMountPrefix();
    std::string versionFilePath = mountPrefix + "/conf/version.txt";
    std::fstream versionFile;

    if(not std::filesystem::exists(versionFilePath)) {
        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 HasReturnvaluesIF::RETURN_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 HasReturnvaluesIF::RETURN_OK;
}

void CoreController::initPrint() {
#if OBSW_VERBOSE_LEVEL >= 1
#if OBSW_USE_TMTC_TCP_BRIDGE == 0
    sif::info << "Created UDP server for TMTC commanding with listener port " <<
            UdpTmTcBridge::DEFAULT_SERVER_PORT << std::endl;
#else
    sif::info << "Created TCP server for TMTC commanding with listener port " <<
            TcpTmTcBridge::DEFAULT_SERVER_PORT << std::endl;
#endif
#endif
}