#include "holly/isp_tsp.hpp"

#include "platform/font.hpp"
#include "platform/ta_parameter_presets.hpp"
#include "platform/texture.hpp"

#include "printf/unparse.h"

namespace font {

  static inline vec2 transform_glyph_texture(const face& face, const vec2& t, int char_code)
  {
    int row = char_code / face.row_stride;
    int col = char_code % face.row_stride;

    return {
      (float)(col * face.width  + t.x * face.width) / (float)(face.texture_width),
      (float)(row * face.height + t.y * face.height) / (float)(face.texture_height),
    };
  }

  static inline vec3 transform_glyph_position(const face& face, const vec2& p, float x, float y, float z)
  {
    return {
      p.x * face.width + x,
      p.y * face.height + y,
      z
    };
  }

  void face::global(ta_parameter_writer& writer) const
  {
    global_polygon_textured(writer,
                            para_control::list_type::punch_through,
                            texture_offset,
                            texture_size | tsp_instruction_word::dst_alpha_instr::inverse_src_alpha,
                            texture_control_word::pixel_format::_4bpp_palette);
  }

  void face::draw_glyph(ta_parameter_writer& writer,
                        const vec3& p,
                        int c,
                        uint32_t base_color) const
  {
    static const vec2 vtx[] = {
      { 0, 0 },
      { 1, 0 },
      { 1, 1 },
      { 0, 1 },
    };

    if (c <= 0x20 || c >= 0x7f) {
      return;
    }
    c -= 0x20;

    vec3 ap = transform_glyph_position(*this, vtx[0], p.x, p.y, p.z);
    vec3 bp = transform_glyph_position(*this, vtx[1], p.x, p.y, p.z);
    vec3 cp = transform_glyph_position(*this, vtx[2], p.x, p.y, p.z);
    vec3 dp = transform_glyph_position(*this, vtx[3], p.x, p.y, p.z);

    vec2 at = transform_glyph_texture(*this, vtx[0], c);
    vec2 bt = transform_glyph_texture(*this, vtx[1], c);
    vec2 ct = transform_glyph_texture(*this, vtx[2], c);
    vec2 dt = transform_glyph_texture(*this, vtx[3], c);

    quad_type_3(writer,
                ap, at,
                bp, bt,
                cp, ct,
                dp, dt,
                base_color);
  }

  static inline int format_float(char * s, float num)
  {
    int offset = 0;
    bool negative = num < 0;
    if (negative) {
      s[offset++] = '-';
      num = -num;
    }
    int32_t whole = num;
    offset += unparse_base10_unsigned(&s[offset], whole, 0, 0);
    s[offset++] = '.';
    int32_t fraction = (int32_t)((num - (float)whole) * 10.0);
    if (fraction < 0)
      fraction = -fraction;
    offset += unparse_base10_unsigned(&s[offset], fraction, 1, '0');
    return offset;
  }

  int face::draw_float(ta_parameter_writer& writer,
                       const vec3& p,
                       float num,
                       uint32_t base_color,
                       int length) const
  {
    char s[20];
    int offset = format_float(s, num);
    s[offset] = 0;
    float x = p.x;
    if (offset < length) {
      x += hori_advance * (length - offset);
    }
    draw_string(writer, {x, p.y, p.z}, s, base_color);
    return length >= offset ? length : offset;
  }

  static inline int format_int(char * s, int num)
  {
    int offset = 0;
    bool negative = num < 0;
    if (negative) {
      s[offset++] = '-';
      num = -num;
    }
    int whole = num;
    offset += unparse_base10_unsigned(&s[offset], whole, 0, 0);
    return offset;
  }

  int face::draw_int(ta_parameter_writer& writer,
                     const vec3& p,
                     int num,
                     uint32_t base_color,
                     int length) const
  {
    char s[20];
    int offset = format_int(s, num);
    s[offset] = 0;
    float x = p.x;
    if (offset < length) {
      x += hori_advance * (length - offset);
    }
    draw_string(writer, {x, p.y, p.z}, s, base_color);
    return length >= offset ? length : offset;
  }

  void face::draw_mat4(ta_parameter_writer& writer,
                       const vec3& p,
                       const mat4x4& mat,
                       uint32_t base_color,
                       int value_length) const
  {
    int len;
    vec3 pos = p;
    for (int row = 0; row < 4; row++) {
      len = draw_float(writer, pos, mat[row][0], base_color, value_length);
      pos.x += hori_advance * len;
      len = draw_float(writer, pos, mat[row][1], base_color, value_length);
      pos.x += hori_advance * len;
      len = draw_float(writer, pos, mat[row][2], base_color, value_length);
      pos.x += hori_advance * len;
      len = draw_float(writer, pos, mat[row][3], base_color, value_length);
      pos.x = p.x;
      pos.y += height;
    }
  }

  const face ter_u12n = {
    .texture_size = tsp_instruction_word::texture_u_size::from_int(128)
                  | tsp_instruction_word::texture_v_size::from_int(64),
    .texture_offset = texture::offset::ter_u12n,
    .texture_width = 128,
    .texture_height = 64,
    .hori_advance = 6,
    .width = 6,
    .height = 12,
    .row_stride = 21,
  };

  const face ter_u32n = {
    .texture_size = tsp_instruction_word::texture_u_size::from_int(256)
                  | tsp_instruction_word::texture_v_size::from_int(256),
    .texture_offset = texture::offset::ter_u32n,
    .texture_width = 256,
    .texture_height = 256,
    .hori_advance = 16,
    .width = 16,
    .height = 32,
    .row_stride = 16,
  };
};
