/*
    * Vfs.cpp
    * Virtual File System with numerical logical drive identifiers
    * Copyright (c) 2025 Daniel Hammer
*/

#include "Vfs.hpp"
#include <Terminal/Terminal.hpp>
#include <CppLib/Spinlock.hpp>

namespace Fs::Vfs {

    static FsDriver* driveTable[MaxDrives];
    static bool driveActive[MaxDrives];
    static uint32_t driveGeneration[MaxDrives];
    static bool initialized = false;

    // Protects driver dispatch from concurrent CPU access. Hot-unplug only
    // flips driveActive so an interrupted dispatch never sees a null driver.
    static kcp::Mutex vfsLock;

    // Parse "N:/path" into drive number and local path.
    // Returns true on success, sets outDrive and outPath.
    static bool ParsePath(const char* path, int& outDrive, const char*& outPath) {
        if (path == nullptr) return false;

        // Parse decimal drive number before ':'
        int drive = 0;
        int i = 0;
        bool hasDigit = false;

        while (path[i] >= '0' && path[i] <= '9') {
            drive = drive * 10 + (path[i] - '0');
            hasDigit = true;
            i++;
        }

        if (!hasDigit) return false;
        if (path[i] != ':') return false;

        // Everything after "N:" is the local path
        outDrive = drive;
        outPath = &path[i + 1];
        return true;
    }

    static void BumpDriveGeneration(int driveNumber) {
        driveGeneration[driveNumber]++;
        if (driveGeneration[driveNumber] == 0) {
            driveGeneration[driveNumber] = 1;
        }
    }

    void Initialize() {
        for (int i = 0; i < MaxDrives; i++) {
            driveTable[i] = nullptr;
            driveActive[i] = false;
            driveGeneration[i] = 1;
        }

        initialized = true;
        Kt::KernelLogStream(Kt::OK, "VFS") << "Initialized (" << MaxDrives << " drives)";
    }

    bool IsInitialized() {
        return initialized;
    }

    int RegisterDrive(int driveNumber, FsDriver* driver) {
        if (driveNumber < 0 || driveNumber >= MaxDrives) return -1;
        if (driver == nullptr) return -1;
        if (!initialized) return -1;

        vfsLock.Acquire();
        if (driveActive[driveNumber]) {
            vfsLock.Release();
            return -1;
        }
        driveTable[driveNumber] = driver;
        BumpDriveGeneration(driveNumber);
        driveActive[driveNumber] = true;
        vfsLock.Release();

        Kt::KernelLogStream(Kt::OK, "VFS") << "Registered drive " << driveNumber;
        return 0;
    }

    int UnregisterDrive(int driveNumber) {
        if (!initialized) return -1;
        if (driveNumber < 0 || driveNumber >= MaxDrives) return -1;

        vfsLock.Acquire();
        if (!driveActive[driveNumber]) {
            vfsLock.Release();
            return -1;
        }
        driveActive[driveNumber] = false;
        BumpDriveGeneration(driveNumber);
        vfsLock.Release();

        Kt::KernelLogStream(Kt::OK, "VFS") << "Unregistered drive " << driveNumber;
        return 0;
    }

    bool IsDriveRegistered(int driveNumber) {
        if (driveNumber < 0 || driveNumber >= MaxDrives) return false;
        vfsLock.Acquire();
        bool registered = driveActive[driveNumber];
        vfsLock.Release();
        return registered;
    }

    int FindLowestAvailableDrive(int firstDrive) {
        if (!initialized) return -1;
        if (firstDrive < 0) firstDrive = 0;

        vfsLock.Acquire();
        for (int i = firstDrive; i < MaxDrives; i++) {
            if (!driveActive[i]) {
                vfsLock.Release();
                return i;
            }
        }

        vfsLock.Release();
        return -1;
    }

    int OpenBackendFile(const char* path, BackendFile& outFile) {
        outFile.driveNumber = -1;
        outFile.localHandle = -1;
        outFile.generation = 0;

        int drive;
        const char* localPath;

        if (!ParsePath(path, drive, localPath)) return -1;
        if (drive < 0 || drive >= MaxDrives || !driveActive[drive] || driveTable[drive] == nullptr) return -1;

        vfsLock.Acquire();
        FsDriver* driver = driveTable[drive];
        uint32_t generation = driveGeneration[drive];
        int localHandle = (driveActive[drive] && driver) ? driver->Open(localPath) : -1;
        vfsLock.Release();
        if (localHandle < 0) return -1;

        outFile.driveNumber = drive;
        outFile.localHandle = localHandle;
        outFile.generation = generation;
        return 0;
    }

    int ReadBackendFile(const BackendFile& file, uint8_t* buffer, uint64_t offset, uint64_t size) {
        vfsLock.Acquire();
        FsDriver* driver = (file.driveNumber >= 0 && file.driveNumber < MaxDrives)
            ? driveTable[file.driveNumber]
            : nullptr;
        if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || !driveActive[file.driveNumber] ||
            driver == nullptr ||
            file.generation != driveGeneration[file.driveNumber] ||
            file.localHandle < 0) {
            vfsLock.Release();
            return -1;
        }

        int result = driver->Read(file.localHandle, buffer, offset, size);
        vfsLock.Release();
        return result;
    }

    uint64_t GetBackendFileSize(const BackendFile& file) {
        vfsLock.Acquire();
        FsDriver* driver = (file.driveNumber >= 0 && file.driveNumber < MaxDrives)
            ? driveTable[file.driveNumber]
            : nullptr;
        if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || !driveActive[file.driveNumber] ||
            driver == nullptr ||
            file.generation != driveGeneration[file.driveNumber] ||
            file.localHandle < 0) {
            vfsLock.Release();
            return 0;
        }

        uint64_t result = driver->GetSize(file.localHandle);
        vfsLock.Release();
        return result;
    }

    bool BackendFileCanWrite(const BackendFile& file) {
        vfsLock.Acquire();
        FsDriver* driver = (file.driveNumber >= 0 && file.driveNumber < MaxDrives)
            ? driveTable[file.driveNumber]
            : nullptr;
        bool canWrite = file.driveNumber >= 0 && file.driveNumber < MaxDrives &&
            driveActive[file.driveNumber] &&
            driver != nullptr &&
            file.generation == driveGeneration[file.driveNumber] &&
            file.localHandle >= 0 &&
            driver->Write != nullptr;
        vfsLock.Release();
        return canWrite;
    }

    void CloseBackendFile(BackendFile& file) {
        vfsLock.Acquire();
        FsDriver* driver = (file.driveNumber >= 0 && file.driveNumber < MaxDrives)
            ? driveTable[file.driveNumber]
            : nullptr;
        if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || !driveActive[file.driveNumber] ||
            driver == nullptr ||
            file.generation != driveGeneration[file.driveNumber] ||
            file.localHandle < 0) {
            vfsLock.Release();
            file.driveNumber = -1;
            file.localHandle = -1;
            file.generation = 0;
            return;
        }

        driver->Close(file.localHandle);
        vfsLock.Release();

        file.driveNumber = -1;
        file.localHandle = -1;
        file.generation = 0;
    }

    int WriteBackendFile(const BackendFile& file, const uint8_t* buffer, uint64_t offset, uint64_t size) {
        vfsLock.Acquire();
        FsDriver* driver = (file.driveNumber >= 0 && file.driveNumber < MaxDrives)
            ? driveTable[file.driveNumber]
            : nullptr;
        if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || !driveActive[file.driveNumber] ||
            driver == nullptr ||
            file.generation != driveGeneration[file.driveNumber] ||
            file.localHandle < 0) {
            vfsLock.Release();
            return -1;
        }

        if (driver->Write == nullptr) { vfsLock.Release(); return -1; }
        int result = driver->Write(file.localHandle, buffer, offset, size);
        vfsLock.Release();
        return result;
    }

    int CreateBackendFile(const char* path, BackendFile& outFile) {
        outFile.driveNumber = -1;
        outFile.localHandle = -1;
        outFile.generation = 0;

        int drive;
        const char* localPath;

        if (!ParsePath(path, drive, localPath)) return -1;
        if (drive < 0 || drive >= MaxDrives || !driveActive[drive] || driveTable[drive] == nullptr) return -1;
        if (driveTable[drive]->Create == nullptr) return -1;

        vfsLock.Acquire();
        FsDriver* driver = driveTable[drive];
        uint32_t generation = driveGeneration[drive];
        int localHandle = (driveActive[drive] && driver && driver->Create)
            ? driver->Create(localPath)
            : -1;
        vfsLock.Release();
        if (localHandle < 0) return -1;

        outFile.driveNumber = drive;
        outFile.localHandle = localHandle;
        outFile.generation = generation;
        return 0;
    }

    int VfsDelete(const char* path) {
        int drive;
        const char* localPath;

        if (!ParsePath(path, drive, localPath)) return -1;
        if (drive < 0 || drive >= MaxDrives || !driveActive[drive] || driveTable[drive] == nullptr) return -1;
        if (driveTable[drive]->Delete == nullptr) return -1;

        vfsLock.Acquire();
        FsDriver* driver = driveTable[drive];
        int result = (driveActive[drive] && driver && driver->Delete)
            ? driver->Delete(localPath)
            : -1;
        vfsLock.Release();
        return result;
    }

    int VfsMkdir(const char* path) {
        int drive;
        const char* localPath;

        if (!ParsePath(path, drive, localPath)) return -1;
        if (drive < 0 || drive >= MaxDrives || !driveActive[drive] || driveTable[drive] == nullptr) return -1;
        if (driveTable[drive]->Mkdir == nullptr) return -1;

        vfsLock.Acquire();
        FsDriver* driver = driveTable[drive];
        int result = (driveActive[drive] && driver && driver->Mkdir)
            ? driver->Mkdir(localPath)
            : -1;
        vfsLock.Release();
        return result;
    }

    int VfsDriveList(int* outDrives, int maxEntries) {
        vfsLock.Acquire();
        int count = 0;
        for (int i = 0; i < MaxDrives && count < maxEntries; i++) {
            if (driveActive[i]) {
                outDrives[count++] = i;
            }
        }
        vfsLock.Release();
        return count;
    }

    int VfsDriveLabel(int driveNumber, char* outLabel, int maxLen) {
        if (outLabel == nullptr || maxLen <= 0) return -1;
        outLabel[0] = '\0';

        vfsLock.Acquire();
        if (driveNumber < 0 || driveNumber >= MaxDrives || !driveActive[driveNumber] ||
            driveTable[driveNumber] == nullptr) {
            vfsLock.Release();
            return -1;
        }

        if (driveTable[driveNumber]->GetLabel == nullptr) {
            vfsLock.Release();
            return 0;
        }

        const char* label = driveTable[driveNumber]->GetLabel();
        if (label == nullptr || label[0] == '\0') {
            vfsLock.Release();
            return 0;
        }

        int copied = 0;
        while (label[copied] != '\0' && copied < maxLen - 1) {
            outLabel[copied] = label[copied];
            copied++;
        }
        outLabel[copied] = '\0';

        vfsLock.Release();
        return copied;
    }

    int VfsRename(const char* oldPath, const char* newPath) {
        int oldDrive, newDrive;
        const char* oldLocal;
        const char* newLocal;

        if (!ParsePath(oldPath, oldDrive, oldLocal)) return -1;
        if (!ParsePath(newPath, newDrive, newLocal)) return -1;

        // Cross-drive rename not supported
        if (oldDrive != newDrive) return -1;
        if (oldDrive < 0 || oldDrive >= MaxDrives || !driveActive[oldDrive] ||
            driveTable[oldDrive] == nullptr) return -1;
        if (driveTable[oldDrive]->Rename == nullptr) return -1;

        vfsLock.Acquire();
        FsDriver* driver = driveTable[oldDrive];
        int result = (driveActive[oldDrive] && driver && driver->Rename)
            ? driver->Rename(oldLocal, newLocal)
            : -1;
        vfsLock.Release();
        return result;
    }

    int VfsReadDir(const char* path, const char** outNames, int maxEntries) {
        int drive;
        const char* localPath;

        if (!ParsePath(path, drive, localPath)) return -1;
        if (drive < 0 || drive >= MaxDrives || !driveActive[drive] || driveTable[drive] == nullptr) return -1;

        vfsLock.Acquire();
        FsDriver* driver = driveTable[drive];
        int result = (driveActive[drive] && driver && driver->ReadDir)
            ? driver->ReadDir(localPath, outNames, maxEntries)
            : -1;
        vfsLock.Release();
        return result;
    }

}
