#include "math/transform.hpp"

#include "platform/graphics_primitive.hpp"

static inline float _intensity(const mat4x4& trans, const vec3& normal)
{
  const vec3 light_vec = {-1, -1, -10};

  vec3 n = normal_multiply(trans, normal);
  float n_dot_l = dot(n, light_vec);
  float intensity = 0.2f;
  if (n_dot_l > 0)
    intensity += 0.5f * n_dot_l * (inverse_length(n) * inverse_length(light_vec));
  return intensity;
}

void draw_cube(ta_parameter_writer& writer,
               const mat4x4& trans,
               const vec3& color)
{
  static const vec3 position[] = {
    {-1.0, -1.0, 1.0},
    {-1.0, 1.0, 1.0},
    {-1.0, -1.0, -1.0},
    {-1.0, 1.0, -1.0},
    {1.0, -1.0, 1.0},
    {1.0, 1.0, 1.0},
    {1.0, -1.0, -1.0},
    {1.0, 1.0, -1.0},
  };
  const int position_length = (sizeof (position)) / (sizeof (position[0]));

  struct quad {
    int a, b, c, d;
  };

  static const quad quads[] = {
    {0, 1, 3, 2},
    {2, 3, 7, 6},
    {6, 7, 5, 4},
    {4, 5, 1, 0},
    {2, 6, 4, 0},
    {7, 3, 1, 5}
  };
  const int quads_length = (sizeof (quads)) / (sizeof (quads[0]));

  vec3 cache[position_length];
  for (int i = 0; i < position_length; i++) {
    cache[i] = screen_transform(trans * position[i]);
  }

  global_polygon_intensity(writer, color);

  for (int i = 0; i < quads_length; i++) {
    const vec3& ap = cache[quads[i].a];
    const vec3& bp = cache[quads[i].b];
    const vec3& cp = cache[quads[i].c];
    const vec3& dp = cache[quads[i].d];

    quad_type_2(writer,
                ap,
                bp,
                cp,
                dp,
                1.0f);
  }
}

void draw_icosphere(ta_parameter_writer& writer,
                    const mat4x4& trans,
                    const vec3& color)
{
  static const vec3 position[] = {
    {0.000000, -1.000000, 0.000000},
    {0.723600, -0.447215, 0.525720},
    {-0.276385, -0.447215, 0.850640},
    {-0.894425, -0.447215, 0.000000},
    {-0.276385, -0.447215, -0.850640},
    {0.723600, -0.447215, -0.525720},
    {0.276385, 0.447215, 0.850640},
    {-0.723600, 0.447215, 0.525720},
    {-0.723600, 0.447215, -0.525720},
    {0.276385, 0.447215, -0.850640},
    {0.894425, 0.447215, 0.000000},
    {0.000000, 1.000000, 0.000000},
  };
  const int position_length = (sizeof (position)) / (sizeof (position[0]));
  static const vec3 normal[] = {
    {0.1876, -0.7947, 0.5774},
    {0.6071, -0.7947, -0.0000},
    {-0.4911, -0.7947, 0.3568},
    {-0.4911, -0.7947, -0.3568},
    {0.1876, -0.7947, -0.5774},
    {0.9822, -0.1876, -0.0000},
    {0.3035, -0.1876, 0.9342},
    {-0.7946, -0.1876, 0.5774},
    {-0.7946, -0.1876, -0.5774},
    {0.3035, -0.1876, -0.9342},
    {0.7946, 0.1876, 0.5774},
    {-0.3035, 0.1876, 0.9342},
    {-0.9822, 0.1876, -0.0000},
    {-0.3035, 0.1876, -0.9342},
    {0.7946, 0.1876, -0.5774},
    {0.4911, 0.7947, 0.3568},
    {-0.1876, 0.7947, 0.5774},
    {-0.6071, 0.7947, -0.0000},
    {-0.1876, 0.7947, -0.5774},
    {0.4911, 0.7947, -0.3568},
  };

  struct pn {
    int p, n;
  };

  struct tri {
    int a;
    int b;
    int c;
    int n;
  };

  static const tri tris[] = {
    {0, 1, 2, 0},
    {1, 0, 5, 1},
    {0, 2, 3, 2},
    {0, 3, 4, 3},
    {0, 4, 5, 4},
    {1, 5, 10, 5},
    {2, 1, 6, 6},
    {3, 2, 7, 7},
    {4, 3, 8, 8},
    {5, 4, 9, 9},
    {1, 10, 6, 10},
    {2, 6, 7, 11},
    {3, 7, 8, 12},
    {4, 8, 9, 13},
    {5, 9, 10, 14},
    {6, 10, 11, 15},
    {7, 6, 11, 16},
    {8, 7, 11, 17},
    {9, 8, 11, 18},
    {10, 9, 11, 19},
  };
  const int tris_length = (sizeof (tris)) / (sizeof (tris[0]));

  static vec3 position_cache[position_length];
  for (int i = 0; i < position_length; i++) {
    position_cache[i] = screen_transform(trans * position[i]);
  }

  global_polygon_intensity(writer, color);

  for (int i = 0; i < tris_length; i++) {
    const vec3& ap = position_cache[tris[i].a];
    const vec3& bp = position_cache[tris[i].b];
    const vec3& cp = position_cache[tris[i].c];
    const float intensity = _intensity(trans, normal[tris[i].n]);

    tri_type_2(writer,
               ap,
               bp,
               cp,
               intensity);
  }
}

void draw_plane(ta_parameter_writer& writer,
                const vec3& k,
                const vec3& n,
                const mat4x4& trans,
                const vec3& color)
{
  static const vec3 position[] = {
    {-1.000000, 0.000000,  1.000000},
    { 1.000000, 0.000000,  1.000000},
    {-1.000000, 0.000000, -1.000000},
    { 1.000000, 0.000000, -1.000000},
  };

  //const vec3 normal = {0.0000 1.0000 0.0000};

  vec3 u = {0, 1, 0};
  if (dot(u, n) > 0.7071f) u = {0, 0, 1};
  vec3 r = normalize(cross(u, n));
  u = normalize(cross(n, r));

  mat4x4 m = {
    r.x, n.x, u.x, k.x,
    r.y, n.y, u.y, k.y,
    r.z, n.z, u.z, k.z,
    0, 0, 0, 1
  };
  mat4x4 t = trans * m;

  vec3 ap = screen_transform(t * position[0]);
  vec3 bp = screen_transform(t * position[1]);
  vec3 cp = screen_transform(t * position[3]);
  vec3 dp = screen_transform(t * position[2]);

  global_polygon_intensity(writer, color);
  quad_type_2(writer,
              ap,
              bp,
              cp,
              dp,
              1.0f);
}

void draw_halfspace(ta_parameter_writer& writer,
                    const vec3& k,
                    const vec3& n,
                    const mat4x4& trans,
                    const vec3& color)
{
  static const vec3 position[] = {
    {-1.000000, 0.000000,  1.000000},
    { 1.000000, 0.000000,  1.000000},
    {-1.000000, 0.000000, -1.000000},
    { 1.000000, 0.000000, -1.000000},
  };

  const vec3 red = {0.906, 0.369, 0.373};

  //const vec3 normal = {0.0000 1.0000 0.0000};

  vec3 u = {0, 1, 0};
  if (dot(u, n) > 0.7071f) u = {0, 0, 1};
  vec3 r = normalize(cross(u, n));
  u = normalize(cross(n, r));

  mat4x4 m = {
    r.x, n.x, u.x, k.x,
    r.y, n.y, u.y, k.y,
    r.z, n.z, u.z, k.z,
    0, 0, 0, 1
  };
  mat4x4 t = trans * m;

  vec3 ap = screen_transform(t * position[0]);
  vec3 bp = screen_transform(t * position[1]);
  vec3 cp = screen_transform(t * position[3]);
  vec3 dp = screen_transform(t * position[2]);

  global_polygon_intensity(writer,
                           color,
                           isp_tsp_instruction_word::culling_mode::cull_if_negative);
  quad_type_2(writer,
              ap,
              bp,
              cp,
              dp,
              1.0f);

  global_polygon_intensity(writer,
                           red,
                           isp_tsp_instruction_word::culling_mode::cull_if_positive);
  quad_type_2(writer,
              ap,
              bp,
              cp,
              dp,
              1.0f);
}

void draw_textured_cube(ta_parameter_writer& writer,
                        const mat4x4& trans,
                        const vec3& scale,
                        const texture::cube_texture_offsets& offsets,
                        const vec3& base_color,
                        float intensity_offset)
{
  static const vec3 position[] = {
    {-0.5, -0.5,  0.5},
    {-0.5,  0.5,  0.5},
    {-0.5, -0.5, -0.5},
    {-0.5,  0.5, -0.5},
    { 0.5, -0.5,  0.5},
    { 0.5,  0.5,  0.5},
    { 0.5, -0.5, -0.5},
    { 0.5,  0.5, -0.5},
  };
  static const vec3 normal[] = {
    {-1.0, -0.0, -0.0},
    {-0.0, -0.0, -1.0},
    { 1.0, -0.0, -0.0},
    {-0.0, -0.0,  1.0},
    {-0.0, -1.0, -0.0},
    {-0.0,  1.0, -0.0},
  };
  static const vec2 texture[] = {
    {0.0, 0.0},
    {1.0, 0.0},
    {1.0, 1.0},
    {0.0, 1.0},
  };
  const int position_length = (sizeof (position)) / (sizeof (position[0]));

  struct quad {
    int a, b, c, d;
  };

  static const quad quads[] = {
    {0, 1, 3, 2}, // front   (z, y)
    {2, 3, 7, 6}, // left    (x, y)
    {6, 7, 5, 4}, // rear    (z, y)
    {4, 5, 1, 0}, // right   (x, y)
    {2, 6, 4, 0}, // bottom  (x, z)
    {7, 3, 1, 5}  // top     (x, z)
  };

  const vec2 uv_scale[] = {
    {scale.z, scale.y},
    {scale.x, scale.y},
    {scale.z, scale.y},
    {scale.x, scale.y},
    {scale.z, scale.x},
    {scale.z, scale.x},
  };

  const int quads_length = (sizeof (quads)) / (sizeof (quads[0]));

  vec3 position_cache[position_length];
  for (int i = 0; i < position_length; i++) {
    position_cache[i] = trans * position[i];
  }

  int texture_uv_size
    = tsp_instruction_word::texture_shading_instruction::modulate
    | tsp_instruction_word::src_alpha_instr::one
    | tsp_instruction_word::dst_alpha_instr::zero
    | tsp_instruction_word::texture_u_size::from_int(32)
    | tsp_instruction_word::texture_v_size::from_int(32);

  int pixel_format
    = texture_control_word::pixel_format::_565;

  vec4 color = {base_color.x, base_color.y, base_color.z, 1};

  global_polygon_textured_intensity(writer,
                                    color,
                                    para_control::list_type::opaque,
                                    offsets.side,
                                    texture_uv_size,
                                    pixel_format);

  for (int i = 0; i < quads_length; i++) {
    if (i == 4)
      global_polygon_textured_intensity(writer,
                                        color,
                                        para_control::list_type::opaque,
                                        offsets.bottom,
                                        texture_uv_size,
                                        pixel_format);
    if (i == 5)
      global_polygon_textured_intensity(writer,
                                        color,
                                        para_control::list_type::opaque,
                                        offsets.top,
                                        texture_uv_size,
                                        pixel_format);

    const vec3& ap = position_cache[quads[i].a];
    const vec3& bp = position_cache[quads[i].b];
    const vec3& cp = position_cache[quads[i].c];
    const vec3& dp = position_cache[quads[i].d];

    const vec2& at = texture[3] * uv_scale[i];
    const vec2& bt = texture[0] * uv_scale[i];
    const vec2& ct = texture[1] * uv_scale[i];
    const vec2& dt = texture[2] * uv_scale[i];

    const float base_intensity = _intensity(trans, normal[i]) + intensity_offset;

    quad_type_7_maybe_clip(writer,
                           ap, at,
                           bp, bt,
                           cp, ct,
                           dp, dt,
                           base_intensity);
  }
}

void draw_textured_cube2(ta_parameter_writer& writer,
                         const mat4x4& trans,
                         int texture_offset1,
                         int texture_offset2,
                         float transition)
{
  static const vec3 position[] = {
    {-0.5, -0.5,  0.5},
    {-0.5,  0.5,  0.5},
    {-0.5, -0.5, -0.5},
    {-0.5,  0.5, -0.5},
    { 0.5, -0.5,  0.5},
    { 0.5,  0.5,  0.5},
    { 0.5, -0.5, -0.5},
    { 0.5,  0.5, -0.5},
  };
  static const vec2 texture[] = {
    {0.0, 0.0},
    {1.0, 0.0},
    {1.0, 1.0},
    {0.0, 1.0},
  };
  const int position_length = (sizeof (position)) / (sizeof (position[0]));

  struct quad {
    int a, b, c, d;
  };

  static const quad quads[] = {
    {0, 1, 3, 2}, // front   (z, y)
    {2, 3, 7, 6}, // left    (x, y)
    {6, 7, 5, 4}, // rear    (z, y)
    {4, 5, 1, 0}, // right   (x, y)
    //{2, 6, 4, 0}, // bottom  (x, z)
    //{7, 3, 1, 5}  // top     (x, z)
  };

  const int quads_length = (sizeof (quads)) / (sizeof (quads[0]));

  vec3 position_cache[position_length];
  for (int i = 0; i < position_length; i++) {
    position_cache[i] = trans * position[i];
  }

  int texture_uv_size
    = tsp_instruction_word::texture_shading_instruction::modulate_alpha
    | tsp_instruction_word::src_alpha_instr::src_alpha
    | tsp_instruction_word::dst_alpha_instr::inverse_src_alpha
    | tsp_instruction_word::use_alpha
    | tsp_instruction_word::texture_u_size::from_int(128)
    | tsp_instruction_word::texture_v_size::from_int(128);

  int pixel_format
    = texture_control_word::pixel_format::_565;

  const vec2 uv_scale = {72.0f / 128.0f, 72.0f / 128.0f};

  for (int i = 0; i < quads_length; i++) {
    const vec3& ap = position_cache[quads[i].a];
    const vec3& bp = position_cache[quads[i].b];
    const vec3& cp = position_cache[quads[i].c];
    const vec3& dp = position_cache[quads[i].d];

    bool flip = (i < 4) && (i % 2 == 0);

    vec2 at;
    vec2 bt;
    vec2 ct;
    vec2 dt;

    if (flip) {
      at = vec2(1.0 - texture[3].x, texture[3].y) * uv_scale;
      bt = vec2(1.0 - texture[0].x, texture[0].y) * uv_scale;
      ct = vec2(1.0 - texture[1].x, texture[1].y) * uv_scale;
      dt = vec2(1.0 - texture[2].x, texture[2].y) * uv_scale;
    } else {
      at = texture[3] * uv_scale;
      bt = texture[0] * uv_scale;
      ct = texture[1] * uv_scale;
      dt = texture[2] * uv_scale;
    }

    const float base_intensity = 0.4f;

    vec4 color1 = {1, 1, 1, 1.0f - transition};

    global_polygon_textured_intensity(writer,
                                      color1,
                                      para_control::list_type::translucent,
                                      texture_offset1,
                                      texture_uv_size,
                                      pixel_format);

    quad_type_7_maybe_clip(writer,
                           ap, at,
                           bp, bt,
                           cp, ct,
                           dp, dt,
                           base_intensity);

    vec4 color2 = {1, 1, 1, transition};

    global_polygon_textured_intensity(writer,
                                      color2,
                                      para_control::list_type::translucent,
                                      texture_offset2,
                                      texture_uv_size,
                                      pixel_format);

    quad_type_7_maybe_clip(writer,
                           ap, at,
                           bp, bt,
                           cp, ct,
                           dp, dt,
                           base_intensity);
  }
}

constexpr inline float pow(float x, int n)
{
  for (int i = 0; i < n; i++)
    x *= x;

  return x;
}

float alpha_mul = 0.9;

void draw_textured_voxel(ta_parameter_writer& writer,
                         const mat4x4& trans,
                         int texture_offset)
{
  static const vec3 position[] = {
    {-0.5,  0.5, 0.0},
    { 0.5,  0.5, 0.0},
    { 0.5, -0.5, 0.0},
    {-0.5, -0.5, 0.0},
  };
  static const vec2 texture[] = {
    {0.0, 0.0},
    {1.0, 0.0},
    {1.0, 1.0},
    {0.0, 1.0},
  };
  const int position_length = (sizeof (position)) / (sizeof (position[0]));

  const vec3 pos_scale = {96 / 32, 1.0f, 1.0f};
  const vec2 uv_scale = {96.0f / 128.0f, 1.0};

  vec3 position_cache[position_length];
  for (int i = 0; i < position_length; i++) {
    position_cache[i] = trans * (position[i] * pos_scale);
  }

  int texture_uv_size
    = tsp_instruction_word::texture_shading_instruction::modulate_alpha
    | tsp_instruction_word::src_alpha_instr::src_alpha
    | tsp_instruction_word::dst_alpha_instr::inverse_src_alpha
    | tsp_instruction_word::texture_u_size::from_int(128)
    | tsp_instruction_word::texture_v_size::from_int(32)
    | tsp_instruction_word::use_alpha;

  int pixel_format
    = texture_control_word::pixel_format::_4444;

  const float table[] = {
    0.063,
    0.063,
    0.063,
    0.074,
    0.077, // after purple
    0.077, // after purple
    0.077, // after purple
    0.077, // after purple
    0.077, // after purple
    0.084, // purple
    0.084, // purple
    0.084, // purple
    0.084, // purple
    0.084, // purple
    0.090,
    0.090,
    0.090,
    0.100,
    0.100,
    0.100,
    0.111,
    0.111,
    0.111,
    0.129,
    0.129,
    0.129,
    0.148,
    0.148,
    0.148,
    0.180,
    0.180,
    0.180,
    0.440, // green
    0.440, // green
    0.440, // green
    0.881, // yellow
    1.0,
    1.0,
    1.0,
    0.881, // yellow
    0.440, // green
    0.440, // green
    0.440, // green
    0.180,
    0.180,
    0.180,
    0.148,
    0.148,
    0.148,
    0.129,
    0.129,
    0.129,
    0.111,
    0.111,
    0.111,
    0.100,
    0.100,
    0.100,
    0.090,
    0.090,
    0.090,
    0.084, // purple
    0.084, // purple
    0.084, // purple
    0.084, // purple
    0.084, // purple
    0.077, // after purple
    0.077, // after purple
    0.077, // after purple
    0.077, // after purple
    0.077, // after purple
    0.074,
    0.063,
    0.063,
    0.063,
  };
  const int table_length = (sizeof (table)) / (sizeof (table[0]));

  constexpr int num_quads = table_length;
  constexpr int half_quads = (num_quads - 1) / 2;
  constexpr float inverse_half_quads = 1.0f / (float)half_quads;


  for (int i = 0; i < num_quads; i++) {
    float z = (float)(i - half_quads) * inverse_half_quads;
    float s = sin<float>(z * pi * 0.5f + pi * 0.5f);
    //printf("%f\n", s);
    //float a = pow(s, 5);
    float a = table[i];
    //float a = alpha_mul;
    vec4 color = {1, 1, 1, a};

    global_polygon_textured_intensity(writer,
                                      color,
                                      para_control::list_type::punch_through,
                                      texture_offset,
                                      texture_uv_size,
                                      pixel_format);

    vec3 z_trans = {0, 0, z * 0.35f};
    const vec3& ap = trans * (position[0] * pos_scale + z_trans);
    const vec3& bp = trans * (position[1] * pos_scale + z_trans);
    const vec3& cp = trans * (position[2] * pos_scale + z_trans);
    const vec3& dp = trans * (position[3] * pos_scale + z_trans);

    const vec2& at = texture[0] * uv_scale;
    const vec2& bt = texture[1] * uv_scale;
    const vec2& ct = texture[2] * uv_scale;
    const vec2& dt = texture[3] * uv_scale;

    //const float base_intensity = _intensity(trans, normal[i]);
    const float base_intensity = 1.0f;

    quad_type_7_maybe_clip(writer,
                           ap, at,
                           bp, bt,
                           cp, ct,
                           dp, dt,
                           base_intensity);
  }
}
