#pragma once
#include "../dependency/minidef/src/TypeDef.h"
#include "../game/Log.h"

#include "bx/string.h"
#include <bx/file.h>
#include <cstdint>

namespace Generated
{
    struct Serializer
    {
        bx::Error Err;
        bx::FilePath Path;
        bx::FileWriter Writer;

        bool Init(const bx::FilePath& path, const char* _4cc)
        {
            if (_4cc == nullptr)
            {
                LOG_ERROR("Provided invalid 4cc!");
                return false;
            }
            if (path.isEmpty())
            {
                LOG_ERROR("Provided empty path!");
                return false;
            }
            Path = path;
            if (!Writer.open(path, false, &Err))
            {
                LOG_ERROR("Failed to open file %s: %s", path.getCPtr(), Err.getMessage().getCPtr());
                return false;
            }
            Writer.write(_4cc, 4, &Err);
            if (!Err.isOk())
            {
                LOG_ERROR("Failed to write to file %s: %s", path.getCPtr(), Err.getMessage().getCPtr());
            }
            return Err.isOk();
        }

        template <typename T> bool WriteT(const T& data)
        {
            uint32_t hash = data.Hash;
            if (!Write(&hash, sizeof(hash))) return false;

            uint32_t defSize = 0;
            if (!Write(&defSize, sizeof(defSize))) return false;

            // auto& definitions = GetDefinitions();
            // if (!Write(&definitions, sizeof(definitions))) return false;

            uint32_t size = sizeof(T);
            if (!Write(&size, sizeof(size))) return false;

            return Save(&data, 1, *this);
        }

        bool Write(const void* data, uint32_t size)
        {
            Writer.write(data, size, &Err);
            if (!Err.isOk()) LOG_ERROR("Write error: %s", Err.getMessage().getCPtr());
            return Err.isOk();
        }

        void Finish()
        {
            Writer.close();
        }

        ~Serializer()
        {
            Writer.close();
        }
    };

    struct Deserializer
    {
        bx::Error Err;
        bx::FilePath Path;
        bx::FileReader Reader;

        bool Init(const bx::FilePath& path, const char* _4cc)
        {
            Path = path;
            if (!Reader.open(path, &Err))
            {
                LOG_ERROR("Failed to open file %s: %s", path.getCPtr(), Err.getMessage().getCPtr());
                return false;
            }
            char read4cc[4]{0};
            int32_t readAmount = Reader.read(read4cc, 4, &Err);
            if (!Err.isOk())
            {
                LOG_ERROR("Failed to read from file %s: %s", path.getCPtr(), Err.getMessage().getCPtr());
                return false;
            }
            if (readAmount < 4)
            {
                LOG_ERROR("Failed to read enough bytes from file %s", path.getCPtr());
                return false;
            }
            if (read4cc[0] != _4cc[0] || read4cc[1] != _4cc[1] || read4cc[2] != _4cc[2] || read4cc[3] != _4cc[3])
            {
                LOG_ERROR("4CC mismatch! %.4s != %.4s", read4cc, _4cc);
                return false;
            }
            return true;
        }

        bool Read(void* data, uint32_t size)
        {
            Reader.read(data, size, &Err);
            if (!Err.isOk()) LOG_ERROR("Read error: %s", Err.getMessage().getCPtr());
            return Err.isOk();
        }

        template <typename T> bool ReadT(T& data)
        {
            uint32_t hash = 0;
            if (!Read(&hash, sizeof(hash))) return false;

            uint32_t defSize = 0;
            if (!Read(&defSize, sizeof(defSize))) return false;

            if (data.Hash != hash)
            {
                LOG_WARN("Hash mismatch! %u != %u", data.Hash, hash);

                // TODO: figure out upgrade data
                data = {};
                return true;
            }
            else
            {
                LOG("Hash match!");

                // Skip definitions, we know they match
                Reader.seek(defSize);

                uint32_t size = 0;
                if (!Read(&size, sizeof(size))) return false;
                if (sizeof(T) != size)
                {
                    LOG_ERROR("Size mismatch! %u != %u", sizeof(T), size);
                    return false;
                }

                if (!Load(&data, 1, *this))
                {
                    LOG_ERROR("Failed to load: %s", Err.getMessage().getCPtr());
                    return false;
                }
            }

            return true;
        }

        void Finish()
        {
            Reader.close();
        }

        ~Deserializer()
        {
            Reader.close();
        }
    };

    template <typename T> bool Save(const T* obj, uint32_t count, Serializer& serializer);
    template <typename T> bool Load(T* obj, uint32_t count, Deserializer& serializer);
} // namespace Generated
