use crate::{
    DecodedKey, HandleControl, KeyCode, KeyboardLayout, LayoutEntry, LayoutEntryKind, Modifiers,
};

pub struct CustomLayout {
    mapping: [LayoutEntry; 256],
}

impl Default for CustomLayout {
    fn default() -> Self {
        Self::new_us104key()
    }
}

impl CustomLayout {
    pub fn new_us104key() -> Self {
        let mut mapping = Self {
            mapping: [LayoutEntry::default(); 256],
        };

        mapping.set(
            KeyCode::BackTick,
            LayoutEntry::regular().unshifted('`').shifted('`'),
        );
        mapping.set(KeyCode::Escape, LayoutEntry::regular().unshifted('\x1B'));
        mapping.set(
            KeyCode::Key0,
            LayoutEntry::regular().unshifted('0').shifted(')'),
        );
        mapping.set(
            KeyCode::Key1,
            LayoutEntry::regular().unshifted('1').shifted('!'),
        );
        mapping.set(
            KeyCode::Key2,
            LayoutEntry::regular().unshifted('2').shifted('@'),
        );
        mapping.set(
            KeyCode::Key3,
            LayoutEntry::regular().unshifted('3').shifted('#'),
        );
        mapping.set(
            KeyCode::Key4,
            LayoutEntry::regular().unshifted('4').shifted('$'),
        );
        mapping.set(
            KeyCode::Key5,
            LayoutEntry::regular().unshifted('5').shifted('%'),
        );
        mapping.set(
            KeyCode::Key6,
            LayoutEntry::regular().unshifted('6').shifted('^'),
        );
        mapping.set(
            KeyCode::Key7,
            LayoutEntry::regular().unshifted('7').shifted('&'),
        );
        mapping.set(
            KeyCode::Key8,
            LayoutEntry::regular().unshifted('8').shifted('*'),
        );
        mapping.set(
            KeyCode::Key9,
            LayoutEntry::regular().unshifted('9').shifted('('),
        );
        mapping.set(
            KeyCode::Minus,
            LayoutEntry::regular().unshifted('-').shifted('_'),
        );
        mapping.set(
            KeyCode::Equals,
            LayoutEntry::regular().unshifted('=').shifted('+'),
        );
        mapping.set(KeyCode::Backspace, LayoutEntry::regular().all('\x08'));
        mapping.set(KeyCode::Tab, LayoutEntry::regular().all('\x09'));
        mapping.set(
            KeyCode::Q,
            LayoutEntry::alphabet()
                .low('q')
                .high('Q')
                .raw_unicode('\u{0011}'),
        );
        mapping.set(
            KeyCode::W,
            LayoutEntry::alphabet()
                .low('w')
                .high('W')
                .raw_unicode('\u{0017}'),
        );
        mapping.set(
            KeyCode::E,
            LayoutEntry::alphabet()
                .low('e')
                .high('E')
                .raw_unicode('\u{0005}'),
        );
        mapping.set(
            KeyCode::R,
            LayoutEntry::alphabet()
                .low('r')
                .high('R')
                .raw_unicode('\u{0012}'),
        );
        mapping.set(
            KeyCode::T,
            LayoutEntry::alphabet()
                .low('t')
                .high('T')
                .raw_unicode('\u{0014}'),
        );
        mapping.set(
            KeyCode::Y,
            LayoutEntry::alphabet()
                .low('y')
                .high('Y')
                .raw_unicode('\u{0019}'),
        );
        mapping.set(
            KeyCode::U,
            LayoutEntry::alphabet()
                .low('u')
                .high('U')
                .raw_unicode('\u{0015}'),
        );
        mapping.set(
            KeyCode::I,
            LayoutEntry::alphabet()
                .low('i')
                .high('I')
                .raw_unicode('\u{0009}'),
        );
        mapping.set(
            KeyCode::O,
            LayoutEntry::alphabet()
                .low('o')
                .high('O')
                .raw_unicode('\u{000F}'),
        );
        mapping.set(
            KeyCode::P,
            LayoutEntry::alphabet()
                .low('p')
                .high('P')
                .raw_unicode('\u{0010}'),
        );
        mapping.set(
            KeyCode::A,
            LayoutEntry::alphabet()
                .low('a')
                .high('A')
                .raw_unicode('\u{0001}'),
        );
        mapping.set(
            KeyCode::S,
            LayoutEntry::alphabet()
                .low('s')
                .high('S')
                .raw_unicode('\u{0013}'),
        );
        mapping.set(
            KeyCode::D,
            LayoutEntry::alphabet()
                .low('d')
                .high('D')
                .raw_unicode('\u{0004}'),
        );
        mapping.set(
            KeyCode::F,
            LayoutEntry::alphabet()
                .low('f')
                .high('F')
                .raw_unicode('\u{0006}'),
        );
        mapping.set(
            KeyCode::G,
            LayoutEntry::alphabet()
                .low('g')
                .high('G')
                .raw_unicode('\u{0007}'),
        );
        mapping.set(
            KeyCode::H,
            LayoutEntry::alphabet()
                .low('h')
                .high('H')
                .raw_unicode('\u{0008}'),
        );
        mapping.set(
            KeyCode::J,
            LayoutEntry::alphabet()
                .low('j')
                .high('J')
                .raw_unicode('\u{000A}'),
        );
        mapping.set(
            KeyCode::K,
            LayoutEntry::alphabet()
                .low('k')
                .high('K')
                .raw_unicode('\u{000B}'),
        );
        mapping.set(
            KeyCode::L,
            LayoutEntry::alphabet()
                .low('l')
                .high('L')
                .raw_unicode('\u{000C}'),
        );
        mapping.set(
            KeyCode::Z,
            LayoutEntry::alphabet()
                .low('z')
                .high('Z')
                .raw_unicode('\u{001A}'),
        );
        mapping.set(
            KeyCode::X,
            LayoutEntry::alphabet()
                .low('x')
                .high('X')
                .raw_unicode('\u{0018}'),
        );
        mapping.set(
            KeyCode::C,
            LayoutEntry::alphabet()
                .low('c')
                .high('C')
                .raw_unicode('\u{0003}'),
        );
        mapping.set(
            KeyCode::V,
            LayoutEntry::alphabet()
                .low('v')
                .high('V')
                .raw_unicode('\u{0016}'),
        );
        mapping.set(
            KeyCode::B,
            LayoutEntry::alphabet()
                .low('b')
                .high('B')
                .raw_unicode('\u{0002}'),
        );
        mapping.set(
            KeyCode::N,
            LayoutEntry::alphabet()
                .low('n')
                .high('N')
                .raw_unicode('\u{000E}'),
        );
        mapping.set(
            KeyCode::M,
            LayoutEntry::alphabet()
                .low('m')
                .high('M')
                .raw_unicode('\u{000D}'),
        );
        mapping.set(
            KeyCode::BracketSquareLeft,
            LayoutEntry::regular().unshifted('{').shifted('['),
        );
        mapping.set(
            KeyCode::BracketSquareRight,
            LayoutEntry::regular().unshifted('}').shifted(']'),
        );
        mapping.set(
            KeyCode::BackSlash,
            LayoutEntry::regular().unshifted('|').shifted('\\'),
        );
        mapping.set(
            KeyCode::SemiColon,
            LayoutEntry::regular().unshifted(';').shifted(':'),
        );
        mapping.set(
            KeyCode::Quote,
            LayoutEntry::regular().unshifted('\'').shifted('"'),
        );
        mapping.set(KeyCode::Enter, LayoutEntry::regular().all('\x0A'));
        mapping.set(
            KeyCode::Comma,
            LayoutEntry::regular().unshifted(',').shifted('<'),
        );
        mapping.set(
            KeyCode::Fullstop,
            LayoutEntry::regular().unshifted('.').shifted('>'),
        );
        mapping.set(
            KeyCode::Slash,
            LayoutEntry::regular().unshifted('/').shifted('?'),
        );
        mapping.set(KeyCode::Spacebar, LayoutEntry::regular().all(' '));
        mapping.set(KeyCode::Delete, LayoutEntry::regular().all('\x7F'));
        mapping.set(KeyCode::NumpadSlash, LayoutEntry::numpad().all('/'));
        mapping.set(KeyCode::NumpadStar, LayoutEntry::numpad().all('*'));
        mapping.set(KeyCode::NumpadMinus, LayoutEntry::numpad().all('-'));
        mapping.set(
            KeyCode::Numpad7,
            LayoutEntry::numpad().low('7').high(KeyCode::Home),
        );
        mapping.set(
            KeyCode::Numpad8,
            LayoutEntry::numpad().low('8').high(KeyCode::ArrowUp),
        );
        mapping.set(
            KeyCode::Numpad9,
            LayoutEntry::numpad().low('9').high(KeyCode::PageUp),
        );
        mapping.set(KeyCode::NumpadPlus, LayoutEntry::numpad().all('+'));
        mapping.set(
            KeyCode::Numpad4,
            LayoutEntry::numpad().low('4').high(KeyCode::ArrowLeft),
        );
        mapping.set(KeyCode::Numpad5, LayoutEntry::numpad().all('5'));
        mapping.set(
            KeyCode::Numpad6,
            LayoutEntry::numpad().low('6').high(KeyCode::ArrowRight),
        );
        mapping.set(
            KeyCode::Numpad1,
            LayoutEntry::numpad().low('1').high(KeyCode::End),
        );
        mapping.set(
            KeyCode::Numpad2,
            LayoutEntry::numpad().low('2').high(KeyCode::ArrowDown),
        );
        mapping.set(
            KeyCode::Numpad3,
            LayoutEntry::numpad().low('3').high(KeyCode::PageDown),
        );
        mapping.set(
            KeyCode::Numpad0,
            LayoutEntry::numpad().low('0').high(KeyCode::Insert),
        );
        mapping.set(
            KeyCode::NumpadPeriod,
            LayoutEntry::numpad().low('.').high('\x7F'),
        );
        mapping.set(KeyCode::NumpadEnter, LayoutEntry::numpad().all('\x0A'));
        mapping
    }
    pub fn new_us105key() -> Self {
        let mut mapping = Self::new_us104key();
        mapping.set(
            KeyCode::BackTick,
            LayoutEntry::regular()
                .unshifted('`')
                .shifted('¬')
                .altgr('|'),
        );
        mapping.set(
            KeyCode::Key2,
            LayoutEntry::regular().unshifted('2').shifted('"'),
        );
        mapping.set(
            KeyCode::Quote,
            LayoutEntry::regular().unshifted('\'').shifted('@'),
        );
        mapping.set(
            KeyCode::Key3,
            LayoutEntry::regular().unshifted('3').shifted('£'),
        );
        mapping.set(
            KeyCode::BackTick,
            LayoutEntry::regular()
                .unshifted('4')
                .shifted('$')
                .altgr('€'),
        );
        mapping.set(
            KeyCode::HashTilde,
            LayoutEntry::regular().unshifted('#').shifted('~'),
        );
        mapping
    }

    pub fn set(&mut self, pos: KeyCode, entry: LayoutEntry) {
        self.mapping[pos as usize] = entry;
    }
}

impl KeyboardLayout for CustomLayout {
    fn map_keycode(
        &self,
        keycode: KeyCode,
        modifiers: &Modifiers,
        handle_ctrl: HandleControl,
    ) -> DecodedKey {
        let map_to_unicode = handle_ctrl == HandleControl::MapLettersToUnicode;
        let spot = &self.mapping[keycode as usize];
        if let Some(k) = if map_to_unicode && modifiers.is_ctrl() {
            match spot.kind {
                LayoutEntryKind::Regular => spot.raw_unicode,
                LayoutEntryKind::Numlockable => None,
                LayoutEntryKind::Capslockable => spot.raw_unicode,
            }
        } else if modifiers.alt_gr {
            match spot.kind {
                LayoutEntryKind::Regular => spot.altgr,
                LayoutEntryKind::Numlockable => None,
                LayoutEntryKind::Capslockable => spot.altgr,
            }
        } else if modifiers.is_shifted() {
            match spot.kind {
                LayoutEntryKind::Regular => spot.shifted,
                LayoutEntryKind::Numlockable => {
                    if modifiers.numlock {
                        spot.locked_shifted
                    } else {
                        spot.shifted
                    }
                }
                LayoutEntryKind::Capslockable => {
                    if modifiers.is_caps() {
                        spot.locked_shifted
                    } else {
                        spot.shifted
                    }
                }
            }
        } else {
            match spot.kind {
                LayoutEntryKind::Regular => spot.unshifted,
                LayoutEntryKind::Numlockable => {
                    if modifiers.numlock {
                        spot.locked
                    } else {
                        spot.unshifted
                    }
                }
                LayoutEntryKind::Capslockable => {
                    if modifiers.is_caps() {
                        spot.locked
                    } else {
                        spot.unshifted
                    }
                }
            }
        } {
            k
        } else {
            DecodedKey::RawKey(keycode as u8)
        }
    }
}
