// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved.
//
// StratoVirt is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan
// PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
//         http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.

use std::fmt::{Display, Formatter, Result as FmtResult};

use log::error;

use super::config::*;
use super::{UsbDeviceRequest, UsbPacket, UsbPacketStatus};
use ui::input::set_kbd_led_state;

/// HID keycode
const HID_KEYBOARD_LEFT_CONTROL: u8 = 0xe0;
#[allow(unused)]
const HID_KEYBOARD_LEFT_SHIFT: u8 = 0xe1;
#[allow(unused)]
const HID_KEYBOARD_LEFT_ALT: u8 = 0xe2;
#[allow(unused)]
const HID_KEYBOARD_LEFT_GUI: u8 = 0xe3;
#[allow(unused)]
const HID_KEYBOARD_RIGHT_CONTROL: u8 = 0xe4;
#[allow(unused)]
const HID_KEYBOARD_RIGHT_SHIFT: u8 = 0xe5;
#[allow(unused)]
const HID_KEYBOARD_RIGHT_ALT: u8 = 0xe6;
const HID_KEYBOARD_RIGHT_GUI: u8 = 0xe7;

/// See the spec section 7.2 Class-Specific Requests
pub const HID_GET_REPORT: u8 = 0x01;
pub const HID_GET_IDLE: u8 = 0x02;
pub const HID_GET_PROTOCOL: u8 = 0x03;
pub const HID_SET_REPORT: u8 = 0x09;
pub const HID_SET_IDLE: u8 = 0x0a;
pub const HID_SET_PROTOCOL: u8 = 0x0b;

/// See the spec section 7.2.5 Get Protocol Request
#[allow(unused)]
const HID_PROTOCTL_BOOT: u8 = 0;
const HID_PROTOCOL_REPORT: u8 = 1;
const KEYCODE_UP: u32 = 0x80;
pub const QUEUE_LENGTH: u32 = 16;
pub const QUEUE_MASK: u32 = QUEUE_LENGTH - 1;
const HID_USAGE_ERROR_ROLLOVER: u8 = 0x1;

/// QKeyCode to HID code table
const HID_CODE: [u8; 0x100] = [
    0x00, 0x29, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x2d, 0x2e, 0x2a, 0x2b,
    0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12, 0x13, 0x2f, 0x30, 0x28, 0xe0, 0x04, 0x16,
    0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f, 0x33, 0x34, 0x35, 0xe1, 0x31, 0x1d, 0x1b, 0x06, 0x19,
    0x05, 0x11, 0x10, 0x36, 0x37, 0x38, 0xe5, 0x55, 0xe2, 0x2c, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e,
    0x3f, 0x40, 0x41, 0x42, 0x43, 0x53, 0x47, 0x5f, 0x60, 0x61, 0x56, 0x5c, 0x5d, 0x5e, 0x57, 0x59,
    0x5a, 0x5b, 0x62, 0x63, 0x46, 0x00, 0x64, 0x44, 0x45, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e,
    0xe8, 0xe9, 0x71, 0x72, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0x00, 0x00, 0x00, 0x00,
    0x88, 0x00, 0x00, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x00, 0x8b, 0x00, 0x89, 0xe7, 0x65,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0xe4, 0x00, 0x00,
    0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0x00,
    0x80, 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x46, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x48, 0x4a, 0x52, 0x4b, 0x00, 0x50, 0x00, 0x4f, 0x00, 0x4d,
    0x51, 0x4e, 0x49, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0xe7, 0x65, 0x66, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
/// Tablet report descriptor
const TABLET_REPORT_DESCRIPTOR: [u8; 89] = [
    0x05, 0x01, // Usage Page (Generic Desktop)
    0x09, 0x02, // Usage (Mouse)
    0xa1, 0x01, // Collection (Application)
    0x09, 0x01, // Usage (Pointer)
    0xa1, 0x00, // Collection (Physical)
    0x05, 0x09, // Usage Page (Button)
    0x19, 0x01, // Usage Minimum (1)
    0x29, 0x05, // Usage Maximum (5)
    0x15, 0x00, // Logical Minimum (0)
    0x25, 0x01, // Logical Maximum (1)
    0x95, 0x05, // Report Count (5)
    0x75, 0x01, // Report Size (1)
    0x81, 0x02, // Input (Data, Variable, Absolute)
    0x95, 0x01, // Report Count (1)
    0x75, 0x03, // Report Size (3)
    0x81, 0x01, // Input (Constant)
    0x05, 0x01, // Usage Page (Generic Desktop)
    0x09, 0x30, // Usage (X)
    0x09, 0x31, // Usage (Y)
    0x15, 0x00, // Logical Minimum (0)
    0x26, 0xff, 0x7f, // Logical Maximum (0x7fff)
    0x35, 0x00, // Physical Minimum (0)
    0x46, 0xff, 0x7f, // Physical Maximum (0x7fff)
    0x75, 0x10, // Report Size (16)
    0x95, 0x02, // Report Count (2)
    0x81, 0x02, // Input (Data, Variable, Absolute)
    0x05, 0x01, // Usage Page (Generic Desktop)
    0x09, 0x38, // Usage (Wheel)
    0x15, 0x81, // Logical Minimum (-0x7f)
    0x25, 0x7f, // Logical Maximum (0x7f)
    0x35, 0x00, // Physical Minimum (same as logical)
    0x45, 0x00, // Physical Maximum (same as logical)
    0x75, 0x08, // Report Size (8)
    0x95, 0x01, // Report Count (1)
    0x81, 0x06, // Input (Data, Variable, Relative)
    0x05, 0x0c, // Usage Page (Consumer Device)
    0x0a, 0x38, 0x02, // Usage (AC Pan)
    0x15, 0x81, // Logical Minimum (-0x7f)
    0x25, 0x7f, // Logical Maximum (0x7f)
    0x75, 0x08, // Report Size (8)
    0x95, 0x01, // Report Count (1)
    0x81, 0x06, // Input (Data, Variable, Relative)
    0xc0, 0xc0, // End Collection
];
/// Keyboard report descriptor
const KEYBOARD_REPORT_DESCRIPTOR: [u8; 63] = [
    0x05, 0x01, // Usage Page (Generic Desktop)
    0x09, 0x06, // Usage (Keyboard)
    0xa1, 0x01, // Collection (Application)
    0x75, 0x01, // Report Size (1)
    0x95, 0x08, // Report Count (8)
    0x05, 0x07, // Usage Page (Key Codes)
    0x19, 0xe0, // Usage Minimum (224)
    0x29, 0xe7, // Usage Maximum (231)
    0x15, 0x00, // Logical Minimum (0)
    0x25, 0x01, // Logical Maximum (1)
    0x81, 0x02, // Input (Data, Variable, Absolute)
    0x95, 0x01, // Report Count (1)
    0x75, 0x08, // Report Size (8)
    0x81, 0x01, // Input (Constant)
    0x95, 0x05, // Report Count (5)
    0x75, 0x01, // Report Size (1)
    0x05, 0x08, // Usage Page (LEDs)
    0x19, 0x01, // Usage Minimum (1)
    0x29, 0x05, // Usage Maximum (5)
    0x91, 0x02, // Output (Data, Variable, Absolute)
    0x95, 0x01, // Report Count (1)
    0x75, 0x03, // Report Size (3)
    0x91, 0x01, // Output (Constant)
    0x95, 0x06, // Report Count (6)
    0x75, 0x08, // Report Size (8)
    0x15, 0x00, // Logical Minimum (0)
    0x25, 0xff, // Logical Maximum (255)
    0x05, 0x07, // Usage Page (Key Codes)
    0x19, 0x00, // Usage Minimum (0)
    0x29, 0xff, // Usage Maximum (255)
    0x81, 0x00, // Input (Data, Array)
    0xc0, // End Collection
];

/// HID type
#[derive(Debug)]
pub enum HidType {
    Mouse,
    Tablet,
    Keyboard,
    UnKnown,
}

/// HID keyboard including keycode and modifier.
pub struct HidKeyboard {
    /// Receive keycode from VNC.
    pub keycodes: [u32; QUEUE_LENGTH as usize],
    pub modifiers: u16,
    /// Send keycode to driver.
    pub key_buf: [u8; QUEUE_LENGTH as usize],
    pub key_num: u32,
}

impl HidKeyboard {
    fn new() -> HidKeyboard {
        HidKeyboard {
            keycodes: [0; QUEUE_LENGTH as usize],
            modifiers: 0,
            key_buf: [0; QUEUE_LENGTH as usize],
            key_num: 0,
        }
    }

    fn reset(&mut self) {
        self.keycodes.iter_mut().for_each(|x| *x = 0);
        self.modifiers = 0;
        self.key_buf.iter_mut().for_each(|x| *x = 0);
        self.key_num = 0;
    }
}

/// HID pointer event including position and button state.
#[derive(Debug, Clone, Copy, Default)]
pub struct HidPointerEvent {
    /// Direction: left to right.
    pub pos_x: u32,
    /// Direction: up to down.
    pub pos_y: u32,
    /// Vertical scroll wheel.
    pub v_wheel: i32,
    /// Horizontal scroll wheel.
    pub h_wheel: i32,
    pub button_state: u32,
}

/// HID pointer which include hid pointer event.
pub struct HidPointer {
    pub queue: [HidPointerEvent; QUEUE_LENGTH as usize],
}

impl HidPointer {
    fn new() -> Self {
        HidPointer {
            queue: [HidPointerEvent::default(); QUEUE_LENGTH as usize],
        }
    }

    fn reset(&mut self) {
        self.queue
            .iter_mut()
            .for_each(|x| *x = HidPointerEvent::default());
    }
}

/// Human Interface Device.
pub struct Hid {
    pub(crate) head: u32,
    pub(crate) num: u32,
    pub(crate) kind: HidType,
    protocol: u8,
    idle: u8,
    pub(crate) keyboard: HidKeyboard,
    pub(crate) pointer: HidPointer,
}

impl Hid {
    pub fn new(kind: HidType) -> Self {
        Hid {
            head: 0,
            num: 0,
            kind,
            protocol: 0,
            idle: 0,
            keyboard: HidKeyboard::new(),
            pointer: HidPointer::new(),
        }
    }

    pub fn reset(&mut self) {
        self.head = 0;
        self.num = 0;
        self.protocol = HID_PROTOCOL_REPORT;
        self.idle = 0;
        self.keyboard.reset();
        self.pointer.reset();
    }

    fn convert_to_hid_code(&mut self) {
        if self.num == 0 {
            return;
        }
        let slot = self.head & QUEUE_MASK;
        self.increase_head();
        self.num -= 1;
        let keycode = self.keyboard.keycodes[slot as usize];
        let key = keycode & 0x7f;
        let index = key | ((self.keyboard.modifiers as u32 & (1 << 8)) >> 1);
        let hid_code = HID_CODE[index as usize];
        self.keyboard.modifiers &= !(1 << 8);
        trace::usb_convert_to_hid_code(&hid_code, &index, &key);

        if hid_code == 0x0 {
            return;
        }
        if hid_code == HID_KEYBOARD_LEFT_CONTROL && self.keyboard.modifiers & (1 << 9) == (1 << 9) {
            self.keyboard.modifiers ^= (1 << 8) | (1 << 9);
            return;
        }
        if (HID_KEYBOARD_LEFT_CONTROL..=HID_KEYBOARD_RIGHT_GUI).contains(&hid_code)
            && keycode & KEYCODE_UP == KEYCODE_UP
        {
            self.keyboard.modifiers &= !(1 << (hid_code & 0x0f));
            return;
        }
        if (HID_KEYBOARD_LEFT_CONTROL..=0xe9).contains(&hid_code) {
            self.keyboard.modifiers |= 1 << (hid_code & 0x0f);
            return;
        }
        // Invalid code.
        if (0xea..=0xef).contains(&hid_code) {
            error!("Convert error, invalid code {}", hid_code);
            return;
        }
        if keycode & KEYCODE_UP == KEYCODE_UP {
            let mut i = self.keyboard.key_num as i32 - 1;
            while i >= 0 {
                if self.keyboard.key_buf[i as usize] == hid_code {
                    self.keyboard.key_num -= 1;
                    self.keyboard.key_buf[i as usize] =
                        self.keyboard.key_buf[self.keyboard.key_num as usize];
                    self.keyboard.key_buf[self.keyboard.key_num as usize] = 0x0;
                    break;
                }
                i -= 1;
            }
        } else {
            let mut i = self.keyboard.key_num as i32 - 1;
            while i >= 0 {
                if self.keyboard.key_buf[i as usize] == hid_code {
                    break;
                }
                i -= 1;
            }
            if i < 0 && self.keyboard.key_num < self.keyboard.key_buf.len() as u32 {
                self.keyboard.key_buf[self.keyboard.key_num as usize] = hid_code;
                self.keyboard.key_num += 1;
            }
        }
    }

    fn keyboard_poll(&mut self) -> Vec<u8> {
        let mut data = vec![0; 8];
        self.convert_to_hid_code();
        data[0] = self.keyboard.modifiers as u8;
        data[1] = 0;
        let len = data.len() - 2;
        if self.keyboard.key_num > 6 {
            for i in 0..len {
                data[i + 2] = HID_USAGE_ERROR_ROLLOVER;
            }
        } else {
            data[2..(len + 2)].clone_from_slice(&self.keyboard.key_buf[..len]);
        }
        data
    }

    fn pointer_poll(&mut self) -> Vec<u8> {
        let index = self.head;
        if self.num != 0 {
            self.increase_head();
            self.num -= 1;
        }
        let evt = &mut self.pointer.queue[(index & QUEUE_MASK) as usize];
        vec![
            evt.button_state as u8,
            evt.pos_x as u8,
            (evt.pos_x >> 8) as u8,
            evt.pos_y as u8,
            (evt.pos_y >> 8) as u8,
            evt.v_wheel as u8,
            evt.h_wheel as u8,
        ]
    }

    fn increase_head(&mut self) {
        if self.head + 1 >= QUEUE_LENGTH {
            self.head = 0;
        } else {
            self.head += 1;
        }
    }

    /// USB HID device handle control packet.
    pub fn handle_control_packet(
        &mut self,
        packet: &mut UsbPacket,
        device_req: &UsbDeviceRequest,
        data: &mut [u8],
    ) {
        match device_req.request_type {
            USB_INTERFACE_IN_REQUEST => {
                self.do_interface_in_request(packet, device_req, data);
            }
            USB_INTERFACE_CLASS_IN_REQUEST => {
                self.do_interface_class_in_request(packet, device_req, data);
            }
            USB_INTERFACE_CLASS_OUT_REQUEST => {
                self.do_interface_class_out_request(packet, device_req, data);
            }
            _ => {
                error!("Unhandled request {}", device_req.request);
                packet.status = UsbPacketStatus::Stall;
            }
        }
    }

    fn do_interface_in_request(
        &mut self,
        packet: &mut UsbPacket,
        device_req: &UsbDeviceRequest,
        data: &mut [u8],
    ) {
        match device_req.request {
            USB_REQUEST_GET_DESCRIPTOR => match device_req.value >> 8 {
                0x22 => match self.kind {
                    HidType::Tablet => {
                        data[..TABLET_REPORT_DESCRIPTOR.len()]
                            .clone_from_slice(&TABLET_REPORT_DESCRIPTOR[..]);
                        packet.actual_length = TABLET_REPORT_DESCRIPTOR.len() as u32;
                    }
                    HidType::Keyboard => {
                        data[..KEYBOARD_REPORT_DESCRIPTOR.len()]
                            .clone_from_slice(&KEYBOARD_REPORT_DESCRIPTOR[..]);
                        packet.actual_length = KEYBOARD_REPORT_DESCRIPTOR.len() as u32;
                    }
                    _ => {
                        error!("Unknown HID type");
                        packet.status = UsbPacketStatus::Stall;
                    }
                },
                _ => {
                    error!("Invalid value: {:?}", device_req);
                    packet.status = UsbPacketStatus::Stall;
                }
            },
            _ => {
                error!("Unhandled request {}", device_req.request);
                packet.status = UsbPacketStatus::Stall;
            }
        }
    }

    fn do_interface_class_in_request(
        &mut self,
        packet: &mut UsbPacket,
        device_req: &UsbDeviceRequest,
        data: &mut [u8],
    ) {
        match device_req.request {
            HID_GET_REPORT => match self.kind {
                HidType::Tablet => {
                    let buf = self.pointer_poll();
                    data[0..buf.len()].copy_from_slice(buf.as_slice());
                    packet.actual_length = buf.len() as u32;
                }
                HidType::Keyboard => {
                    let buf = self.keyboard_poll();
                    data[0..buf.len()].copy_from_slice(buf.as_slice());
                    packet.actual_length = buf.len() as u32;
                }
                _ => {
                    error!("Unsupported HID type for report");
                    packet.status = UsbPacketStatus::Stall;
                }
            },
            HID_GET_PROTOCOL => {
                data[0] = self.protocol;
                packet.actual_length = 1;
            }
            HID_GET_IDLE => {
                data[0] = self.idle;
                packet.actual_length = 1;
            }
            _ => {
                error!("Unhandled request {}", device_req.request);
                packet.status = UsbPacketStatus::Stall;
            }
        }
    }

    fn do_interface_class_out_request(
        &mut self,
        packet: &mut UsbPacket,
        device_req: &UsbDeviceRequest,
        data: &[u8],
    ) {
        match device_req.request {
            HID_SET_REPORT => match self.kind {
                HidType::Keyboard => {
                    trace::usb_keyboard_set_report(&data[0]);
                    set_kbd_led_state(data[0]);
                }
                _ => {
                    error!("Unsupported to set report");
                    packet.status = UsbPacketStatus::Stall;
                }
            },
            HID_SET_PROTOCOL => {
                self.protocol = device_req.value as u8;
            }
            HID_SET_IDLE => {
                self.idle = (device_req.value >> 8) as u8;
            }
            _ => {
                error!("Unhandled request {}", device_req.request);
                packet.status = UsbPacketStatus::Stall;
            }
        }
    }

    /// USB HID device handle data packet.
    pub fn handle_data_packet(&mut self, p: &mut UsbPacket) {
        match p.pid as u8 {
            USB_TOKEN_IN => {
                self.handle_token_in(p);
            }
            _ => {
                error!("Unhandled packet {}", p.pid);
                p.status = UsbPacketStatus::Stall;
            }
        };
    }

    fn handle_token_in(&mut self, p: &mut UsbPacket) {
        let mut buf = Vec::new();
        if p.ep_number == 1 {
            if self.num == 0 {
                trace::usb_no_data_in_usb_device();
                p.status = UsbPacketStatus::Nak;
                return;
            }
            match self.kind {
                HidType::Keyboard => {
                    buf = self.keyboard_poll();
                }
                HidType::Tablet => {
                    buf = self.pointer_poll();
                }
                _ => {
                    error!("Unsupported HID device");
                    p.status = UsbPacketStatus::Stall;
                }
            }
            let len = buf.len();
            p.transfer_packet(&mut buf, len);
        } else {
            error!("Unhandled endpoint {}", p.ep_number);
            p.status = UsbPacketStatus::Stall;
        }
    }
}

impl Display for Hid {
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
        write!(
            f,
            "HID head {} num {} kind {:?} protocol {} idle {}",
            self.head, self.num, self.kind, self.protocol, self.idle
        )
    }
}