#include <SDL2/SDL.h>
#include <memory>
#include "core/character.h"
#include "core/input.h"
#include "core/renderer.h"
#include "core/world.h"
#include "enemies.h"
#include "player_logic.h"

using namespace rogue;

// the DOOM I/II solution :)
enum GameState {
	MainMenu,
	Game,
	GameOver,
	Escape
};

CharacterData const player_data{
	.health = 20,
	.damage = 2,
	.sprite = 3
};

CharacterData const boss_data{
	.health = 10,
	.damage = 3,
	.sprite = 0
};

CharacterData const critte_data{
	.health = 4,
	.damage = 1,
	.sprite = 2
};

template <typename TLogicType>
static Character create_character(CharacterData const &stats, Tile tile) {
	return Character(tile, new TLogicType, stats);
}

int main(int argc, char *argv[]) {
	GameState game_state{GameState::MainMenu};
	WorldGenerator generator{WorldGenerator()
		.with_world_size(10)
			.with_chunk_size(10)
			.with_room_size(6, 10)
			.with_connecting_chance(5)
		.with_player(std::bind(&create_character<PlayerCharacterLogic>, player_data, std::placeholders::_1))
		.with_enemy(std::bind(&create_character<CritteLogic>, critte_data, std::placeholders::_1), 2)
		.with_enemy(std::bind(&create_character<CritteLogic>, critte_data, std::placeholders::_1), 2)
		.with_enemy(std::bind(&create_character<CritteLogic>, critte_data, std::placeholders::_1), 2)
		.with_boss(std::bind(&create_character<CritteLogic>, boss_data, std::placeholders::_1))};
	RenderData data{RenderDataSetup()
		// create window and renderer, allowing for texture creation
		.with_window("roguelike", SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_FULLSCREEN)
		.with_renderer(SDL_RENDERER_ACCELERATED)
			// setup resources
			.with_resource_path("resources/")
				// sprites
				.with_sprite("wizard.png")
				.with_sprite("wall.png")
				.with_sprite("critte.png")
				.with_sprite("player.png")
				// menus
				.with_menu("maze.png")
				.with_menu("game_over.png")
				.with_menu("escape.png")
		.build()
	};
	// use <functional> features to capture game_state and listen for input within main()
	KeyEventHandler return_to_main_input{{SDL_SCANCODE_RETURN},
		[&game_state](bool down) {
			if(!down) return;
			switch(game_state) {
				case GameState::Game: // do nothing, let the input be handled by the world/characters
				case GameState::MainMenu: // main menu starts with direction input not enter
					break; 
				case GameState::GameOver:
					game_state = GameState::MainMenu;
					break;
				case GameState::Escape:
					game_state = GameState::MainMenu;
					break;
			}
		},
		false
	};
	KeyEventHandler start_game_input{{
			SDL_SCANCODE_H, SDL_SCANCODE_J, SDL_SCANCODE_K, SDL_SCANCODE_L,
			SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_UP, SDL_SCANCODE_DOWN
		},
		[&game_state](bool down) {
			if(!down) return;
			switch(game_state) {
				case GameState::Game:
				case GameState::GameOver:
				case GameState::Escape:
					break;
				case GameState::MainMenu:
					game_state = GameState::Game;
					break;
			}
		},
		false
	};
	Render::provide_render_data(data);
	std::unique_ptr<World> world{nullptr};
	SDL_Event evt;
	for(;;) {
		// DOOM-style menus
		switch(game_state) {
			case GameState::MainMenu:
				Render::draw_menu(0);
				break;
			case GameState::Game:
				// generate environment when needed
				if(world == nullptr) {
					world.reset(generator.generate().release());
				}
				world->act_if_requested();
				world->render();
				// game end conditions (player or boss death)
				if(world->get_player()->health <= 0) {
					game_state = GameState::GameOver;
					world.reset();
				} else if(world->get_boss()->health <= 0) {
					game_state = GameState::Escape;
					world.reset();
				}
				break;
			case GameState::GameOver:
				Render::draw_menu(1);
				break;
			case GameState::Escape:
				Render::draw_menu(2);
				break;
		}
		Render::present();
		if(SDL_WaitEvent(&evt)) {
			if(evt.type == SDL_QUIT) {
				goto break_main_loop;
			} else {
				Input::process_event(evt);
			}
		}
	} break_main_loop:
	return 0;
}
