use crate::*;
use bedrock_core::*;
use phosphor::*;

use std::collections::VecDeque;

macro_rules! fn_on_scroll {
    ($fn_name:ident($value:ident, $delta:ident)) => {
        pub fn $fn_name(&mut self, delta: f32) {
            self.$delta += delta;
            while self.$delta >= 1.0 {
                self.$value = self.$value.saturating_add(1);
                self.$delta -= 1.0;
                self.wake = true;
            }
            while self.$delta <= -1.0 {
                self.$value = self.$value.saturating_sub(1);
                self.$delta += 1.0;
                self.wake = true;
            }
        }
    };
}


pub struct InputDevice {
    pub wake: bool,
    pub accessed: bool,

    pub pointer_active: bool,
    pub pointer_buttons: u8,
    pub position: ScreenPosition,
    pub x_read: u16,
    pub y_read: u16,

    pub h_scroll: i8,
    pub v_scroll: i8,
    pub h_scroll_delta: f32,
    pub v_scroll_delta: f32,

    pub keyboard_active: bool,
    pub characters: VecDeque<u8>,
    pub navigation: u8,
    pub modifiers: u8,

    pub gamepad_1: u8,
    pub gamepad_2: u8,
    pub gamepad_3: u8,
    pub gamepad_4: u8,
}

impl InputDevice {
    pub fn new() -> Self {
        Self {
            wake: false,
            accessed: false,

            pointer_active: false,
            pointer_buttons: 0,

            position: ScreenPosition::ZERO,
            x_read: 0,
            y_read: 0,

            h_scroll: 0,
            v_scroll: 0,
            h_scroll_delta: 0.0,
            v_scroll_delta: 0.0,

            keyboard_active: true,
            characters: VecDeque::new(),
            modifiers: 0,
            navigation: 0,

            gamepad_1: 0,
            gamepad_2: 0,
            gamepad_3: 0,
            gamepad_4: 0,
        }
    }

    pub fn on_cursor_enter(&mut self) {
        self.pointer_active = true;
        self.wake = true;
    }

    pub fn on_cursor_exit(&mut self) {
        self.pointer_active = false;
        self.wake = true;
    }

    pub fn on_cursor_move(&mut self, position: Position) {
        let screen_position = ScreenPosition {
            x: position.x as i16 as u16,
            y: position.y as i16 as u16,
        };
        if self.position != screen_position {
            self.position = screen_position;
            self.wake = true;
        }
    }

    pub fn on_mouse_button(&mut self, button: MouseButton, action: Action) {
        let mask = match button {
            MouseButton::Left   => 0x80,
            MouseButton::Middle => 0x40,
            MouseButton::Right  => 0x20,
            _ => return,
        };
        let pointer_buttons = match action {
            Action::Pressed  => self.pointer_buttons |  mask,
            Action::Released => self.pointer_buttons & !mask,
        };
        if self.pointer_buttons != pointer_buttons {
            self.pointer_buttons = pointer_buttons;
            self.wake = true;
        }
    }

    fn_on_scroll!(on_horizontal_scroll(h_scroll, h_scroll_delta));
    fn_on_scroll!(on_vertical_scroll(v_scroll, v_scroll_delta));

    pub fn read_horizontal_scroll(&mut self) -> u8 {
        std::mem::take(&mut self.h_scroll) as u8
    }

    pub fn read_vertical_scroll(&mut self) -> u8 {
        std::mem::take(&mut self.v_scroll) as u8
    }

    pub fn on_character(&mut self, character: char) {
        let character = match character {
            '\r' => '\n',
            _ => character,
        };
        let mut bytes = [0; 4];
        let string = character.encode_utf8(&mut bytes);
        for byte in string.bytes() {
            self.characters.push_back(byte);
        }
        self.wake = true;
    }

    pub fn on_keypress(&mut self, key: KeyCode, action: Action) {
        let shift = self.modifiers & 0x40 != 0;
        let mask = match key {
            KeyCode::ArrowUp     => 0x80,  // up
            KeyCode::ArrowDown   => 0x40,  // down
            KeyCode::ArrowLeft   => 0x20,  // left
            KeyCode::ArrowRight  => 0x10,  // right
            KeyCode::Enter       => 0x08,  // confirm
            KeyCode::Escape      => 0x04,  // cancel
            KeyCode::Tab => match shift {  // shift
                false            => 0x02,  // next
                true             => 0x01   // previous
            },
            _ => return,
        };
        let navigation = match action {
            Action::Pressed  => self.navigation |  mask,
            Action::Released =>  self.navigation & !mask,
        };
        if self.navigation != navigation {
            self.navigation = navigation;
            self.wake = true;
        }
    }

    pub fn on_modifier(&mut self, state: ModifiersState) {
        let mut modifiers = 0;
        if state.control_key() { modifiers |= 0x80 }
        if state.shift_key()   { modifiers |= 0x40 }
        if state.alt_key()     { modifiers |= 0x20 }
        if state.super_key()   { modifiers |= 0x10 }
        if self.modifiers != modifiers  {
            self.modifiers = modifiers;
            self.wake = true;
        }
    }
}

impl Device for InputDevice {
    fn read(&mut self, port: u8) -> u8 {
        self.accessed = true;
        match port {
            0x0 => read_b!(self.pointer_active),
            0x1 => self.pointer_buttons,
            0x2 => self.read_horizontal_scroll(),
            0x3 => self.read_vertical_scroll(),
            0x4 => { self.x_read = self.position.x as u16; read_h!(self.x_read) },
            0x5 => read_l!(self.position.x),
            0x6 => { self.y_read = self.position.y as u16; read_h!(self.y_read) },
            0x7 => read_l!(self.position.y),
            0x8 => read_b!(self.keyboard_active),
            0x9 => self.characters.pop_front().unwrap_or(0),
            0xa => self.navigation,
            0xb => self.modifiers,
            0xc => self.gamepad_1,
            0xd => self.gamepad_2,
            0xe => self.gamepad_3,
            0xf => self.gamepad_4,
            _ => unreachable!(),
        }
    }

    fn write(&mut self, port: u8, _value: u8) -> Option<Signal> {
        self.accessed = true;
        match port {
            0x0 => (),
            0x1 => (),
            0x2 => (),
            0x3 => (),
            0x4 => (),
            0x5 => (),
            0x6 => (),
            0x7 => (),
            0x8 => (),
            0x9 => self.characters.clear(),
            0xa => (),
            0xb => (),
            0xc => (),
            0xd => (),
            0xe => (),
            0xf => (),
            _ => unreachable!(),
        };
        return None;
    }

    fn wake(&mut self) -> bool {
        self.accessed = true;
        std::mem::take(&mut self.wake)
    }
}