#include "math/float_types.hpp"
#include "math/transform.hpp"

#include "ta_parameter.hpp"

#include "scene/logo/scene.hpp"
#include "scene/logo/sound.hpp"

#include "texture.hpp"
#include "framebuffer.hpp"

#include "model/blender_export.h"
#include "model/32bitlogo/model.h"

static vec3 screen_transform(const vec3& v)
{
  return {v.x, v.y, 1.0f / v.z};
}

static inline float light_intensity(vec3 n, vec3 l)
{
  float ambient = 0.4f;
  float diffuse_strength = 0.7f;

  float n_dot_l = dot(n, l);
  float diffuse = 0;
  if (n_dot_l > 0)
    diffuse = diffuse_strength * n_dot_l * (inverse_length(n) * inverse_length(l));

  return ambient + diffuse;
}

vec3 light_vec = (vec3){-1, -1, -1} - (vec3){0, 0, 0};

static void render_mesh(ta_parameter_writer& writer, const mesh& mesh, const mat4x4& trans, float base_intensity, bool wireframe)
{
  if (wireframe) {
    global_polygon_untextured(writer,
                              para_control::list_type::opaque,
                              tsp_instruction_word::dst_alpha_instr::zero);
  } else {
    uint32_t texture_size = tsp_instruction_word::texture_u_size::from_int(8)
                          | tsp_instruction_word::texture_v_size::from_int(8);

    global_polygon_intensity(writer,
                             para_control::list_type::opaque,
                             texture::offset::logo,
                             texture_size,
                             texture_control_word::pixel_format::_565);
  }

  vec3 position_cache[mesh.position_length];
  for (int i = 0; i < mesh.position_length; i++) {
    position_cache[i] = screen_transform(trans * mesh.position[i]);
  }
  vec3 normal_cache[mesh.polygons_length];
  for (int i = 0; i < mesh.polygons_length; i++) {
    normal_cache[i] = normal_multiply(trans, mesh.polygon_normal[i]);
  }

  const int green = (int)(255.f * base_intensity) << 8;

  for (int i = 0; i < mesh.polygons_length; i++) {
    const polygon& p = mesh.polygons[i];

    vec3 ap = position_cache[p.a];
    vec3 bp = position_cache[p.b];
    vec3 cp = position_cache[p.c];
    vec3 dp = position_cache[p.d];

    if (wireframe) {
      line_type_0(writer, ap, bp, green);
      line_type_0(writer, bp, cp, green);
      if (p.d == -1) {
        line_type_0(writer, cp, ap, green);
      } else {
        line_type_0(writer, cp, dp, green);
        line_type_0(writer, dp, ap, green);
      }
    } else {
      vec2 at = mesh.uv_layers[0][p.uv_index + 0];
      vec2 bt = mesh.uv_layers[0][p.uv_index + 1];
      vec2 ct = mesh.uv_layers[0][p.uv_index + 2];
      vec2 dt = mesh.uv_layers[0][p.uv_index + 3];

      float intensity = light_intensity(normal_cache[i], light_vec);

      if (p.d == -1) {
        tri_type_7(writer,
                   ap, at,
                   bp, bt,
                   cp, ct,
                   intensity);
      } else {
        quad_type_7(writer,
                    ap, at,
                    bp, bt,
                    cp, ct,
                    dp, dt,
                    intensity);
      }
    }
  }
}

namespace scene::logo {

  const struct scene::scene scene = {
    .ta_alloc = ta_alloc_ctrl::pt_opb::no_list
              | ta_alloc_ctrl::tm_opb::no_list
              | ta_alloc_ctrl::t_opb::no_list
              | ta_alloc_ctrl::om_opb::no_list
              | ta_alloc_ctrl::o_opb::_32x4byte,
    .opb_size = {
      .opaque = 32 * 4,
      .opaque_modifier = 0,
      .translucent = 0,
      .translucent_modifier = 0,
      .punch_through = 0
    },
    .transfer = transfer,
    .init = init
  };

  static int tick = 0;
  static int last_tick = 0;

  struct keyframe {
    float i; // intensity
    float rx; // rotate_x
    float ry; // rotate_y
    float s; // scale
    float duration;
  };

  const struct keyframe keyframes[] = {
    {
      .i = 0,
      .rx = 0,
      .ry = pi,
      .s = 0.01,
      .duration = 1.0 / (5.2 * 60),
    },
    {
      .i = 1,
      .rx = 0,
      .ry = pi,
      .s = 0.1,
      .duration = 1.0 / (4.5 * 60),
    },
    {
      .i = 1,
      .rx = pi / 4,
      .ry = pi + pi / 4,
      .s = 0.7,
      .duration = 1.0 / (10 * 60),
    },
  };

  const int keyframes_length = (sizeof (keyframes)) / (sizeof (keyframes[0]));

  static inline float clamp(float f)
  {
    if (f > 1.0)
      return 1.0;
    else if (f < 0.0)
      return 0.0;
    else
      return f;
  }

  static inline keyframe interpolate(const keyframe& a, const keyframe& b, const float dt)
  {
    float ratio = clamp(dt * a.duration);

    float di = b.i - a.i;
    float drx = b.rx - a.rx;
    float dry = b.ry - a.ry;
    float ds = b.s - a.s;

    return {
      .i = a.i + di * ratio,
      .rx = a.rx + drx * ratio,
      .ry = a.ry + dry * ratio,
      .s = a.s + ds * ratio,
      .duration = a.duration,
    };
  }

  void transfer(ta_multiwriter& multi)
  {
    vec3 t = {framebuffer.px_width / 2.f, framebuffer.px_height / 2.f, 0};
    float s = framebuffer.px_height / 3.f;

    static int keyframe_ix = 0;

    float dt = tick - last_tick;
    if (dt * keyframes[keyframe_ix].duration >= 1) {
      if (keyframe_ix < (keyframes_length - 2)) {
        last_tick = tick;
        dt = 0;
        keyframe_ix += 1;
      }
    }
    keyframe k = interpolate(keyframes[keyframe_ix], keyframes[keyframe_ix + 1], dt);

    mat4x4 trans
      = translate(t)
      * scale((vec3){s, s, 1})
      * translate((vec3){0, 0, 10})
      //* rotate_x(pi / 8)
      //* rotate_y(pi + pi/8)
      * scale(k.s)
      * rotate_x(k.rx)
      * rotate_y(k.ry)
      * scale((vec3){-1, -1, 1});

    render_mesh(multi.op, mesh_thirty_two, trans, k.i, tick < (9.85 * 60));
    if (keyframe_ix > 0) {
      render_mesh(multi.op, mesh_bit, trans, k.i, tick < (10.85 * 60));
      render_mesh(multi.op, mesh_jam, trans, k.i, tick < (11.85 * 60));
    }

    tick += 1;
  }

  void init()
  {
    ::scene::logo::sound::init();
  }
}
