#include "render.hpp"
#include "ansi_parser.hpp"
#include <cmath>
#include <fmt/core.h>
#include <array>
#include "map.hpp"
#include <iostream>
#include "color.hpp"

using namespace fmt;

SFMLRender::SFMLRender() :
  $window(sf::VideoMode($config.video_x,$config.video_y), "Roguish"),
  $map_font_size(0),
  $line_spacing(0),
  $default_fg(color::LIGHT_MID),
  $default_bg(color::BLACK),
  $ansi($default_fg, $default_bg)
{
  // force true color, but maybe I want to support different color sets
  $font.loadFromFile("./assets/text.otf");
  $font.setSmooth(false);
  $ui_text.setFont($font);
  $ui_text.setPosition(0,0);
  $ui_text.setCharacterSize($config.ui_font_size);
  $ui_text.setFillColor(color::LIGHT_MID);
  sf::Glyph glyph = $font.getGlyph($config.ui_base_char, $config.ui_font_size, false);
  $ui_bounds = glyph.bounds;
}

sf::Sprite &SFMLRender::get_text_sprite(wchar_t tile) {
  if(!$sprites.contains(tile)) {
    sf::Glyph glyph = $font.getGlyph(tile, $map_font_size, false);
    // WARNING! we actually have to do this here because SFML caches
    // the glyphs on the font texture, so this gets loaded each time
    // we get a new glyph from the font.
    $font_texture = $font.getTexture($map_font_size);
    sf::Sprite sprite($font_texture);
    sprite.setTextureRect(glyph.textureRect);
    $sprites[tile] = sprite;
  }

  return $sprites[tile];
}


bool SFMLRender::resize_grid(int new_size, Panel &panel_out) {
  auto glyph = $font.getGlyph($config.bg_tile, new_size, false);
  int view_x = std::ceil(($config.video_x - panel_out.x) / glyph.bounds.width);
  int view_y = std::ceil(($config.video_y - panel_out.y) / glyph.bounds.height);

  // looks good, set 'em all
  $base_glyph = glyph;
  $map_font_size = new_size;
  $sprites.clear(); // need to reset the sprites for the new size
  $line_spacing = $font.getLineSpacing($map_font_size);
  $bg_sprite = get_text_sprite($config.bg_tile);
  $bg_bounds = $bg_sprite.getLocalBounds();
  panel_out.resize(view_x, view_y);
  return true;
}

inline void configure_tile(const sf::Sprite &sprite, sf::FloatRect &sp_bounds,  sf::FloatRect bg_bounds, float &width_delta, float &height_delta) {
  // should look into caching all this instead of calcing it each time
  sp_bounds = sprite.getLocalBounds();

  // calculate where to center the sprite, but only if it's smaller
  width_delta = bg_bounds.width > sp_bounds.width ? (bg_bounds.width - sp_bounds.width) / 2 : 0;
  height_delta = bg_bounds.height > sp_bounds.width ? (bg_bounds.height - sp_bounds.height) / 2 : 0;
}

void SFMLRender::render_grid(const std::wstring &text, sf::Color default_fg, sf::Color default_bg, float x, float y) {
  wchar_t last_tile = '#';
  sf::FloatRect sp_bounds;
  float width_delta = 0;
  float height_delta = 0;
  sf::Sprite &sprite = get_text_sprite(last_tile);
  const float start_x = x;
  // BUG: get default_fg from panel too
  sf::Color cur_fg = default_fg;
  sf::Color cur_bg = default_bg;

  // make a copy so we don't modify the cached one
  $ansi.parse(text, [&](sf::Color fg, sf::Color bg) {
      cur_fg = fg;
      cur_bg = bg;
    },

    [&](wchar_t tile) {
      switch(tile) {
        case '\r': break; // ignore it
        case '\n': {
            // don't bother processing newlines, just skip
            y += $line_spacing;
            x = start_x;
          }
          break;
        default: {

          // only get a new sprite if the tile changed
          if(last_tile != tile) {
            last_tile = tile; // update last tile seen
            sprite = get_text_sprite(tile);
            configure_tile(sprite, sp_bounds, $bg_bounds, width_delta, height_delta);
          }

          sprite.setPosition({x+width_delta, y+height_delta});
          sprite.setColor(cur_fg);

          // only draw background char if it's different from default
          if(cur_bg != default_bg) {
            $bg_sprite.setPosition({x, y});
            $bg_sprite.setColor(cur_bg);
            $window.draw($bg_sprite);
          }

          $window.draw(sprite);
          // next cell
          x += $base_glyph.advance;
        }
      }
  });
}

inline sf::FloatRect draw_chunk(sf::RenderWindow& window,
    sf::FloatRect ui_bounds, sf::Text& text, sf::Color default_bg,
    sf::Color bgcolor, int bg_box_offset, float x, float y, std::wstring &out)
{
  text.setString(out);
  text.setPosition({x, y});
  // get a base character for the cell size
  sf::FloatRect bounds(x, y, ui_bounds.width * out.size(), ui_bounds.height);
  if(default_bg != bgcolor) {
    sf::RectangleShape backing({bounds.width, bounds.height});
    backing.setFillColor(bgcolor);
    backing.setPosition({bounds.left, bounds.top + bg_box_offset});
    window.draw(backing);
  }

  window.draw(text);
  out.clear();
  return bounds;
}

void SFMLRender::render_text(const std::wstring &text, sf::Color default_fg, sf::Color default_bg, float start_x, float start_y) {
  std::wstring out;
  float x = start_x;
  float y = start_y;
  sf::Color cur_bg = default_bg;

  // start with the default_fg until it's changed
  $ui_text.setFillColor(default_fg);

  $ansi.parse(text,
    [&](sf::Color fg, sf::Color bg) {
        if(out.size() > 0 ) {
          auto bounds = draw_chunk($window,
              $ui_bounds, $ui_text,
              default_bg, cur_bg, $config.bg_box_offset, x, y, out);
          x += bounds.width;
        }
        cur_bg = bg;
        $ui_text.setFillColor(fg);
    },
    [&](wchar_t tile) {
      switch(tile) {
        case '\r': break; // ignore it
        case '\n': {
          sf::FloatRect bounds;

          if(out.size() > 0) {
            bounds = draw_chunk($window, $ui_bounds,
                $ui_text, default_bg, cur_bg, $config.bg_box_offset, x, y, out);
          } else {
            bounds = $ui_text.getLocalBounds();
          }

          y += bounds.height;
          x = start_x; // reset to the original position
        }
        break;
        default:
          out += tile;
          break;
      }
    }
  );

  if(out.size() > 0) {
    draw_chunk($window, $ui_bounds, $ui_text, default_bg, cur_bg, $config.bg_box_offset, x, y, out);
  }
}

/*
 * Does not render the panel, you have to do that so you can control
 * when things render.
 */
void SFMLRender::draw(Panel &panel, float x_offset, float y_offset) {
  const std::wstring &panelout = panel.to_string();

  // BUG: ui vs bg doesn't make sense. maybe grid vs. text?
  auto bounds = panel.grid ? $bg_bounds : $ui_bounds;

  sf::RectangleShape backing(
      sf::Vector2f(bounds.width * panel.width + panel.border_px,
        bounds.height * panel.height + panel.border_px));

  backing.setFillColor(panel.default_bg);

  if(panel.has_border) {
    backing.setOutlineColor(panel.border_color);
    backing.setOutlineThickness(panel.border_px);
  }

  backing.setPosition(panel.x + x_offset, panel.y + y_offset);
  $window.draw(backing);

  if(panel.grid) {
    render_grid(panelout, panel.default_fg, panel.default_bg, panel.x + x_offset, panel.y + y_offset);
  } else {
    render_text(panelout, panel.default_fg, panel.default_bg, panel.x + x_offset, panel.y + y_offset);
  }
}
