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

#include "platform/graphics_primitive.hpp"
#include "platform/font.hpp"
#include "platform/emulator_detected.hpp"

#include "demo/lizard/main.hpp"
#include "demo/lizard/levels.hpp"

#include "texture/igh25_box_top_32.data.h"

#include "sh7091/sh7091.hpp"
#include "sh7091/sh7091_bits.hpp"

#include "xm_player/playlist.hpp"

#include "assert.h"

extern float alpha_mul;

const int lizard_walking_frames[] = {
  texture::offset::walking_frame0000,
  texture::offset::walking_frame0001,
  texture::offset::walking_frame0002,
  texture::offset::walking_frame0003,
  texture::offset::walking_frame0004,
  texture::offset::walking_frame0005,
  texture::offset::walking_frame0006,
};
const int lizard_walking_frames_count = (sizeof (lizard_walking_frames)) / (sizeof (lizard_walking_frames[0]));

const int lizard_turning_frames[] = {
  texture::offset::turning_frame0000,
  texture::offset::turning_frame0001,
  texture::offset::turning_frame0002,
  texture::offset::turning_frame0003,
  texture::offset::turning_frame0004,
  texture::offset::turning_frame0005,
  texture::offset::turning_frame0006,
};
const int lizard_turning_frames_count = (sizeof (lizard_turning_frames)) / (sizeof (lizard_turning_frames[0]));

int last_drawn_frame;

namespace demo {
  static inline void print_mat4x4(const mat4x4& mat)
  {
    for (int i = 0; i < 4; i++) {
      printf("%f %f %f %f\n", mat[i][0], mat[i][1], mat[i][2], mat[i][3]);
    }
  }

  world::platform * find_end_platform(world::level& level)
  {
    float end_x = 81;
    float end_z = 212;
    for (int i = 0; i < level.platforms_length; i++) {
      world::platform * p = &level.platforms[i];
      if (p->position.x == end_x && p->position.z == end_z) {
        return p;
      }
    }
    return nullptr;
  }

  mat4x4 lizard::init()
  {
    button_press = false;
    y_press_count = 0;
    platform_touch_count = 0;
    total_tick = 0;

    vx = -pi / 3.5;
    vy = -pi / 8;

    current_level = &demo::igh25_map1_level;

    world::table_build(*current_level);
    end_platform = find_end_platform(*current_level);

    lizard_position = {2.5, 1, 2.5};
    //lizard_position = {218.5, 0, 65.5};
    //lizard_position = {81, 100, 212};
    lizard_velocity = {0, 0, 0};

    if (!emulator_detected) {
      emulator_detected_hud_frames = 60 * 30;
      playlist::next();
    } else {
      emulator_detected_hud_frames = 0;
    }

    return view_trans;
  }

  static float sign(float n)
  {
    if (n > 0)
      return 1;
    else
      return -1;
  }

  void lizard::update()
  {
    if (end_platform->touched) {
      sh7091.TMU.TSTR &= (~tmu::tstr::str1::counter_start) & 0xff; // stop TCNT1
    }

    lizard_rotation *= 0.8;

    lizard_turning_frame += lizard_rotation * 5;

    lizard_heading += lizard_rotation;

    if (abs(lizard_velocity.x) > 0.10)
      lizard_velocity.x = 0.10 * sign(lizard_velocity.x);
    if (abs(lizard_velocity.y) > 0.20)
      lizard_velocity.y = 0.20 * sign(lizard_velocity.y);
    if (abs(lizard_velocity.z) > 0.10)
      lizard_velocity.z = 0.10 * sign(lizard_velocity.z);

    lizard_velocity.x *= 0.8;
    lizard_velocity.y *= 0.99;
    lizard_velocity.z *= 0.8;

    vec2 frame_velocity = vec2(lizard_velocity.x, lizard_velocity.z);
    lizard_walking_frame += magnitude(frame_velocity) * 5;

    world::platform * p = lizard_collide();
    collided = (p != nullptr);
    if (!collided) {
      lizard_velocity.y -= 0.01;
    } else {
      if (p->touched == false) {
        platform_touch_count += 1;
        last_platform1 = last_platform;
        last_platform = p;
      }

      p->touched = true;
      //lizard_position.y -= -lizard_velocity.y;
      float pp = p->position.y + p->scale.y * 0.5;
      //lizard_velocity.y *= 0.1;
      lizard_position.y = pp + 0.5;
    }

    lizard_position.x += lizard_velocity.x;
    lizard_position.y += lizard_velocity.y;
    lizard_position.z += lizard_velocity.z;

    view_trans
      = translate(vec3(0, 0, 3))
      * rotate_x(vy)
      * rotate_y(vx)
      * translate(-lizard_position);
  }

  void lizard::y()
  {
    y_press_count += 1;
    lizard_velocity = {0, 0, 0};
    if (last_platform1 == nullptr) {
      lizard_position = {2.5, 1, 2.5};
    } else {
      lizard_position = last_platform1->position;
      lizard_position.y += 0.5;
      if (last_platform->touched == true)
        platform_touch_count -= 1;
      last_platform->touched = false;
      last_platform = last_platform1;
    }
  }

  void lizard::a()
  {
    if (!button_press) {
      button_press = true;

      sh7091.TMU.TSTR &= (~tmu::tstr::str1::counter_start) & 0xff; // stop TCNT1
      sh7091.TMU.TOCR = tmu::tocr::tcoe::tclk_is_external_clock_or_input_capture;
      sh7091.TMU.TCR1 = tmu::tcr1::tpsc::p_phi_1024; // 1024 / 50MHz = 20.48 μs
      sh7091.TMU.TCOR1 = 0xffff'ffff;
      sh7091.TMU.TCNT1 = 0xffff'ffff;
      sh7091.TMU.TSTR |= tmu::tstr::str1::counter_start;

      total_tick = 0xffff'ffff;
    }
    //lizard_velocity.y -= 0.01;
    if (collided) {
      lizard_velocity.y = 30;
    }
  }

  void lizard::start()
  {
  }

  void lizard::ra()
  {
    vx += 0.01f;
  }

  void lizard::la()
  {
    vx -= 0.01f;
  }

  void lizard::ua()
  {
    vy += 0.01f;
  }

  void lizard::da()
  {
    vy -= 0.01f;
  }

  void lizard::x()
  {
    //lizard_rotation += pi * (1.0f / 160);
  }

  void lizard::b()
  {
    //lizard_rotation -= pi * (1.0f / 160);
  }

  void lizard::analog(float dl, float dr, float dx, float dy)
  {
    //float forward = (dr - dl) * -0.001f;
    //alpha_mul += forward;
    float forward = (dr - dl) * -0.1f;
    //vx += forward * 0.5f;

    lizard_velocity.x += -cos(lizard_heading) * forward;
    lizard_velocity.z += sin(lizard_heading) * forward;

    lizard_rotation += -dx * 0.03f;

    //vy += dy * 0.03f;
    //vx += dx * 0.03f;
  }

  world::platform * lizard::lizard_collide()
  {
    world::platform * p = world::table_lookup(lizard_position.x, lizard_position.z);
    if (p == nullptr)
      return nullptr;

    float ym = lizard_position.y - 0.5;
    float yp = lizard_position.y + 0.5;

    float pm = p->position.y - p->scale.y * 0.5;
    float pp = p->position.y + p->scale.y * 0.5;

    if (ym < pp && yp > pm)
      return p;
    else
      return nullptr;
  }

  const char * emulator_speech[] = {
    "This counterfeit Dreamcast failed a",
    "CORE rasterization test.",
    "",
    "Dreamcast emulator behavior is highly",
    "divergent from genuine Sega Dreamcast",
    "hardware.",
    "",
    "Some emulator authors deliberately",
    "choose to forgo accuracy, and instead ",
    "develop a distinct and unrelated",
    "fantasy-platform."
  };
  const int emulator_speech_lines = (sizeof (emulator_speech)) / (sizeof (emulator_speech[0]));

  void lizard::draw_hud(ta_parameter_writer& writer)
  {
    const int framebuffer_width_2 = framebuffer::framebuffer.px_width >> 1;
    const int framebuffer_height_2 = framebuffer::framebuffer.px_height >> 1;
    vec3 center_p = vec3(0, 5, 10);

    font::ter_u12n.global(writer);
    {
      const int title_length = 12;
      const int title_width_2 = (font::ter_u12n.hori_advance * title_length) >> 1;
      const int x = framebuffer_width_2 - title_width_2;
      center_p.x = x;
      //font::ter_u12n.draw_string(writer, center_p, "demo: lizard", 0xffffffff);
    }
    //center_p.y += font::ter_u12n.height;
    {
      const int title_length = 46;
      const int title_width_2 = (font::ter_u12n.hori_advance * title_length) >> 1;
      const int x = framebuffer_width_2 - title_width_2;
      center_p.x = x;
      font::ter_u12n.draw_string(writer, center_p, "objective: find and touch the glowing platform", 0xffffffff);
    }

    vec3 status_p = vec3(10, framebuffer::framebuffer.px_height - 24, 10);
    font::ter_u12n.draw_string(writer, status_p, "score:", 0xffffffff);
    //font::ter_u12n.draw_float(writer, status_p, (float)last_drawn_frame, 0xffffffff, 10);
    status_p.x += font::ter_u12n.hori_advance * 7;
    font::ter_u12n.draw_int(writer, status_p, platform_touch_count, 0xffffffff, 4);

    vec3 status_q = vec3(framebuffer::framebuffer.px_width - font::ter_u12n.hori_advance * 15, framebuffer::framebuffer.px_height - 24, 10);
    font::ter_u12n.draw_string(writer, status_q, "y presses:", 0xffffffff);
    status_q.x += font::ter_u12n.hori_advance * 11;
    font::ter_u12n.draw_int(writer, status_q, y_press_count, 0xffffffff, 0);

    vec3 status_r = vec3(framebuffer::framebuffer.px_width - font::ter_u12n.hori_advance * 15, framebuffer::framebuffer.px_height - (24 + font::ter_u12n.height), 10);
    font::ter_u12n.draw_string(writer, status_r, "time:", 0xffffffff);
    float time = (float)(0xffffffff - sh7091.TMU.TCNT1) * (0.020480f / 1000.f);
    font::ter_u12n.draw_float(writer, status_r, time, 0xffffffff, 12);

    const int height_2 = (font::ter_u32n.height * (emulator_speech_lines + 2)) >> 1;
    const int y = framebuffer_height_2 - height_2;
    vec3 center_e = vec3(8, y, 10);
    if (emulator_detected_hud_frames < 60 * 30) {
      font::ter_u32n.global(writer);;
      for (int i = 0; i < emulator_speech_lines; i++) {
        font::ter_u32n.draw_string(writer, center_e, emulator_speech[i], 0xffffffff);
        font::ter_u32n.draw_string(writer, {center_e.x + 2, center_e.y + 0, center_e.z - 1}, emulator_speech[i], 0x00000000);
        font::ter_u32n.draw_string(writer, {center_e.x - 2, center_e.y + 0, center_e.z - 1}, emulator_speech[i], 0x00000000);
        font::ter_u32n.draw_string(writer, {center_e.x + 0, center_e.y + 2, center_e.z - 1}, emulator_speech[i], 0x00000000);
        font::ter_u32n.draw_string(writer, {center_e.x + 0, center_e.y - 2, center_e.z - 1}, emulator_speech[i], 0x00000000);
        center_e.y += font::ter_u32n.height;
      }
      center_e.y += font::ter_u32n.height;
      int timeout = 30 - (emulator_detected_hud_frames / 60);
      font::ter_u32n.draw_int(writer, center_e, timeout, 0xffffffff, 0);
      emulator_detected_hud_frames += 1;
      if (emulator_detected_hud_frames >= 60 * 30) {
        playlist::next();
      }
    } else if (end_platform != nullptr && end_platform->touched) {
      font::ter_u32n.global(writer);
      const int title_length = 18;
      const int title_width_2 = (font::ter_u12n.hori_advance * title_length) >> 1;
      const int x = framebuffer_width_2 - title_width_2;
      center_e.x = x;

      const char * s = "objective complete";
      font::ter_u32n.draw_string(writer, center_e, s, 0xffffffff);
      font::ter_u32n.draw_string(writer, {center_e.x + 2, center_e.y + 0, center_e.z - 1}, s, 0x00000000);
      font::ter_u32n.draw_string(writer, {center_e.x - 2, center_e.y + 0, center_e.z - 1}, s, 0x00000000);
      font::ter_u32n.draw_string(writer, {center_e.x + 0, center_e.y + 2, center_e.z - 1}, s, 0x00000000);
      font::ter_u32n.draw_string(writer, {center_e.x + 0, center_e.y - 2, center_e.z - 1}, s, 0x00000000);
    }
  }

  void lizard::draw_platform(ta_parameter_writer& writer, const mat4x4& trans, const world::platform * p)
  {
    mat4x4 t
      = trans
      * translate(p->position)
      * scale(p->scale);

    float intensity_offset;
    vec3 base_color;
    if (p == end_platform) {
      base_color = vec3(0.5, 1, 0.5);
      intensity_offset = sin(end_platform_tick * 0.1f) * 0.4f + 0.5f;
      end_platform_tick += 1;
    } else if (p->touched) {
      intensity_offset = 0.5;
      base_color = vec3(1, 1, 1);
    } else {
      intensity_offset = 0;
      base_color = vec3(1, 0.5, 0.5);
    }

    draw_textured_cube(writer,
                       t,
                       p->scale,
                       texture::cube_type_1,
                       base_color,
                       intensity_offset);
  }

  void lizard::draw_lizard(ta_parameter_writer& writer, const mat4x4& trans)
  {
    mat4x4 t
      = trans
      * translate(lizard_position)
      * rotate_y(lizard_heading);

    /*
    draw_icosphere(writer,
                   t,
                   vec3(1, 0.5, 0));
    */
    if (abs(lizard_rotation) > 0.01) {
      int frame = ((int)lizard_turning_frame) % lizard_turning_frames_count;
      if (frame < 0)
        frame = lizard_turning_frames_count + frame;
      last_drawn_frame = frame;

      draw_textured_voxel(writer,
                          t,
                          lizard_turning_frames[frame]);
    } else {
      int frame = ((int)lizard_walking_frame) % lizard_walking_frames_count;
      if (frame < 0)
        frame = lizard_walking_frames_count + frame;
      last_drawn_frame = frame;

      draw_textured_voxel(writer,
                          t,
                          lizard_walking_frames[frame]);
    }
  }

  void lizard::draw_cover(ta_parameter_writer& writer, const mat4x4& trans)
  {
    mat4x4 t
      = trans
      * translate(lizard_position)
      * scale(1000.f);

    int texture_offset = playlist::playlist[playlist::state.playlist_ix].cover_ix;
    if (cover_ix_transition >= 1.0) {
      if (texture_offset != last_cover_texture_offset) {
        cover_ix_transition = 0.0;
      }
    } else {
      cover_ix_transition += 0.001f;
      if (cover_ix_transition >= 1.0) {
        last_cover_texture_offset = texture_offset;
      }
    }
    draw_textured_cube2(writer, t, last_cover_texture_offset, texture_offset, cover_ix_transition);
  }

  void lizard::draw(ta_parameter_writer& writer, const mat4x4& _)
  {
    // punch through
    const mat4x4& trans = view_trans;

    draw_hud(writer);
    draw_lizard(writer, trans);

    writer.append<ta_global_parameter::end_of_list>() =
      ta_global_parameter::end_of_list(para_control::para_type::end_of_list);

    // opaque modifier

    mat4x4 t
      = trans
      * translate(lizard_position)
      * rotate_y(lizard_heading)
      * scale(vec3(1.0, 1.0, 1.0));

    draw_lizard_mv(writer,
                   t);

    writer.append<ta_global_parameter::end_of_list>() =
      ta_global_parameter::end_of_list(para_control::para_type::end_of_list);

    // translucent

    draw_cover(writer, trans);

    writer.append<ta_global_parameter::end_of_list>() =
      ta_global_parameter::end_of_list(para_control::para_type::end_of_list);

    // opaque

    //draw_axis(writer, trans * translate(lizard_position));

    for (int i = 0; i < current_level->platforms_length; i++) {
      draw_platform(writer, trans, &current_level->platforms[i]);
    }

    writer.append<ta_global_parameter::end_of_list>() =
      ta_global_parameter::end_of_list(para_control::para_type::end_of_list);
  }
}
