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

#include <limits>
#include <thread>

#include "spdlog/spdlog.h"

#include "rasterizer.h"

Rasterizer::Rasterizer(const int width, const int height)
    : width_(width), height_(height),
      shading_points_(std::vector<std::vector<RasterizerResult>>(
          height, std::vector<RasterizerResult>(width, RasterizerResult{}))) {}

std::vector<std::vector<RasterizerResult>>
Rasterizer::rasterize(const std::vector<Mesh> &meshes) {
  for (int m_id = 0; m_id < meshes.size(); ++m_id) {
    const auto &primitives = (meshes[m_id]).get_primitives();

    const int thread_num = 8;
    std::vector<std::thread> rasterize_threads;
    for (int begin = 0, offset = primitives.size() / thread_num,
             remainer = (primitives.size() % thread_num);
         begin < primitives.size();) {
      int end = begin + offset;
      if (remainer > 0) {
        end += 1;
        remainer -= 1;
      }
      rasterize_threads.push_back(std::thread(
          [this, m_id](const std::vector<Triangle> &primitives, const int begin,
                       const int end) {
            this->rasterize(m_id, primitives, begin, end);
          },
          (meshes[m_id]).get_primitives(), begin, end));
      begin = end;
    }
    for (auto &t : rasterize_threads) {
      t.join();
    }
  }
  return shading_points_;
}

void Rasterizer::rasterize(const int mesh_idx,
                           const std::vector<Triangle> &primitives,
                           const int begin, const int end) {
  for (int t_id = begin; t_id < end; ++t_id) {
    const auto &t = primitives[t_id];
    const auto &triangle_points = t.get_vertex_position();

    const auto &aabb = t.axis_align_bbox();
    const int x_min = std::min(std::max(0, aabb.x), width_);
    const int x_max = std::min(std::max(0, aabb.x + aabb.width), width_);
    const int y_min = std::min(std::max(0, aabb.y), height_);
    const int y_max = std::min(std::max(0, aabb.y + aabb.height), height_);

    for (int i = y_min; i < y_max; ++i) {
      for (int j = x_min; j <= x_max; ++j) {
        auto &property = shading_points_[i][j];
        const auto &[is_inside, z_screen] =
            inside(Point2d{j + 0.5, i + 0.5}, t);
        if (is_inside && z_screen > property.depth) {
          property.depth = z_screen;
          property.triangle.mesh_index = mesh_idx;
          property.triangle.triangle_index = t_id;
        }
      }
    }
  }
}

void Rasterizer::reset() {
  shading_points_ = std::vector<std::vector<RasterizerResult>>(
      height_, std::vector<RasterizerResult>(width_, RasterizerResult{}));
}

std::pair<bool, double> Rasterizer::inside(const Point2d &p_screen,
                                           const Triangle &t) {
  const auto points = t.get_vertex_position();

  const auto plane_normal = t.normal_vector();
  const auto plane_point = points[0];
  const auto z_screen =
      plane_point.z() - (plane_normal.x() * (p_screen.x() - plane_point.x()) +
                         plane_normal.y() * (p_screen.y() - plane_point.y())) /
                            plane_normal.z();
  const Point3d p{p_screen.x(), p_screen.y(), z_screen};

  const auto v1 = points[1] - points[0];
  const auto v2 = points[2] - points[1];
  const auto v3 = points[0] - points[2];

  const auto c1 = v1.cross(p - points[0]).normalized();
  const auto c2 = v2.cross(p - points[1]).normalized();
  const auto c3 = v3.cross(p - points[2]).normalized();

  const auto r1 = c1.dot(c2);
  const auto r2 = c1.dot(c3);
  const auto r3 = c2.dot(c3);

  return {r1 > 0 && r2 > 0 && r3 > 0, z_screen};
}
