#![warn(clippy::all,clippy::pedantic)]
mod map;
mod map_builder;
mod camera;
mod components;
mod spawner;
mod systems;
mod turn_state;

mod prelude {
    pub use bracket_lib:: prelude::*;
    pub use legion::*;
    pub use legion::world::SubWorld;
    pub use legion::systems::CommandBuffer;

    pub const SCREEN_HEIGHT:i32 = 50;
    pub const SCREEN_WIDTH:i32 = 80;
    pub const DISPLAY_WIDTH:i32 = SCREEN_WIDTH/2;
    pub const DISPLAY_HEIGHT:i32 = SCREEN_HEIGHT/2;
    pub const TILE_WIDTH:i32 = 32;
    pub const TILE_HEIGHT:i32 = 32;
    pub const TERMINAL_WIDTH:i32 = 8;
    pub const TERMINAL_HEIGHT: i32 = 8;

    pub use crate::map::*;
    pub use crate::map_builder::*;
    pub use crate::camera::*;
    pub use crate::components::*;
    pub use crate::spawner::*;
    pub use crate::systems::*;
    pub use crate::turn_state::*;
}

use prelude::*;
/*
enum GameMode{
    Menu,
    Playing,
    End,
}*/

// game state implementation
struct State {
    ecs: World,
    resources: Resources,
    //systems: Schedule
    input_systems: Schedule,
    //player_systems: Schedule,
    monster_systems: Schedule
}

impl State{
    fn new() -> Self {
        let mut ecs = World::default();
        let mut resources = Resources::default();
        let mut rng = RandomNumberGenerator::new();
        let mut map_builder = MapBuilder::new(&mut rng);

        //spawns the player into the world (pushed into ecs)
        spawn_player(&mut ecs, map_builder.player_start);
        //spawn amulet of yala
        //spawn_amulet_of_yala(&mut ecs, map_builder.amulet_start);
        let exit_idx = map_builder.map.point2d_to_index(map_builder.amulet_start);
        map_builder.map.tiles[exit_idx] = TileType::Exit;
        
        //put monsters onto the map
        /*map_builder.rooms
            .iter()
            .skip(1) //the player room
            .map(|r|r.center())
            .for_each(|pos|spawn_monster(&mut ecs, &mut rng, pos));*/
        /*map_builder.monster_spawns
            .iter()
            .for_each(|pos| spawn_entity(&mut ecs, &mut rng, *pos));*/
        spawn_level(
            &mut ecs,
            &mut rng,
            0,
            &map_builder.monster_spawns
        );

        //add maps and camera into resources list to be used in the system
        resources.insert(map_builder.map);
        resources.insert(Camera::new(map_builder.player_start));
        resources.insert(TurnState::AwaitingInput);
        resources.insert(map_builder.theme);
        
        Self{
            ecs,
            resources,
            //systems: build_scheduler() //system is build where modular systems take input from the resources
            input_systems: build_input_scheduler(),
            //player_systems: build_player_scheduler(),
            monster_systems: build_monster_scheduler()
        }
    }

    //game over screen
    fn game_over(&mut self, ctx: &mut BTerm)
    {
        ctx.set_active_console(3);
        ctx.print_color_centered(2, RED, BLACK, "Your quest has ended.");
        ctx.print_color_centered(4, WHITE, BLACK, 
                "Slain by a monster, your gero's journey has come to a \
                premature end.");
        ctx.print_color_centered(5, WHITE, BLACK, 
                    "The Amulet of Yala remains unclaimed, and your home town \
                    is not saved.");
        ctx.print_color_centered(8, YELLOW, BLACK, 
                    "Don't worry, you can always try again to be a new hero");
        ctx.print_color_centered(9, GREEN, BLACK, 
                    "Press 1 to play again.");
            
        if let Some(VirtualKeyCode::Key1) = ctx.key {
               self.world_reset();
        }
    }

    //victory screen
    fn victory(&mut self, ctx: &mut BTerm)
    {
        ctx.set_active_console(3);
        ctx.print_color_centered(2, GREEN, BLACK, "You have won!");
        ctx.print_color_centered(4, WHITE, BLACK,
        "You put on the Amulet of Yala and feel its power course through \
        your veins.");
        ctx.print_color_centered(5, WHITE, BLACK,
        "Your town is saved, and you can return to your normal life.");
        ctx.print_color_centered(7, GREEN, BLACK, "Press 1 to \
        play again");

        if let Some(VirtualKeyCode::Key1) = ctx.key {
            self.world_reset();
        }
    }

    fn set_resources(&mut self, map_builder: MapBuilder, rng: &mut RandomNumberGenerator, map_level: usize)
    {
        /*map_builder.monster_spawns
            .iter()
            .for_each(|pos| spawn_entity(&mut self.ecs, rng, *pos));*/
        spawn_level(
                &mut self.ecs,
                rng,
                map_level,
                &map_builder.monster_spawns
            );
        self.resources.insert(map_builder.map);
        self.resources.insert(Camera::new(map_builder.player_start));
        self.resources.insert(TurnState::AwaitingInput);
        self.resources.insert(map_builder.theme);
    }
    //resetting the world after dying
    fn world_reset(&mut self)
    {
        self.ecs = World::default();
        self.resources = Resources::default();
        let mut rng = RandomNumberGenerator::new();
        let mut map_builder = MapBuilder::new(&mut rng);
        spawn_player(&mut self.ecs, map_builder.player_start);
        //spawn_amulet_of_yala(&mut self.ecs, map_builder.amulet_start);
        let exit_idx = map_builder.map.point2d_to_index(map_builder.amulet_start);
        map_builder.map.tiles[exit_idx] = TileType::Exit;
        /*map_builder.rooms
                .iter()
                .skip(1)
                .map(|r|r.center())
                .for_each(|pos|spawn_monster(&mut self.ecs, &mut rng, pos));*/
        self.set_resources(map_builder, &mut rng, 0);
    }

    fn advance_level(&mut self)
    {
        let player_entity = *<Entity>::query()
            .filter(component::<Player>())
            .iter(&mut self.ecs)
            .nth(0)
            .unwrap();
        
        use std::collections::HashSet;
        let mut entities_to_keep = HashSet::new();
        entities_to_keep.insert(player_entity);
        //remembering the entities
        <(Entity, &Carried)>::query()
            .iter(&self.ecs)
            .filter(|(_e, carry)| carry.0 == player_entity)
            .map(|(e, carry)| *e)
            .for_each(|e|{entities_to_keep.insert(e);
            });

        //remove from the command buffer
        let mut cb = CommandBuffer::new(&mut self.ecs);
        for e in Entity::query().iter(&self.ecs){
            if !entities_to_keep.contains(e) {
                cb.remove(*e);
            }
        }
        cb.flush(&mut self.ecs);

        <&mut FieldOfView>::query()
            .iter_mut(&mut self.ecs)
            .for_each(|fov|fov.is_dirty = true);

        let mut rng = RandomNumberGenerator::new();
        let mut map_builder = MapBuilder::new(&mut rng);

        let mut map_level = 0;
        <(&mut Player, &mut Point)>::query()
            .iter_mut(&mut self.ecs)
            .for_each(|(player, pos)|{
                player.map_level += 1;
                map_level = player.map_level;
                pos.x = map_builder.player_start.x;
                pos.y = map_builder.player_start.y;
            }
        );

        if map_level == 2 {
            spawn_amulet_of_yala(&mut self.ecs, map_builder.amulet_start);
        } 
        else{
            let exit_idx = map_builder.map.point2d_to_index(map_builder.amulet_start);
            map_builder.map.tiles[exit_idx] = TileType::Exit;
        }

        self.set_resources(map_builder, &mut rng, map_level as usize);
    }
}

impl GameState for State{
    fn tick(&mut self, ctx: &mut BTerm){
        ctx.set_active_console(1);
        ctx.cls();
        //ctx.print(1,1, "Hello Dungeon Crawler!");
        ctx.set_active_console(2);
        //ctx returns tuples of x and y, convert to point for consistency
        self.resources.insert(Point::from_tuple(ctx.mouse_pos()));

        ctx.cls();
        ctx.set_active_console(3);
        ctx.cls();
        self.resources.insert(ctx.key);
        //self.systems.execute(&mut self.ecs, &mut self.resources); //world and resources passed into the system
        let current_state = self.resources.get::<TurnState>().unwrap().clone();
        match current_state {
            TurnState::AwaitingInput => {
                self.input_systems.execute(&mut self.ecs, &mut self.resources);
            },
            /*TurnState::PlayerTurn => {
                self.player_systems.execute(&mut self.ecs, &mut self.resources);
            }*/
            TurnState::MonsterTurn => {
                self.monster_systems.execute(&mut self.ecs, &mut self.resources);
            }

            TurnState::GameOver => {
                self.game_over(ctx);
            }

            TurnState::Victory => {
                self.victory(ctx);
            }

            TurnState::NextLevel => {
                self.advance_level();
            }
        }
        render_draw_buffer(ctx).expect("Render error");
    }
}
//main loop of game
fn main()-> BError {
    
    let context = BTermBuilder::simple80x50()
        .with_title("Dungeon Crawler")
        .with_fps_cap(30.0)
        .with_dimensions(DISPLAY_WIDTH, DISPLAY_HEIGHT)
        .with_tile_dimensions(TILE_WIDTH, TILE_HEIGHT)
        .with_resource_path("resources/")
        .with_font("dungeonfont.png", TILE_WIDTH, TILE_HEIGHT)
        .with_font("terminal8x8.png", TERMINAL_WIDTH, TERMINAL_HEIGHT)
        .with_simple_console(DISPLAY_WIDTH, DISPLAY_HEIGHT, "dungeonfont.png")
        .with_simple_console_no_bg(DISPLAY_WIDTH, DISPLAY_HEIGHT, "dungeonfont.png")
        .with_simple_console_no_bg(SCREEN_WIDTH*2, SCREEN_HEIGHT*2, "terminal8x8.png")
        .build()?;

    main_loop(context, State::new())
}
