//
// Created by vlad on 23.03.23.
//

#include "MeshLoader.h"
#include <vector>
#include <glm/vec3.hpp>
#include <fstream>
#include <iostream>
#include <glm/vec2.hpp>
#include <sstream>


struct MeshPoint {
    glm::vec3 point;
    glm::vec3 normal;
    glm::vec3 color;
};

struct Material {
    std::string name;
    glm::vec3 color;
};

static void loadMaterials(std::vector<Material>& materials, const std::string &filename) {
    std::ifstream file(filename, std::ios::in);
    if (!file.is_open()) {
        std::cout << "[MeshLoader] Warming: failed to open material file \"" << filename << "\"" << std::endl;
        return;
    }

    std::string line;
    std::string mtl_name;
    while (std::getline(file, line)) {
        if (line.find("newmtl ") == 0) {
            mtl_name = line.substr(7);
        } else if (line.find("Kd ") == 0) {
            // цвет
            char* end = nullptr;

            Material m{};
            m.name = mtl_name;

            m.color.x = std::strtof(line.c_str() + 3, &end);
            m.color.y = std::strtof(end, &end);
            m.color.z = std::strtof(end, &end);

            materials.push_back(m);
        }
    }
}

Mesh *MeshLoader::loadMesh(const std::string &filename) {
    std::vector<glm::vec3> points;
    std::vector<glm::vec3> normals;
    std::vector<MeshPoint> mesh_points;

    std::vector<Material> materials;
    loadMaterials(materials, filename + ".mtl");

    std::ifstream file(filename + ".obj", std::ios::in);

    if (!file.is_open()) {
        std::cout << "[MeshLoader] Error: failed to open model file \"" << filename << ".obj\"" << std::endl;
        return nullptr;
    }

    int line_number = 1;
    glm::vec3 color(0.5f, 0.0f, 1.0f); // дефолтный цвет
    std::string source;
    while (std::getline(file, source)) {
        if (source.find("v ") == 0) {
            // вершина
            char* end = nullptr;

            glm::vec3 tmp_point(0, 0, 0);
            tmp_point.x = std::strtof(source.c_str() + 2, &end);
            tmp_point.y = std::strtof(end, &end);
            tmp_point.z = std::strtof(end, &end);
            points.push_back(tmp_point);
        } else if (source.find("vn ") == 0) {
            // нормаль
            char* end = nullptr;

            glm::vec3 tmp_point(0, 0, 0);
            tmp_point.x = std::strtof(source.c_str() + 3, &end);
            tmp_point.y = std::strtof(end, &end);
            tmp_point.z = std::strtof(end, &end);
            normals.push_back(tmp_point);
        } else if (source.find("f ") == 0) {
            // фрагмент, он же треугольник
            std::istringstream source_stream(source.c_str() + 2);
            std::string point;
            while (getline(source_stream, point, ' ')) {
                std::istringstream point_stream(point);
                std::string vertex, texture, normal;
                getline(point_stream, vertex, '/');
                getline(point_stream, texture, '/');
                getline(point_stream, normal, '\0'); // до конца строки

                MeshPoint p{};
                // сначала точка
                int tmp = std::atoi(vertex.c_str());
                if (tmp > 0) {
                    tmp -= 1;
                    p.point = points[tmp];
                }

                tmp = std::atoi(normal.c_str());
                if (tmp > 0) {
                    tmp -= 1;
                    p.normal = normals[tmp];
                }

                p.color = color;

                mesh_points.push_back(p);
            }
        } else if (source.find("usemtl ") == 0) {
            // материал
            std::string mtl_name = source.substr(7);

            for (const auto& m: materials) {
                if (m.name == mtl_name) {
                    color = m.color;
                    break;
                }
            }
        }

        line_number++;
    }

    // тут имеем готовый массив вершин
    // размер меша = кол-во вершин * (3 + 3 + 3)
    const int vertices = (int)mesh_points.size();
    auto* out_mesh = new float[vertices * (3 + 3 + 3)];

    int curr_index = 0;
    for (const auto& p : mesh_points) {
        // точка
        out_mesh[curr_index++] = p.point.x;
        out_mesh[curr_index++] = p.point.y;
        out_mesh[curr_index++] = p.point.z;

        // нормаль
        out_mesh[curr_index++] = p.normal.x;
        out_mesh[curr_index++] = p.normal.y;
        out_mesh[curr_index++] = p.normal.z;

        // цвет
        out_mesh[curr_index++] = p.color.x;
        out_mesh[curr_index++] = p.color.y;
        out_mesh[curr_index++] = p.color.z;
    }

    const int attrs[] = {3, 3, 3, 0};
    Mesh* m = new Mesh(out_mesh, vertices, attrs);

    delete[] out_mesh;
    return m;
}
