#include "graphics_primitive.hpp"
#include "cover.hpp"
#include "ta_parameter.hpp"
#include "playlist.hpp"
#include "texture.hpp"
#include "framebuffer.hpp"

#include "holly/background.hpp"
#include "holly/holly.hpp"

namespace scene::tracker::cover {

  struct cover {
    uint32_t texture_offset;
    uint32_t texture_size;
    float texture_width;
    float texture_height;
    float width;
    float height;
    float scale;
    vec3 color;
    uint32_t texture_full_offset;
  };

  int cover_ix = -1;

  static float zoom_interp = 0.0;

  static vec3 bg_color = {0, 0, 0};

  const uint32_t full_texture_size = tsp_instruction_word::texture_u_size::from_int(512)
                                   | tsp_instruction_word::texture_v_size::from_int(512);

  const float full_texture_width = 1.0 / 512.f;
  const float full_texture_height = 1.0 / 512.f;

  const float full_width = 432.0f;
  const float full_height = 432.0f;

  static const cover covers[] = {
    [thebeach] = {
      .texture_offset = texture::offset::thebeach,
      .texture_size = tsp_instruction_word::texture_u_size::from_int(128)
                    | tsp_instruction_word::texture_v_size::from_int(128),
      .texture_width = 1.0f / 128.0f,
      .texture_height = 1.0f / 128.0f,
      .width = 72,
      .height = 72,
      .scale = 6,
      .color = {0x1a, 0x13, 0x11},
    },
    [silvertrees] = {
      .texture_offset = texture::offset::silvertrees,
      .texture_size = tsp_instruction_word::texture_u_size::from_int(128)
                    | tsp_instruction_word::texture_v_size::from_int(128),
      .texture_width = 1.0f / 128.0f,
      .texture_height = 1.0f / 128.0f,
      .width = 72,
      .height = 72,
      .scale = 6,
      .color = {0x0d, 0x10, 0x1a},
      .texture_full_offset = texture::offset::silvertreesfull,
    },
    [redtree] = {
      .texture_offset = texture::offset::redtree,
      .texture_size = tsp_instruction_word::texture_u_size::from_int(128)
                    | tsp_instruction_word::texture_v_size::from_int(128),
      .texture_width = 1.0f / 128.0f,
      .texture_height = 1.0f / 128.0f,
      .width = 72,
      .height = 72,
      .scale = 6,
      .color = {0x1a, 0x00, 0x00},
      .texture_full_offset = texture::offset::redtreefull,
    },
    [mountain] = {
      .texture_offset = texture::offset::mountain,
      .texture_size = tsp_instruction_word::texture_u_size::from_int(128)
                    | tsp_instruction_word::texture_v_size::from_int(128),
      .texture_width = 1.0f / 128.0f,
      .texture_height = 1.0f / 128.0f,
      .width = 72,
      .height = 72,
      .scale = 6,
      .color = {0x00, 0x02, 0x1a},
    },
    [mossycottage] = {
      .texture_offset = texture::offset::mossycottage,
      .texture_size = tsp_instruction_word::texture_u_size::from_int(128)
                    | tsp_instruction_word::texture_v_size::from_int(128),
      .texture_width = 1.0f / 128.0f,
      .texture_height = 1.0f / 128.0f,
      .width = 72,
      .height = 72,
      .scale = 6,
      .color = {0x0f, 0x1a, 0x14},
    },
    [clocks] = {
      .texture_offset = texture::offset::clocks,
      .texture_size = tsp_instruction_word::texture_u_size::from_int(128)
                    | tsp_instruction_word::texture_v_size::from_int(128),
      .texture_width = 1.0f / 128.0f,
      .texture_height = 1.0f / 128.0f,
      .width = 72,
      .height = 72,
      .scale = 6,
      .color = {0x00, 0x0a, 0x1a},
    },
    [tree] = {
      .texture_offset = texture::offset::tree,
      .texture_size = tsp_instruction_word::texture_u_size::from_int(128)
                    | tsp_instruction_word::texture_v_size::from_int(128),
      .texture_width = 1.0f / 128.0f,
      .texture_height = 1.0f / 128.0f,
      .width = 72,
      .height = 72,
      .scale = 6,
      .color = {0x0d, 0x1a, 0x0e},
    },
    [moonmountains] = {
      .texture_offset = texture::offset::moonmountains,
      .texture_size = tsp_instruction_word::texture_u_size::from_int(128)
                    | tsp_instruction_word::texture_v_size::from_int(128),
      .texture_width = 1.0f / 128.0f,
      .texture_height = 1.0f / 128.0f,
      .width = 72,
      .height = 72,
      .scale = 6,
      .color = {0x00, 0x06, 0x1a},
    },
  };

  constexpr inline vec3 transform_position_fs(const cover& cover,
                                              const vec2& p,
                                              float z)
  {
    float width = cover.width * cover.scale;
    float height = cover.height * cover.scale;
    float x = (framebuffer.px_width - width) / 2;
    float y = (framebuffer.px_height - height) / 2;

    return {
      x + p.x * width,
      y + p.y * height,
      z};
  }

  constexpr inline vec3 transform_position(const cover& cover,
                                           const vec2& p,
                                           float x, float y, float z)
  {
    float width = 128;
    float height = 128;
    float x_offset = (128.0f - width) * 0.5f;
    float y_offset = (128.0f - height) * 0.5f;
    if (x_offset < 0) x_offset = 0;
    if (y_offset < 0) y_offset = 0;

    return {
      x + x_offset + p.x * width,
      y + y_offset + p.y * height,
      z
    };
  }

  constexpr inline vec2 transform_texture_full(const vec2& t)
  {
    return {
      (t.x * full_width) * full_texture_width,
      (t.y * full_height) * full_texture_height,
    };
  }

  constexpr inline vec2 transform_texture(const cover& cover,
                                          const vec2& t)
  {
    return {
      (t.x * cover.width) * cover.texture_width,
      (t.y * cover.height) * cover.texture_height,
    };
  }

  constexpr vec2 vtx[] = {
    {0, 0},
    {1, 0},
    {1, 1},
    {0, 1},
  };

  static inline vec3 lerp(const vec3& a, const vec3& b, float f)
  {
    float dx = b.x - a.x;
    float dy = b.y - a.y;
    float dz = b.z - a.z;

    return {
      a.x + dx * f,
      a.y + dy * f,
      a.z + dz * f,
    };
  }

  const static float alpha = 207;

  void draw_shroud(ta_parameter_writer& writer, const cover& cover)
  {
    global_polygon_translucent(writer);

    uint32_t a = ((uint32_t)(zoom_interp * alpha)) << 24;
    float z = 1.0 / 3.5f;

    const vec3& bg = cover.color;
    int color = (((int)bg.x) << 16) | (((int)bg.y) << 8) | (((int)bg.z) << 0);

    quad_type_0(writer,
                {0, 0, z},
                {(float)framebuffer.px_width, 0, z},
                {(float)framebuffer.px_width, (float)framebuffer.px_height, z},
                {0, (float)framebuffer.px_height, z},
                a | color);
  }

  void update_background(const cover& cover)
  {
    vec3 newbg = lerp(bg_color, cover.color, 0.005);

    if (newbg.x != bg_color.x || newbg.y != bg_color.y || newbg.z != bg_color.z) {
      bg_color = newbg;
      int color = (((int)newbg.x) << 16) | (((int)newbg.y) << 8) | (((int)newbg.z) << 0);

      background_parameter2(texture_memory_alloc.background[1].start,
                            color);
      holly.VO_BORDER_COL = color;
    }
  }

  void draw(ta_multiwriter& multi, float x, float y, bool zoom)
  {
    if (cover_ix < 0)
      return;

    const cover& cover = covers[cover_ix];

    update_background(cover);

    float z = 1.0 / 3.0f;

    int base_color = 0xffffff;

    if (zoom) {
      zoom_interp += 0.05;
      if (zoom_interp > 1)
        zoom_interp = 1;
    } else {
      zoom_interp -= 0.05;
      if (zoom_interp < 0)
        zoom_interp = 0;
    }

    vec3 ap0 = transform_position(cover, vtx[0], x, y, z);
    vec3 bp0 = transform_position(cover, vtx[1], x, y, z);
    vec3 cp0 = transform_position(cover, vtx[2], x, y, z);
    vec3 dp0 = transform_position(cover, vtx[3], x, y, z);

    vec3 ap1 = transform_position_fs(cover, vtx[0], z);
    vec3 bp1 = transform_position_fs(cover, vtx[1], z);
    vec3 cp1 = transform_position_fs(cover, vtx[2], z);
    vec3 dp1 = transform_position_fs(cover, vtx[3], z);

    vec3 ap = lerp(ap0, ap1, zoom_interp);
    vec3 bp = lerp(bp0, bp1, zoom_interp);
    vec3 cp = lerp(cp0, cp1, zoom_interp);
    vec3 dp = lerp(dp0, dp1, zoom_interp);

    vec2 at = transform_texture(cover, vtx[0]);
    vec2 bt = transform_texture(cover, vtx[1]);
    vec2 ct = transform_texture(cover, vtx[2]);
    vec2 dt = transform_texture(cover, vtx[3]);

    if (cover.texture_full_offset == 0) {
      uint32_t texture_size = cover.texture_size
                            | tsp_instruction_word::dst_alpha_instr::zero;

      global_polygon_textured(multi.op,
                              para_control::list_type::translucent,
                              cover.texture_offset,
                              texture_size,
                              texture_control_word::pixel_format::_565);

      quad_type_3(multi.op,
                  ap, at,
                  bp, bt,
                  cp, ct,
                  dp, dt,
                  base_color);
    } else {
      vec2 atf = transform_texture_full(vtx[0]);
      vec2 btf = transform_texture_full(vtx[1]);
      vec2 ctf = transform_texture_full(vtx[2]);
      vec2 dtf = transform_texture_full(vtx[3]);

      uint32_t texture_size = cover.texture_size
                            | tsp_instruction_word::dst_alpha_instr::zero;

      global_polygon_textured_tl(multi.op,
                                 para_control::list_type::translucent,
                                 cover.texture_offset,
                                 texture_size,
                                 texture_control_word::pixel_format::_565);

      uint32_t alpha0 = (uint32_t)(255.0 * (1.0 - zoom_interp)) << 24;

      quad_type_3(multi.op,
                  ap, at,
                  bp, bt,
                  cp, ct,
                  dp, dt,
                  alpha0 | base_color);

      uint32_t alpha1 = (uint32_t)(255.0 * (zoom_interp)) << 24;

      global_polygon_textured_tl(multi.op,
                                 para_control::list_type::translucent,
                                 cover.texture_full_offset,
                                 full_texture_size | tsp_instruction_word::dst_alpha_instr::inverse_src_alpha,
                                 texture_control_word::pixel_format::_565);

      quad_type_3(multi.op,
                  ap, atf,
                  bp, btf,
                  cp, ctf,
                  dp, dtf,
                  alpha1 | base_color);
    }

    if (zoom_interp > 0)
      draw_shroud(multi.op, cover);
  }
}
