#pragma once
#include <vector>
#include <utility>
#include <string>
#include <random>
#include <algorithm>
#include <fmt/core.h>
#include "point.hpp"
#include "tser.hpp"

#define INV_WALL 0
#define INV_SPACE 1
#define WALL_VALUE 1
#define SPACE_VALUE 0

constexpr int GAME_MAP_X = 90;
constexpr int GAME_MAP_Y = 90;

struct Room {
  size_t x = 0;
  size_t y = 0;
  size_t width = 0;
  size_t height = 0;
  Point entry;
  Point exit;

  DEFINE_SERIALIZABLE(Room, x, y, width, height);
};

typedef std::vector<int> MatrixRow;
typedef std::vector<MatrixRow> Matrix;

void dump_map(const std::string &msg, Matrix &map);
void add_neighbors(Matrix &closed, size_t j, size_t i);

class Map {
public:
  Matrix $input_map;
  Matrix $walls;
  Matrix $paths;
  std::vector<Room> $rooms;
  int $limit = 0;

  // make explicit
  Map(Matrix input_map, Matrix walls_map, int limit) :
    $input_map(input_map),
    $walls(walls_map), $limit(limit) {
  }

  // make random
  Map(size_t width, size_t height);

  // disable copying
  Map(Map &map) = delete;

  Matrix& paths() { return $paths; }
  Matrix& input_map() { return $input_map; }
  Matrix& walls() { return $walls; }
  int limit() { return $limit; }
  size_t width() { return $walls[0].size(); }
  size_t height() { return $walls.size(); }
  int distance(Point to) { return $paths[to.y][to.x]; }
  Room &room(size_t at) {
    return $rooms[at];
  }

  size_t room_count() {
    return $rooms.size();
  }

  void make_room(size_t origin_y, size_t origin_x, size_t width, size_t height);

  void add_door(Room &room);
  bool inmap(size_t x, size_t y);
  bool iswall(size_t x, size_t y);

  bool can_move(Point move_to) {
    return inmap(move_to.x, move_to.y) &&
      !iswall(move_to.x, move_to.y);
  }

  bool neighbors(Point &out, bool up);
  void generate();
  void place_rooms(Room &root);
  void make_paths();
  void partition_map(Room &cur, int depth);
  void set_target(const Point &at, int value=0);
  void clear_target(const Point &at);
  bool walk(Point &src, Point &target);
  void set_door(Room &room, int value);
  void dump();
  Point place_entity(size_t room_index);

  Point map_to_camera(const Point &loc, const Point &cam_orig) {
    return {loc.x - cam_orig.x, loc.y - cam_orig.y};
  }

  Point center_camera(const Point &around, size_t view_x, size_t view_y) {
    size_t start_x = std::clamp(int(around.x - view_x / 2), 0, int(width() - view_x));
    size_t start_y = std::clamp(int(around.y - view_y / 2), 0, int(height() - view_y));
    return {start_x, start_y};
  }
};
