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

using std::vector;

namespace lighting {
  void LightRender::render_light(LightSource source, Point at) {
    Point min, max;
    light_box(source, at, min, max);
    clear_light_target(at);
    vector<Point> has_light;

    for(size_t y = min.y; y <= max.y; ++y) {
      auto &light_row = $lightmap[y];
      auto &path_row = $paths.$paths[y];

      for(size_t x = min.x; x <= max.x; ++x) {
        if(path_row[x] != WALL_PATH_LIMIT) {
          light_row[x] = light_level(source.strength, x, y);
          has_light.push_back({x,y});
        }
      }
    }

    const int wall_light = source.strength + WALL_LIGHT_LEVEL;
    for(auto point : has_light) {
      for(int j = -1;point.y+j >= 0 && j <= 1 && point.y+j < $height; j++) {
        auto &path_row = $paths.$paths[point.y+j];
        auto &light_row = $lightmap[point.y+j];

        for(int i = -1; point.x+i >= 0 && i <= 1 && point.x+i < $width; i++) {
          if(path_row[point.x+i] == WALL_PATH_LIMIT) {
            light_row[point.x+i] = light_level(wall_light, point.x, point.y);
          }
        }
      }
    }
  }

  int LightRender::light_level(int level, size_t x, size_t y) {
    size_t at = level + $paths.$paths[y][x];
    int cur_level = $lightmap[y][x];
    int new_level = at < lighting::LEVELS.size() ? lighting::LEVELS[at] : lighting::MIN;
    return cur_level < new_level ? new_level : cur_level;
  }


  void LightRender::reset_light() {
    for(auto &row : $lightmap) {
      row.assign(row.size(), lighting::MIN);
    }
  }

  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::path_light(Matrix &walls) {
    $paths.compute_paths(walls);
  }

  void LightRender::light_box(LightSource source, Point from, Point &min_out, Point &max_out) {
    using std::min, std::max;
    min_out.x = max(int(from.x), source.distance) - source.distance;
    max_out.x = min(from.x + source.distance, $width - 1);
    min_out.y = max(int(from.y), source.distance) - source.distance;
    max_out.y = min(from.y + source.distance, $height - 1);
  }
}
