use crate::escape::{
    CharacterAttribute, ControlSequence, CursorStyle, EraseInDisplay, EraseInLine,
    QueryKeyModifiers,
};

pub enum CsiState {
    // CSI ...
    Normal(CsiArgs),
    // CSI # ...
    PrefixHash(CsiArgs),
    // CSI ? ...
    PrefixQuestion(CsiArgs),
    // CSI > ...
    PrefixGt(CsiArgs),
    // CSI = ...
    PrefixEq(CsiArgs),
    // CSI ! ...
    PrefixExcl(CsiArgs),
    // CSI & ...
    PrefixAnd(CsiArgs),
    // CSI " ...
    PrefixDquote(CsiArgs),
}

pub struct CsiArgs {
    args: Vec<u32>,
    suffix: char,
}

impl CsiState {
    pub(super) const fn new() -> Self {
        Self::Normal(CsiArgs::new())
    }

    pub(super) fn push(&mut self, ch: char) -> (Option<ControlSequence>, bool) {
        match self {
            Self::Normal(args) => match (ch, args.suffix) {
                // CSI ? ...
                ('?', _) if args.is_empty() => {
                    *self = Self::PrefixQuestion(CsiArgs::new());
                    (None, false)
                }

                (_, _) if args.push(ch) => (None, false),
                // CSI Ps @         Insert Ps (Blank) Character(s) (default = 1)
                ('@', '\0') => todo!(),
                // CSI Ps SP @      Shift left Ps column(s) (default = 1)
                ('@', ' ') => todo!(),
                // CSI Ps A         Cursor Up Ps Times (default = 1)
                ('A', '\0') => (Some(ControlSequence::MoveCursorUp(args.get_or(0, 1))), true),
                // CSI Ps SP A      Shift right Ps column(s) (default = 1)
                ('A', ' ') => todo!(),
                // CSI Ps B         Cursor Down Ps Times (default = 1)
                ('B', '\0') => (
                    Some(ControlSequence::MoveCursorDown(args.get_or(0, 1))),
                    true,
                ),
                // CSI Ps C         Cursor Forward Ps Times (default = 1)
                ('C', '\0') => (
                    Some(ControlSequence::MoveCursorForward(args.get_or(0, 1))),
                    true,
                ),
                // CSI Ps D         Cursor Backward Ps Times (default = 1)
                ('D', '\0') => todo!(),
                // CSI Ps E         Cursor Next Line Ps Times (default = 1)
                ('E', '\0') => todo!(),
                // CSI Ps F         Cursor Preceding Line Ps Times (default = 1)
                ('F', '\0') => todo!(),
                // CSI Ps G         Cursor Character Absolute [column] (default = [row, 1])
                ('G', '\0') => todo!(),
                // CSI Ps ; Ps H    Cursor Position [row;column] (default = [1,1])
                ('H', '\0') => (
                    Some(ControlSequence::SetCursorPosition(
                        args.get_or(0, 1),
                        args.get_or(1, 1),
                    )),
                    true,
                ),
                // CSI Ps I         Cursor Forward Tabulation Ps tab stops (default = 1)
                ('I', '\0') => todo!(),
                // CSI Ps J         Erase in Display
                ('J', '\0') => match args.get(0) {
                    // Ps = 3 ⇒ Erase Saved Lines
                    3 => (
                        Some(ControlSequence::EraseInDisplay(EraseInDisplay::SavedLines)),
                        true,
                    ),
                    // Ps = 2 ⇒ Erase All
                    2 => (
                        Some(ControlSequence::EraseInDisplay(EraseInDisplay::All)),
                        true,
                    ),
                    // Ps = 1 ⇒ Erase Above
                    1 => (
                        Some(ControlSequence::EraseInDisplay(EraseInDisplay::Above)),
                        true,
                    ),
                    // Ps = 0 ⇒ Erase Below (default)
                    _ => (
                        Some(ControlSequence::EraseInDisplay(EraseInDisplay::Below)),
                        true,
                    ),
                },
                // CSI Ps K         Erase in Line
                ('K', '\0') => match args.get(0) {
                    // Ps = 2 ⇒ Erase All
                    2 => (Some(ControlSequence::EraseInLine(EraseInLine::All)), true),
                    // Ps = 1 ⇒ Erase to Left
                    1 => (
                        Some(ControlSequence::EraseInLine(EraseInLine::ToLeft)),
                        true,
                    ),
                    // Ps = 0 ⇒ Erase to Right (default)
                    _ => (
                        Some(ControlSequence::EraseInLine(EraseInLine::ToRight)),
                        true,
                    ),
                },
                // CSI Ps L         Insert Ps Line(s) (default = 1)
                ('L', '\0') => (Some(ControlSequence::InsertLines(args.get_or(0, 1))), true),
                // CSI Ps M         Delete Ps Line(s) (default = 1)
                ('M', '\0') => (Some(ControlSequence::DeleteLines(args.get_or(0, 1))), true),
                // CSI Ps P         Delete Ps Character(s) (default = 1)
                ('P', '\0') => todo!(),
                // CSI Pm # P       Push current colors onto stack
                ('P', '#') => todo!(),
                // CSI Pm # Q       Pop colors from the stack
                ('Q', '#') => todo!(),
                // CSI Ps S         Scroll up Ps lines (default = 1)
                ('S', '\0') => todo!(),
                // CSI Ps T         Scroll down Ps lines (default = 1)
                // CSI Ps ; Ps ; Ps ; Ps ; Ps T
                //                  Initiate highlight mouse tracking
                //                  [func;startx;starty;firstrow;lastrow]
                ('T', '\0') => todo!(),
                // CSI Ps X         Erase Ps Character(s) (default = 1)
                ('X', '\0') => (
                    Some(ControlSequence::EraseCharacters(args.get_or(0, 1))),
                    true,
                ),
                // CSI Ps Z         Cursor Backward Tabulation Ps tab stops (default = 1)
                ('Z', '\0') => todo!(),
                // CSI Ps ^         Scroll down Ps lines (default = 1)
                ('^', '\0') => todo!(),
                // CSI Ps `         Character Position Absolute [column] (default = [row,1])
                ('`', '\0') => todo!(),
                // CSI Ps a         Character Position Relative [columns] (default = [row,col+1])
                ('a', '\0') => todo!(),
                // CSI Ps b         Repeat the preceding graphic character Ps times
                ('b', '\0') => todo!(),
                // CSI Ps c         Send Device Attributes (Primary DA)
                //                  Ps = 0 ⇒ request attributes from terminal
                ('c', '\0') => (
                    Some(ControlSequence::SendDeviceAttributes(args.get(0))),
                    true,
                ),
                // CSI Ps d         Line Position Absolute [row] (default = [1,column])
                ('d', '\0') => todo!(),
                // CSI Ps e         Line Position Relative [rows] (default = [row+1,column])
                ('e', '\0') => todo!(),
                // CSI Ps ; Ps f    Horizontal and Vertical Position [row;column] (default=[1,1])
                ('f', '\0') => (
                    Some(ControlSequence::SetCursorPosition(
                        args.get_or(0, 1),
                        args.get_or(1, 1),
                    )),
                    true,
                ),
                // CSI Ps g         Tab Clear
                //                  Ps = 0 ⇒ Clear Current Column (default)
                //                  Ps = 3 ⇒ Clear All
                ('g', '\0') => todo!(),
                // CSI Pm h         Set Mode
                //                  Ps = 2 ⇒ Keyboard Action Mode
                //                  Ps = 4 ⇒ Insert Mode
                //                  Ps = 12 ⇒ Send/receive
                //                  Ps = 20 ⇒ Automatic Newline
                ('h', '\0') => todo!(),
                // CSI Ps i         Media Copy
                //                  Ps = 0 ⇒ Print screen (default)
                //                  Ps = 4 ⇒ Turn off printer controller mode
                //                  Ps = 5 ⇒ Turn on printer controller mode
                //                  Ps = 10 ⇒ HTML screen dump
                //                  Ps = 11 ⇒ SVG screen dump
                ('i', '\0') => todo!(),
                // CSI Pm l         Reset Mode
                //                  Ps = 2 ⇒ Keyboard Action Mode
                //                  Ps = 4 ⇒ Replace Mode
                //                  Ps = 12 ⇒ Send/receive
                //                  Ps = 20 ⇒ Normal Linefeed
                ('l', '\0') => todo!(),
                // CSI Pm m         Character Attributes
                ('m', '\0') => (Self::handle_sgr(args), true),
                // CSI Ps n         Device Status Report
                ('n', '\0') => match args.get_or(0, 5) {
                    // Ps = 6 ⇒ Report Cursor Position
                    6 => (Some(ControlSequence::ReportCursorPosition), true),
                    // Ps = 5 ⇒ Status Report
                    _ => (Some(ControlSequence::DeviceStatusReport), true),
                },
                // CSI Pl ; Pc " p  Set conformance level
                //                  Pl = 6 1 ⇒ level 1
                //                  Pl = 6 2 ⇒ level 2
                //                  Pl = 6 3 ⇒ level 3
                //                  Pl = 6 4 ⇒ level 4
                //                  Pl = 6 5 ⇒ level 5
                //                  Pc = 0 ⇒ 8-bit controls
                //                  Pc = 1 ⇒ 7-bit controls
                //                  Pc = 2 ⇒ 8-bit controls
                ('p', '"') => todo!(),
                // CSI Ps $ p       Request ANSI mode
                ('p', '$') => todo!(),
                // CSI Pm # p       Push video attributes onto stack
                ('p', '#') => todo!(),
                // CSI Ps SP q      Set cursor style
                ('q', ' ') => {
                    let style = match args.get_or(0, 1) {
                        // Ps = 0 ⇒ blinking block
                        0 => CursorStyle::Block(true),
                        // Ps = 2 ⇒ steady block
                        2 => CursorStyle::Block(false),
                        // Ps = 3 ⇒ blinking underline
                        3 => CursorStyle::Underline(true),
                        // Ps = 4 ⇒ steady underline
                        4 => CursorStyle::Underline(false),
                        // Ps = 5 ⇒ blinking bar
                        5 => CursorStyle::Bar(true),
                        // Ps = 6 ⇒ steady bar
                        6 => CursorStyle::Bar(false),
                        // Ps = 1 ⇒ blinking block (default)
                        _ => CursorStyle::Block(true),
                    };
                    (Some(ControlSequence::SetCursorStyle(style)), true)
                }
                // CSI Ps " q       Select character protection attribute
                ('q', '"') => todo!(),
                // CSI Ps q         Load LEDs
                ('q', '\0') => todo!(),
                // CSI Pt ; Pl ; Pb ; Pr ; Pm $ r
                //                  Change Attributes in Rectangular Area
                ('r', '$') => todo!(),
                // CSI Ps ; Ps r    Set Scrolling Region [top;bottom]
                ('r', '\0') => (
                    Some(ControlSequence::SetScrollingRegion(
                        args.get_or(0, 1),
                        args.get_or(1, 1),
                    )),
                    true,
                ),
                // CSI s
                // CSI Pl ; Pr s
                ('s', '\0') => todo!(),
                // CSI Ps ; Ps ; Ps t
                // TODO
                ('t', '\0') => (None, true),
                // CSI Ps SP t
                ('t', ' ') => todo!(),
                // CSI Pt ; Pl ; Pb ; Pr ; Pm $ t
                ('t', '$') => todo!(),
                // CSI u
                ('u', '\0') => todo!(),
                // CSI Ps SP u
                ('u', ' ') => todo!(),
                // CSI Pt ; Pl ; Pb ; Pr ; Pp ; Pt ; Pl ; Pp $ v
                ('v', '$') => todo!(),
                // CSI Ps $ w
                ('w', '$') => todo!(),
                // CSI Pt ; Pl ; Pb ; Pr ' w
                ('w', '\'') => todo!(),
                // CSI Ps x
                ('x', '\0') => todo!(),
                // CSI Ps * x
                ('x', '*') => todo!(),
                // CSI Pc ; Pt ; Pl ; Pb ; Pr $ x
                ('x', '$') => todo!(),
                // CSI Ps # y
                ('y', '#') => todo!(),
                // CSI Pi ; Pg ; Pt; Pl ; Pb ; Pr * y
                ('y', '*') => todo!(),
                // CSI Ps ; Pu ' z
                ('z', '\'') => todo!(),
                // CSI Pt ; Pl ; Pb ; Pr $ z
                ('z', '$') => todo!(),
                // CSI Pm ' {
                ('{', '\'') => todo!(),
                // CSI Pm # {
                ('{', '#') => todo!(),
                // CSI Pt ; Pl ; Pb ; Pr $ {
                ('{', '$') => todo!(),
                // CSI Pt ; Pl ; Pb ; Pr # |
                ('|', '#') => todo!(),
                // CSI Ps $ |
                ('|', '$') => todo!(),
                // CSI Ps ' |
                ('|', '\'') => todo!(),
                // CSI Ps * |
                ('|', '*') => todo!(),
                // CSI Ps ; Pf ; Pb , |
                ('|', ',') => todo!(),
                // CSI Ps ; Pf ; Pb , }
                ('}', ',') => todo!(),
                // CSI Ps ' }
                ('}', '\'') => todo!(),
                // CSI Ps $ }
                ('}', '$') => todo!(),
                // CSI Ps ' ~
                ('~', '\'') => todo!(),
                // CSI Ps $ ~
                ('~', '$') => todo!(),
                _ => (None, true),
            },
            Self::PrefixQuestion(args) => match (ch, args.suffix) {
                (_, _) if args.push(ch) => (None, false),
                // CSI ? Ps J               Erase in Display
                ('J', '\0') => todo!("CSI ? Ps J"),
                // CSI ? Ps K               Erase in Line
                ('K', '\0') => todo!("CSI ? Ps K"),
                // CSI ? Pi ; Pa ; Pv S
                ('S', '\0') => todo!("CSI ? Pi ; Pa ; Pv S"),
                // CSI ? 5 W
                ('W', '\0') if args.get(0) == 5 => todo!("CSI ? 5 W"),
                // CSI ? Pp g
                ('g', '\0') => todo!("CSI ? Pp g"),
                // CSI ? Pm h               DEC Private Mode Set
                ('h', '\0') => (Self::handle_decset(args.get(0), true), true),
                // CSI ? Ps i
                ('i', '\0') => todo!("CSI ? Ps i"),
                // CSI ? Pm l
                ('l', '\0') => (Self::handle_decset(args.get(0), false), true),
                // CSI ? Pp m               Query key modifier options
                ('m', '\0') => {
                    let query = match args.get(0) {
                        0 => QueryKeyModifiers::ModifyKeyboard,
                        1 => QueryKeyModifiers::ModifyCursorKeys,
                        2 => QueryKeyModifiers::ModifyFunctionKeys,
                        3 => QueryKeyModifiers::ModifyKeypadKeys,
                        4 => QueryKeyModifiers::ModifyOtherKeys,
                        6 => QueryKeyModifiers::ModifyModifierKeys,
                        7 => QueryKeyModifiers::ModifySpecialKeys,
                        _ => return (None, true),
                    };

                    (Some(ControlSequence::QueryKeyModifiers(query)), true)
                }
                // CSI ? Ps n
                ('n', '\0') => todo!("CSI ? Ps n"),
                // CSI ? Ps $ p             Request DEC private mode
                ('p', '$') => (Some(ControlSequence::Decrqm), true),
                // CSI ? Pm r
                ('r', '\0') => todo!("CSI ? Pm r"),
                // CSI ? Pm s
                ('s', '\0') => todo!("CSI ? Pm s"),

                _ => (None, true),
            },
            _ => todo!(),
        }
    }

    fn handle_sgr(args: &CsiArgs) -> Option<ControlSequence> {
        let pm = args.get(0);
        let ps = args.get(1);
        let attr = match (pm, ps) {
            // Ps = 0 ⇒ Normal (default)
            (0, _) => CharacterAttribute::Reset,
            // Ps = 1 ⇒ Bold
            (1, _) => CharacterAttribute::Bold,
            // Ps = 2 ⇒ Faint
            (2, _) => CharacterAttribute::Faint,
            // Ps = 3 ⇒ Italicized
            (3, _) => CharacterAttribute::Italicized(true),
            // Ps = 4 ⇒ Underlined
            (4, _) => CharacterAttribute::Underlined(true),
            // Ps = 5 ⇒ Blink
            (5, _) => CharacterAttribute::Blinking(true),
            // Ps = 7 ⇒ Inverse
            (7, _) => CharacterAttribute::Inverse(true),
            // Ps = 8 ⇒ Invisible
            (8, _) => CharacterAttribute::Hidden(true),
            // Ps = 9 ⇒ Crossed-out characters
            (9, _) => CharacterAttribute::CrossedOut(true),
            // Ps = 2 1 ⇒ Doubly underlined
            (21, _) => return None,
            // Ps = 2 2 ⇒ Normal (neither bold nor faint)
            (22, _) => CharacterAttribute::Normal,
            // Ps = 2 3 ⇒ Not italicized
            (23, _) => CharacterAttribute::Italicized(false),
            // Ps = 2 4 ⇒ Not underlined
            (24, _) => CharacterAttribute::Underlined(false),
            // Ps = 2 5 ⇒ Steady (not blinking)
            (25, _) => CharacterAttribute::Blinking(false),
            // Ps = 2 7 ⇒ Positive (not inverse)
            (27, _) => CharacterAttribute::Inverse(false),
            // Ps = 2 8 ⇒ Visible (not hidden)
            (28, _) => CharacterAttribute::Hidden(false),
            // Ps = 2 9 ⇒ Not crossed out
            (29, _) => CharacterAttribute::CrossedOut(false),
            // Ps = 3 x ⇒ Set foreground color
            (30..=37, _) => CharacterAttribute::FgIndex(Some(pm as u8 - 30)),
            (39, _) => CharacterAttribute::FgIndex(None),
            // Ps = 4 x ⇒ Set background color
            (40..=47, _) => CharacterAttribute::BgIndex(Some(pm as u8 - 30)),
            (49, _) => CharacterAttribute::BgIndex(None),
            // Ps = 9 x ⇒ Set foreground color
            (90..=97, _) => CharacterAttribute::FgIndex(Some(pm as u8 - 90)),
            // Ps = 1 0 x ⇒ Set background color
            (100..=107, _) => CharacterAttribute::BgIndex(Some(pm as u8 - 100)),
            // Ps = 38 ; 2 ; Pi ; Pr ; Pg ; Pb ⇒ Set foreground color (RGB)
            //      38 ; 2 ; Pr ; Pg ; Pb
            (38, 2) => match args.args.len() {
                // 38 ; 2 ; Pi ; Pr ; Pg ; Pb
                6 => CharacterAttribute::ForegroundRgb(
                    args.get(3) as u8,
                    args.get(4) as u8,
                    args.get(5) as u8,
                ),
                // 38 ; 2 ; Pr ; Pg ; Pb
                5 => CharacterAttribute::ForegroundRgb(
                    args.get(2) as u8,
                    args.get(3) as u8,
                    args.get(4) as u8,
                ),
                _ => return None,
            },
            // Ps = 48 ; 2 ; Pi ; Pr ; Pg ; Pb ⇒ Set background color (RGB)
            //      48 ; 2 ; Pr ; Pg ; Pb
            (48, 2) => match args.args.len() {
                // 48 ; 2 ; Pi ; Pr ; Pg ; Pb
                6 => CharacterAttribute::BackgroundRgb(
                    args.get(3) as u8,
                    args.get(4) as u8,
                    args.get(5) as u8,
                ),
                // 48 ; 2 ; Pr ; Pg ; Pb
                5 => CharacterAttribute::BackgroundRgb(
                    args.get(2) as u8,
                    args.get(3) as u8,
                    args.get(4) as u8,
                ),
                _ => return None,
            },
            (_, _) => return None,
        };

        Some(ControlSequence::SetCharacterAttribute(attr))
    }

    fn handle_decset(pm: u32, set: bool) -> Option<ControlSequence> {
        match pm {
            // Application cursor keys
            1 => Some(ControlSequence::SetApplicationCursor(set)),
            // Start blinking cursor TODO
            12 => None,
            // Show cursor TODO
            25 => None,
            // TODO Send Mouse X & Y on button press
            1000 => None,
            // TODO Use Cell Motion Mouse Tracking
            1002 => None,
            // TODO Send FocusIn/FocusOut events
            1004 => None,
            // TODO Enable SGR Mouse Mode
            1006 => None,
            // TODO Use Alternate Screen Buffer
            1047 => None,
            // TODO Save cursor as in DECSC
            1048 => todo!(),
            // TODO Save cursor as in DECSC and switch to alternate screen buffer, clearing it
            // first
            1049 => Some(ControlSequence::SetAlternateMode(set)),
            // Ps = 2 0 0 4 ⇒ Set bracketed paste mode
            2004 => Some(ControlSequence::SetBracketedPasteMode(set)),
            _ => todo!("Unhandled DECSET {pm}"),
        }
    }
}

impl CsiArgs {
    const fn new() -> Self {
        Self {
            args: Vec::new(),
            suffix: '\0',
        }
    }

    fn push(&mut self, ch: char) -> bool {
        match ch {
            ch if ch.is_ascii_digit() => {
                let digit = (ch as u8 - b'0') as u32;
                if let Some(last) = self.args.last_mut() {
                    *last *= 10;
                    *last += digit;
                } else {
                    self.args.push(digit);
                }
                true
            }
            ';' => {
                self.args.push(0);
                true
            }
            ' ' | '#' | '$' | '\'' | ',' | '?' if !self.args.is_empty() && self.suffix == '\0' => {
                self.suffix = ch;
                true
            }
            _ => false,
        }
    }

    fn get(&self, index: usize) -> u32 {
        self.args.get(index).copied().unwrap_or_default()
    }

    fn get_or(&self, index: usize, default: u32) -> u32 {
        self.args.get(index).copied().unwrap_or(default)
    }

    fn is_empty(&self) -> bool {
        self.args.is_empty() && self.suffix == '\0'
    }
}
