#include "plugin.h"

#include <gfx/asset.h>
#include <gfx/render_backend.h>
#include <gfx/renderer.h>
#include <gfx/scene.h>
#include <gfx/util/geometry.h>
#include <gfx/util/shader.h>

#include <math/camera.h>

#include <assert.h>
#include <stdlib.h>

// Default texture to load if no texture is provided.
static const char* DEFAULT_TEXTURE = "/assets/skybox/clouds1/clouds1_west.bmp";
// static const char* DEFAULT_TEXTURE = "/assets/checkerboard.jpg";

struct State {
  Scene*       scene;
  SceneCamera* camera;
};

bool init(Game* game, State** pp_state) {
  assert(game);
  assert(pp_state);

  State* state = calloc(1, sizeof(State));
  if (!state) {
    goto cleanup;
  }

  // Usage: [texture file]
  const char* texture_file = game->argc > 1 ? game->argv[1] : DEFAULT_TEXTURE;

  RenderBackend* render_backend = gfx_get_render_backend(game->gfx);

  Texture* texture = gfx_load_texture(
      render_backend, &(LoadTextureCmd){
                          .origin                = TextureFromFile,
                          .type                  = LoadTexture,
                          .filtering             = LinearFiltering,
                          .mipmaps               = false,
                          .data.texture.filepath = mstring_make(texture_file)});
  if (!texture) {
    goto cleanup;
  }

  ShaderProgram* shader = gfx_make_view_texture_shader(render_backend);
  if (!shader) {
    goto cleanup;
  }

  Geometry* geometry = gfx_make_quad_11(render_backend);
  if (!geometry) {
    goto cleanup;
  }

  MaterialDesc material_desc = (MaterialDesc){.num_uniforms = 1};
  material_desc.uniforms[0]  = (ShaderUniform){
       .type          = UniformTexture,
       .value.texture = texture,
       .name          = sstring_make("Texture")};
  Material* material = gfx_make_material(&material_desc);
  if (!material) {
    goto cleanup;
  }

  const MeshDesc mesh_desc =
      (MeshDesc){.geometry = geometry, .material = material, .shader = shader};
  Mesh* mesh = gfx_make_mesh(&mesh_desc);
  if (!mesh) {
    goto cleanup;
  }

  SceneObject* object = gfx_make_object();
  if (!object) {
    goto cleanup;
  }
  gfx_add_object_mesh(object, mesh);

  if (!(state->scene = gfx_make_scene())) {
    goto cleanup;
  }

  SceneNode* node = gfx_make_object_node(object);
  if (!node) {
    goto cleanup;
  }
  SceneNode* root = gfx_get_scene_root(state->scene);
  if (!root) {
    goto cleanup;
  }
  gfx_set_node_parent(node, root);

  if (!(state->camera = gfx_make_camera())) {
    goto cleanup;
  }

  *pp_state = state;
  return true;

cleanup:
  shutdown(game, state);
  if (state) {
    free(state);
  }
  return false;
}

void shutdown(Game* game, State* state) {
  assert(game);
  if (state) {
    gfx_destroy_camera(&state->camera);
    gfx_destroy_scene(&state->scene);
    // State freed by plugin engine.
  }
}

void render(const Game* game, const State* state) {
  assert(game);
  assert(state);

  Renderer* renderer = gfx_get_renderer(game->gfx);
  gfx_render_scene(
      renderer, &(RenderSceneParams){
                    .mode   = RenderDefault,
                    .scene  = state->scene,
                    .camera = state->camera});
}

void resize(Game* game, State* state, int width, int height) {
  assert(game);
  assert(state);

  const R    fovy       = 90 * TO_RAD;
  const R    aspect     = (R)width / (R)height;
  const R    near       = 0.1;
  const R    far        = 1000;
  const mat4 projection = mat4_perspective(fovy, aspect, near, far);

  Camera* camera     = gfx_get_camera_camera(state->camera);
  camera->projection = projection;
}