//
// Created by martin on 12.11.21.
//

#include "SkinnedMesh.h"

#include <HandmadeMath.h>
#include <imgui.h>

#include "sokol_gfx.h"
#include "util/sokol_gl.h"

static void draw_vec(const ozz::math::SimdFloat4& vec) {
  sgl_v3f(ozz::math::GetX(vec), ozz::math::GetY(vec), ozz::math::GetZ(vec));
}

static void draw_line(
    const ozz::math::SimdFloat4& v0,
    const ozz::math::SimdFloat4& v1) {
  draw_vec(v0);
  draw_vec(v1);
}

// this draws a wireframe 3d rhombus between the current and parent joints
void SkinnedMesh::DrawJoint(int joint_index, int parent_joint_index) {
  if (parent_joint_index < 0) {
    return;
  }

  using namespace ozz::math;

  const Float4x4& m0 = m_model_matrices[joint_index];
  const Float4x4& m1 = m_model_matrices[parent_joint_index];

  const SimdFloat4 p0 = m0.cols[3];
  const SimdFloat4 p1 = m1.cols[3];
  const SimdFloat4 ny = m1.cols[1];
  const SimdFloat4 nz = m1.cols[2];

  const SimdFloat4 len = SplatX(Length3(p1 - p0)) * simd_float4::Load1(0.1f);

  const SimdFloat4 pmid = p0 + (p1 - p0) * simd_float4::Load1(0.66f);
  const SimdFloat4 p2 = pmid + ny * len;
  const SimdFloat4 p3 = pmid + nz * len;
  const SimdFloat4 p4 = pmid - ny * len;
  const SimdFloat4 p5 = pmid - nz * len;

  sgl_c3f(1.0f, 1.0f, 0.0f);
  draw_line(p0, p2);
  draw_line(p0, p3);
  draw_line(p0, p4);
  draw_line(p0, p5);
  draw_line(p1, p2);
  draw_line(p1, p3);
  draw_line(p1, p4);
  draw_line(p1, p5);
  draw_line(p2, p3);
  draw_line(p3, p4);
  draw_line(p4, p5);
  draw_line(p5, p2);
}

SkinnedMesh::~SkinnedMesh() {
  while (m_animations.size() > 0) {
    ozz::animation::Animation* animation_ptr =
        m_animations[m_animations.size() - 1];
    delete animation_ptr;
    m_animations.pop_back();
  }
}

bool SkinnedMesh::LoadSkeleton(const char* filename) {
  //  const char* skeleton_file = "../media/skeleton.ozz";
  const char* skeleton_file = "../media/MixamoYBot-skeleton.ozz";

  assert(filename);
  ozz::log::Out() << "Loading skeleton archive " << filename << "."
                  << std::endl;
  ozz::io::File file(filename, "rb");
  if (!file.opened()) {
    ozz::log::Err() << "Failed to open skeleton file " << filename << "."
                    << std::endl;
    return false;
  }
  ozz::io::IArchive archive(&file);
  if (!archive.TestTag<ozz::animation::Skeleton>()) {
    ozz::log::Err() << "Failed to load skeleton instance from file " << filename
                    << "." << std::endl;
    return false;
  }

  // Once the tag is validated, reading cannot fail.
  archive >> m_skeleton;

  const int num_soa_joints = m_skeleton.num_soa_joints();
  const int num_joints = m_skeleton.num_joints();
  m_local_matrices.resize(num_soa_joints);
  m_model_matrices.resize(num_joints);
  m_cache.Resize(num_joints);
  std::cout << "Successfully loaded " << skeleton_file
            << " (soa: " << num_soa_joints << ", joints: " << num_joints << ")"
            << std::endl;

  return true;
}

bool SkinnedMesh::LoadAnimation(const char* filename) {
  ozz::animation::Animation* animation_ptr = new ozz::animation::Animation();

  assert(filename);
  ozz::log::Out() << "Loading animation archive: " << filename << "."
                  << std::endl;
  ozz::io::File file(filename, "rb");
  if (!file.opened()) {
    ozz::log::Err() << "Failed to open animation file " << filename << "."
                    << std::endl;
    return false;
  }
  ozz::io::IArchive archive(&file);
  if (!archive.TestTag<ozz::animation::Animation>()) {
    ozz::log::Err() << "Failed to load animation instance from file "
                    << filename << "." << std::endl;
    return false;
  }

  // Once the tag is validated, reading cannot fail.
  archive >> *animation_ptr;

  m_animations.push_back(animation_ptr);
  m_animation_names.push_back(filename);

  return true;
}

void SkinnedMesh::SetCurrentAnimation(int index) {
  if (index < 0 || index >= m_animations.size()) {
    ozz::log::Err() << "Invalid animation index " << index << " valid range: ["
                    << 0 << ", " << m_animations.size() << "]'" << std::endl;
  }

  m_current_animation = m_animations[index];
}

//bool LoadMesh (const char* filename);

void SkinnedMesh::EvalAnimation(float in_time) {
  const float anim_duration = m_current_animation->duration();
  float anim_ratio = fmodf((float)in_time / anim_duration, 1.0f);

  // sample animation
  ozz::animation::SamplingJob sampling_job;
  sampling_job.animation = m_current_animation;
  sampling_job.cache = &m_cache;
  sampling_job.ratio = anim_ratio;
  sampling_job.output = make_span(m_local_matrices);
  sampling_job.Run();

  // convert joint matrices from local to model space
  ozz::animation::LocalToModelJob ltm_job;
  ltm_job.skeleton = &m_skeleton;
  ltm_job.input = make_span(m_local_matrices);
  ltm_job.output = make_span(m_model_matrices);
  ltm_job.Run();
}

void SkinnedMesh::DrawSkeleton() {
  sgl_matrix_mode_modelview();
  sgl_push_matrix();
  hmm_mat4 scale_mat = HMM_Scale(HMM_Vec3(0.01f, 0.01f, 0.01f));
  sgl_mult_matrix((const float*)&scale_mat);

  const int num_joints = m_skeleton.num_joints();
  ozz::span<const int16_t> joint_parents = m_skeleton.joint_parents();
  sgl_begin_lines();
  for (int joint_index = 0; joint_index < num_joints; joint_index++) {
    DrawJoint(joint_index, joint_parents[joint_index]);
  }
  sgl_end();
  sgl_pop_matrix();
}

void SkinnedMesh::DrawDebugUi() {
  ImGui::Begin("SkinnedMesh");

//  ImGui::Checkbox("Time override", &state.time.anim_ratio_ui_override);

  //  const float anim_duration = state.ozz->animation.duration();
  //  if (!state.time.anim_ratio_ui_override) {
  //    state.time.anim_ratio =
  //        fmodf((float)state.time.absolute / anim_duration, 1.0f);
  //  }
  //  ImGui::SliderFloat("Time", &state.time.anim_ratio, 0.f, 1.f);

//  ImGui::Text(
//      "Application average %.3f ms/frame (%.1f FPS)",
//      1000.0f / ImGui::GetIO().Framerate,
//      ImGui::GetIO().Framerate);

  ImGui::End();

}