use crate::{CombatStats, InputEvent, Map, MoveEvent, Position, RunState, State, WantsToMelee};
use rltk::{Rltk, VirtualKeyCode};
use specs::prelude::*;

/// Handles converting low-level `InputEvent` components (which may
/// represent one of several actions, or even a completely invalid
/// action such as walking into a wall) and dispatches them to the
/// relevant high-level intention (e.g. moving in a direction or
/// attacking a target)
pub struct HandleInputEvent;

impl<'a> System<'a> for HandleInputEvent {
    type SystemData = (
        WriteStorage<'a, InputEvent>,
        ReadExpect<'a, Map>,
        Entities<'a>,
        ReadStorage<'a, Position>,
        ReadStorage<'a, CombatStats>,
        WriteStorage<'a, WantsToMelee>,
        WriteStorage<'a, MoveEvent>,
    );

    fn run(
        &mut self,
        (mut event, map, entities, position, stats, mut attack, mut mov): Self::SystemData,
    ) {
        for (e, ev, pos) in (&entities, &event, &position).join() {
            match *ev {
                InputEvent::PlayerMovement { delta_x, delta_y } => {
                    // depending on what's in the target cell, we
                    // either want to attack or move
                    let tgt_x = pos.x + delta_x;
                    let tgt_y = pos.y + delta_y;
                    let destination = map.xy_idx(tgt_x, tgt_y);
                    if let Some(target) = map.get_attackable(destination, &stats) {
                        attack
                            .insert(e, WantsToMelee { target })
                            .expect("Unable to add WantsToAttack");
                    } else {
                        mov.insert(
                            e,
                            MoveEvent {
                                destination,
                                tgt_x,
                                tgt_y,
                            },
                        )
                        .expect("Unable to add MoveEvent");
                    }
                }
            }
        }
        event.clear();
    }
}

/// Translates keyboard input into the appropriate `InputEvent` value
pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState {
    let player = *gs.ecs.fetch::<Entity>();
    let mut input = gs.ecs.write_storage::<InputEvent>();
    // Player movement
    match ctx.key {
        None => return RunState::AwaitingInput, // Nothing happened
        Some(key) => match key {
            VirtualKeyCode::Left | VirtualKeyCode::Numpad4 | VirtualKeyCode::H => input.insert(
                player,
                InputEvent::PlayerMovement {
                    delta_x: -1,
                    delta_y: 0,
                },
            ),

            VirtualKeyCode::Right | VirtualKeyCode::Numpad6 | VirtualKeyCode::L => input.insert(
                player,
                InputEvent::PlayerMovement {
                    delta_x: 1,
                    delta_y: 0,
                },
            ),

            VirtualKeyCode::Up | VirtualKeyCode::Numpad8 | VirtualKeyCode::K => input.insert(
                player,
                InputEvent::PlayerMovement {
                    delta_x: 0,
                    delta_y: -1,
                },
            ),

            VirtualKeyCode::Down | VirtualKeyCode::Numpad2 | VirtualKeyCode::J => input.insert(
                player,
                InputEvent::PlayerMovement {
                    delta_x: 0,
                    delta_y: 1,
                },
            ),

            // Diagonals
            VirtualKeyCode::Numpad9 | VirtualKeyCode::U => input.insert(
                player,
                InputEvent::PlayerMovement {
                    delta_x: 1,
                    delta_y: -1,
                },
            ),

            VirtualKeyCode::Numpad7 | VirtualKeyCode::Y => input.insert(
                player,
                InputEvent::PlayerMovement {
                    delta_x: -1,
                    delta_y: -1,
                },
            ),

            VirtualKeyCode::Numpad3 | VirtualKeyCode::N => input.insert(
                player,
                InputEvent::PlayerMovement {
                    delta_x: 1,
                    delta_y: 1,
                },
            ),

            VirtualKeyCode::Numpad1 | VirtualKeyCode::B => input.insert(
                player,
                InputEvent::PlayerMovement {
                    delta_x: -1,
                    delta_y: 1,
                },
            ),

            _ => return RunState::AwaitingInput,
        },
    }
    .expect("Unable to insert player movement");
    RunState::PlayerTurn
}
