/*
 * meli
 *
 * Copyright 2017-2020 Manos Pitsidianakis
 *
 * This file is part of meli.
 *
 * meli is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * meli is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with meli. If not, see <http://www.gnu.org/licenses/>.
 */

use super::*;
use crate::terminal::{cells::*, Color};
use melib::error::{MeliError, Result};
use melib::text_processing::wcwidth;
use nix::sys::wait::WaitStatus;
use nix::sys::wait::{waitpid, WaitPidFlag};
/**
 * `EmbedGrid` manages the terminal grid state of the embed process.
 *
 * The embed process sends bytes to the master end (see super mod) and interprets them in a state
 * machine stored in `State`. Escape codes are translated as changes to the grid, eg changes in a
 * cell's colors.
 *
 * The main process copies the grid whenever the actual terminal is redrawn.
 **/

#[derive(Debug)]
enum ScreenBuffer {
    Normal,
    Alternate,
}

#[derive(Debug)]
pub struct EmbedGrid {
    cursor: (usize, usize),
    /// [top;bottom]
    scroll_region: ScrollRegion,
    pub alternate_screen: CellBuffer,
    pub state: State,
    /// (width, height)
    pub terminal_size: (usize, usize),
    initialized: bool,
    fg_color: Color,
    bg_color: Color,
    /// Store the fg/bg color when highlighting the cell where the cursor is so that it can be
    /// restored afterwards
    prev_fg_color: Option<Color>,
    prev_bg_color: Option<Color>,

    show_cursor: bool,
    origin_mode: bool,
    auto_wrap_mode: bool,
    /// If next grapheme should be placed in the next line
    /// This should be reset whenever the cursor value changes
    wrap_next: bool,
    /// Store state in case a multi-byte character is encountered
    codepoints: CodepointBuf,
    pub normal_screen: CellBuffer,
    screen_buffer: ScreenBuffer,
}

#[derive(Debug)]
pub struct EmbedTerminal {
    pub grid: EmbedGrid,
    pub stdin: std::fs::File,
    /// Pid of the embed process
    pub child_pid: nix::unistd::Pid,
}

impl EmbedTerminal {
    pub fn new(stdin: std::fs::File, child_pid: nix::unistd::Pid) -> Self {
        EmbedTerminal {
            grid: EmbedGrid::new(),
            stdin,
            child_pid,
        }
    }

    pub fn set_terminal_size(&mut self, new_val: (usize, usize)) {
        self.grid.set_terminal_size(new_val);
        let winsize = Winsize {
            ws_row: <u16>::try_from(new_val.1).unwrap(),
            ws_col: <u16>::try_from(new_val.0).unwrap(),
            ws_xpixel: 0,
            ws_ypixel: 0,
        };
        let master_fd = self.stdin.as_raw_fd();
        let _ = unsafe { set_window_size(master_fd, &winsize) };
        let _ = nix::sys::signal::kill(self.child_pid, nix::sys::signal::SIGWINCH);
    }

    pub fn wake_up(&self) {
        let _ = nix::sys::signal::kill(self.child_pid, nix::sys::signal::SIGCONT);
    }

    pub fn stop(&self) {
        //debug!("stopping");
        let _ = nix::sys::signal::kill(debug!(self.child_pid), nix::sys::signal::SIGSTOP);
    }

    pub fn is_active(&self) -> Result<WaitStatus> {
        debug!(waitpid(self.child_pid, Some(WaitPidFlag::WNOHANG),))
            .map_err(|e| MeliError::new(e.to_string()))
    }

    pub fn process_byte(&mut self, byte: u8) {
        let Self {
            ref mut grid,
            ref mut stdin,
            child_pid: _,
        } = self;
        grid.process_byte(stdin, byte);
    }
}

#[derive(Debug, PartialEq)]
enum CodepointBuf {
    None,
    TwoCodepoints(u8),
    ThreeCodepoints(u8, Option<u8>),
    FourCodepoints(u8, Option<u8>, Option<u8>),
}

impl EmbedGrid {
    pub fn new() -> Self {
        let mut normal_screen = CellBuffer::default();
        normal_screen.set_growable(true);
        EmbedGrid {
            cursor: (0, 0),
            scroll_region: ScrollRegion {
                top: 0,
                bottom: 0,
                left: 0,
                ..Default::default()
            },
            terminal_size: (0, 0),
            initialized: false,
            alternate_screen: CellBuffer::default(),
            state: State::Normal,
            fg_color: Color::Default,
            bg_color: Color::Default,
            prev_fg_color: None,
            prev_bg_color: None,
            show_cursor: true,
            auto_wrap_mode: true,
            wrap_next: false,
            origin_mode: false,
            codepoints: CodepointBuf::None,
            normal_screen,
            screen_buffer: ScreenBuffer::Normal,
        }
    }

    pub fn buffer(&self) -> &CellBuffer {
        match self.screen_buffer {
            ScreenBuffer::Normal => &self.normal_screen,
            ScreenBuffer::Alternate => &self.alternate_screen,
        }
    }

    pub fn buffer_mut(&mut self) -> &mut CellBuffer {
        match self.screen_buffer {
            ScreenBuffer::Normal => &mut self.normal_screen,
            ScreenBuffer::Alternate => &mut self.alternate_screen,
        }
    }

    pub fn set_terminal_size(&mut self, new_val: (usize, usize)) {
        if new_val == self.terminal_size && self.initialized {
            return;
        }
        self.initialized = true;
        //debug!("resizing to {:?}", new_val);
        self.scroll_region.top = 0;
        self.scroll_region.bottom = new_val.1.saturating_sub(1);

        self.terminal_size = new_val;
        if !self.alternate_screen.resize(new_val.0, new_val.1, None) {
            panic!(
                "Terminal size too big: ({} cols, {} rows)",
                new_val.0, new_val.1
            );
        }
        self.alternate_screen.clear(Some(Cell::default()));
        if !self.normal_screen.resize(new_val.0, new_val.1, None) {
            panic!(
                "Terminal size too big: ({} cols, {} rows)",
                new_val.0, new_val.1
            );
        }
        self.normal_screen.clear(Some(Cell::default()));
        self.cursor = (0, 0);
        self.wrap_next = false;
    }

    pub fn process_byte(&mut self, stdin: &mut std::fs::File, byte: u8) {
        let EmbedGrid {
            ref mut cursor,
            ref mut scroll_region,
            ref mut terminal_size,
            ref mut alternate_screen,
            ref mut state,
            ref mut fg_color,
            ref mut bg_color,
            ref mut prev_fg_color,
            ref mut prev_bg_color,
            ref mut codepoints,
            ref mut show_cursor,
            ref mut auto_wrap_mode,
            ref mut wrap_next,
            ref mut origin_mode,
            ref mut screen_buffer,
            ref mut normal_screen,
            initialized: _,
        } = self;
        let mut grid = normal_screen;

        let is_alternate = match *screen_buffer {
            ScreenBuffer::Normal => false,
            _ => {
                grid = alternate_screen;
                true
            }
        };

        macro_rules! increase_cursor_y {
            () => {
                cursor.1 += 1;
                if !is_alternate {
                    cursor.0 = 0;
                    if cursor.1 >= terminal_size.1 {
                        if !grid.resize(std::cmp::max(1, grid.cols()), grid.rows() + 2, None) {
                            return;
                        }
                        scroll_region.bottom += 1;
                        terminal_size.1 += 1;
                    }
                }
            };
        }

        macro_rules! increase_cursor_x {
            () => {
                if cursor.0 + 1 < terminal_size.0 {
                    cursor.0 += 1;
                } else if is_alternate && *auto_wrap_mode {
                    *wrap_next = true;
                } else if !is_alternate {
                    cursor.0 = 0;
                    increase_cursor_y!();
                }
            };
        }

        macro_rules! cursor_x {
            () => {{
                if cursor.0 >= terminal_size.0 {
                    cursor.0 = terminal_size.0.saturating_sub(1);
                }
                cursor.0
            }};
        }
        macro_rules! cursor_y {
            () => {
                if is_alternate {
                    std::cmp::min(
                        cursor.1 + scroll_region.top,
                        terminal_size.1.saturating_sub(1),
                    )
                } else {
                    cursor.1
                }
            };
        }
        macro_rules! cursor_val {
            () => {
                (cursor_x!(), cursor_y!())
            };
        }

        let mut state = state;
        match (byte, &mut state) {
            (b'\x1b', State::Normal) => {
                *state = State::ExpectingControlChar;
            }
            (b']', State::ExpectingControlChar) => {
                let buf1 = SmallVec::new();
                *state = State::Osc1(buf1);
            }
            (b'[', State::ExpectingControlChar) => {
                *state = State::Csi;
            }
            (b'(', State::ExpectingControlChar) => {
                *state = State::G0;
            }
            (b'D', State::ExpectingControlChar) => {
                // ESCD Linefeed
                //debug!("{}", EscCode::from((&(*state), byte)));
                if cursor.1 == scroll_region.bottom {
                    grid.scroll_up(scroll_region, scroll_region.top, 1);
                } else {
                    cursor.1 += 1;
                }
                *wrap_next = false;
                *state = State::Normal;
            }
            (b'J', State::ExpectingControlChar) => {
                // ESCJ Erase from the cursor to the end of the screen
                //debug!("sending {}", EscCode::from((&(*state), byte)));
                //debug!("erasing from {:?} to {:?}", cursor, terminal_size);
                for y in cursor.1..terminal_size.1 {
                    for x in cursor.0..terminal_size.0 {
                        grid[(x, y)] = Cell::default();
                    }
                }
                *state = State::Normal;
            }
            (b'K', State::ExpectingControlChar) => {
                // ESCK Erase from the cursor to the end of the line
                //debug!("sending {}", EscCode::from((&(*state), byte)));
                for x in cursor.0..terminal_size.0 {
                    grid[(x, cursor.1)] = Cell::default();
                }
                *state = State::Normal;
            }
            (_, State::ExpectingControlChar) => {
                //debug!(
                //    "unrecognised: byte is {} and state is {:?}",
                //    byte as char, state
                //);
                *state = State::Normal;
            }
            (b'?', State::Csi) => {
                let buf1 = SmallVec::new();
                *state = State::CsiQ(buf1);
            }
            /* OSC stuff */
            (c, State::Osc1(ref mut buf)) if (c >= b'0' && c <= b'9') || c == b'?' => {
                buf.push(c);
            }
            (b';', State::Osc1(ref mut buf1_p)) => {
                let buf1 = std::mem::replace(buf1_p, SmallVec::new());
                let buf2 = SmallVec::new();
                *state = State::Osc2(buf1, buf2);
            }
            (c, State::Osc2(_, ref mut buf)) if (c >= b'0' && c <= b'9') || c == b'?' => {
                buf.push(c);
            }
            /* Normal */
            (b'\r', State::Normal) => {
                //debug!("carriage return x-> 0, cursor was: {:?}", cursor);
                cursor.0 = 0;
                *wrap_next = false;
                //debug!("cursor became: {:?}", cursor);
            }
            (b'\n', State::Normal) => {
                //debug!("setting cell {:?} char '{}'", cursor, c as char);
                //debug!("newline y-> y+1, cursor was: {:?}", cursor);

                if cursor.1 + 1 < terminal_size.1 || !is_alternate {
                    if cursor.1 == scroll_region.bottom && is_alternate {
                        grid.scroll_up(scroll_region, cursor.1, 1);
                    } else {
                        increase_cursor_y!();
                    }
                }
                *wrap_next = false;
                //debug!("cursor became: {:?}", cursor);
            }
            (b'', State::Normal) => {
                //debug!("Visual bell ^G, ignoring {:?}", cursor);
            }
            (0x08, State::Normal) => {
                /* Backspace */
                //debug!("backspace x-> x-1, cursor was: {:?}", cursor);
                if cursor.0 > 0 {
                    cursor.0 -= 1;
                }
                //debug!("cursor became: {:?}", cursor);
            }
            (c, State::Normal) => {
                /* Character to be printed. */
                let c = if *codepoints == CodepointBuf::None && c & 0x80 == 0 {
                    /* This is a one byte char */
                    c as char
                } else {
                    match codepoints {
                        CodepointBuf::None if c & 0b1110_0000 == 0b1100_0000 => {
                            *codepoints = CodepointBuf::TwoCodepoints(c);
                            return;
                        }
                        CodepointBuf::None if c & 0b1111_0000 == 0b1110_0000 => {
                            *codepoints = CodepointBuf::ThreeCodepoints(c, None);
                            return;
                        }
                        CodepointBuf::None if c & 0b1111_1000 == 0b1111_0000 => {
                            *codepoints = CodepointBuf::FourCodepoints(c, None, None);
                            return;
                        }
                        CodepointBuf::TwoCodepoints(b) => {
                            //debug!("two byte char = ");
                            unsafe { std::str::from_utf8_unchecked(&[*b, c]) }
                                .chars()
                                .next()
                                .unwrap()
                        }
                        CodepointBuf::ThreeCodepoints(b, Some(b1)) => {
                            //debug!("three byte char = ",);
                            unsafe { std::str::from_utf8_unchecked(&[*b, *b1, c]) }
                                .chars()
                                .next()
                                .unwrap()
                        }
                        CodepointBuf::ThreeCodepoints(_, ref mut b @ None) => {
                            *b = Some(c);
                            return;
                        }
                        CodepointBuf::FourCodepoints(b, Some(b1), Some(b2)) => {
                            //debug!("four byte char = ",);
                            unsafe { std::str::from_utf8_unchecked(&[*b, *b1, *b2, c]) }
                                .chars()
                                .next()
                                .unwrap()
                        }
                        CodepointBuf::FourCodepoints(_, ref mut b1 @ None, None) => {
                            *b1 = Some(c);
                            return;
                        }
                        CodepointBuf::FourCodepoints(_, _, ref mut b2 @ None) => {
                            *b2 = Some(c);
                            return;
                        }
                        _ => {
                            //debug!(
                            //    "invalid utf8 sequence: codepoints = {:?} and c={}",
                            //    codepoints, c
                            //);
                            *codepoints = CodepointBuf::None;
                            return;
                        }
                    }
                };
                //debug!("c = {:?}\tcursor={:?}", c, cursor);
                *codepoints = CodepointBuf::None;
                if *auto_wrap_mode && *wrap_next {
                    *wrap_next = false;
                    if cursor.1 == scroll_region.bottom {
                        grid.scroll_up(scroll_region, scroll_region.top, 1);
                    } else {
                        cursor.1 += 1;
                    }
                    cursor.0 = 0;
                }

                //if c == '↪' {
                //debug!("↪ cursor is {:?}", cursor_val!());
                //}
                grid[cursor_val!()].set_ch(c);
                grid[cursor_val!()].set_fg(*fg_color);
                grid[cursor_val!()].set_bg(*bg_color);
                match wcwidth(u32::from(c)) {
                    Some(0) | None => {
                        /* Skip drawing zero width characters */
                        grid[cursor_val!()].set_empty(true);
                    }
                    Some(1) => {}
                    Some(n) => {
                        /* Grapheme takes more than one column, so the next cell will be
                         * drawn over. Set it as empty to skip drawing it. */
                        for _ in 1..n {
                            increase_cursor_x!();
                            grid[cursor_val!()].set_empty(true);
                            grid[cursor_val!()].set_fg(*fg_color);
                            grid[cursor_val!()].set_bg(*bg_color);
                        }
                    }
                }
                increase_cursor_x!();
            }
            (b'u', State::Csi) => {
                /* restore cursor */
                //debug!("restore cursor {}", EscCode::from((&(*state), byte)));
                *show_cursor = true;
                *state = State::Normal;
            }
            (b'm', State::Csi) => {
                /* Reset character Attributes (SGR).  Ps = 0  -> Normal (default), VT100 */
                //debug!("{}", EscCode::from((&(*state), byte)));
                *fg_color = Color::Default;
                *bg_color = Color::Default;
                grid[cursor_val!()].set_fg(Color::Default);
                grid[cursor_val!()].set_bg(Color::Default);
                *state = State::Normal;
            }
            (b'C', State::Csi) => {
                // ESC[C    CSI Cursor Forward one Time
                //debug!("cursor forward one time, cursor was: {:?}", cursor);
                cursor.0 = std::cmp::min(cursor.0 + 1, terminal_size.0.saturating_sub(1));
                *wrap_next = false;
                //debug!("cursor became: {:?}", cursor);
                *state = State::Normal;
            }
            /* CSI ? stuff */
            (c, State::CsiQ(ref mut buf)) if c >= b'0' && c <= b'9' => {
                buf.push(c);
            }
            (b'h', State::CsiQ(ref buf)) => {
                match buf.as_slice() {
                    b"6" => {
                        *origin_mode = true;
                    }
                    b"7" => {
                        *auto_wrap_mode = true;
                    }
                    b"25" => {
                        *show_cursor = true;
                        *prev_fg_color = Some(grid[cursor_val!()].fg());
                        *prev_bg_color = Some(grid[cursor_val!()].bg());
                        grid[cursor_val!()].set_fg(Color::Black);
                        grid[cursor_val!()].set_bg(Color::White);
                    }
                    b"1047" | b"1049" => {
                        *screen_buffer = ScreenBuffer::Alternate;
                    }
                    _ => {}
                }

                //debug!("{}", EscCode::from((&(*state), byte)));
                *state = State::Normal;
            }
            (b'l', State::CsiQ(ref mut buf)) => {
                match buf.as_slice() {
                    b"6" => {
                        *origin_mode = false;
                    }
                    b"7" => {
                        *auto_wrap_mode = false;
                    }
                    b"25" => {
                        *show_cursor = false;
                        if let Some(fg_color) = prev_fg_color.take() {
                            grid[cursor_val!()].set_fg(fg_color);
                        } else {
                            grid[cursor_val!()].set_fg(*fg_color);
                        }
                        if let Some(bg_color) = prev_bg_color.take() {
                            grid[cursor_val!()].set_bg(bg_color);
                        } else {
                            grid[cursor_val!()].set_bg(*bg_color);
                        }
                    }
                    b"1047" | b"1049" => {
                        *screen_buffer = ScreenBuffer::Normal;
                    }
                    _ => {}
                }
                //debug!("{}", EscCode::from((&(*state), byte)));
                *state = State::Normal;
            }
            /* END OF CSI ? stuff */
            (c, State::Csi) if c >= b'0' && c <= b'9' => {
                let mut buf1 = SmallVec::new();
                buf1.push(c);
                *state = State::Csi1(buf1);
            }
            (b'J', State::Csi) => {
                /* Erase in Display (ED), VT100.*/
                /* Erase Below (default). */
                clear_area(
                    grid,
                    (
                        (
                            0,
                            std::cmp::min(
                                cursor.1 + 1 + scroll_region.top,
                                terminal_size.1.saturating_sub(1),
                            ),
                        ),
                        (
                            terminal_size.0.saturating_sub(1),
                            terminal_size.1.saturating_sub(1),
                        ),
                    ),
                    Default::default(),
                );
                //debug!("{}", EscCode::from((&(*state), byte)));
                *state = State::Normal;
            }
            (b'K', State::Csi) => {
                /* Erase in Line (ED), VT100.*/
                /* Erase to right (Default) */
                //debug!("{}", EscCode::from((&(*state), byte)));
                for x in cursor.0..terminal_size.0 {
                    grid[(x, cursor.1)] = Cell::default();
                }
                *state = State::Normal;
            }
            (b'L', State::Csi) | (b'L', State::Csi1(_)) => {
                /* Insert n blank lines (default 1) */
                let n = if let State::Csi1(ref buf1) = state {
                    unsafe { std::str::from_utf8_unchecked(buf1) }
                        .parse::<usize>()
                        .unwrap()
                } else {
                    1
                };

                grid.scroll_down(scroll_region, cursor.1, n);

                //debug!("{}", EscCode::from((&(*state), byte)));
                *state = State::Normal;
            }
            (b'M', State::Csi) | (b'M', State::Csi1(_)) => {
                /* Delete n lines (default 1) */
                let n = if let State::Csi1(ref buf1) = state {
                    unsafe { std::str::from_utf8_unchecked(buf1) }
                        .parse::<usize>()
                        .unwrap()
                } else {
                    1
                };

                grid.scroll_up(scroll_region, cursor.1, n);

                //debug!("{}", EscCode::from((&(*state), byte)));
                *state = State::Normal;
            }
            (b'A', State::Csi) => {
                // Move cursor up 1 line
                //debug!("cursor up 1 times, cursor was: {:?}", cursor);
                if cursor.1 > 0 {
                    cursor.1 -= 1;
                } else {
                    //debug!("cursor.1 == 0");
                }
                *wrap_next = false;
                //debug!("cursor became: {:?}", cursor);
                *state = State::Normal;
            }
            (b'K', State::Csi1(buf)) if buf.as_ref() == b"0" => {
                /* Erase in Line (ED), VT100.*/
                /* Erase to right (Default) */
                //debug!("{}", EscCode::from((&(*state), byte)));
                for x in cursor.0..terminal_size.0 {
                    grid[(x, cursor.1)] = Cell::default();
                }
                *state = State::Normal;
            }
            (b'K', State::Csi1(buf)) if buf.as_ref() == b"1" => {
                /* Erase in Line (ED), VT100.*/
                /* Erase to left (Default) */
                for x in 0..=cursor.0 {
                    grid[(x, cursor.1)] = Cell::default();
                }
                //debug!("{}", EscCode::from((&(*state), byte)));
                *state = State::Normal;
            }
            (b'K', State::Csi1(buf)) if buf.as_ref() == b"2" => {
                /* Erase in Line (ED), VT100.*/
                /* Erase all */
                for y in 0..terminal_size.1 {
                    for x in 0..terminal_size.0 {
                        grid[(x, y)] = Cell::default();
                    }
                }
                //debug!("{}", EscCode::from((&(*state), byte)));
                clear_area(
                    grid,
                    ((0, 0), pos_dec(*terminal_size, (1, 1))),
                    Default::default(),
                );
                *state = State::Normal;
            }
            (b'J', State::Csi1(ref buf)) if buf.as_ref() == b"0" => {
                /* Erase in Display (ED), VT100.*/
                /* Erase Below (default). */
                clear_area(
                    grid,
                    (
                        (
                            0,
                            std::cmp::min(
                                cursor.1 + 1 + scroll_region.top,
                                terminal_size.1.saturating_sub(1),
                            ),
                        ),
                        (
                            terminal_size.0.saturating_sub(1),
                            terminal_size.1.saturating_sub(1),
                        ),
                    ),
                    Default::default(),
                );
                //debug!("{}", EscCode::from((&(*state), byte)));
                *state = State::Normal;
            }
            (b'J', State::Csi1(ref buf)) if buf.as_ref() == b"1" => {
                /* Erase in Display (ED), VT100.*/
                /* Erase Above */
                clear_area(
                    grid,
                    (
                        (0, 0),
                        (
                            terminal_size.0.saturating_sub(1),
                            cursor.1.saturating_sub(1) + scroll_region.top,
                        ),
                    ),
                    Default::default(),
                );
                //debug!("{}", EscCode::from((&(*state), byte)));
                *state = State::Normal;
            }
            (b'J', State::Csi1(ref buf)) if buf.as_ref() == b"2" => {
                /* Erase in Display (ED), VT100.*/
                /* Erase All */
                clear_area(
                    grid,
                    ((0, 0), pos_dec(*terminal_size, (1, 1))),
                    Default::default(),
                );
                //debug!("{}", EscCode::from((&(*state), byte)));
                *state = State::Normal;
            }
            (b'X', State::Csi1(ref buf)) => {
                /* Erase Ps Character(s) (default = 1) (ECH)..*/
                let ps = unsafe { std::str::from_utf8_unchecked(buf) }
                    .parse::<usize>()
                    .unwrap();

                let mut ctr = 0;
                let (mut cur_x, mut cur_y) = cursor_val!();
                while ctr < ps {
                    if cur_x >= terminal_size.0 {
                        cur_y += 1;
                        cur_x = 0;
                        if cur_y >= terminal_size.1 {
                            break;
                        }
                    }
                    grid[(cur_x, cur_y)] = Cell::default();
                    cur_x += 1;
                    ctr += 1;
                }
                //debug!("Erased {} Character(s)", ps);
                *state = State::Normal;
            }
            (b't', State::Csi1(buf)) => {
                /* Window manipulation */
                if buf.as_ref() == b"18" || buf.as_ref() == b"19" {
                    // Ps = 18 → Report the size of the text area in characters as CSI 8 ; height ; width t
                    //debug!("report size of the text area");
                    //debug!("got {}", EscCode::from((&(*state), byte)));
                    stdin.write_all(b"\x1b[8;").unwrap();
                    stdin
                        .write_all((terminal_size.1).to_string().as_bytes())
                        .unwrap();
                    stdin.write_all(&[b';']).unwrap();
                    stdin
                        .write_all((terminal_size.0).to_string().as_bytes())
                        .unwrap();
                    stdin.write_all(&[b't']).unwrap();
                    stdin.flush().unwrap();
                } else {
                    //debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
                }
                *state = State::Normal;
            }
            (b'n', State::Csi1(_)) => {
                // Ps = 6  ⇒  Report Cursor Position (CPR) [row;column].
                // Result is CSI r ; c R
                //debug!("report cursor position");
                //debug!("got {}", EscCode::from((&(*state), byte)));
                stdin.write_all(&[b'\x1b', b'[']).unwrap();
                stdin
                    .write_all((cursor.1 + 1).to_string().as_bytes())
                    .unwrap();
                stdin.write_all(&[b';']).unwrap();
                stdin
                    .write_all((cursor.0 + 1).to_string().as_bytes())
                    .unwrap();
                stdin.write_all(&[b'R']).unwrap();
                stdin.flush().unwrap();
                *state = State::Normal;
            }
            (b'A', State::Csi1(buf)) => {
                // Move cursor up n lines
                let offset = unsafe { std::str::from_utf8_unchecked(buf) }
                    .parse::<usize>()
                    .unwrap();
                //debug!("cursor up {} times, cursor was: {:?}", offset, cursor);
                if cursor.1 >= offset {
                    cursor.1 -= offset;
                } else {
                    //debug!("offset > cursor.1");
                }
                //debug!("cursor became: {:?}", cursor);
                *state = State::Normal;
            }
            (b'B', State::Csi1(buf)) => {
                // ESC[{buf}B   CSI Cursor Down {buf} Times
                let offset = unsafe { std::str::from_utf8_unchecked(buf) }
                    .parse::<usize>()
                    .unwrap();
                //debug!("cursor down {} times, cursor was: {:?}", offset, cursor);
                if cursor.1 == scroll_region.bottom {
                    /* scroll down */
                    for y in scroll_region.top..scroll_region.bottom {
                        for x in 0..terminal_size.1 {
                            grid[(x, y)] = grid[(x, y + 1)];
                        }
                    }
                    for x in 0..terminal_size.1 {
                        grid[(x, scroll_region.bottom)] = Cell::default();
                    }
                } else if offset + cursor.1 < terminal_size.1 {
                    cursor.1 += offset;
                }
                if scroll_region.top + cursor.1 >= terminal_size.1 {
                    cursor.1 = terminal_size.1.saturating_sub(1);
                }
                *wrap_next = false;
                //debug!("cursor became: {:?}", cursor);
                *state = State::Normal;
            }
            (b'D', State::Csi1(buf)) => {
                // ESC[{buf}D   CSI Cursor Backward {buf} Times
                let offset = unsafe { std::str::from_utf8_unchecked(buf) }
                    .parse::<usize>()
                    .unwrap();
                if cursor.0 >= offset {
                    cursor.0 -= offset;
                }
                //debug!(
                //   "ESC[ {} D cursor backwards cursor became: {:?}",
                //    offset, cursor
                // );
                *state = State::Normal;
            }
            (b'E', State::Csi1(buf)) => {
                // ESC[{buf}E   CSI Cursor Next Line {buf} Times
                let offset = unsafe { std::str::from_utf8_unchecked(buf) }
                    .parse::<usize>()
                    .unwrap();
                //debug!(
                //    "cursor next line {} times, cursor was: {:?}",
                //    offset, cursor
                //);
                if offset + cursor.1 < terminal_size.1 {
                    cursor.1 += offset;
                }
                if scroll_region.top + cursor.1 >= terminal_size.1 {
                    cursor.1 = terminal_size.1.saturating_sub(1);
                }
                cursor.0 = 0;
                *wrap_next = false;
                //debug!("cursor became: {:?}", cursor);
                *state = State::Normal;
            }
            (b'F', State::Csi1(buf)) => {
                // ESC[{buf}F   CSI Cursor Previous Line {buf} Times
                let offset = unsafe { std::str::from_utf8_unchecked(buf) }
                    .parse::<usize>()
                    .unwrap();
                //debug!(
                //    "cursor previous line {} times, cursor was: {:?}",
                //    offset, cursor
                //);
                cursor.1 = cursor.1.saturating_sub(offset);
                cursor.0 = 0;
                *wrap_next = false;
                //debug!("cursor became: {:?}", cursor);
                *state = State::Normal;
            }
            (b'G', State::Csi1(_)) | (b'G', State::Csi) => {
                // ESC[{buf}G   Cursor Character Absolute  [column={buf}] (default = [row,1])
                let new_col = if let State::Csi1(buf) = state {
                    unsafe { std::str::from_utf8_unchecked(buf) }
                        .parse::<usize>()
                        .unwrap()
                } else {
                    1
                };
                //debug!("cursor absolute {}, cursor was: {:?}", new_col, cursor);
                if new_col < terminal_size.0 {
                    cursor.0 = new_col.saturating_sub(1);
                } else {
                    //debug!(
                    //    "error: new_cal = {} > terminal.size.0 = {}\nterminal_size = {:?}",
                    //    new_col, terminal_size.0, terminal_size
                    //);
                }
                *wrap_next = false;
                //debug!("cursor became: {:?}", cursor);
                *state = State::Normal;
            }
            (b'C', State::Csi1(buf)) => {
                // ESC[{buf}C   CSI Cursor Forward {buf} Times
                let offset = unsafe { std::str::from_utf8_unchecked(buf) }
                    .parse::<usize>()
                    .unwrap();
                //debug!("cursor forward {} times, cursor was: {:?}", offset, cursor);
                if cursor.0 + offset < terminal_size.0 {
                    cursor.0 += offset;
                }
                //debug!("cursor became: {:?}", cursor);
                *state = State::Normal;
            }
            (b'P', State::Csi1(_)) | (b'P', State::Csi) => {
                // ESC[{buf}P   CSI Delete {buf} characters, default = 1
                let offset = if let State::Csi1(buf) = state {
                    unsafe { std::str::from_utf8_unchecked(buf) }
                        .parse::<usize>()
                        .unwrap()
                } else {
                    1
                };

                for i in 0..(terminal_size.0 - cursor.0 - offset) {
                    grid[(cursor.0 + i, cursor.1)] = grid[(cursor.0 + i + offset, cursor.1)];
                }
                for x in (terminal_size.0 - offset)..terminal_size.0 {
                    grid[(x, cursor.1)].set_ch(' ');
                }
                //debug!(
                //    "Delete {} Character(s) with cursor at {:?}  ",
                //    offset, cursor
                //);
                *state = State::Normal;
            }
            (b'd', State::Csi1(_)) | (b'd', State::Csi) => {
                /* CSI Pm d Line Position Absolute [row] (default = [1,column]) (VPA). */
                let row = if let State::Csi1(buf) = state {
                    unsafe { std::str::from_utf8_unchecked(buf) }
                        .parse::<usize>()
                        .unwrap()
                } else {
                    1
                };
                //debug!(
                //    "Line position absolute row {} with cursor at {:?}",
                //    row, cursor
                //);
                cursor.1 = row.saturating_sub(1);
                if scroll_region.top + cursor.1 >= terminal_size.1 {
                    cursor.1 = terminal_size.1.saturating_sub(1);
                }
                *wrap_next = false;
                //debug!("cursor became: {:?}", cursor);
                *state = State::Normal;
            }
            (b';', State::Csi1(ref mut buf1_p)) => {
                let buf1 = std::mem::replace(buf1_p, SmallVec::new());
                let buf2 = SmallVec::new();
                *state = State::Csi2(buf1, buf2);
            }
            (b'm', State::Csi1(ref buf1)) => {
                // Character Attributes.
                match buf1.as_slice() {
                    b"0" => {
                        *fg_color = Color::Default;
                        *bg_color = Color::Default;
                    }
                    b"1" => { /* bold */ }
                    b"7" => {
                        /* Inverse */
                        let temp = *fg_color;
                        *fg_color = *bg_color;
                        *bg_color = temp;
                    }
                    b"27" => {
                        /* Inverse off */
                        let temp = *fg_color;
                        *fg_color = *bg_color;
                        *bg_color = temp;
                    }
                    b"30" => *fg_color = Color::Black,
                    b"31" => *fg_color = Color::Red,
                    b"32" => *fg_color = Color::Green,
                    b"33" => *fg_color = Color::Yellow,
                    b"34" => *fg_color = Color::Blue,
                    b"35" => *fg_color = Color::Magenta,
                    b"36" => *fg_color = Color::Cyan,
                    b"37" => *fg_color = Color::White,

                    b"39" => *fg_color = Color::Default,
                    b"40" => *bg_color = Color::Black,
                    b"41" => *bg_color = Color::Red,
                    b"42" => *bg_color = Color::Green,
                    b"43" => *bg_color = Color::Yellow,
                    b"44" => *bg_color = Color::Blue,
                    b"45" => *bg_color = Color::Magenta,
                    b"46" => *bg_color = Color::Cyan,
                    b"47" => *bg_color = Color::White,

                    b"49" => *bg_color = Color::Default,

                    b"90" => *fg_color = Color::Black,
                    b"91" => *fg_color = Color::Red,
                    b"92" => *fg_color = Color::Green,
                    b"93" => *fg_color = Color::Yellow,
                    b"94" => *fg_color = Color::Blue,
                    b"95" => *fg_color = Color::Magenta,
                    b"96" => *fg_color = Color::Cyan,
                    b"97" => *fg_color = Color::White,

                    b"100" => *bg_color = Color::Black,
                    b"101" => *bg_color = Color::Red,
                    b"102" => *bg_color = Color::Green,
                    b"103" => *bg_color = Color::Yellow,
                    b"104" => *bg_color = Color::Blue,
                    b"105" => *bg_color = Color::Magenta,
                    b"106" => *bg_color = Color::Cyan,
                    b"107" => *bg_color = Color::White,
                    _ => {}
                }
                grid[cursor_val!()].set_fg(*fg_color);
                grid[cursor_val!()].set_bg(*bg_color);
                *state = State::Normal;
            }
            (b'm', State::Csi2(ref buf1, ref buf2)) => {
                for b in &[buf1, buf2] {
                    match b.as_slice() {
                        b"0" => {
                            *fg_color = Color::Default;
                            *bg_color = Color::Default;
                        }
                        b"1" => { /* bold */ }
                        b"7" => {
                            /* Inverse */
                            let temp = *fg_color;
                            *fg_color = *bg_color;
                            *bg_color = temp;
                        }
                        b"27" => {
                            /* Inverse off */
                            let temp = *fg_color;
                            *fg_color = *bg_color;
                            *bg_color = temp;
                        }
                        b"30" => *fg_color = Color::Black,
                        b"31" => *fg_color = Color::Red,
                        b"32" => *fg_color = Color::Green,
                        b"33" => *fg_color = Color::Yellow,
                        b"34" => *fg_color = Color::Blue,
                        b"35" => *fg_color = Color::Magenta,
                        b"36" => *fg_color = Color::Cyan,
                        b"37" => *fg_color = Color::White,

                        b"39" => *fg_color = Color::Default,
                        b"40" => *bg_color = Color::Black,
                        b"41" => *bg_color = Color::Red,
                        b"42" => *bg_color = Color::Green,
                        b"43" => *bg_color = Color::Yellow,
                        b"44" => *bg_color = Color::Blue,
                        b"45" => *bg_color = Color::Magenta,
                        b"46" => *bg_color = Color::Cyan,
                        b"47" => *bg_color = Color::White,

                        b"49" => *bg_color = Color::Default,

                        b"90" => *fg_color = Color::Black,
                        b"91" => *fg_color = Color::Red,
                        b"92" => *fg_color = Color::Green,
                        b"93" => *fg_color = Color::Yellow,
                        b"94" => *fg_color = Color::Blue,
                        b"95" => *fg_color = Color::Magenta,
                        b"96" => *fg_color = Color::Cyan,
                        b"97" => *fg_color = Color::White,

                        b"100" => *bg_color = Color::Black,
                        b"101" => *bg_color = Color::Red,
                        b"102" => *bg_color = Color::Green,
                        b"103" => *bg_color = Color::Yellow,
                        b"104" => *bg_color = Color::Blue,
                        b"105" => *bg_color = Color::Magenta,
                        b"106" => *bg_color = Color::Cyan,
                        b"107" => *bg_color = Color::White,
                        _ => {}
                    }
                }
                grid[cursor_val!()].set_fg(*fg_color);
                grid[cursor_val!()].set_bg(*bg_color);
                *state = State::Normal;
            }
            (c, State::Csi1(ref mut buf)) if (c >= b'0' && c <= b'9') || c == b' ' => {
                buf.push(c);
            }
            (b';', State::Csi2(ref mut buf1_p, ref mut buf2_p)) => {
                let buf1 = std::mem::replace(buf1_p, SmallVec::new());
                let buf2 = std::mem::replace(buf2_p, SmallVec::new());
                let buf3 = SmallVec::new();
                *state = State::Csi3(buf1, buf2, buf3);
            }
            (b't', State::Csi2(_, _)) => {
                //debug!("ignoring {}", EscCode::from((&(*state), byte)));
                // Window manipulation, skip it
                *state = State::Normal;
            }
            (b'H', State::Csi2(_, _)) | (b'H', State::Csi) => {
                //Cursor Position [row;column] (default = [1,1]) (CUP).
                let (orig_x, mut orig_y) = if let State::Csi2(ref y, ref x) = state {
                    (
                        unsafe { std::str::from_utf8_unchecked(x) }
                            .parse::<usize>()
                            .unwrap_or(1),
                        unsafe { std::str::from_utf8_unchecked(y) }
                            .parse::<usize>()
                            .unwrap_or(1),
                    )
                } else {
                    (1, 1)
                };

                let (min_y, max_y) = if *origin_mode {
                    //debug!(*origin_mode);
                    orig_y += scroll_region.top;
                    (scroll_region.top, scroll_region.bottom)
                } else {
                    (0, terminal_size.1.saturating_sub(1))
                };

                cursor.0 = std::cmp::min(orig_x - 1, terminal_size.0.saturating_sub(1));
                cursor.1 = std::cmp::max(min_y, std::cmp::min(max_y, orig_y - 1));
                *wrap_next = false;

                //debug!("{}", EscCode::from((&(*state), byte)),);
                //debug!(
                //    "cursor set to ({},{}), cursor was: {:?}",
                //    orig_x, orig_y, cursor
                //);

                //debug!("cursor became: {:?}", cursor);
                *state = State::Normal;
            }
            (c, State::Csi2(_, ref mut buf)) if c >= b'0' && c <= b'9' => {
                buf.push(c);
            }
            (b'r', State::Csi2(_, _)) | (b'r', State::Csi) => {
                /* CSI Ps ; Ps r Set Scrolling Region [top;bottom] (default = full size of window) (DECSTBM). */
                let (top, bottom) = if let State::Csi2(ref top, ref bottom) = state {
                    (
                        unsafe { std::str::from_utf8_unchecked(top) }
                            .parse::<usize>()
                            .unwrap_or(1),
                        unsafe { std::str::from_utf8_unchecked(bottom) }
                            .parse::<usize>()
                            .unwrap_or(1),
                    )
                } else {
                    (1, terminal_size.1)
                };

                if bottom > top {
                    scroll_region.top = top - 1;
                    scroll_region.bottom = bottom - 1;
                    *cursor = (0, 0);
                    *wrap_next = false;
                }
                //debug!("set scrolling region to {:?}", scroll_region);
                *state = State::Normal;
            }
            (b't', State::Csi3(_, _, _)) => {
                //debug!("ignoring {}", EscCode::from((&(*state), byte)));
                // Window manipulation, skip it
                *state = State::Normal;
            }

            (c, State::Csi3(_, _, ref mut buf)) if c >= b'0' && c <= b'9' => {
                buf.push(c);
            }
            (b'm', State::Csi3(ref buf1, ref buf2, ref buf3))
                if buf1.as_ref() == b"38" && buf2.as_ref() == b"5" =>
            {
                /* Set character attributes | foreground color */
                *fg_color = if let Ok(byte) =
                    u8::from_str_radix(unsafe { std::str::from_utf8_unchecked(buf3) }, 10)
                {
                    //debug!("parsed buf as {}", byte);
                    Color::Byte(byte)
                } else {
                    Color::Default
                };
                grid[cursor_val!()].set_fg(*fg_color);
                *state = State::Normal;
            }
            (b'm', State::Csi3(ref buf1, ref buf2, ref buf3))
                if buf1.as_ref() == b"48" && buf2.as_ref() == b"5" =>
            {
                /* Set character attributes | background color */
                *bg_color = if let Ok(byte) =
                    u8::from_str_radix(unsafe { std::str::from_utf8_unchecked(buf3) }, 10)
                {
                    //debug!("parsed buf as {}", byte);
                    Color::Byte(byte)
                } else {
                    Color::Default
                };
                grid[cursor_val!()].set_bg(*bg_color);
                *state = State::Normal;
            }
            (_, State::Csi) => {
                debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
                *state = State::Normal;
            }
            (_, State::Csi1(_)) => {
                debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
                *state = State::Normal;
            }
            (_, State::Csi2(_, _)) => {
                debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
                *state = State::Normal;
            }
            (_, State::Csi3(_, _, _)) => {
                debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
                *state = State::Normal;
            }
            (_, State::Osc1(_)) => {
                debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
                *state = State::Normal;
            }
            (_, State::Osc2(_, _)) => {
                debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
                *state = State::Normal;
            }
            (_, State::CsiQ(_)) => {
                debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
                *state = State::Normal;
            }
            /* other stuff */
            (_, State::G0) => {
                debug!("ignoring {}", EscCode::from((&(*state), byte)));
                *state = State::Normal;
            }
        }
    }
}
