use bitflags::bitflags;
use x86_64::instructions::port::Port;

const ADDRESS_PORT_ADDRESS: u16 = 0x64;
const DATA_PORT_ADDRESS: u16 = 0x60;
const GET_STATUS_BYTE: u8 = 0x20;
const SET_STATUS_BYTE: u8 = 0x60;

const DISABLE_FIRST: u8 = 0xAD;
const DISABLE_SECOND: u8 = 0xA7;
const ENABLE_FIRST: u8 = 0xAE;
const ENABLE_SECOND: u8 = 0xA8;

bitflags! {
    /// Represents the flags currently set for the mouse.
    #[derive(Default)]
    pub struct MouseFlags: u8 {
        /// Whether or not the left mouse button is pressed.
        const LEFT_BUTTON = 0b0000_0001;

        /// Whether or not the right mouse button is pressed.
        const RIGHT_BUTTON = 0b0000_0010;

        /// Whether or not the middle mouse button is pressed.
        const MIDDLE_BUTTON = 0b0000_0100;

        /// Whether or not the packet is valid or not.
        const ALWAYS_ONE = 0b0000_1000;

        /// Whether or not the x delta is negative.
        const X_SIGN = 0b0001_0000;

        /// Whether or not the y delta is negative.
        const Y_SIGN = 0b0010_0000;

        /// Whether or not the x delta overflowed.
        const X_OVERFLOW = 0b0100_0000;

        /// Whether or not the y delta overflowed.
        const Y_OVERFLOW = 0b1000_0000;
    }
}

#[repr(u8)]
enum Command {
    EnablePacketStreaming = 0xF4,
    SetDefaults = 0xF6,
}

/// A basic interface to interact with a PS2 mouse.
#[derive(Debug)]
pub struct Mouse {
    command_port: Port<u8>,
    data_port: Port<u8>,
    current_packet: u8,
    current_state: MouseState,
    completed_state: MouseState,
    on_complete: Option<fn(MouseState)>,
}

impl Default for Mouse {
    fn default() -> Mouse {
        Mouse::new()
    }
}

/// A snapshot of the mouse flags, x delta and y delta.
#[derive(Debug, Copy, Clone, Default)]
pub struct MouseState {
    flags: MouseFlags,
    x: i16,
    y: i16,
}

impl MouseState {
    /// Returns a new `MouseState`.
    pub const fn new() -> MouseState {
        MouseState {
            flags: MouseFlags::empty(),
            x: 0,
            y: 0,
        }
    }

    /// Returns true if the left mouse button is currently down.
    pub fn _left_button_down(&self) -> bool {
        self.flags.contains(MouseFlags::LEFT_BUTTON)
    }

    /// Returns true if the left mouse button is currently up.
    pub fn _left_button_up(&self) -> bool {
        !self.flags.contains(MouseFlags::LEFT_BUTTON)
    }

    /// Returns true if the right mouse button is currently down.
    pub fn _right_button_down(&self) -> bool {
        self.flags.contains(MouseFlags::RIGHT_BUTTON)
    }

    /// Returns true if the right mouse button is currently up.
    pub fn _right_button_up(&self) -> bool {
        !self.flags.contains(MouseFlags::RIGHT_BUTTON)
    }

    /// Returns true if the x axis has moved.
    pub fn _x_moved(&self) -> bool {
        self.x != 0
    }

    /// Returns true if the y axis has moved.
    pub fn _y_moved(&self) -> bool {
        self.y != 0
    }

    /// Returns true if the x or y axis has moved.
    pub fn _moved(&self) -> bool {
        self._x_moved() || self._y_moved()
    }

    /// Returns the x delta of the mouse state.
    pub fn get_x(&self) -> i16 {
        self.x
    }

    /// Returns the y delta of the mouse state.
    pub fn get_y(&self) -> i16 {
        self.y
    }
}

impl Mouse {
    /// Creates a new `Mouse`.
    pub const fn new() -> Mouse {
        Mouse {
            command_port: Port::new(ADDRESS_PORT_ADDRESS),
            data_port: Port::new(DATA_PORT_ADDRESS),
            current_packet: 0,
            current_state: MouseState::new(),
            completed_state: MouseState::new(),
            on_complete: None,
        }
    }

    /// Returns the last completed state of the mouse.
    pub fn _get_state(&self) -> MouseState {
        self.completed_state
    }

    // super helpful resource, albeit in C
    // https://github.com/29jm/SnowflakeOS/blob/master/kernel/src/devices/ps2.c#L18
    /// Attempts to initialize a `Mouse`. If successful, interrupts will be generated
    /// as `PIC offset + 12`.
    pub fn init(&mut self) -> Result<(), &'static str> {
        // Disable both PS/2 device ports
        // Even if only one is present, disabling the second is harmless
        self.write_command_port(DISABLE_FIRST)?;
        self.write_command_port(DISABLE_SECOND)?;

        // Flush output buffer: if the controller had anything to say, ignore it
        unsafe {
            self.data_port.read();
        }

        debug!("mouse driver: writing GET_STATUS to port...");
        self.write_command_port(GET_STATUS_BYTE)?;
        debug!("mouse driver: reading status from port...");
        let status = self.read_data_port()? | 0x02;

        debug!("Got status {}", status);

        // self.write_command_port(0xa8)?;

        self.write_command_port(SET_STATUS_BYTE)?;

        self.write_data_port(status & 0xDF)?;

        self.send_command(Command::SetDefaults)?;
        self.send_command(Command::EnablePacketStreaming)?;

        self.write_command_port(ENABLE_FIRST)?;
        self.write_command_port(ENABLE_SECOND)?;

        // Some keyboards actually send a reply, flush it
        unsafe {
            self.data_port.read();
        }

        Ok(())
    }

    /// Attempts to process a packet.
    pub fn process_packet(&mut self, packet: u8) {
        match self.current_packet {
            0 => {
                let flags = MouseFlags::from_bits_truncate(packet);
                if !flags.contains(MouseFlags::ALWAYS_ONE) {
                    return;
                }
                self.current_state.flags = flags;
            }
            1 => self.process_x_movement(packet),
            2 => {
                self.process_y_movement(packet);
                self.completed_state = self.current_state;
                if let Some(on_complete) = self.on_complete {
                    on_complete(self.completed_state);
                }
            }
            _ => unreachable!(),
        }
        self.current_packet = (self.current_packet + 1) % 3;
    }

    /// Sets the `on_complete` function to be called when a packet is completed.
    pub fn set_on_complete(&mut self, handler: fn(MouseState)) {
        self.on_complete = Some(handler);
    }

    fn process_x_movement(&mut self, packet: u8) {
        if !self.current_state.flags.contains(MouseFlags::X_OVERFLOW) {
            self.current_state.x = if self.current_state.flags.contains(MouseFlags::X_SIGN) {
                self.sign_extend(packet)
            } else {
                packet as i16
            };
        }
    }

    fn process_y_movement(&mut self, packet: u8) {
        if !self.current_state.flags.contains(MouseFlags::Y_OVERFLOW) {
            self.current_state.y = if self.current_state.flags.contains(MouseFlags::Y_SIGN) {
                self.sign_extend(packet)
            } else {
                packet as i16
            };
        }
    }

    fn read_data_port(&mut self) -> Result<u8, &'static str> {
        // INFO: What the fuck
        debug!("owo");
        self.wait_for_read()?;
        // HERESY: Stop
        debug!("what's this");
        Ok(unsafe { self.data_port.read() })
    }

    fn send_command(&mut self, command: Command) -> Result<(), &'static str> {
        self.write_command_port(0xD4)?;
        self.write_data_port(command as u8)?;
        if self.read_data_port()? != 0xFA {
            return Err("mouse did not respond to the command");
        }
        Ok(())
    }

    fn sign_extend(&self, packet: u8) -> i16 {
        ((packet as u16) | 0xFF00) as i16
    }

    fn write_command_port(&mut self, value: u8) -> Result<(), &'static str> {
        debug!("mouse driver: waiting for write");
        self.wait_for_write()?;
        unsafe {
            self.command_port.write(value);
        }
        debug!("mouse driver: command written");
        Ok(())
    }

    fn write_data_port(&mut self, value: u8) -> Result<(), &'static str> {
        self.wait_for_write()?;
        unsafe {
            self.data_port.write(value);
        }
        Ok(())
    }

    fn wait_for_read(&mut self) -> Result<(), &'static str> {
        let timeout = 100_000;
        for _x in 0..timeout {
            let value = unsafe { self.command_port.read() };
            if (value & 0x1) == 0x1 {
                return Ok(());
            }
        }
        Err("wait for mouse read timeout")
    }

    fn wait_for_write(&mut self) -> Result<(), &'static str> {
        let timeout = 100_000;
        for _ in 0..timeout {
            let value = unsafe { self.command_port.read() };
            if (value & 0x2) == 0x0 {
                return Ok(());
            }
        }
        Err("wait for mouse write timeout")
    }
}
