#include "BlueprintSerializer.h"

#include <sstream>
#include <stdexcept>

#include "toml.hpp"

#include "BuildingType.h"
#include "Rotation.h"
#include "ShipLayout.h"

namespace
{

std::string rotationToString(Rotation r)
{
    switch (r)
    {
    case Rotation::North: return "north";
    case Rotation::East:  return "east";
    case Rotation::South: return "south";
    case Rotation::West:  return "west";
    }
    return "north";
}

Rotation parseRotation(const std::string& s)
{
    if (s == "east")  { return Rotation::East; }
    if (s == "south") { return Rotation::South; }
    if (s == "west")  { return Rotation::West; }
    return Rotation::North;
}

} // namespace

namespace BlueprintSerializer
{

std::string serialize(const std::vector<Blueprint>& blueprints)
{
    toml::array bpArr;
    for (const Blueprint& bp : blueprints)
    {
        toml::array bldArr;
        for (const BlueprintBuilding& b : bp.buildings)
        {
            toml::table bldTbl;
            bldTbl.insert("type",      buildingTypeId(b.type));
            bldTbl.insert("rotation",  rotationToString(b.rotation));
            bldTbl.insert("offset_x",  static_cast<int64_t>(b.offset.x()));
            bldTbl.insert("offset_y",  static_cast<int64_t>(b.offset.y()));
            bldTbl.insert("recipe_id", b.recipeId);
            if (!b.splitterFilterA.empty())
            {
                toml::array filterArr;
                for (const ItemType& item : b.splitterFilterA)
                {
                    filterArr.push_back(item.id);
                }
                bldTbl.insert("filter_a", std::move(filterArr));
            }
            if (!b.splitterFilterB.empty())
            {
                toml::array filterArr;
                for (const ItemType& item : b.splitterFilterB)
                {
                    filterArr.push_back(item.id);
                }
                bldTbl.insert("filter_b", std::move(filterArr));
            }
            if (b.shipLayout.has_value())
            {
                toml::array modArr;
                for (const PlacedModule& pm : b.shipLayout->placedModules)
                {
                    toml::table modTbl;
                    modTbl.insert("type",     pm.moduleId);
                    modTbl.insert("x",        static_cast<int64_t>(pm.position.x()));
                    modTbl.insert("y",        static_cast<int64_t>(pm.position.y()));
                    modTbl.insert("rotation", rotationToString(pm.rotation));
                    modArr.push_back(std::move(modTbl));
                }
                bldTbl.insert("modules", std::move(modArr));
            }
            bldArr.push_back(std::move(bldTbl));
        }

        toml::table bpTbl;
        bpTbl.insert("name",      bp.name.toStdString());
        bpTbl.insert("buildings", std::move(bldArr));
        bpArr.push_back(std::move(bpTbl));
    }

    toml::table root;
    root.insert("blueprint", std::move(bpArr));

    std::ostringstream oss;
    oss << root;
    return oss.str();
}

std::vector<Blueprint> deserialize(const std::string& tomlContent)
{
    toml::table root;
    try
    {
        root = toml::parse(tomlContent);
    }
    catch (const toml::parse_error& e)
    {
        std::ostringstream msg;
        msg << "TOML parse error: " << e.description() << " at " << e.source().begin;
        throw std::runtime_error(msg.str());
    }

    std::vector<Blueprint> result;

    const toml::array* bpArr = root["blueprint"].as_array();
    if (!bpArr) { return result; }

    for (std::size_t i = 0; i < bpArr->size(); ++i)
    {
        const toml::table* bpTbl = (*bpArr)[i].as_table();
        if (!bpTbl)
        {
            throw std::runtime_error("blueprint[" + std::to_string(i) + "] is not a table");
        }

        Blueprint bp;
        bp.name = QString::fromStdString((*bpTbl)["name"].value_or(std::string{}));

        const toml::array* bldArr = (*bpTbl)["buildings"].as_array();
        if (bldArr)
        {
            for (std::size_t j = 0; j < bldArr->size(); ++j)
            {
                const toml::table* bldTbl = (*bldArr)[j].as_table();
                if (!bldTbl)
                {
                    throw std::runtime_error(
                        "blueprint[" + std::to_string(i) + "].buildings[" +
                        std::to_string(j) + "] is not a table");
                }

                const std::string typeStr = (*bldTbl)["type"].value_or(std::string{});
                const std::optional<BuildingType> parsedType = parseBuildingType(typeStr);
                if (!parsedType)
                {
                    throw std::runtime_error("unknown building type: '" + typeStr + "'");
                }

                BlueprintBuilding bb;
                bb.type     = *parsedType;
                bb.rotation = parseRotation((*bldTbl)["rotation"].value_or(std::string{}));
                bb.offset.setX(static_cast<int>((*bldTbl)["offset_x"].value_or(int64_t{0})));
                bb.offset.setY(static_cast<int>((*bldTbl)["offset_y"].value_or(int64_t{0})));
                bb.recipeId = (*bldTbl)["recipe_id"].value_or(std::string{});

                const toml::array* filterAArr = (*bldTbl)["filter_a"].as_array();
                if (filterAArr)
                {
                    for (std::size_t k = 0; k < filterAArr->size(); ++k)
                    {
                        const std::optional<std::string> itemId =
                            (*filterAArr)[k].value<std::string>();
                        if (itemId) { bb.splitterFilterA.push_back(ItemType{*itemId}); }
                    }
                }
                const toml::array* filterBArr = (*bldTbl)["filter_b"].as_array();
                if (filterBArr)
                {
                    for (std::size_t k = 0; k < filterBArr->size(); ++k)
                    {
                        const std::optional<std::string> itemId =
                            (*filterBArr)[k].value<std::string>();
                        if (itemId) { bb.splitterFilterB.push_back(ItemType{*itemId}); }
                    }
                }

                const toml::array* modArr = (*bldTbl)["modules"].as_array();
                if (modArr)
                {
                    ShipLayoutConfig layout;
                    for (std::size_t k = 0; k < modArr->size(); ++k)
                    {
                        const toml::table* modTbl = (*modArr)[k].as_table();
                        if (!modTbl) { continue; }
                        const std::optional<std::string> modType   = (*modTbl)["type"].value<std::string>();
                        const std::optional<int64_t>     x         = (*modTbl)["x"].value<int64_t>();
                        const std::optional<int64_t>     y         = (*modTbl)["y"].value<int64_t>();
                        const std::optional<std::string> rotStr    = (*modTbl)["rotation"].value<std::string>();
                        if (!modType || !x || !y || !rotStr) { continue; }
                        PlacedModule pm;
                        pm.moduleId = *modType;
                        pm.position = QPoint(static_cast<int>(*x), static_cast<int>(*y));
                        pm.rotation = parseRotation(*rotStr);
                        layout.placedModules.push_back(std::move(pm));
                    }
                    bb.shipLayout = std::move(layout);
                }
                bp.buildings.push_back(std::move(bb));
            }
        }

        result.push_back(std::move(bp));
    }

    return result;
}

} // namespace BlueprintSerializer
