#pragma once

#include "holly/ta_parameter.hpp"
#include "holly/ta_vertex_parameter.hpp"
#include "holly/ta_global_parameter.hpp"
#include "holly/isp_tsp.hpp"
#include "holly/texture_memory_alloc9.hpp"
#include "holly/framebuffer.hpp"

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

static inline void global_polygon_modifier_volume(ta_parameter_writer& writer)
{
   uint32_t parameter_control_word = para_control::para_type::polygon_or_modifier_volume
                                   | para_control::list_type::opaque_modifier_volume
                                   ;

   uint32_t isp_tsp_instruction_word = isp_tsp_instruction_word::volume_instruction::normal_polygon
                                     | isp_tsp_instruction_word::culling_mode::no_culling;

  writer.append<ta_global_parameter::modifier_volume>() =
    ta_global_parameter::modifier_volume(parameter_control_word,
                                         isp_tsp_instruction_word);
}

static inline void global_polygon_modifier_volume_last_in_volume(ta_parameter_writer& writer)
{
  const uint32_t last_parameter_control_word = para_control::para_type::polygon_or_modifier_volume
                                             | para_control::list_type::opaque_modifier_volume
                                             | obj_control::volume::modifier_volume::last_in_volume;

  const uint32_t last_isp_tsp_instruction_word = isp_tsp_instruction_word::volume_instruction::inside_last_polygon
                                               | isp_tsp_instruction_word::culling_mode::no_culling;

  writer.append<ta_global_parameter::modifier_volume>() =
    ta_global_parameter::modifier_volume(last_parameter_control_word,
                                         last_isp_tsp_instruction_word);
}

static inline void global_polygon_intensity(ta_parameter_writer& writer,
                                            const vec3& color,
                                            uint32_t culling_mode = isp_tsp_instruction_word::culling_mode::no_culling)
{
  const uint32_t parameter_control_word = para_control::para_type::polygon_or_modifier_volume
                                        | para_control::list_type::opaque
                                        | obj_control::col_type::intensity_mode_1
                                        ;

  const uint32_t isp_tsp_instruction_word = isp_tsp_instruction_word::depth_compare_mode::greater_or_equal
                                          | culling_mode;

  const uint32_t tsp_instruction_word = tsp_instruction_word::texture_shading_instruction::decal
                                      | tsp_instruction_word::src_alpha_instr::one
                                      | tsp_instruction_word::dst_alpha_instr::zero
                                      | tsp_instruction_word::fog_control::no_fog;

  const uint32_t texture_control_word = 0;

  writer.append<ta_global_parameter::polygon_type_1>() =
    ta_global_parameter::polygon_type_1(parameter_control_word,
                                        isp_tsp_instruction_word,
                                        tsp_instruction_word,
                                        texture_control_word,
                                        1.0f, color.x, color.y, color.z);
}

static inline void global_polygon_textured_intensity(ta_parameter_writer& writer,
                                                     const vec4& color,
                                                     int list_type,
                                                     int texture_offset,
                                                     int texture_uv_size,
                                                     int pixel_format)
{
  const uint32_t parameter_control_word = para_control::para_type::polygon_or_modifier_volume
                                        | list_type
                                        | obj_control::col_type::intensity_mode_1
                                        | obj_control::texture
                                        ;

  const uint32_t isp_tsp_instruction_word = isp_tsp_instruction_word::depth_compare_mode::greater_or_equal
                                          | isp_tsp_instruction_word::culling_mode::no_culling;//cull_if_positive;

  const uint32_t tsp_instruction_word = tsp_instruction_word::fog_control::no_fog
                                      | texture_uv_size;

  const uint32_t texture_address = texture_memory_alloc.texture.start + texture_offset;
  const uint32_t texture_control_word = pixel_format
                                      | texture_control_word::scan_order::twiddled
                                      | texture_control_word::texture_address(texture_address / 8);

  writer.append<ta_global_parameter::polygon_type_1>() =
    ta_global_parameter::polygon_type_1(parameter_control_word,
                                        isp_tsp_instruction_word,
                                        tsp_instruction_word,
                                        texture_control_word,
                                        color.w, color.x, color.y, color.z);
}

static inline void global_polygon_textured(ta_parameter_writer& writer,
                                           uint32_t list_type,
                                           uint32_t texture_offset,
                                           uint32_t texture_size,
                                           uint32_t pixel_format)
{
  const uint32_t parameter_control_word = para_control::para_type::polygon_or_modifier_volume
                                        | list_type
                                        | obj_control::col_type::packed_color
                                        | obj_control::texture
                                        ;

  const uint32_t isp_tsp_instruction_word = isp_tsp_instruction_word::depth_compare_mode::greater_or_equal
                                          | isp_tsp_instruction_word::culling_mode::no_culling;

  const uint32_t tsp_instruction_word = tsp_instruction_word::texture_shading_instruction::modulate
                                      | tsp_instruction_word::src_alpha_instr::one
                                      | tsp_instruction_word::fog_control::no_fog
                                      | texture_size;

  const uint32_t texture_address = texture_memory_alloc.texture.start + texture_offset;
  const uint32_t texture_control_word = pixel_format
                                      | texture_control_word::scan_order::twiddled
                                      | texture_control_word::texture_address(texture_address / 8);

  writer.append<ta_global_parameter::polygon_type_0>() =
    ta_global_parameter::polygon_type_0(parameter_control_word,
                                        isp_tsp_instruction_word,
                                        tsp_instruction_word,
                                        texture_control_word,
                                        0, // data_size_for_sort_dma
                                        0  // next_address_for_sort_dma
                                        );
}

static inline void quad_type_2(ta_parameter_writer& writer,
                               const vec3& ap,
                               const vec3& bp,
                               const vec3& cp,
                               const vec3& dp,
                               float base_intensity)
{
  writer.append<ta_vertex_parameter::polygon_type_2>() =
    ta_vertex_parameter::polygon_type_2(polygon_vertex_parameter_control_word(false),
                                        ap.x, ap.y, ap.z,
                                        base_intensity);

  writer.append<ta_vertex_parameter::polygon_type_2>() =
    ta_vertex_parameter::polygon_type_2(polygon_vertex_parameter_control_word(false),
                                        bp.x, bp.y, bp.z,
                                        base_intensity);

  writer.append<ta_vertex_parameter::polygon_type_2>() =
    ta_vertex_parameter::polygon_type_2(polygon_vertex_parameter_control_word(false),
                                        dp.x, dp.y, dp.z,
                                        base_intensity);

  writer.append<ta_vertex_parameter::polygon_type_2>() =
    ta_vertex_parameter::polygon_type_2(polygon_vertex_parameter_control_word(true),
                                        cp.x, cp.y, cp.z,
                                        base_intensity);
}

static inline void tri_type_2(ta_parameter_writer& writer,
                              const vec3& ap,
                              const vec3& bp,
                              const vec3& cp,
                              float base_intensity)
{
  writer.append<ta_vertex_parameter::polygon_type_2>() =
    ta_vertex_parameter::polygon_type_2(polygon_vertex_parameter_control_word(false),
                                        ap.x, ap.y, ap.z,
                                        base_intensity);

  writer.append<ta_vertex_parameter::polygon_type_2>() =
    ta_vertex_parameter::polygon_type_2(polygon_vertex_parameter_control_word(false),
                                        bp.x, bp.y, bp.z,
                                        base_intensity);

  writer.append<ta_vertex_parameter::polygon_type_2>() =
    ta_vertex_parameter::polygon_type_2(polygon_vertex_parameter_control_word(true),
                                        cp.x, cp.y, cp.z,
                                        base_intensity);
}

static inline void quad_type_3(ta_parameter_writer& writer,
                               const vec3& ap, const vec2& at,
                               const vec3& bp, const vec2& bt,
                               const vec3& cp, const vec2& ct,
                               const vec3& dp, const vec2& dt,
                               uint32_t base_color)
{
  writer.append<ta_vertex_parameter::polygon_type_3>() =
    ta_vertex_parameter::polygon_type_3(polygon_vertex_parameter_control_word(false),
                                        ap.x, ap.y, ap.z,
                                        at.x, at.y,
                                        base_color, 0);

  writer.append<ta_vertex_parameter::polygon_type_3>() =
    ta_vertex_parameter::polygon_type_3(polygon_vertex_parameter_control_word(false),
                                        bp.x, bp.y, bp.z,
                                        bt.x, bt.y,
                                        base_color, 0);

  writer.append<ta_vertex_parameter::polygon_type_3>() =
    ta_vertex_parameter::polygon_type_3(polygon_vertex_parameter_control_word(false),
                                        dp.x, dp.y, dp.z,
                                        dt.x, dt.y,
                                        base_color, 0);

  writer.append<ta_vertex_parameter::polygon_type_3>() =
    ta_vertex_parameter::polygon_type_3(polygon_vertex_parameter_control_word(true),
                                        cp.x, cp.y, cp.z,
                                        ct.x, ct.y,
                                        base_color, 0);
}

static inline void quad_type_7(ta_parameter_writer& writer,
                               const vec3& ap, const vec2& at,
                               const vec3& bp, const vec2& bt,
                               const vec3& cp, const vec2& ct,
                               const vec3& dp, const vec2& dt,
                               float base_intensity)
{
  writer.append<ta_vertex_parameter::polygon_type_7>() =
    ta_vertex_parameter::polygon_type_7(polygon_vertex_parameter_control_word(false),
                                        ap.x, ap.y, ap.z,
                                        at.x, at.y,
                                        base_intensity, 0);

  writer.append<ta_vertex_parameter::polygon_type_7>() =
    ta_vertex_parameter::polygon_type_7(polygon_vertex_parameter_control_word(false),
                                        bp.x, bp.y, bp.z,
                                        bt.x, bt.y,
                                        base_intensity, 0);

  writer.append<ta_vertex_parameter::polygon_type_7>() =
    ta_vertex_parameter::polygon_type_7(polygon_vertex_parameter_control_word(false),
                                        dp.x, dp.y, dp.z,
                                        dt.x, dt.y,
                                        base_intensity, 0);

  writer.append<ta_vertex_parameter::polygon_type_7>() =
    ta_vertex_parameter::polygon_type_7(polygon_vertex_parameter_control_word(true),
                                        cp.x, cp.y, cp.z,
                                        ct.x, ct.y,
                                        base_intensity, 0);
}

static inline void tri_type_7(ta_parameter_writer& writer,
                              const vec3& ap, const vec2& at,
                              const vec3& bp, const vec2& bt,
                              const vec3& cp, const vec2& ct,
                              float base_intensity)
{
  writer.append<ta_vertex_parameter::polygon_type_7>() =
    ta_vertex_parameter::polygon_type_7(polygon_vertex_parameter_control_word(false),
                                        ap.x, ap.y, ap.z,
                                        at.x, at.y,
                                        base_intensity, 0);

  writer.append<ta_vertex_parameter::polygon_type_7>() =
    ta_vertex_parameter::polygon_type_7(polygon_vertex_parameter_control_word(false),
                                        bp.x, bp.y, bp.z,
                                        bt.x, bt.y,
                                        base_intensity, 0);

  writer.append<ta_vertex_parameter::polygon_type_7>() =
    ta_vertex_parameter::polygon_type_7(polygon_vertex_parameter_control_word(true),
                                        cp.x, cp.y, cp.z,
                                        ct.x, ct.y,
                                        base_intensity, 0);
}

#define _fsrra(n) (1.0f / (sqrt<float>(n)))
static inline vec3 screen_transform(const vec3& v)
{
  float tx = framebuffer::framebuffer.px_width / 2.f;
  float ty = framebuffer::framebuffer.px_height / 2.f;
  float s = framebuffer::framebuffer.px_height / 2.f;

  float z = _fsrra(v.z);
  float z2 = z * z;
  return {(-v.x) * s * z2 + tx , (-v.y) * s * z2 + ty , z2};
}
#undef _fsrra

static inline void tri_type_7_clip(ta_parameter_writer& writer,
                                   const vec3& ap, const vec2& at,
                                   const vec3& bp, const vec2& bt,
                                   const vec3& cp, const vec2& ct,
                                   float base_intensity)
{
  const vec3 plane_point = {0.f, 0.f, 0.5f};
  const vec3 plane_normal = {0.f, 0.f, 1.f};

  vec3 preclip_position[] = {ap, bp, cp};
  vec2 preclip_texture[]  = {at, bt, ct};

  vec3 clip_position[4];
  vec2 clip_texture[4];

  int output_length = geometry::clip_polygon_2<3>(clip_position,
                                                  clip_texture ,
                                                  plane_point,
                                                  plane_normal,
                                                  preclip_position,
                                                  preclip_texture);

  {
    vec3 ap = screen_transform(clip_position[0]);
    vec3 bp = screen_transform(clip_position[1]);
    vec3 cp = screen_transform(clip_position[2]);

    const vec2& at = clip_texture[0];
    const vec2& bt = clip_texture[1];
    const vec2& ct = clip_texture[2];

    if (output_length >= 4) {
      vec3 dp = screen_transform(clip_position[3]);
      const vec2& dt = clip_texture[3];
      quad_type_7(writer,
                  ap, at,
                  bp, bt,
                  cp, ct,
                  dp, dt,
                  base_intensity);
    } else {
      tri_type_7(writer,
                 ap, at,
                 bp, bt,
                 cp, ct,
                 base_intensity);
    }
  }
}

static inline void quad_type_7_maybe_clip(ta_parameter_writer& writer,
                                          const vec3& ap, const vec2& at,
                                          const vec3& bp, const vec2& bt,
                                          const vec3& cp, const vec2& ct,
                                          const vec3& dp, const vec2& dt,
                                          float base_intensity)
{
  if (ap.z < 0 && bp.z < 0 && cp.z < 0 && dp.z < 0) {
    return;
  }

  if (ap.z < 0 || bp.z < 0 || cp.z < 0 || dp.z < 0) {
    tri_type_7_clip(writer,
                    ap, at,
                    bp, bt,
                    cp, ct,
                    base_intensity);

    tri_type_7_clip(writer,
                    ap, at,
                    cp, ct,
                    dp, dt,
                    base_intensity);
  } else {
    quad_type_7(writer,
                screen_transform(ap), at,
                screen_transform(bp), bt,
                screen_transform(cp), ct,
                screen_transform(dp), dt,
                base_intensity);
  }
}
