#include "wdt_loader.h"
#include "wow_chunk_reader.h"

#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
#include <godot_cpp/variant/vector3.hpp>

#include <fstream>
#include <cstring>

using namespace godot;

#pragma pack(push, 1)

struct MPHD {
    uint32_t flags;
    uint32_t something;
    uint32_t unused[6];
};

struct MAINEntry {
    uint32_t flags;  // bit 0 = has_adt
    uint32_t async_id;
};

struct WDTMODFEntry {
    uint32_t nameId;
    uint32_t uniqueId;
    float    pos[3];
    float    rot[3];
    float    bboxMin[3];
    float    bboxMax[3];
    uint16_t flags;
    uint16_t doodadSet;
    uint16_t nameSet;
    uint16_t padding;
};

#pragma pack(pop)

std::vector<uint8_t> WDTLoader::read_file(const std::string &path) {
    std::ifstream f(path, std::ios::binary | std::ios::ate);
    if (!f) return {};
    auto sz = f.tellg(); f.seekg(0);
    std::vector<uint8_t> buf(sz);
    f.read(reinterpret_cast<char *>(buf.data()), sz);
    return buf;
}

std::string WDTLoader::to_std(const String &s) {
    return std::string(s.utf8().get_data());
}

String WDTLoader::to_godot(const std::string &s) {
    return String(s.c_str());
}

Dictionary WDTLoader::load_wdt(const String &path) {
    Dictionary result;
    const std::string p = to_std(path);
    std::vector<uint8_t> buf = read_file(p);
    if (buf.empty()) {
        UtilityFunctions::push_warning("WDTLoader: cannot open file: ", path);
        return result;
    }
    _parse_wdt(buf.data(), buf.size(), result);
    return result;
}

Dictionary WDTLoader::load_wdt_from_bytes(const PackedByteArray &bytes) {
    Dictionary result;
    if (bytes.size() <= 0) return result;
    _parse_wdt(bytes.ptr(), (size_t)bytes.size(), result);
    return result;
}

void WDTLoader::_parse_wdt(const uint8_t *raw, size_t len, Dictionary &result) {
    ChunkReader reader(raw, len);

    uint32_t version = 0;
    uint32_t flags = 0;
    bool found_mphd = false;
    Array tiles;

    WoWChunk c;
    while (reader.next(c)) {
        if (c.is("MVER") && c.size >= 4) {
            std::memcpy(&version, c.data, 4);
        } else if (c.is("MPHD") && c.size >= sizeof(MPHD)) {
            const MPHD &mphd = c.as<MPHD>();
            flags = mphd.flags;
            found_mphd = true;
        } else if (c.is("MAIN")) {
            const uint32_t expected = 64 * 64 * (uint32_t)sizeof(MAINEntry);
            if (c.size < expected) {
                UtilityFunctions::push_warning("WDTLoader: MAIN chunk too small: ", (int)c.size);
                continue;
            }
            const MAINEntry *entries = c.array<MAINEntry>();
            for (int y = 0; y < 64; ++y) {
                for (int x = 0; x < 64; ++x) {
                    const MAINEntry &e = entries[y * 64 + x];
                    if (!(e.flags & 0x1)) continue;
                    Dictionary td;
                    td["x"] = (int)x;
                    td["y"] = (int)y;
                    td["flags"] = (int)e.flags;
                    tiles.push_back(td);
                }
            }
        } else if (c.is("MWMO") && c.size > 0) {
            // Global WMO filename (null-terminated).
            const char *s = reinterpret_cast<const char *>(c.data);
            size_t n = c.size;
            while (n > 0 && s[n - 1] == '\0') --n;
            result["global_wmo_name"] = String(std::string(s, n).c_str());
        } else if (c.is("MODF") && c.size >= sizeof(WDTMODFEntry)) {
            const WDTMODFEntry &e = c.as<WDTMODFEntry>();

            float gx, gy, gz;
            wow_to_godot(e.pos[0], e.pos[1], e.pos[2], gx, gy, gz);

            float rx, ry, rz;
            wow_rot_to_godot(e.rot[0], e.rot[1], e.rot[2], rx, ry, rz);
            // Global WMOs (MODF) get the same extra yaw as tile WMOs — see
            // the matching comment in adt_loader.cpp MODF loop.
            ry -= (float)(M_PI * 0.5);

            Dictionary placement;
            placement["name_id"]   = (int)e.nameId;
            placement["unique_id"] = (int)e.uniqueId;
            placement["pos"]       = Vector3(gx, gy, gz);
            placement["rot"]       = Vector3(rx, ry, rz);
            placement["flags"]     = (int)e.flags;
            placement["doodad_set"] = (int)e.doodadSet;
            placement["name_set"]   = (int)e.nameSet;
            result["global_wmo_placement"] = placement;
        }
    }

    if (!found_mphd) {
        UtilityFunctions::push_warning("WDTLoader: MPHD chunk missing");
    }

    result["version"] = (int)version;
    result["flags"]   = (int)flags;
    result["has_global_wmo"]     = (flags & 0x1) != 0;
    result["big_alpha"]          = (flags & 0x4) != 0;
    result["sort_by_size_class"] = (flags & 0x8) != 0;
    result["tiles"] = tiles;
}

void WDTLoader::_bind_methods() {
    ClassDB::bind_method(D_METHOD("load_wdt",            "path"),  &WDTLoader::load_wdt);
    ClassDB::bind_method(D_METHOD("load_wdt_from_bytes", "bytes"), &WDTLoader::load_wdt_from_bytes);
}
