#include "engine/image_quad.h"

#include "base/log.h"
#include "engine/engine.h"
#include "engine/renderer/geometry.h"
#include "engine/renderer/shader.h"
#include "engine/renderer/texture.h"

using namespace base;

namespace eng {

ImageQuad::ImageQuad() = default;

ImageQuad::~ImageQuad() {
  Destory();
}

void ImageQuad::Create(const std::string& asset_name,
                       std::array<int, 2> num_frames,
                       int frame_width,
                       int frame_height) {
  texture_ = Engine::Get().AcquireTexture(asset_name);

  num_frames_ = std::move(num_frames);
  frame_width_ = frame_width;
  frame_height_ = frame_height;

  if ((frame_width_ > 0 && frame_height_ > 0) || texture_->IsValid())
    AutoScale();

  asset_name_ = asset_name;
}

void ImageQuad::Destory() {
  Engine::Get().ReleaseTexture(asset_name_);
  texture_ = nullptr;
}

void ImageQuad::AutoScale() {
  auto& engine = Engine::Get();
  Vector2f dimensions = {GetFrameWidth(), GetFrameHeight()};
  Vector2f size = engine.ToScale(dimensions);
  float s =
      static_cast<float>(engine.image_dpi()) * engine.GetImageScaleFactor();
  size *= static_cast<float>(engine.GetDeviceDpi()) / s;
  SetSize(size);
}

void ImageQuad::SetCustomShader(Shader* shader) {
  custom_shader_ = shader;
  custom_uniforms_.clear();
}

void ImageQuad::SetFrame(size_t frame) {
  DCHECK(frame < GetNumFrames())
      << "asset: " << asset_name_ << " frame: " << frame;
  current_frame_ = frame;
}

size_t ImageQuad::GetNumFrames() const {
  return num_frames_[0] * num_frames_[1];
}

void ImageQuad::Draw(float frame_frac) {
  DCHECK(IsVisible());

  if (!texture_ || !texture_->IsValid())
    return;

  texture_->Activate();

  Vector2f tex_scale = {GetFrameWidth() / texture_->GetWidth(),
                        GetFrameHeight() / texture_->GetHeight()};

  Shader* shader =
      custom_shader_ ? custom_shader_ : Engine::Get().GetPassThroughShader();

  shader->Activate();
  shader->SetUniform("offset", position_);
  shader->SetUniform("scale", GetSize());
  shader->SetUniform("rotation", rotation_);
  shader->SetUniform("tex_offset", GetUVOffset(current_frame_));
  shader->SetUniform("tex_scale", tex_scale);
  shader->SetUniform("projection", Engine::Get().GetProjectionMatrix());
  shader->SetUniform("color", color_);
  shader->SetUniform("texture_0", 0);
  if (custom_shader_) {
    for (auto& cu : custom_uniforms_)
      std::visit(
          [shader, &cu](auto&& arg) { shader->SetUniform(cu.first, arg); },
          cu.second);
  }
  shader->UploadUniforms();

  Engine::Get().GetQuad()->Draw();
}

float ImageQuad::GetFrameWidth() const {
  return frame_width_ > 0 ? (float)frame_width_
                          : texture_->GetWidth() / (float)num_frames_[0];
}

float ImageQuad::GetFrameHeight() const {
  return frame_height_ > 0 ? (float)frame_height_
                           : texture_->GetHeight() / (float)num_frames_[1];
}

// Return the uv offset for the given frame.
Vector2f ImageQuad::GetUVOffset(size_t frame) const {
  DCHECK(frame < GetNumFrames())
      << "asset: " << asset_name_ << " frame: " << frame;
  return {(float)(frame % num_frames_[0]), (float)(frame / num_frames_[0])};
}

}  // namespace eng
