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

#include "texture.h"
#include "spdlog/spdlog.h"
#include "util/interpolation.h"
#include <cmath>
#include <opencv2/highgui/highgui.hpp>

Texture::Texture(const std::filesystem::path &texture_path) {
  if (!std::filesystem::exists(texture_path)) {
    spdlog::warn("Texture {} is not existed!", texture_path.c_str());
    throw std::filesystem::filesystem_error("File not found",
                                            std::error_code{});
  }
  texture_ = cv::imread(texture_path.c_str());
  spdlog::debug("Texture at {}, shape: ({},{})", texture_path.c_str(),
                texture_.cols, texture_.rows);
}

cv::Vec3b Texture::sample(const Point2d &texture_coordinate) {
  cv::Vec3b res{0, 0, 0};

  double x = texture_coordinate.x() * (texture_.cols - 1);
  if (x < 0.) {
    x += texture_.cols;
  }
  double y = (1. - texture_coordinate.y()) * (texture_.rows - 1);
  if (y < 0.) {
    y = std::fabs(y);
  }

  const int x_f = std::max(0., x);
  const int x_c = std::min(texture_.cols - 1, static_cast<int>(x + 1.));
  const float t_x = x - x_f;
  const int y_f = std::max(0., y);
  const int y_c = std::min(texture_.rows - 1, static_cast<int>(y + 1.));
  const float t_y = y - y_f;

  auto in_range = [](auto index, auto range_begin, auto range_end) -> bool {
    return index >= range_begin && index < range_end;
  };

  if (in_range(t_x, 0., 1.) && in_range(t_y, 0., 1.) &&
      in_range(x_f, 0, texture_.cols) && in_range(x_c, 0, texture_.cols) &&
      in_range(y_f, 0, texture_.rows) && in_range(y_c, 0, texture_.rows)) {
    const cv::Vec3b &a = texture_.at<cv::Vec3b>(y_f, x_f);
    const cv::Vec3b &b = texture_.at<cv::Vec3b>(y_c, x_f);
    const cv::Vec3b &c = texture_.at<cv::Vec3b>(y_f, x_c);
    const cv::Vec3b &d = texture_.at<cv::Vec3b>(y_c, x_c);

    res = bilerp(t_x, t_y, a, b, c, d);
  } else {
    spdlog::warn(
        "Interpolation factor is invalid, {}, {}, {}, {}, {}, {}, ({}, {}).",
        t_x, t_y, x_f, x_c, y_f, y_c, texture_coordinate.x(),
        texture_coordinate.y());
  }
  return res;
}
