#pragma once
#include <codecvt>
#include "asset.h"

class BinaryStream
{
public:
    enum class ByteOrder : uint8_t { BigEndian, LittleEndian, Host };
    static ByteOrder sys_order()
    {
        union {
            uint32_t i;
            char c[4];
        } bint{ 0x01020304 };

        return bint.c[0] == 1 ? ByteOrder::BigEndian : ByteOrder::LittleEndian;
    }
    template<typename T> static T htonx(T x)
    {
        static auto sys = sys_order();
        return sys == ByteOrder::BigEndian ? x : swap(x);
    }
    template<typename T> static T ntohx(T x)
    {
        static auto sys = sys_order();
        return sys == ByteOrder::BigEndian ? x : swap(x);
    }
    template<typename T> static T swap(T x)
    {
#if _MSC_VER >= 1400

        if (sizeof(T) == 2)
        {
            auto y = _byteswap_ushort(*reinterpret_cast<uint16_t*>(&x));
            return *reinterpret_cast<T*>(&y);
        }
        else if (sizeof(T) == 4)
        {
            auto y = _byteswap_ulong(*reinterpret_cast<uint32_t*>(&x));
            return *reinterpret_cast<T*>(&y);
        }
        else if (sizeof(T) == 8)
        {
            auto y = _byteswap_uint64(*reinterpret_cast<uint64_t*>(&x));
            return *reinterpret_cast<T*>(&y);
        }
#else
        auto p = reinterpret_cast<uint8_t*>(&x);
        if (sizeof(T) == 2)
        {
            std::swap(p[0], p[1]);
        }
        else if (sizeof(T) == 4)
        {
            std::swap(p[0], p[3]);
            std::swap(p[1], p[2]);
        }
        else if (sizeof(T) == 8)
        {
            std::swap(p[0], p[7]);
            std::swap(p[1], p[6]);
            std::swap(p[2], p[5]);
            std::swap(p[3], p[4]);
        }
        else
        {
            static_assert(true, "Should not reach here");
        }
        return x;
#endif
    }
protected:
    bool m_swap = false;
    ByteOrder m_byte_order = ByteOrder::Host;
};

class BinaryStreamReader : public BinaryStream
{
public:
    BinaryStreamReader(const BinaryStreamReader&) = delete;
    BinaryStreamReader() = default;
    ~BinaryStreamReader();
    void init(uint8_t* data_ptr, size_t size, ByteOrder byte_order = ByteOrder::Host);
    bool load(const std::string& path, ByteOrder byte_order = ByteOrder::Host);
    size_t pos();
    void skip(size_t bytes);
    // snap to the next 4-alignment
    void snap();
    bool eof();
    bool has_data(size_t sz);
    uint8_t  ru8();
    uint16_t ru16();
    uint32_t ru32();
    uint64_t ru64();
    int8_t   ri8();
    int16_t  ri16();
    int32_t  ri32();
    int64_t  ri64();
    float    rflt();
    double   rdbl();
    std::string pick(size_t chars);
    std::string rstring();
    std::string rstring(size_t len);
    std::wstring rwstring();
    std::wstring rwstring(size_t len);
    std::string rpascal();
    std::vector<uint8_t> rraw();
    std::vector<uint8_t> rraw(size_t bytes);
    std::vector<uint8_t> rrle(size_t encoded_bytes);
    std::string rkey_or_string();
protected:
    template<typename T> inline T align4(T x)
    { 
        return ((x - T{ 1 }) & (~(T{ 3 }))) + T{ 4 };
    }
    template<typename T> T read()
    {
        T ret{};
        if (m_ptr)
        {
            if (has_data(sizeof(T)))
                ret = *reinterpret_cast<T*>(m_cur);
            m_cur += sizeof(T);
        }
        return ret;
    }
    template<typename T> T* advance(size_t bytes)
    {
        T* ret = nullptr;
        if (m_ptr)
        {
            if (has_data(bytes))
                ret = reinterpret_cast<T*>(m_cur);
            m_cur += bytes;
        }
        return ret;
    }
private:
    uint8_t *m_ptr = nullptr;
    uint8_t *m_cur = nullptr;
    size_t m_size = 0;
    Asset m_asset;
};

class BinaryStreamWriter : public BinaryStream
{
public:
    std::vector<uint8_t> m_data;
    BinaryStreamWriter(const BinaryStreamWriter&) = delete;
    BinaryStreamWriter() = default;
    ~BinaryStreamWriter() = default;
    void init(ByteOrder byte_order = ByteOrder::Host);
    bool save(const std::string& path) const;
    void skip(size_t bytes, uint8_t fill = 0);
    // snap to the next 4-alignment
    void snap();
    void wu8 (uint8_t  v);
    void wu16(uint16_t v);
    void wu32(uint32_t v);
    void wu64(uint64_t v);
    void wi8 (int8_t   v);
    void wi16(int16_t  v);
    void wi32(int32_t  v);
    void wi64(int64_t  v);
    void wflt(float    v);
    void wdbl(double   v);
    void wstring(std::string s);
    void wstring_raw(std::string s);
    void wwstring(std::wstring s);
    void wwstring_raw(std::wstring s);
    void wpascal(std::string s);
    void wraw(std::vector<uint8_t> raw);
    void wrle(std::vector<uint8_t> data);
    void wkey_or_string(std::string s);
protected:
    template<typename T> void write(T x)
    {
        uint8_t* bytes = reinterpret_cast<uint8_t*>(&x);
        m_data.insert(m_data.end(), bytes, bytes + sizeof(T));
    }
    template<typename T> void write(const T* ptr, size_t size)
    {
        const uint8_t* bytes = reinterpret_cast<const uint8_t*>(ptr);
        m_data.insert(m_data.end(), bytes, bytes + size * sizeof(T));
    }
};
