use ggez::event::{Axis, Button, EventHandler, GamepadId};
use ggez::input::keyboard::{KeyCode, KeyMods};
use ggez::input::mouse::MouseButton;
use ggez::{Context, GameResult};

pub enum Trans {
    None,
    Push(Box<dyn State>),
    Switch(Box<dyn State>),
    Replace(Box<dyn State>),
    Pop,
}

pub trait State {
    /// when state is initialized
    fn on_start(&mut self, _ctx: &mut Context) -> GameResult {
        Ok(())
    }

    /// when state was removed
    fn on_stop(&mut self, _ctx: &mut Context) -> GameResult {
        Ok(())
    }

    /// update per frame
    fn update(&mut self, ctx: &mut Context) -> GameResult<Trans>;

    /// draw
    fn draw(&mut self, ctx: &mut Context) -> GameResult<Trans>;

    /// when other state was pushed on top of the current state
    fn on_pause(&mut self, _ctx: &mut Context) -> GameResult {
        Ok(())
    }

    /// when current state was active again
    fn on_resume(&mut self, _ctx: &mut Context) -> GameResult {
        Ok(())
    }

    /// A mouse button was pressed
    ///
    /// The default implementation of this will call ggez::event::quit()
    /// when the escape key is pressed. If you override this with your own
    /// event handler you have to re-implment that functionality yourself.
    fn mouse_button_down_event(
        &mut self,
        _ctx: &mut Context,
        _button: MouseButton,
        _x: f32,
        _y: f32,
    ) -> GameResult<Trans> {
        Ok(Trans::None)
    }

    /// A mouse button was released
    fn mouse_button_up_event(
        &mut self,
        _ctx: &mut Context,
        _button: MouseButton,
        _x: f32,
        _y: f32,
    ) -> GameResult<Trans> {
        Ok(Trans::None)
    }

    /// The mouse was moved; it provides both absolute x and y coordinates in the window,
    /// and relative x and y coordinates compared to its last position.
    fn mouse_motion_event(
        &mut self,
        _ctx: &mut Context,
        _x: f32,
        _y: f32,
        _dx: f32,
        _dy: f32,
    ) -> GameResult<Trans> {
        Ok(Trans::None)
    }

    /// The mousewheel was scrolled, vertically (y, positive away from and negative toward the user)
    /// or horizontally (x, positive to the right and negative to the left).
    fn mouse_wheel_event(&mut self, _ctx: &mut Context, _x: f32, _y: f32) -> GameResult<Trans> {
        Ok(Trans::None)
    }

    /// A keyboard button was pressed.
    ///
    /// The default implementation of this will call `ggez::event::quit()`
    /// when the escape key is pressed.  If you override this with
    /// your own event handler you have to re-implment that
    /// functionality yourself.
    fn key_down_event(
        &mut self,
        _ctx: &mut Context,
        _keycode: KeyCode,
        _keymods: KeyMods,
        _repeat: bool,
    ) -> GameResult<Trans> {
        Ok(Trans::None)
    }

    /// A keyboard button was released.
    fn key_up_event(
        &mut self,
        _ctx: &mut Context,
        _keycode: KeyCode,
        _keymods: KeyMods,
    ) -> GameResult<Trans> {
        Ok(Trans::None)
    }

    /// A unicode character was received, usually from keyboard input.
    /// This is the intended way of facilitating text input.
    fn text_input_event(&mut self, _ctx: &mut Context, _character: char) -> GameResult<Trans> {
        Ok(Trans::None)
    }

    fn gamepad_button_down_event(
        &mut self,
        _ctx: &mut Context,
        _btn: Button,
        _id: GamepadId,
    ) -> GameResult<Trans> {
        Ok(Trans::None)
    }

    /// A gamepad button was released; `id` identifies which gamepad.
    /// Use [`input::gamepad()`](../input/fn.gamepad.html) to get more info about
    /// the gamepad.
    fn gamepad_button_up_event(
        &mut self,
        _ctx: &mut Context,
        _btn: Button,
        _id: GamepadId,
    ) -> GameResult<Trans> {
        Ok(Trans::None)
    }

    /// A gamepad axis moved; `id` identifies which gamepad.
    /// Use [`input::gamepad()`](../input/fn.gamepad.html) to get more info about
    /// the gamepad.
    fn gamepad_axis_event(
        &mut self,
        _ctx: &mut Context,
        _axis: Axis,
        _value: f32,
        _id: GamepadId,
    ) -> GameResult<Trans> {
        Ok(Trans::None)
    }

    /// Called when the window is shown or hidden.
    fn focus_event(&mut self, _ctx: &mut Context, _gained: bool) -> GameResult<Trans> {
        Ok(Trans::None)
    }

    /// Called upon a quit event.  If it returns true,
    /// the game does not exit (the quit event is cancelled).
    fn quit_event(&mut self, _ctx: &mut Context) -> bool {
        println!("quit_event() callback called, quitting...");
        false
    }

    /// Called when the user resizes the window, or when it is resized
    /// via [`graphics::set_mode()`](../graphics/fn.set_mode.html).
    fn resize_event(&mut self, _ctx: &mut Context, _width: f32, _height: f32) -> GameResult<Trans> {
        Ok(Trans::None)
    }
}

pub struct StateManager {
    state_stack: Vec<Box<dyn State>>,
}

impl StateManager {
    pub fn new(ctx: &mut Context, mut initial_state: Box<dyn State>) -> Self {
        let mut states = Vec::new();
        initial_state.on_start(ctx).unwrap();
        states.push(initial_state);

        Self {
            state_stack: states,
        }
    }

    fn handle_transform(&mut self, ctx: &mut Context, transform: Trans) {
        match transform {
            Trans::None => {}
            Trans::Pop => {
                if let Some(state) = self.state_stack.last_mut() {
                    state.on_stop(ctx).unwrap();
                }

                self.state_stack.pop();

                if let Some(state) = self.state_stack.last_mut() {
                    state.on_resume(ctx).unwrap();
                }
            }
            Trans::Push(mut state) => {
                if let Some(state) = self.state_stack.last_mut() {
                    state.on_pause(ctx).unwrap();
                }

                state.on_start(ctx).unwrap();
                self.state_stack.push(state);
            }
            Trans::Switch(mut state) => {
                if let Some(state) = self.state_stack.last_mut() {
                    state.on_stop(ctx).unwrap();
                }
                self.state_stack.pop();

                state.on_start(ctx).unwrap();
                self.state_stack.push(state);
            }
            Trans::Replace(state) => {
                while let Some(mut state) = self.state_stack.pop() {
                    state.on_stop(ctx).unwrap()
                }
                self.state_stack.push(state);
            }
        }
    }
}

impl EventHandler for StateManager {
    fn update(&mut self, ctx: &mut Context) -> GameResult<()> {
        let current_state = self.state_stack.last_mut().unwrap();
        let transform = current_state.update(ctx)?;
        self.handle_transform(ctx, transform);

        Ok(())
    }

    fn draw(&mut self, ctx: &mut Context) -> GameResult<()> {
        let current_state = self.state_stack.last_mut().unwrap();
        let transform = current_state.draw(ctx)?;
        self.handle_transform(ctx, transform);

        Ok(())
    }

    fn mouse_button_down_event(&mut self, ctx: &mut Context, button: MouseButton, x: f32, y: f32) {
        let current_state = self.state_stack.last_mut().unwrap();
        let transform = current_state
            .mouse_button_down_event(ctx, button, x, y)
            .unwrap();
        self.handle_transform(ctx, transform);
    }

    fn mouse_button_up_event(&mut self, ctx: &mut Context, button: MouseButton, x: f32, y: f32) {
        let current_state = self.state_stack.last_mut().unwrap();
        let transform = current_state
            .mouse_button_up_event(ctx, button, x, y)
            .unwrap();
        self.handle_transform(ctx, transform);
    }

    fn mouse_motion_event(&mut self, ctx: &mut Context, x: f32, y: f32, dx: f32, dy: f32) {
        let current_state = self.state_stack.last_mut().unwrap();
        let transform = current_state.mouse_motion_event(ctx, x, y, dx, dy).unwrap();
        self.handle_transform(ctx, transform);
    }

    fn mouse_wheel_event(&mut self, ctx: &mut Context, x: f32, y: f32) {
        let current_state = self.state_stack.last_mut().unwrap();
        let transform = current_state.mouse_wheel_event(ctx, x, y).unwrap();
        self.handle_transform(ctx, transform);
    }

    fn key_down_event(
        &mut self,
        ctx: &mut Context,
        keycode: KeyCode,
        keymods: KeyMods,
        repeat: bool,
    ) {
        let current_state = self.state_stack.last_mut().unwrap();
        let transform = current_state
            .key_down_event(ctx, keycode, keymods, repeat)
            .unwrap();
        self.handle_transform(ctx, transform);
    }

    fn key_up_event(&mut self, ctx: &mut Context, keycode: KeyCode, keymods: KeyMods) {
        let current_state = self.state_stack.last_mut().unwrap();
        let transform = current_state.key_up_event(ctx, keycode, keymods).unwrap();
        self.handle_transform(ctx, transform);
    }

    fn gamepad_button_down_event(&mut self, ctx: &mut Context, btn: Button, id: GamepadId) {
        let current_state = self.state_stack.last_mut().unwrap();
        let transform = current_state
            .gamepad_button_down_event(ctx, btn, id)
            .unwrap();
        self.handle_transform(ctx, transform);
    }

    fn gamepad_button_up_event(&mut self, ctx: &mut Context, btn: Button, id: GamepadId) {
        let current_state = self.state_stack.last_mut().unwrap();
        let transform = current_state.gamepad_button_up_event(ctx, btn, id).unwrap();
        self.handle_transform(ctx, transform);
    }

    fn gamepad_axis_event(&mut self, ctx: &mut Context, axis: Axis, value: f32, id: GamepadId) {
        let current_state = self.state_stack.last_mut().unwrap();
        let transform = current_state
            .gamepad_axis_event(ctx, axis, value, id)
            .unwrap();
        self.handle_transform(ctx, transform);
    }

    fn focus_event(&mut self, ctx: &mut Context, gained: bool) {
        let current_state = self.state_stack.last_mut().unwrap();
        let transform = current_state.focus_event(ctx, gained).unwrap();
        self.handle_transform(ctx, transform);
    }

    fn resize_event(&mut self, ctx: &mut Context, width: f32, height: f32) {
        let current_state = self.state_stack.last_mut().unwrap();
        let transform = current_state.resize_event(ctx, width, height).unwrap();
        self.handle_transform(ctx, transform);
    }
}
