/*
 * Copyright 2022-2024 Frederico de Oliveira Linhares
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "skeletal_mesh.hpp"

#include "binary_reader.hpp"
#include "../com/command.hpp"
#include "../int/core.hpp"
#include "skeletal_mesh_vertex.hpp"

namespace
{

// Data that is only needed for the command chain but not for the SkeletalMesh
// goes here.
struct MeshBuilder
{
  std::string mesh_path;
  BluCat::GRA::SkeletalMesh *mesh;

  MeshBuilder(BluCat::GRA::SkeletalMesh *m, std::string mp);
  MeshBuilder(BluCat::GRA::SkeletalMesh *m, const char* mp);
};

MeshBuilder::MeshBuilder(BluCat::GRA::SkeletalMesh *m, std::string mp):
  mesh{m},
  mesh_path{mp}
{
}

MeshBuilder::MeshBuilder(BluCat::GRA::SkeletalMesh *m, const char *mp):
  MeshBuilder{m, std::string(mp)}
{
}

void
load_mesh(void *obj)
{
  auto self = static_cast<MeshBuilder*>(obj);

  BinaryReader input{self->mesh_path};

  self->mesh->queue_family =
    BluCat::INT::core.vk_device_with_swapchain->get_queue_family_with_graphics();

  { // Load vertexes.
    auto vertex_count{input.read_ui32()};
    std::vector<BluCat::GRA::SkeletalMeshVertex> vertexes{vertex_count};

    for(auto i{0}; i < vertex_count; i++)
    {
      vertexes[i].position = input.read_vec3();
      vertexes[i].normal = input.read_vec3();
      vertexes[i].texture_coord = input.read_vec2();

      for(auto ii{0}; ii < BluCat::GRA::SKELETAL_MESH_MAX_NUM_OF_INFLUENCING_BONES;
	  ii++)
	vertexes[i].bone_ids[ii] = input.read_ui32();

      for(auto ii{0}; ii < BluCat::GRA::SKELETAL_MESH_MAX_NUM_OF_INFLUENCING_BONES;
	  ii++)
	vertexes[i].bone_weights[ii] = input.read_float();
    }

    void *vertexes_data{vertexes.data()};
    size_t vertexes_size = sizeof(vertexes[0]) * vertexes.size();
    self->mesh->source_vertex_buffer = new BluCat::GRA::SourceBuffer{
      self->mesh->queue_family->device, vertexes_data, vertexes_size};
    self->mesh->vertex_buffer = new BluCat::GRA::DestinationBuffer{
      self->mesh->queue_family, self->mesh->source_vertex_buffer,
      VK_BUFFER_USAGE_VERTEX_BUFFER_BIT};
  }

  { // Load indexes.
    self->mesh->index_count = input.read_ui32();
    std::vector<uint32_t> indexes(self->mesh->index_count);

    for(auto i{0}; i < self->mesh->index_count; i++)
      indexes[i] = input.read_ui32();

    void *indexes_data{indexes.data()};
    size_t indexes_size{sizeof(indexes[0]) * indexes.size()};
    BluCat::GRA::SourceBuffer source_index_buffer{
      self->mesh->queue_family->device, indexes_data, indexes_size};
    self->mesh->index_buffer = new BluCat::GRA::DestinationBuffer{
      self->mesh->queue_family, &source_index_buffer,
      VK_BUFFER_USAGE_INDEX_BUFFER_BIT};
  }

  { // Load bones
    auto bone_count{input.read_ui32()};
    self->mesh->bones.reserve(bone_count);
    for(int i{0}; i < bone_count; i++)
      self->mesh->bones.emplace_back(input.read_mat4());
  }

  { // Load animations
    auto num_animations{input.read_ui32()};
    self->mesh->animations.resize(num_animations);
    for(uint32_t i{0}; i < num_animations; i++)
    {
      auto duration{input.read_double()};
      self->mesh->animations[i].final_time = (float)duration;

      auto ticks_per_second{input.read_double()};

      auto num_bone_transforms{input.read_ui32()};
      std::vector<BluCat::GRA::BoneTransform> *bone_transforms =
	&(self->mesh->animations[i].bone_transforms);
      bone_transforms->resize(num_bone_transforms);
      for(uint32_t bone_transform_index{0};
	  bone_transform_index < num_bone_transforms; bone_transform_index++)
      {
	auto bone_id{input.read_ui32()};

	auto num_positions{input.read_ui32()};
	BluCat::GRA::Channel<glm::vec3> *positions =
	  &((*bone_transforms)[bone_transform_index].positions);
	for(auto position_key_index{0}; position_key_index < num_positions;
	    position_key_index++)
	{
	  auto vec3{input.read_vec3()};
	  auto timestamp{input.read_double()};
	  positions->key_frames.emplace_back(
	    vec3, static_cast<float>(timestamp));
	}

	auto num_rotations{input.read_ui32()};
	BluCat::GRA::Channel<glm::quat> *rotations =
	  &((*bone_transforms)[bone_transform_index].rotations);
	for(auto rotation_key_index{0}; rotation_key_index < num_rotations;
	    rotation_key_index++)
	{
	  auto quat{input.read_quat()};
	  auto timestamp{input.read_double()};
	  rotations->key_frames.emplace_back(
	    quat, static_cast<float>(timestamp));
	}

	auto num_scales{input.read_ui32()};
	BluCat::GRA::Channel<glm::vec3> *scales =
	  &((*bone_transforms)[bone_transform_index].scales);
	for(auto scaling_key_index{0}; scaling_key_index < num_scales;
	    scaling_key_index++)
	{
	  auto vec3{input.read_vec3()};
	  auto timestamp{input.read_double()};
	  scales->key_frames.emplace_back(vec3, static_cast<float>(timestamp));
	}
      }
    }
  }
}

void
unload_mesh(void *obj)
{
  auto self = static_cast<MeshBuilder*>(obj);

  delete self->mesh->index_buffer;
  delete self->mesh->vertex_buffer;
  delete self->mesh->source_vertex_buffer;
}

static const CommandChain loader{
  {&load_mesh, &unload_mesh}
};

}

namespace BluCat::GRA
{

SkeletalMesh::SkeletalMesh(std::string mesh_path)
{
  MeshBuilder mesh_builder(this, mesh_path);
  loader.execute(&mesh_builder);
}

SkeletalMesh::SkeletalMesh(const char* mesh_path):
  SkeletalMesh{std::string(mesh_path)}
{
}

SkeletalMesh::~SkeletalMesh()
{
  MeshBuilder mesh_builder(this, "");
  loader.revert(&mesh_builder);
}

}