#include "lights.hpp"
#include "constants.hpp"
#include "textures.hpp"
#include <vector>

using std::vector;

namespace lighting {

  LightRender::LightRender(Matrix& tiles) :
    $width(matrix::width(tiles)),
    $height(matrix::height(tiles)),
    $lightmap(matrix::make($width, $height)),
    $ambient(matrix::make($width, $height)),
    $paths($width, $height),
    $fow(matrix::make($width, $height))
  {
    auto& tile_ambient = textures::get_ambient_light();

    for(matrix::each_cell it{tiles}; it.next();) {
      size_t tile_id = tiles[it.y][it.x];
      $ambient[it.y][it.x] = MIN + tile_ambient[tile_id];
    }
  }

  void LightRender::render_square_light(LightSource source, Point at, PointList &has_light) {
    for(matrix::box it{$lightmap, at.x, at.y, (size_t)floor(source.radius)}; it.next();) {
      if($paths.$paths[it.y][it.x] != WALL_PATH_LIMIT) {
        $lightmap[it.y][it.x] = light_level(source.strength, it.distance(), it.x, it.y);
        has_light.emplace_back(it.x, it.y);
      }
    }
  }

  /*
   * NOTE: This really doesn't need to calculate light all the time. It doesn't
   * change around the light source until the lightsource is changed, so the
   * light levels could be placed in a Matrix inside LightSource, calculated once
   * and then simply "applied" to the area where the entity is located.  The only
   * thing that would need to be calculated each time is the walls.
   */
  void LightRender::render_light(LightSource source, Point at) {
    clear_light_target(at);
    PointList has_light;

    render_square_light(source, at, has_light);

    for(auto point : has_light) {
      for(matrix::compass it{$lightmap, point.x, point.y}; it.next();) {
        if($paths.$paths[it.y][it.x] == WALL_PATH_LIMIT) {
          $lightmap[it.y][it.x] = light_level(source.strength, 1.5f, point.x, point.y);
        }
      }
    }
  }

  int LightRender::light_level(int strength, float distance, size_t x, size_t y) {
    int boosted = strength + BOOST;
    int new_level = distance <= 1.0f ? boosted : boosted / sqrt(distance);
    int cur_level = $lightmap[y][x];
    return cur_level < new_level ? new_level : cur_level;
  }

  void LightRender::reset_light() {
    $lightmap = $ambient;
  }

  void LightRender::clear_light_target(const Point &at) {
    $paths.clear_target(at);
  }

  void LightRender::set_light_target(const Point &at, int value) {
    $paths.set_target(at, value);
  }

  void LightRender::update_fow(Point at, LightSource source) {
    for(matrix::circle it{$lightmap, at, source.radius}; it.next();) {
      for(auto x = it.left; x < it.right; x++) {
        $fow[it.y][x] = $lightmap[it.y][x];
      }
    }
  }

  void LightRender::path_light(Matrix &walls) {
    $paths.compute_paths(walls);
  }
}
