#ifndef ROGUE_WORLD_H
#define ROGUE_WORLD_H

#include <utility>
#include <vector>
#include <SDL2/SDL_rect.h>
#include "character.h"
#include "core/input.h"
#include "core/roguedefs.h"

namespace rogue {
class Character;

struct TileData {
	Character *character{nullptr};
	bool is_wall{false};
};

struct Room {
	// bitmask of hallways
	Directions hallway_paths{0};
	int chunk_size{0};
	bool initialized{false};
	// the starting tile and length of the walls
	SDL_Rect rect{0, 0, 1, 1};
	void draw(Tile pivot);
	bool tile_is_wall(Tile local_tile) const;
};

// represents the current state of the game environment and actors
class World {
	friend class WorldGenerator;
public:
	World();
	void act_if_requested(); // will call 'act' when 'turn_requested' is set
	void render(); // render the world, includes ui, environment and characters
	Room &get_room(Chunk chunk); // returns the room at the given chunk
	Character *get_player(); // returns the player character instance
	Character *get_boss(); // returns the boss character instance
	Chunk get_chunk(Tile tile); // returns the chunk at the given world-space tile
	TileData query_tile(Tile tile); // returns data related to the given world-space tile
private:
	void act();
	void on_input(bool pressed); // registered with the 'direction_pressed' key event handler
private:
	int chunk_size{1}; // size of chunks
	unsigned shear{0}; // number of chunks before the next row (width of the map in chunks)
	bool turn_requested{false}; // flag for requesting a turn, consumed by 'act_if_requested' set by 'on_input'
							    // this flag exists (instead of just calling 'act' from 'on_input' to avoid moving the world
								// before the player's input is registered by the PlayerCharacterLogic
	Character player{}; // player character, should be instantiated by world generator
	Character boss{}; // boss character, should be instantiated by world generator
	std::vector<Room> rooms{}; // all of the rooms in the map in order, this is a 2d array that wraps at 'shear'
	std::vector<Character> characters{}; // all of the characters in the map, including dead ones
	KeyEventHandler direction_pressed; // triggered when the player presses any direction, calls 'on_input'
};

// Builder/factory for configuring and generating game environments and enemies
class WorldGenerator {
public:
	WorldGenerator() = default;
	// set the chunk size, this also limits the size of rooms,
	// chunks are always squares with a size of side_length * side_length
	WorldGenerator &with_chunk_size(unsigned side_length);
	// set the size of the world,
	// the world is always a square with a size of side_length * side_length
	WorldGenerator &with_world_size(unsigned side_length);
	// set the minimum and maximum side length for rooms,
	// rooms are generated with random sizes for both width and height
	WorldGenerator &with_room_size(unsigned min_side_length, unsigned max_side_length);
	// set reusable factory object for the player
	WorldGenerator &with_player(Character::Spawner spawner);
	// add an enemy that can be spawned in rooms,
	// pass in a factory object and the inverse of the frequency
	// (think of it as "1 in every N rooms will have this enemy")
	WorldGenerator &with_enemy(Character::Spawner spawner, int inv_frequency);
	// set the factory object used to spawn the boss
	WorldGenerator &with_boss(Character::Spawner spawner);
	// set the chance that a room will have a connection in inverse frequency
	// keep in mind this is rolled per room pair that isn't already connected, so 2 times per room
	WorldGenerator &with_connecting_chance(unsigned num);
	// generates a new world using the current configuration, requires all settings to be set
	std::unique_ptr<World> generate();
private:
	// set up for Recursive Backtracking Maze Generation
	void generate_world(std::unique_ptr<World> &world, Chunk start);
	// Recursive Backtracking Maze Generation
	void generate_room(std::unique_ptr<World> &world, Chunk const &last);
	// spawn enemies in a generated room
	// generates a boss enemy for the first room it's called on.
	void add_enemies(std::unique_ptr<World> &world, Chunk const &chunk, Room &room);
	// add the player to a room (does not have to be generated, as the player always spawns at the center)
	void setup_player(std::unique_ptr<World> &world, Chunk spawn_chunk);
private:
	// Factory objects for enemies
	Character::Spawner player_spawner{};
	Character::Spawner boss_spawner{};
	// factory object, inverse frequency
	std::vector<std::pair<Character::Spawner, int>> enemy_spawners{};
	// sizes
	int chunk_side_length{0}, world_side_length{0};
	int max_room_size{0}, min_room_size{0};
	unsigned connecting_chance{1};
	// set by 'generate_enemies' when the boss is generated
	bool boss_generated{false};
};
}

#endif // !ROGUE_WORLD_H
