#pragma once

#include <wolv/types.hpp>
#include <wolv/io/fs.hpp>
#include <wolv/io/handle.hpp>

#include <atomic>
#include <cstdio>
#include <optional>
#include <string>
#include <jthread.hpp>
#include <mutex>
#include <condition_variable>
#include <vector>
#include <functional>

#include <sys/types.h>
#include <sys/stat.h>

#if defined(OS_WINDOWS)
    using HANDLE = void*;
#endif

namespace wolv::io {

    class File {
    public:
        enum class Mode {
            Read,
            Write,
            Create
        };

        explicit File(const std::fs::path &path, Mode mode) noexcept;
        File() noexcept = default;
        File(const File &) = delete;
        File(File &&other) noexcept;

        ~File();

        File &operator=(File &&other) noexcept;

        [[nodiscard]] bool isValid() const;
        [[nodiscard]] const std::optional<i64>& getOpenError() const {
            return m_openError;
        }

        File clone();

        void seek(u64 offset);
        void open();
        void close();

        bool map();
        void unmap();
        [[nodiscard]] u8* getMapping() const { return this->m_map; }

        using Result = i64;

        Result readBuffer(u8 *buffer, size_t size);
        [[nodiscard]] std::vector<u8> readVector(size_t numBytes = 0);
        [[nodiscard]] std::string readString(size_t numBytes = 0);
        [[nodiscard]] std::u8string readU8String(size_t numBytes = 0);

        Result readBufferAtomic(u64 address, u8 *buffer, size_t size);
        [[nodiscard]] std::vector<u8> readVectorAtomic(u64 address, size_t numBytes);
        [[nodiscard]] std::string readStringAtomic(u64 address, size_t numBytes);
        [[nodiscard]] std::u8string readU8StringAtomic(u64 address, size_t numBytes);

        Result writeBuffer(const u8 *buffer, size_t size);
        Result writeVector(const std::vector<u8> &bytes);
        Result writeString(const std::string &string);
        Result writeU8String(const std::u8string &string);

        Result writeBufferAtomic(u64 address, const u8 *buffer, size_t size);
        Result writeVectorAtomic(u64 address, const std::vector<u8> &bytes);
        Result writeStringAtomic(u64 address, const std::string &string);
        Result writeU8StringAtomic(u64 address, const std::u8string &string);

        [[nodiscard]] size_t getSize() const;
        void setSize(u64 size);

        bool flush();
        bool remove();

        [[nodiscard]] FILE* getHandle() const;
        [[nodiscard]] NativeHandle getNativeHandle() const { return m_handle; }
        [[nodiscard]] const std::fs::path &getPath() const { return this->m_path; }

        void disableBuffering();

        [[nodiscard]] std::optional<struct stat> getFileInfo();

    private:
        void updateSize() const;

    private:
        mutable FILE *m_fileHandle = nullptr;
        NativeHandle m_handle;

        std::fs::path m_path;
        Mode m_mode = Mode::Read;
        u8 *m_map = nullptr;
        std::optional<i64> m_openError;

        mutable bool m_sizeValid = false;
        mutable size_t m_fileSize = 0;
    };

    class StoppableSleep {
    public:
        StoppableSleep(const std::stop_token &st) : m_st(st) {}
        bool sleep(u32 duration);
        bool shouldStop() {
            return m_st.stop_requested();
        }

    private:
        std::stop_token m_st;
        std::mutex m_mtx;
        std::condition_variable_any m_cv;
    };

    class ChangeTracker {
    public:
        ChangeTracker() = default;
        explicit ChangeTracker(std::fs::path path) : m_path(std::move(path)) { }
        explicit ChangeTracker(const File &file) : m_path(file.getPath()) { }
        ~ChangeTracker() { this->stopTracking(); }

        ChangeTracker(const ChangeTracker &) = delete;
        ChangeTracker(ChangeTracker &&) = default;

        ChangeTracker& operator=(const ChangeTracker &) = delete;
        ChangeTracker& operator=(ChangeTracker &&other) noexcept;

        [[nodiscard]] const std::fs::path& getPath() const { return this->m_path; }

        void startTracking(const std::function<void()> &callback);
        void stopTracking();

    private:
        static void trackImpl(std::stop_token stoken, const std::fs::path &path, const std::function<void()> &callback);

    private:
        std::fs::path m_path;
        std::jthread m_thread;
    };

}