#include <utility>
#include <chrono>
#include <wolv/io/file.hpp>
#include <wolv/utils/string.hpp>

namespace wolv::io {

    File File::clone() {
        return File(m_path, m_mode);
    }

    bool File::remove() {
        this->unmap();
        this->close();

        return fs::remove(this->m_path);
    }

    size_t File::getSize() const {
        if (!m_sizeValid) {
            updateSize();
        }
        return m_fileSize;
    }

    std::vector<u8> File::readVector(size_t numBytes) {
        if (!isValid())
            return {};

        auto size = numBytes == 0 ? getSize() : numBytes;
        if (size == 0)
            return {};

        std::vector<u8> bytes(size);
        auto bytesRead = readBuffer(bytes.data(), bytes.size());
        if (bytesRead < 0)
            return {};

        bytes.resize(bytesRead);

        return bytes;
    }

    std::string File::readString(size_t numBytes) {
        if (!isValid())
            return {};

        auto bytes = this->readVector(numBytes);

        if (bytes.empty())
            return "";

        auto cString = reinterpret_cast<const char *>(bytes.data());
        return { cString, util::strnlen(cString, bytes.size()) };
    }

    std::u8string File::readU8String(size_t numBytes) {
        if (!isValid())
            return {};

        auto bytes = this->readVector(numBytes);

        if (bytes.empty())
            return u8"";

        auto cString = reinterpret_cast<const char8_t *>(bytes.data());
        return { cString, util::strnlen(reinterpret_cast<const char*>(bytes.data()), bytes.size()) };
    }

    std::vector<u8> File::readVectorAtomic(u64 address, size_t numBytes) {
        if (!isValid())
            return {};

        auto size = numBytes == 0 ? getSize() : numBytes;
        if (size == 0)
            return {};

        std::vector<u8> bytes(size);
        auto bytesRead = readBufferAtomic(address, bytes.data(), bytes.size());
        if (bytesRead < 0)
            return {};

        bytes.resize(bytesRead);

        return bytes;
    }

    std::string File::readStringAtomic(u64 address, size_t numBytes) {
        if (!isValid())
            return {};

        auto bytes = this->readVectorAtomic(address, numBytes);

        if (bytes.empty())
            return "";

        auto cString = reinterpret_cast<const char *>(bytes.data());
        return { cString, util::strnlen(cString, bytes.size()) };
    }

    std::u8string File::readU8StringAtomic(u64 address, size_t numBytes) {
        if (!isValid())
            return {};

        auto bytes = this->readVectorAtomic(address, numBytes);

        if (bytes.empty())
            return u8"";

        auto cString = reinterpret_cast<const char8_t *>(bytes.data());
        return { cString, util::strnlen(reinterpret_cast<const char*>(bytes.data()), bytes.size()) };
    }

    File::Result File::writeVector(const std::vector<u8> &bytes) {
        return writeBuffer(bytes.data(), bytes.size());
    }

    File::Result File::writeString(const std::string &string) {
        return writeBuffer(reinterpret_cast<const u8*>(string.data()), string.size());
    }

    File::Result File::writeU8String(const std::u8string &string) {
        return writeBuffer(reinterpret_cast<const u8*>(string.data()), string.size());
    }

    File::Result File::writeVectorAtomic(u64 address, const std::vector<u8> &bytes) {
        return writeBufferAtomic(address, bytes.data(), bytes.size());
    }

    File::Result File::writeStringAtomic(u64 address, const std::string &string) {
        return writeBufferAtomic(address, reinterpret_cast<const u8*>(string.data()), string.size());
    }

    File::Result File::writeU8StringAtomic(u64 address, const std::u8string &string) {
        return writeBufferAtomic(address, reinterpret_cast<const u8*>(string.data()), string.size());
    }

    bool StoppableSleep::sleep(u32 duration) {
        std::unique_lock lk(m_mtx);
        bool shouldStop = false;

        std::stop_callback cb(m_st, [&]{
            m_cv.notify_all();
            shouldStop = true;
        });

        m_cv.wait_for(lk, std::chrono::milliseconds(duration), [&]{return shouldStop;});

        return shouldStop;
    }

    ChangeTracker& ChangeTracker::operator=(ChangeTracker &&other) noexcept {
        stopTracking();
        m_path = std::move(other.m_path);
        m_thread = std::move(other.m_thread);

        return *this;
    }

    void ChangeTracker::startTracking(const std::function<void()> &callback) {
        if (m_path.empty())
            return;

        m_thread = std::jthread(trackImpl, m_path, callback);
    }

    void ChangeTracker::stopTracking() {
        m_thread.request_stop();
        if (m_thread.joinable())
            m_thread.join();
    }

}