#include "fl/file_system.h"
#include "fl/unused.h"
#include "fl/warn.h"

#ifdef __EMSCRIPTEN__
#include "platforms/wasm/fs_wasm.h"
#elif __has_include(<SD.h>)
// work in progress.
// #include "platforms/fs_sdcard_arduino.hpp"
#endif

#include "fl/json.h"
#include "fl/namespace.h"
#include "fl/screenmap.h"
#include "fl/unused.h"

namespace fl {

class NullFileHandle : public FileHandle {
  public:
    NullFileHandle() = default;
    ~NullFileHandle() override {}

    bool available() const override { return false; }
    size_t size() const override { return 0; }
    size_t read(uint8_t *dst, size_t bytesToRead) override {
        FASTLED_UNUSED(dst);
        FASTLED_UNUSED(bytesToRead);
        return 0;
    }
    size_t pos() const override { return 0; }
    const char *path() const override { return "NULL FILE HANDLE"; }
    bool seek(size_t pos) override {
        FASTLED_UNUSED(pos);
        return false;
    }
    void close() override {}
    bool valid() const override {
        FASTLED_WARN("NullFileHandle is not valid");
        return false;
    }
};

using namespace fl;

class NullFileSystem : public FsImpl {
  public:
    NullFileSystem() {
        FASTLED_WARN("NullFileSystem instantiated as a placeholder, please "
                     "implement a file system for your platform.");
    }
    ~NullFileSystem() override {}

    bool begin() override { return true; }
    void end() override {}

    void close(FileHandlePtr file) override {
        // No need to do anything for in-memory files
        FASTLED_UNUSED(file);
        FASTLED_WARN("NullFileSystem::close");
    }

    FileHandlePtr openRead(const char *_path) override {
        FASTLED_UNUSED(_path);
        FileHandlePtr out = FileHandlePtr::TakeOwnership(new NullFileHandle());
        return out;
    }
};

bool FileSystem::beginSd(int cs_pin) {
    mFs = make_sdcard_filesystem(cs_pin);
    if (!mFs) {
        return false;
    }
    mFs->begin();
    return true;
}

bool FileSystem::begin(FsImplPtr platform_filesystem) {
    mFs = platform_filesystem;
    if (!mFs) {
        return false;
    }
    mFs->begin();
    return true;
}

size_t FileHandle::bytesLeft() const { return size() - pos(); }

FileSystem::FileSystem() : mFs() {}

void FileSystem::end() {
    if (mFs) {
        mFs->end();
    }
}

bool FileSystem::readJson(const char *path, JsonDocument *doc) {
    Str text;
    if (!readText(path, &text)) {
        return false;
    }
    return parseJson(text.c_str(), doc);
}

bool FileSystem::readScreenMaps(const char *path,
                                FixedMap<Str, ScreenMap, 16> *out, Str *error) {
    Str text;
    if (!readText(path, &text)) {
        FASTLED_WARN("Failed to read file: " << path);
        if (error) {
            *error = "Failed to read file: ";
            error->append(path);
        }
        return false;
    }
    Str err;
    bool ok = ScreenMap::ParseJson(text.c_str(), out, &err);
    if (!ok) {
        FASTLED_WARN("Failed to parse screen map: " << err.c_str());
        *error = err;
        return false;
    }
    return true;
}

bool FileSystem::readScreenMap(const char *path, const char *name,
                               ScreenMap *out, Str *error) {
    Str text;
    if (!readText(path, &text)) {
        FASTLED_WARN("Failed to read file: " << path);
        if (error) {
            *error = "Failed to read file: ";
            error->append(path);
        }
        return false;
    }
    Str err;
    bool ok = ScreenMap::ParseJson(text.c_str(), name, out, &err);
    if (!ok) {
        FASTLED_WARN("Failed to parse screen map: " << err.c_str());
        *error = err;
        return false;
    }
    return true;
}

void FileSystem::close(FileHandlePtr file) { mFs->close(file); }

FileHandlePtr FileSystem::openRead(const char *path) {
    return mFs->openRead(path);
}
Video FileSystem::openVideo(const char *path, size_t pixelsPerFrame, float fps,
                            size_t nFrameHistory) {
    Video video(pixelsPerFrame, fps, nFrameHistory);
    FileHandlePtr file = openRead(path);
    if (!file) {
        video.setError(fl::Str("Could not open file: ").append(path));
        return video;
    }
    video.begin(file);
    return video;
}

bool FileSystem::readText(const char *path, fl::Str *out) {
    FileHandlePtr file = openRead(path);
    if (!file) {
        FASTLED_WARN("Failed to open file: " << path);
        return false;
    }
    size_t size = file->size();
    out->reserve(size + out->size());
    bool wrote = false;
    while (file->available()) {
        uint8_t buf[64];
        size_t n = file->read(buf, sizeof(buf));
        // out->append(buf, n);
        out->append((const char *)buf, n);
        wrote = true;
    }
    file->close();
    FASTLED_DBG_IF(!wrote, "Failed to write any data to the output string.");
    return wrote;
}
} // namespace fl

namespace fl {
__attribute__((weak)) FsImplPtr make_sdcard_filesystem(int cs_pin) {
    FASTLED_UNUSED(cs_pin);
    FsImplPtr out = FsImplPtr::TakeOwnership(new NullFileSystem());
    return out;
}

} // namespace fl
