#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 = 30,
	.damage = 2,
	.sprite = 3
};

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

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


int main(int argc, char *argv[]) {
	SDL_Init(SDL_INIT_EVERYTHING);
	// Game state, simple way of managing which menu/screen the player is currently on
	GameState game_state{GameState::MainMenu};
	// World generator, can be used to generate new rooms when the player restarts the game
	WorldGenerator generator{WorldGenerator()
		.with_world_size(6)
			// world generation
			.with_chunk_size(8)
			.with_room_size(5, 8)
			.with_connecting_chance(5)
		// setup character spawners/character factories
		.with_player(Character::make_spawner<PlayerCharacterLogic>(player_data))
		// enemies and boss all use the same "AI" with different data
		.with_enemy(Character::make_spawner<CritteLogic>(critte_data), 2)
		.with_enemy(Character::make_spawner<CritteLogic>(critte_data), 3)
		.with_enemy(Character::make_spawner<CritteLogic>(critte_data), 10)
		.with_boss(Character::make_spawner<CritteLogic>(boss_data))};
	// renderdata, passed to Render but kept here so that RAII will call the destructor when main pops off the stack
	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()
	};
	// again, provided to the renderer here, but ownership remains local
	Render::provide_render_data(data);
	// use <functional> features to capture game_state and listen for input within main()
	// the victory and death screens only allow jumping back with return/enter
	KeyEventHandler return_to_main_input{{SDL_SCANCODE_RETURN},
		[&game_state](bool down) {
			if(!down) {
				return;
			} else if(game_state == GameState::GameOver || game_state == GameState::Escape) {
				game_state = GameState::MainMenu;
			}
		},
		false
	};
	// The game is started by pressing one of the input directions as a soft tutorial
	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;
			} else if(game_state == GameState::MainMenu) {
				game_state = GameState::Game;
			}
		},
		false
	};
	// create an empty world, filled in when the game is started by the player
	std::unique_ptr<World> world{nullptr};
	// main loop, continues until broken out of
	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());
				}
				// tick characters (if requested) and render
				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::finish_frame();
		// receive input
		SDL_Event evt;
		if(SDL_WaitEvent(&evt)) {
			if(evt.type == SDL_QUIT) { // shortcirquit for quitting when requested
				break; // quit by breaking out of the main loop
			} else {
				Input::process_event(evt);
			}
		}
	}
	SDL_Quit();
	return 0;
}
