#include "Models.hpp"
#include <fstream>
#include <map>

using namespace render;

std::unique_ptr<Model> (*Model::createFunc)(const Data&) = nullptr;
std::unique_ptr<LodModel> (*LodModel::createFunc)(const LodData&) = nullptr;

const std::vector<glm::vec3> Shape::SKY_CUBE = {
    {-1.0f,  1.0f, -1.0f}, {-1.0f, -1.0f, -1.0f}, { 1.0f, -1.0f, -1.0f}, { 1.0f, -1.0f, -1.0f}, { 1.0f,  1.0f, -1.0f}, {-1.0f,  1.0f, -1.0f},
    {-1.0f, -1.0f,  1.0f}, {-1.0f, -1.0f, -1.0f}, {-1.0f,  1.0f, -1.0f}, {-1.0f,  1.0f, -1.0f}, {-1.0f,  1.0f,  1.0f}, {-1.0f, -1.0f,  1.0f},
    { 1.0f, -1.0f, -1.0f}, { 1.0f, -1.0f,  1.0f}, { 1.0f,  1.0f,  1.0f}, { 1.0f,  1.0f,  1.0f}, { 1.0f,  1.0f, -1.0f}, { 1.0f, -1.0f, -1.0f},
    {-1.0f, -1.0f,  1.0f}, {-1.0f,  1.0f,  1.0f}, { 1.0f,  1.0f,  1.0f}, { 1.0f,  1.0f,  1.0f}, { 1.0f, -1.0f,  1.0f}, {-1.0f, -1.0f,  1.0f},
    {-1.0f,  1.0f, -1.0f}, { 1.0f,  1.0f, -1.0f}, { 1.0f,  1.0f,  1.0f}, { 1.0f,  1.0f,  1.0f}, {-1.0f,  1.0f,  1.0f}, {-1.0f,  1.0f, -1.0f},
    {-1.0f, -1.0f, -1.0f}, {-1.0f, -1.0f,  1.0f}, { 1.0f, -1.0f, -1.0f}, { 1.0f, -1.0f, -1.0f}, {-1.0f, -1.0f,  1.0f}, { 1.0f, -1.0f,  1.0f}
};
const std::vector<glm::vec3> Shape::LINE_CUBE = {
    {0, 0, 0}, {0, 0, 1}, {0, 0, 1}, {0, 1, 1}, {0, 1, 1}, {0, 1, 0}, {0, 1, 0}, {0, 0, 0}, {1, 0, 0}, {1, 0, 1}, {1, 0, 1}, {1, 1, 1},
    {1, 1, 1}, {1, 1, 0}, {1, 1, 0}, {1, 0, 0}, {0, 0, 0}, {1, 0, 0}, {0, 0, 1}, {1, 0, 1}, {0, 1, 1}, {1, 1, 1}, {0, 1, 0}, {1, 1, 0}
};
constexpr auto SINPIOVER4 = 0.853553391f;
const std::vector<glm::vec3> Shape::LINE_SPHERE = {
    {1, .5f, .5f}, {SINPIOVER4, SINPIOVER4, .5f}, {SINPIOVER4, SINPIOVER4, .5f}, {.5f, 1, .5f},
    {.5f, 1, .5f}, {1-SINPIOVER4, SINPIOVER4, .5f}, {1-SINPIOVER4, SINPIOVER4, .5f}, {0, .5f, .5f},
    {1, .5f, .5f}, {SINPIOVER4, 1-SINPIOVER4, .5f}, {SINPIOVER4, 1-SINPIOVER4, .5f}, {.5f, 0, .5f},
    {.5f, 0, .5f}, {1-SINPIOVER4, 1-SINPIOVER4, .5f}, {1-SINPIOVER4, 1-SINPIOVER4, .5f}, {0, .5f, .5f},
    {1, .5f, .5f}, {SINPIOVER4, .5f, SINPIOVER4}, {SINPIOVER4, .5f, SINPIOVER4}, {.5f, .5f, 1},
    {.5f, .5f, 1}, {1-SINPIOVER4, .5f, SINPIOVER4}, {1-SINPIOVER4, .5f, SINPIOVER4}, {0, .5f, .5f},
    {1, .5f, .5f}, {SINPIOVER4, .5f, 1-SINPIOVER4}, {SINPIOVER4, .5f, 1-SINPIOVER4}, {.5f, .5f, 0},
    {.5f, .5f, 0}, {1-SINPIOVER4, .5f, 1-SINPIOVER4}, {1-SINPIOVER4, .5f, 1-SINPIOVER4}, {0, .5f, .5f},
    {.5f, 1, .5f}, {.5f, SINPIOVER4, SINPIOVER4}, {.5f, SINPIOVER4, SINPIOVER4}, {.5f, .5f, 1},
    {.5f, .5f, 1}, {.5f, 1-SINPIOVER4, SINPIOVER4}, {.5f, 1-SINPIOVER4, SINPIOVER4}, {.5f, 0, .5f},
    {.5f, 1, .5f}, {.5f, SINPIOVER4, 1-SINPIOVER4}, {.5f, SINPIOVER4, 1-SINPIOVER4}, {.5f, .5f, 0},
    {.5f, .5f, 0}, {.5f, 1-SINPIOVER4, 1-SINPIOVER4}, {.5f, 1-SINPIOVER4, 1-SINPIOVER4}, {.5f, 0, .5f}
};
const std::pair<std::vector<glm::vec3>, std::vector<glm::vec4>> ColoredShape::LINE_CUBE = {Shape::LINE_CUBE,
{
    {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1},
    {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}
}};

void indexVBO(const std::vector<PackedVertexData> &in_vertices, std::vector<glm::u16> &out_indices, std::vector<PackedVertexData> &out_vertices);

Model::Data::Data(const std::vector<PackedVertexData> &vs, const std::vector<glm::u16> &indices): indices(indices), vertices(vs) { }
Model::Data::Data(std::istream& in) {
    {
        size_t i_size;
        in.read(reinterpret_cast<char *>(&i_size), sizeof(i_size));
        indices.resize(i_size);
        in.read(reinterpret_cast<char *>(indices.data()), sizeof(glm::u16) * i_size);
    }
    size_t v_size;
    in.read(reinterpret_cast<char *>(&v_size), sizeof(v_size));
    vertices.resize(v_size, PackedVertexData(0, 0, 0, 0, 0, 0, 0, 0));
    in.read(reinterpret_cast<char *>(vertices.data()), sizeof(PackedVertexData) * v_size);
}
void Model::Data::serialize(std::ostream& out) {
    {
        size_t i_size = indices.size();
        out.write(reinterpret_cast<char *>(&i_size), sizeof(i_size));
        out.write(reinterpret_cast<char *>(indices.data()), sizeof(glm::u16) * i_size);
    }
    size_t v_size = vertices.size();
    out.write(reinterpret_cast<char *>(&v_size), sizeof(v_size));
    out.write(reinterpret_cast<char *>(vertices.data()), sizeof(PackedVertexData) * v_size);
}
void Model::Data::index(const std::vector<PackedVertexData>& vs) {
    indexVBO(vs, indices, vertices);
}

void indexVBO(const std::vector<PackedVertexData> &in_vertices, std::vector<glm::u16> &out_indices,
    std::vector<PackedVertexData> &out_vertices)
{
    std::map<PackedVertexData, glm::u16> VertexToOutIndex;

    auto getSimilarVertexIndex_fast = [&] (const PackedVertexData &packed, glm::u16 &out) {
        auto it = VertexToOutIndex.find(packed);
        if (it == VertexToOutIndex.end()) {
            return false;
        }else{
            out = it->second;
            return true;
        }
    };

    out_indices.reserve(in_vertices.size());
    for (const auto& vertex: in_vertices) {
        glm::u16 index;
        if (getSimilarVertexIndex_fast(vertex, index)) {
            out_indices.push_back(index);
        } else {
            out_vertices.push_back(vertex);
            out_indices.push_back(out_vertices.size() - 1);
            VertexToOutIndex[vertex] = out_indices.back();
        }
    }
}