use oden_js::{module::native::NativeModuleBuilder, ContextRef};
use std::cell::RefCell;
use std::sync::Arc;
use winit::event::{ElementState, KeyboardInput, VirtualKeyCode};

/// An abstraction over hardware keys.
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
enum Button {
    Up,
    Down,
    Left,
    Right,
}

impl From<Button> for u32 {
    fn from(v: Button) -> Self {
        match v {
            Button::Up => 0,
            Button::Down => 1,
            Button::Left => 2,
            Button::Right => 3,
        }
    }
}

impl TryFrom<u32> for Button {
    type Error = &'static str;
    fn try_from(v: u32) -> Result<Self, Self::Error> {
        match v {
            0 => Ok(Button::Up),
            1 => Ok(Button::Down),
            2 => Ok(Button::Left),
            3 => Ok(Button::Right),
            _ => Err("unknown button value"),
        }
    }
}

struct InputState {
    up_pressed: bool,
    down_pressed: bool,
    left_pressed: bool,
    right_pressed: bool,
}

impl InputState {
    fn new() -> Self {
        InputState {
            up_pressed: false,
            down_pressed: false,
            left_pressed: false,
            right_pressed: false,
        }
    }

    fn handle_keyboard_input(&mut self, input: &KeyboardInput) -> bool {
        let pressed = input.state == ElementState::Pressed;
        match input.virtual_keycode {
            Some(VirtualKeyCode::Left) => {
                self.left_pressed = pressed;
                true
            }
            Some(VirtualKeyCode::Right) => {
                self.right_pressed = pressed;
                true
            }
            Some(VirtualKeyCode::Up) => {
                self.up_pressed = pressed;
                true
            }
            Some(VirtualKeyCode::Down) => {
                self.down_pressed = pressed;
                true
            }
            _ => false,
        }
    }
}

struct InputImpl {
    state: RefCell<InputState>,
}

impl InputImpl {
    fn new() -> Self {
        InputImpl {
            state: RefCell::new(InputState::new()),
        }
    }

    fn handle_keyboard_input(&self, input: &KeyboardInput) -> bool {
        self.state.borrow_mut().handle_keyboard_input(input)
    }

    fn btn(&self, button: u32) -> bool {
        if let Ok(b) = Button::try_from(button) {
            let state = self.state.borrow();
            match b {
                Button::Up => state.up_pressed,
                Button::Down => state.down_pressed,
                Button::Left => state.left_pressed,
                Button::Right => state.right_pressed,
            }
        } else {
            false
        }
    }
}

pub struct InputAPI {
    input: Arc<InputImpl>,
}

impl InputAPI {
    pub fn define(ctx: &ContextRef) -> oden_js::Result<Self> {
        let input = Arc::new(InputImpl::new());
        let mut builder = NativeModuleBuilder::new(ctx);
        {
            let input = input.clone();
            builder.export(
                "btn",
                ctx.new_fn(move |_ctx: &ContextRef, b: u32| input.btn(b))?,
            )?;
        }
        builder.export("BUTTON_UP", ctx.new_u32(Button::Up)?)?;
        builder.export("BUTTON_DOWN", ctx.new_u32(Button::Down)?)?;
        builder.export("BUTTON_LEFT", ctx.new_u32(Button::Left)?)?;
        builder.export("BUTTON_RIGHT", ctx.new_u32(Button::Right)?)?;
        builder.build("input-core")?;
        Ok(InputAPI { input })
    }

    pub fn handle_keyboard_input(&mut self, input: &KeyboardInput) -> bool {
        self.input.handle_keyboard_input(input)
    }
}
