#include <stdint.h>

#include "memorymap.hpp"

#include "systembus.hpp"
#include "systembus_bits.hpp"

#include "holly/background.hpp"
#include "holly/core.hpp"
#include "holly/core_bits.hpp"
#include "holly/holly.hpp"
#include "holly/isp_tsp.hpp"
#include "holly/region_array.hpp"
#include "holly/ta_bits.hpp"
#include "holly/ta_fifo_polygon_converter.hpp"
#include "holly/ta_global_parameter.hpp"
#include "holly/ta_parameter.hpp"
#include "holly/ta_vertex_parameter.hpp"
#include "holly/texture_memory_alloc9.hpp"
#include "holly/video_output.hpp"

#include "framebuffer.hpp"
#include "graphics.hpp"
#include "texture.hpp"
#include "scene/scene.hpp"
#include "ta_multiwriter.hpp"
#include "cursor.hpp"
#include "ta_parameter.hpp"

static volatile int ta_in_use = 0;
static volatile int core_in_use = 0;
static volatile int next_frame = 0;
static volatile int framebuffer_ix = 0;
static volatile int next_frame_ix = 0;

static void * op_buf = nullptr;
static int op_offset = 0;

void graphics_interrupt(uint32_t istnrm)
{
  if (istnrm & istnrm::v_blank_in) {
    system.ISTNRM = istnrm::v_blank_in;

    next_frame = 1;
    holly.FB_R_SOF1 = texture_memory_alloc.framebuffer[next_frame_ix].start;
  }

  if (istnrm & istnrm::end_of_render_tsp) {
    system.ISTNRM = istnrm::end_of_render_tsp
                  | istnrm::end_of_render_isp
                  | istnrm::end_of_render_video;

    next_frame_ix = framebuffer_ix;
    framebuffer_ix += 1;
    if (framebuffer_ix >= 3) framebuffer_ix = 0;

    core_in_use = 0;
  }

  if (istnrm & istnrm::end_of_transferring_punch_through_list) {
    system.ISTNRM = istnrm::end_of_transferring_punch_through_list;

    assert(op_buf != nullptr);
    assert(op_offset > 0);

    ta_polygon_converter_writeback(op_buf, op_offset);
    ta_polygon_converter_transfer(op_buf, op_offset);
  }

  if (istnrm & istnrm::end_of_transferring_opaque_list) {
    system.ISTNRM = istnrm::end_of_transferring_opaque_list;

    core_in_use = 1;
    core_start_render2(texture_memory_alloc.region_array.start,
                       texture_memory_alloc.isp_tsp_parameters.start,
                       texture_memory_alloc.background[1].start,
                       texture_memory_alloc.framebuffer[framebuffer_ix].start,
                       framebuffer.px_width);

    ta_in_use = 0;
  }
}

void transfer_palettes()
{
  holly.PAL_RAM_CTRL = pal_ram_ctrl::pixel_format::argb1555;

  holly.PALETTE_RAM[0] = 0;
  holly.PALETTE_RAM[1] = 0xffff;

  holly.PT_ALPHA_REF = 0x80;
}

void graphics_init()
{
  holly.SOFTRESET = softreset::pipeline_soft_reset
		  | softreset::ta_soft_reset;
  holly.SOFTRESET = 0;

  scaler_init();
  core_init();
  core_param_init();
  spg_set_mode_640x480();
  framebuffer_init();

  background_parameter2(texture_memory_alloc.background[1].start,
                        0xff800080);

  texture::transfer_texture_memory();
  transfer_palettes();
}

void graphics_scene_init(const opb_size * opb_size)
{
  region_array_multipass(framebuffer.tile_width(),
                         framebuffer.tile_height(),
                         opb_size,
                         ta_cont_count,
                         texture_memory_alloc.region_array.start,
                         texture_memory_alloc.object_list.start);
}

void graphics_cursor(ta_multiwriter& multi)
{
  global_polygon_untextured(multi.op,
                            para_control::list_type::opaque,
                            tsp_instruction_word::dst_alpha_instr::zero);

  for (int i = 0; i < 4; i++) {
    const cursor::cursor& c = cursor::state[i];
    if (!c.active)
      continue;

    quad_type_0(multi.op,
                {c.x - 1, c.y - 1, 10},
                {c.x + 1, c.y - 1, 10},
                {c.x + 1, c.y + 1, 10},
                {c.x - 1, c.y + 1, 10},
                0xffffff);
  }
}

void graphics_event(ta_multiwriter& multi)
{
  multi.op.offset = 0;
  multi.pt.offset = 0;

  graphics_cursor(multi);
  scene::current_scene->transfer(multi);

  while (ta_in_use);
  while (core_in_use);
  ta_in_use = 1;
  ta_polygon_converter_init2(texture_memory_alloc.isp_tsp_parameters.start,
                             texture_memory_alloc.isp_tsp_parameters.end,
                             texture_memory_alloc.object_list.start,
                             texture_memory_alloc.object_list.end,
                             scene::current_scene->opb_size.total(),
                             scene::current_scene->ta_alloc,
                             framebuffer.tile_width(),
                             framebuffer.tile_height());

  if (multi.op.offset != 0) {
    multi.op.append<ta_global_parameter::end_of_list>() =
      ta_global_parameter::end_of_list(para_control::para_type::end_of_list);
  }

  if (multi.pt.offset != 0) {
    multi.pt.append<ta_global_parameter::end_of_list>() =
      ta_global_parameter::end_of_list(para_control::para_type::end_of_list);
  }

  if (multi.pt.offset != 0) {
    op_buf = multi.op.buf;
    op_offset = multi.op.offset;

    ta_polygon_converter_writeback(multi.pt.buf, multi.pt.offset);
    ta_polygon_converter_transfer(multi.pt.buf, multi.pt.offset);
  } else {
    op_buf = nullptr;
    op_offset = 0;

    ta_polygon_converter_writeback(multi.op.buf, multi.op.offset);
    ta_polygon_converter_transfer(multi.op.buf, multi.op.offset);
  }

  while (next_frame == 0);
  next_frame = 0;
}
