// Copyright 2024 SquareBlock Inc. All Rights Reserved.
// Author: tianlei.richard@qq.com (tianlei.richard)

#include "mesh.h"
#include "OBJ_Loader.h"
#include "spdlog/spdlog.h"
#include <filesystem>

#define OBJL_VEC2_TO_EIGEN_VECTOR(objl_v)                                      \
  Eigen::Vector2d { objl_v.X, objl_v.Y }
#define OBJL_VEC3_TO_EIGEN_VECTOR(objl_v)                                      \
  Eigen::Vector3d { objl_v.X, objl_v.Y, objl_v.Z }

Mesh::Mesh(const std::vector<std::shared_ptr<Vertex>> &vertices,
           const std::vector<Triangle> &primitives,
           const std::shared_ptr<PhoneMaterial> &phone_material)
    : vertices_(vertices), primitives_(primitives), material_(phone_material) {}

std::vector<std::shared_ptr<Vertex>>::size_type Mesh::get_vertex_size() const {
  return vertices_.size();
};

const decltype(Vertex::texture_coordinate) &
Mesh::get_texture_coordinate(const unsigned int index) const {
  return (vertices_[index])->texture_coordinate;
}

Point3d Mesh::get_normal_vector(const unsigned int index) const {
  return (vertices_[index])->normal;
}

const std::vector<Triangle> &Mesh::get_primitives() const {
  return primitives_;
};

std::vector<Triangle> &Mesh::get_primitives() { return primitives_; };

std::shared_ptr<PhoneMaterial> &Mesh::get_material() { return material_; }

const std::shared_ptr<PhoneMaterial> &Mesh::get_material() const {
  return material_;
}

std::vector<Mesh> Mesh::load_mesh(
    const std::string &obj_path,
    const std::unordered_map<Texture::TextureType, std::string> texture_path) {
  const auto &obj_file_path = std::filesystem::path(obj_path);
  const auto &material_directory = obj_file_path.parent_path();

  objl::Loader loader{};
  assert(loader.LoadFile(obj_path.c_str()));

  std::vector<Mesh> res;
  for (const auto &mesh : loader.LoadedMeshes) {
    spdlog::info("Current mesh has {} vertices.", mesh.Vertices.size());

    std::vector<std::shared_ptr<Vertex>> vertices;
    for (decltype(mesh.Vertices)::size_type i = 0; i < mesh.Vertices.size();
         i++) {
      vertices.push_back(std::make_shared<Vertex>(
          OBJL_VEC3_TO_EIGEN_VECTOR(mesh.Vertices[i].Position),
          OBJL_VEC3_TO_EIGEN_VECTOR(mesh.Vertices[i].Normal),
          OBJL_VEC2_TO_EIGEN_VECTOR(mesh.Vertices[i].TextureCoordinate)));
    }
    std::vector<Triangle> primitives;
    for (decltype(mesh.Indices)::size_type i = 0; i < mesh.Indices.size();
         i += 3) {
      primitives.push_back(Triangle(vertices, mesh.Indices[i],
                                    mesh.Indices[i + 1], mesh.Indices[i + 2]));
    }

    std::unordered_map<Texture::TextureType, std::shared_ptr<Texture>> textures;
    if (!texture_path.empty()) {
      for (const auto &[type, path] : texture_path) {
        textures.insert({type, std::make_shared<Texture>(path)});
      }
    } else if (!mesh.MeshMaterial.map_Kd.empty()) {
      textures.insert({Texture::DiffuseMap,
                       std::make_shared<Texture>(material_directory /
                                                 mesh.MeshMaterial.map_Kd)});
    }
    auto material = std::make_shared<PhoneMaterial>(
        textures, std::make_shared<Lamp>(Point3d{}, 192.));

    res.push_back(Mesh(vertices, primitives, material));
  }
  return res;
}
