#include "gltf_view.h"

#include <gfx/renderer.h>
#include <gfx/util/scene.h>
#include <gfx/util/skyquad.h>
#include <gfx/util/texture.h>
#include <math/camera.h>
#include <math/spatial3.h>

#include <stdlib.h>

// Paths to various scene files.
/*static const char* BOX     = "/assets/models/box.gltf";
static const char* SUZANNE = "/assets/models/suzanne.gltf";
static const char* SPONZA =
    "/assets/glTF-Sample-Models/2.0/Sponza/glTF/Sponza.gltf";
static const char* FLIGHT_HELMET =
    "/assets/glTF-Sample-Models/2.0/FlightHelmet/glTF/FlightHelmet.gltf";
static const char* DAMAGED_HELMET =
    "/assets/glTF-Sample-Models/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf";*/
static const char* GIRL =
    "/home/jeanne/Nextcloud/assets/models/girl/girl-with-ground.gltf";

#define DEFAULT_SCENE_FILE GIRL

/// Load the skyquad texture.
static Texture* load_environment_map(RenderBackend* render_backend) {
  return gfx_load_texture(
      render_backend,
      &(LoadTextureCmd){
          .origin                 = TextureFromFile,
          .type                   = LoadCubemap,
          .colour_space           = sRGB,
          .filtering              = NearestFiltering,
          .mipmaps                = false,
          .data.cubemap.filepaths = {
                                     mstring_make("/assets/skybox/clouds1/clouds1_east.bmp"),
                                     mstring_make("/assets/skybox/clouds1/clouds1_west.bmp"),
                                     mstring_make("/assets/skybox/clouds1/clouds1_up.bmp"),
                                     mstring_make("/assets/skybox/clouds1/clouds1_down.bmp"),
                                     mstring_make("/assets/skybox/clouds1/clouds1_south.bmp"),
                                     mstring_make("/assets/skybox/clouds1/clouds1_north.bmp")}
  });
}

/// Load the skyquad and return the environment light node.
static SceneNode* load_skyquad(RenderBackend* render_backend, SceneNode* root) {
  assert(render_backend);
  assert(root);

  Texture* environment_map = load_environment_map(render_backend);
  if (!environment_map) {
    return 0;
  }

  return gfx_setup_skyquad(render_backend, root, environment_map);
}

/// Load the 3D scene.
static SceneNode* load_scene(Game* game, const char* scene_filepath) {
  assert(game);
  assert(game->gfx);
  assert(game->scene);

  SceneNode*     root           = gfx_get_scene_root(game->scene);
  RenderBackend* render_backend = gfx_get_render_backend(game->gfx);

  Camera* camera = gfx_get_camera_camera(game->camera);
  spatial3_set_position(&camera->spatial, vec3_make(0, 0, 2));

  SceneNode* sky_light_node = load_skyquad(render_backend, root);
  if (!sky_light_node) {
    return 0;
  }

  SceneNode* scene_node = gfx_load_scene(
      game->gfx, sky_light_node,
      &(LoadSceneCmd){.origin = SceneFromFile, .filepath = scene_filepath});
  if (!scene_node) {
    return 0;
  }

  gfx_log_node_hierarchy(root);

  return scene_node;
}

State* init(Game* game) {
  assert(game);

  State* state = calloc(1, sizeof(State));
  return state;
}

bool boot(State* state, Game* game) {
  assert(state);
  assert(game);

  const int    argc = game->argc;
  const char** argv = game->argv;

  // Usage: <scene file>
  const char* scene_filepath = argc > 1 ? argv[1] : DEFAULT_SCENE_FILE;

  SceneNode* node = load_scene(game, scene_filepath);
  if (!node) {
    return false;
  }
  Anima* anima = gfx_get_node_anima(node);
  gfx_play_animation(
      anima, &(AnimationPlaySettings){.name = "Walk", .loop = true});

  return true;
}

void update(State* state, Game* game, double t, double dt) {
  assert(state);
  assert(game);
  assert(game->scene);
  assert(game->camera);

  gfx_animate_scene(game->scene, (R)t);

  const vec3 orbit_point = vec3_make(0, 2, 0);
  Camera*    camera      = gfx_get_camera_camera(game->camera);
  spatial3_orbit(
      &camera->spatial, orbit_point,
      /*radius=*/2.5,
      /*azimuth=*/t * 0.5, /*zenith=*/0);
  spatial3_lookat(&camera->spatial, orbit_point);
}

/// Render the bounding boxes of all scene objects.
static void render_bounding_boxes(ImmRenderer* imm, const SceneNode* node) {
  if (gfx_get_node_type(node) == ObjectNode) {
    // TODO: Look at the scene log. The JointNodes are detached from the
    // ObjectNodes. This is why the boxes are not being transformed as expected
    // here. Anima needs to animate boxes? Use OOBB in addition to AABB?
    const mat4         model = gfx_get_node_global_transform(node);
    const SceneObject* obj   = gfx_get_node_object(node);
    const aabb3        box   = gfx_calc_object_aabb(obj);
    gfx_imm_set_model_matrix(imm, &model);
    gfx_imm_draw_aabb(imm, box);
  }

  // Render children's boxes.
  for (NodeIter it = gfx_get_node_child(node); it;
       it          = gfx_get_next_child(it)) {
    render_bounding_boxes(imm, gfx_get_iter_node(it));
  }
}

void render(State* state, const Game* game) {
  assert(state);
  assert(game);
  assert(game->gfx);
  assert(game->scene);
  assert(game->camera);

  ImmRenderer* imm = gfx_get_imm_renderer(game->gfx);
  assert(imm);
  gfx_imm_start(imm);
  gfx_imm_set_camera(imm, gfx_get_camera_camera(game->camera));
  gfx_imm_set_colour(imm, vec4_make(0.2, 0.2, 1.0, 0.3));
  render_bounding_boxes(imm, gfx_get_scene_root(game->scene));
  gfx_imm_end(imm);
}