#include "./Elements.hpp"
#include "core/utils/io.hpp"

using namespace world::server;

#undef LOG_PREFIX
#define LOG_PREFIX "Server: "

Elements::Elements(): EdittableElements(true) { }

void Elements::readIndex(io::ifstream& index) {
    size_t count = 0;
    index.read_cast(count);
    nodes.reserve(count);
    hierarchy.reserve(count);
    for (size_t i = 0; i < count && !index.eof(); i++) {
        generational::id::idx_t idx;
        index.read_cast(idx);
        uint8_t flag;
        index.read_cast(flag);
        const node_id id = generational::id(idx, 0, flag);
        const Type type = GetType(id);
        const auto readStartPoint = [&] {
            const auto fixed = Has<Flag::Fixed>(id);
            start_point start(generational::id(), transform(), FIXED);
            if (!fixed) {
                generational::id::idx_t parent;
                index.read_cast(parent);
                start.parent = generational::id(parent, 0);
            }
            index.read_cast(start.relative);
            if (!fixed) {
                index.read_cast(start.vel);
            }
            return start;
        };
        switch(type) {
            case Type::Instance: {
                const auto start = readStartPoint();
                generational::id::idx_t model;
                index.read_cast(model);
                createInstanceAt(idx, flag, start, generational::id(model));
                break;
            }
            case Type::Part: {
                const auto start = readStartPoint();
                glm::usvec3 size;
                index.read_cast(size);
                createPartAt(idx, flag, start, size);
                break;
            }
            case Type::Area: {;
                const auto start = readStartPoint();
                world::server::Area::params ps{};
                index.read_cast(ps.radius);
                index.read_cast(ps.genId);
                {
                    size_t len;
                    index.read_cast(len);
                    ps.params.resize(len);
                    index.read(ps.params.data(), ps.params.size());
                }
                createAreaAt(idx, flag, start, ps);
                break;
            }
            default:
                LOG_W("Load bad node type " << (int)type);
                break;
        }
    }
    LOG_T(nodes.size() << " nodes loaded");
}
void Elements::writeIndex(io::ofstream& index) const {
    index.write_cast(nodes.size());
    nodes.iter([&](const node_id& id, const Node& node) {
        index.write_cast(id.val.index());
        const uint8_t flag = id.val.flag();
        index.write_cast(flag);
        const Type type = GetType(id);
        const auto writeStartPoint = [&] {
            const auto fixed = Has<Flag::Fixed>(id);
            if (fixed) {
                index.write_cast(node.absolute);
            } else {
                const auto it = hierarchy.get(id);
                assert(it != nullptr);
                index.write_cast(it->parent.val.index());
                index.write_cast(it->relative);
                index.write_cast(it->vel);
            }
        };
        switch(type) {
            case Type::Instance: {
                writeStartPoint();
                index.write_cast(NodeOf<Instance>::Make(&node)->get()->type.val.index());
                break;
            }
            case Type::Part: {
                writeStartPoint();
                index.write_cast(NodeOf<Part>::Make(&node)->get()->size);
                break;
            }
            case Type::Area: {
                writeStartPoint();
                const auto& ps = NodeOf<Area>::Make(&node)->get()->getParams();
                index.write_cast(ps.radius);
                index.write_cast(ps.genId);
                index.write_cast(ps.params.size());
                index.write(ps.params.data(), ps.params.size());
                break;
            }
            default:
                LOG_W("Write bad node type " << (int)type);
                break;
        }
    });
}

world::instance_id Elements::createInstance(const start_point& sp, model_id m, uint8_t flag) {
    flag = Set<Flag::Added>(flag | static_cast<int>(Type::Instance));
    auto& model = models.at(m);
    const auto id = create(sp, flag, model.origin ? model.origin : std::make_shared<Instance>(m));
    model.instances.push_back(id.val.index());
    return id.val;
}
world::instance_id Elements::createInstanceAt(generational::id::idx_t i, uint8_t flag, const start_point& sp, model_id m) {
    assert(Is<Type::Instance>(generational::id(i, 0, flag)));
    auto& model = models.at(m);
    const auto id = createAt(i, sp, flag, model.origin ? model.origin : std::make_shared<Instance>(m));
    model.instances.push_back(id.val.index());
    return id.val;
}
world::part_id Elements::createPart(const start_point& sp, glm::usvec3 size) {
    constexpr auto FLAG = Set<Flag::Added>(static_cast<int>(Type::Part));
    const auto id = create(sp, FLAG, std::make_shared<Part>(size));
    parts.push_back(id.val.index());
    return id.val;
}
world::part_id Elements::createPartAt(generational::id::idx_t i, uint8_t flag, const start_point& sp, glm::usvec3 size) {
    assert(Is<Type::Part>(generational::id(i, 0, flag)));
    const auto id = createAt(i, sp, flag, std::make_shared<Part>(size));
    parts.push_back(id.val.index());
    return id.val;
}
world::area_id Elements::createArea(const start_point& sp, const Area::params& ps) {
    constexpr auto FLAG = Set<Flag::Added>(static_cast<int>(Type::Area));
    const auto id = create(sp, FLAG, std::make_shared<Area>(ps));
    areas.push_back(id.val.index());
    return id.val;
}
world::area_id Elements::createAreaAt(generational::id::idx_t i, uint8_t flag, const start_point& sp, const world::server::Area::params& ps) {
    assert(Is<Type::Area>(generational::id(i, 0, flag)));
    const auto id = createAt(i, sp, flag, std::make_shared<Area>(ps));
    areas.push_back(id.val.index());
    return id.val;
}