#include "guecs/ui.hpp"
#include "guecs/sfml/backend.hpp"
#include <cassert>
#include <iostream>

namespace guecs {
  using std::make_shared;

  void Textual::init(lel::Cell &cell, shared_ptr<sf::Font> font_ptr) {
    assert(font_ptr != nullptr && "you failed to initialize this WideText");
    if(font == nullptr) font = font_ptr;
    if(text == nullptr) text = make_shared<sf::Text>(*font, content, size);
    text->setFillColor(color);

    if(centered) {
      auto bounds = text->getLocalBounds();
      auto text_cell = lel::center(bounds.size.x, bounds.size.y, cell);
      // this stupid / 2 is because SFML renders from baseline rather than from the claimed bounding box
      text->setPosition({float(text_cell.x), float(text_cell.y) - text_cell.h / 2});
    } else {
      text->setPosition({float(cell.x + padding * 2), float(cell.y + padding * 2)});
    }

    text->setCharacterSize(size);
  }

  void Textual::render(sf::RenderWindow& window, sf::Shader *shader_ptr) {
    window.draw(*text, shader_ptr);
  }

  void Textual::update(const wstring& new_content) {
    content = new_content;
    text->setString(content);
  }

  void Sprite::update(const string& new_name) {
    if(new_name != name) {
      name = new_name;
      auto sprite_texture = BACKEND->texture_get(name);
      sprite->setTexture(*sprite_texture.texture);
      sprite->setTextureRect(sprite_texture.sprite->getTextureRect());
    }
  }

  void Sprite::init(lel::Cell &cell) {
    auto sprite_texture = BACKEND->texture_get(name);

    sprite = make_shared<sf::Sprite>(
        *sprite_texture.texture,
        sprite_texture.sprite->getTextureRect());

    sprite->setPosition({
        float(cell.x + padding),
        float(cell.y + padding)});

    auto bounds = sprite->getLocalBounds();

    sprite->setScale({
        float(cell.w - padding * 2) / bounds.size.x,
        float(cell.h - padding * 2) / bounds.size.y});
  }

  void Sprite::render(sf::RenderWindow& window, sf::Shader *shader_ptr) {
    window.draw(*sprite, shader_ptr);
  }

  void Rectangle::init(lel::Cell& cell) {
    sf::Vector2f size{float(cell.w) - padding * 2, float(cell.h) - padding * 2};
    if(shape == nullptr) shape = make_shared<sf::RectangleShape>(size);
    shape->setPosition({float(cell.x + padding), float(cell.y + padding)});
    shape->setFillColor(color);
    shape->setOutlineColor(border_color);
    shape->setOutlineThickness(border_px);
  }

  void Rectangle::render(sf::RenderWindow& window, sf::Shader *shader_ptr) {
    window.draw(*shape, shader_ptr);
  }

  void Meter::init(lel::Cell& cell, Rectangle& bg) {
    bg.shape->setFillColor(color);
    init(cell);
  }

  void Meter::init(lel::Cell& cell) {
    bar.init(cell);
  }

  void Meter::render(lel::Cell& cell, sf::RenderWindow& window, sf::Shader *shader_ptr) {
    float level = std::clamp(percent, 0.0f, 1.0f) * float(cell.w);
    // ZED: this 6 is a border width, make it a thing
    bar.shape->setSize({std::max(level, 0.0f), float(cell.h - 6)});
    window.draw(*bar.shape, shader_ptr);
  }

  void Sound::play(bool hover) {
    if(!hover) {
      BACKEND->sound_play(on_click);
    }
  }

  void Sound::stop(bool hover) {
    if(!hover) {
      BACKEND->sound_stop(on_click);
    }
  }

  void Background::init() {
    assert(w > 0.0f && "Background.w must be > 0.0f. Forgot $gui.$parser?");
    assert(h > 0.0f && "Background.h must be > 0.0f. Forgot $gui.$parser?");

    std::cout << "x=" << x << " y=" << y << " w=" << w << " h=" << h << std::endl;
    sf::Vector2f size{float(w), float(h)};
    if(shape == nullptr) {
      shape = make_shared<sf::RectangleShape>(size);
    } else {
      shape->setSize(size);
    }
    shape->setPosition({float(x), float(y)});
    shape->setFillColor(color);
    assert(shape != nullptr && "failed to make rectangle");
  }

  void Background::render(sf::RenderWindow& window) {
    window.draw(*shape);
  }

  void Effect::init(lel::Cell &cell) {
    $shader = BACKEND->shader_get(name);
    $shader->setUniform("u_resolution", sf::Vector2f({float(cell.w), float(cell.h)}));
    $clock = std::make_shared<sf::Clock>();
  }

  void Effect::render() {
    sf::Time cur_time = $clock->getElapsedTime();
    float u_time = cur_time.asSeconds();

    if(u_time < $u_time_end) {
      $shader->setUniform("u_duration", duration);
      $shader->setUniform("u_time_end", $u_time_end);
      $shader->setUniform("u_time", u_time);
    } else {
      $active = false;
    }
  }

  void Effect::run() {
    $active = true;
    sf::Time u_time = $clock->getElapsedTime();
    $u_time_end = u_time.asSeconds() + duration;
  }

  sf::Shader* Effect::get_shader(bool is_shape) {
    sf::Shader *shader_ptr = nullptr;

    if(BACKEND->shader_updated()) {
      $shader = BACKEND->shader_get(name);
    }

    if($active) {
      $shader->setUniform("is_shape", is_shape);
      // NOTE: this is needed because SFML doesn't handle shared_ptr
      shader_ptr = $shader.get();
    }

    return shader_ptr;
  }

  void Effect::stop() {
    $active = false;
  }
}
