/*
    * 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];

    // Protects handle table and driver dispatch from concurrent CPU access.
    // Uses Mutex (not Spinlock) so interrupts stay enabled while held --
    // VFS is never called from interrupt context.
    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;
    }

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

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

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

        driveTable[driveNumber] = driver;
        Kt::KernelLogStream(Kt::OK, "VFS") << "Registered drive " << driveNumber;
        return 0;
    }

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

        int drive;
        const char* localPath;

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

        vfsLock.Acquire();
        int localHandle = driveTable[drive]->Open(localPath);
        vfsLock.Release();
        if (localHandle < 0) return -1;

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

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

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

    uint64_t GetBackendFileSize(const BackendFile& file) {
        vfsLock.Acquire();
        if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || driveTable[file.driveNumber] == nullptr ||
            file.localHandle < 0) {
            vfsLock.Release();
            return 0;
        }

        uint64_t result = driveTable[file.driveNumber]->GetSize(file.localHandle);
        vfsLock.Release();
        return result;
    }

    bool BackendFileCanWrite(const BackendFile& file) {
        vfsLock.Acquire();
        bool canWrite = file.driveNumber >= 0 && file.driveNumber < MaxDrives &&
            driveTable[file.driveNumber] != nullptr &&
            file.localHandle >= 0 &&
            driveTable[file.driveNumber]->Write != nullptr;
        vfsLock.Release();
        return canWrite;
    }

    void CloseBackendFile(BackendFile& file) {
        vfsLock.Acquire();
        if (file.driveNumber < 0 || file.driveNumber >= MaxDrives || driveTable[file.driveNumber] == nullptr ||
            file.localHandle < 0) {
            vfsLock.Release();
            file.driveNumber = -1;
            file.localHandle = -1;
            return;
        }

        driveTable[file.driveNumber]->Close(file.localHandle);
        vfsLock.Release();

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

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

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

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

        int drive;
        const char* localPath;

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

        vfsLock.Acquire();

        int localHandle = driveTable[drive]->Create(localPath);
        vfsLock.Release();
        if (localHandle < 0) return -1;

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

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

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

        vfsLock.Acquire();
        int result = driveTable[drive]->Delete(localPath);
        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 || driveTable[drive] == nullptr) return -1;
        if (driveTable[drive]->Mkdir == nullptr) return -1;

        vfsLock.Acquire();
        int result = driveTable[drive]->Mkdir(localPath);
        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 (driveTable[i] != nullptr) {
                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 || 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 || driveTable[oldDrive] == nullptr) return -1;
        if (driveTable[oldDrive]->Rename == nullptr) return -1;

        vfsLock.Acquire();
        int result = driveTable[oldDrive]->Rename(oldLocal, newLocal);
        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 || driveTable[drive] == nullptr) return -1;

        vfsLock.Acquire();
        int result = driveTable[drive]->ReadDir(localPath, outNames, maxEntries);
        vfsLock.Release();
        return result;
    }

}
