#include <cstdint>

#include "holly/object_list_data.hpp"
#include "holly/isp_tsp.hpp"

namespace software_ta {

constexpr uint32_t tile_size = 32;

constexpr uint32_t framebuffer_width = 640;
constexpr uint32_t framebuffer_height = 480;

constexpr uint32_t tile_width = framebuffer_width / tile_size;
constexpr uint32_t tile_height = framebuffer_height / tile_size;

struct point {
  float x;
  float y;
};

struct quad {
  point a;
  point b;
  point c;
  point d;
};

struct bounding_box {
  point min;
  point max;

  bounding_box(const quad q)
  {
    min.x = (q.a.x < q.b.x) ? q.a.x : q.b.x;
    min.x = (min.x < q.c.x) ? min.x : q.c.x;
    min.x = (min.x < q.d.x) ? min.x : q.d.x;

    min.y = (q.a.y < q.b.y) ? q.a.y : q.b.y;
    min.y = (min.y < q.c.y) ? min.y : q.c.y;
    min.y = (min.y < q.d.y) ? min.y : q.d.y;

    max.x = (q.a.x > q.b.x) ? q.a.x : q.b.x;
    max.x = (max.x > q.c.x) ? max.x : q.c.x;
    max.x = (max.x > q.d.x) ? max.x : q.d.x;

    max.y = (q.a.y > q.b.y) ? q.a.y : q.b.y;
    max.y = (max.y > q.c.y) ? max.y : q.c.y;
    max.y = (max.y > q.d.y) ? max.y : q.d.y;
  }
};

template <int32_t max>
constexpr uint32_t clamp_int(const float x)
{
  const int32_t n = static_cast<int32_t>(x);
  return (n < 0 ? 0 : (n >= max ? (max - 1) : x));
}

constexpr uint32_t tile_start_x(bounding_box bb)
{
  return clamp_int<framebuffer_width>(__builtin_floorf(bb.min.x / tile_size));
}

constexpr uint32_t tile_start_y(bounding_box bb)
{
  return clamp_int<framebuffer_height>(__builtin_floorf(bb.min.y / tile_size));
}

constexpr uint32_t tile_end_x(bounding_box bb)
{
  return clamp_int<framebuffer_height>(__builtin_ceilf(bb.max.x / tile_size));
}

constexpr uint32_t tile_end_y(bounding_box bb)
{
  return clamp_int<framebuffer_height>(__builtin_ceilf(bb.max.y / tile_size));
}

template <int N>
struct object_pointer_block {
  uint32_t pointer[N];
};
static_assert((sizeof (object_pointer_block<8>)) == 32);

template <int N>
void object_pointer_blocks(volatile uint32_t * mem, const quad& quad)
{
  auto block = reinterpret_cast<volatile object_pointer_block<N> *>(mem);
  uint8_t tile_indices[tile_height][tile_width];
  for (uint32_t i = 0; i < tile_width * tile_height; i++) {
    reinterpret_cast<uint8_t *>(tile_indices)[i] = 0;
  }

  bounding_box bb(quad);

  for (uint32_t y = tile_start_y(bb); y < tile_end_y(bb); y++) {
    for (uint32_t x = tile_start_x(bb); x < tile_end_x(bb); x++) {
      uint8_t& ix = tile_indices[y][x];
      auto& opb = block[y * tile_width + x];
      opb.pointer[ix] = object_list_data::pointer_type::quad_array
                      | object_list_data::quad_array::number_of_quads(0)
                      | object_list_data::quad_array::skip(1)
                      | object_list_data::quad_array::start(0);
      ix++;
    }
  }

  for (uint32_t y = 0; y < tile_height; y++) {
    for (uint32_t x = 0; x < tile_width; x++) {
      uint8_t& ix = tile_indices[y][x];
      auto& opb = block[y * tile_width + x];
      opb.pointer[ix] = object_list_data::pointer_type::object_pointer_block_link
                      | object_list_data::object_pointer_block_link::end_of_list;
      ix++;
    }
  }
}

template <int N>
void opb_checkerboard_pattern(volatile uint32_t * mem)
{
  auto block = reinterpret_cast<volatile object_pointer_block<N> *>(mem);
  for (uint32_t y = 0; y < tile_height; y++) {
    for (uint32_t x = 0; x < tile_width; x++) {
      if (((x + (y & 1)) & 1) == 0) continue;

      auto& opb = block[y * tile_width + x];
      opb.pointer[0] = object_list_data::pointer_type::object_pointer_block_link
                      | object_list_data::object_pointer_block_link::end_of_list;
    }
  }
}

struct __untextured_quad_vertex {
  float x;
  float y;
  float z;
  uint32_t color;
};

struct __untextured_quad {
  uint32_t isp_tsp_instruction_word;
  uint32_t tsp_instruction_word;
  uint32_t texture_control_word;
  __untextured_quad_vertex a;
  __untextured_quad_vertex b;
  __untextured_quad_vertex c;
  __untextured_quad_vertex d;
};

void isp_tsp_parameters(volatile uint32_t * mem, const quad& quad)
{
  auto params = reinterpret_cast<volatile __untextured_quad *>(mem);
  params->isp_tsp_instruction_word = isp_tsp_instruction_word::depth_compare_mode::greater
                                   | isp_tsp_instruction_word::culling_mode::no_culling
                                   | isp_tsp_instruction_word::gouraud_shading;

  params->tsp_instruction_word = tsp_instruction_word::src_alpha_instr::one
                               | tsp_instruction_word::dst_alpha_instr::zero
                               | tsp_instruction_word::fog_control::no_fog;

  params->texture_control_word = 0;

  params->a.x = quad.a.x;
  params->a.y = quad.a.y;
  params->a.z = 0.1f;
  params->b.color = 0xff0000ff; // invalid

  params->b.x = quad.b.x;
  params->b.y = quad.b.y;
  params->b.z = 0.1f;
  params->b.color = 0xff00ff00; // invalid

  params->c.x = quad.c.x;
  params->c.y = quad.c.y;
  params->c.z = 0.1f;
  params->c.color = 0xffff0000;

  params->d.x = quad.d.x;
  params->d.y = quad.d.y;
  params->d.z = 0.f; // invalid
  params->b.color = 0xff00ff00; // invalid
}

}

/*
int main()
{
  using namespace software_ta;

  quad q = {
    {200.f, 360.f},
    {200.f, 120.f},
    {440.f, 120.f},
    {440.f, 360.f},
  };

  uint32_t opb_mem[300 * 8];

  object_pointer_blocks<8>(opb_mem, q);
  for (int i = 0; i < 300 * 8; i++) {
    if ((i & 7) == 0)
      std::cout << '\n' << std::dec << i / 8 << ' ';
    std::cout << std::hex << opb_mem[i] << ' ';
  }
  std::cout << '\n';

  uint32_t param_mem[32];
  for (int i = 0; i < 32; i++) {
    param_mem[i] = 0xeeeeeeee;
  }
  isp_tsp_parameters(param_mem, q);
  for (int i = 0; i < 32; i++) {
    std::cout << std::hex << param_mem[i] << '\n';
  }
}
*/
