// ! A virtual terminal device.

use core::ops::Not;
use core::sync::atomic::AtomicU32;
use core::sync::atomic::Ordering;
use kernel::device_interface::character::CharacterDevice;
use shadeable::pixel_format::Rgba64;

pub const VTERM_HEIGHT: u32 = 40;
pub const VTERM_WIDTH: u32 = 100;

pub static VIRTUAL_TERMINAL_COUNT: AtomicU32 = AtomicU32::new(0);

/// Fg and bg colors for vterm
pub type ColorCharacter = (Rgba64, Rgba64);

/// A vterm representation of a character
#[derive(Debug, Clone, Copy)]
pub struct VtermCharacter {
    pub character: char,
    //
    pub style: Style,
    //
    pub char_color: ColorCharacter,
}

#[derive(Default, Debug, Clone, Copy)]

pub struct Style(pub u8);
impl Style {
    pub fn bold(&self) -> bool {
        (self.0 & 0x01) > 0
    }

    pub fn underlined(&self) -> bool {
        (self.0 & 0x02) > 0
    }

    pub fn italic(&self) -> bool {
        (self.0 & 0x04) > 0
    }

    pub fn blinking(&self) -> bool {
        (self.0 & 0x08) > 0
    }

    pub fn reversed(&self) -> bool {
        (self.0 & 0x10) > 0
    }

    pub fn struck(&self) -> bool {
        (self.0 & 0x20) > 0
    }

    #[must_use]
    pub fn set_bold(mut self, v: bool) -> Self {
        if v {
            self.0 |= 0x01;
        } else {
            self.0 &= 0x01u8.not();
        }
        self
    }

    #[must_use]
    pub fn set_underlined(mut self, v: bool) -> Self {
        if v {
            self.0 |= 0x02;
        } else {
            self.0 &= 0x02u8.not();
        }
        self
    }

    #[must_use]
    pub fn set_italic(mut self, v: bool) -> Self {
        if v {
            self.0 |= 0x04;
        } else {
            self.0 &= 0x04u8.not();
        }
        self
    }

    #[must_use]
    pub fn set_blinking(mut self, v: bool) -> Self {
        if v {
            self.0 |= 0x08;
        } else {
            self.0 &= 0x08u8.not();
        }
        self
    }

    #[must_use]
    pub fn set_reversed(mut self, v: bool) -> Self {
        if v {
            self.0 |= 0x10;
        } else {
            self.0 &= 0x10u8.not();
        }
        self
    }

    #[must_use]
    pub fn set_struck(mut self, v: bool) -> Self {
        if v {
            self.0 |= 0x20;
        } else {
            self.0 &= 0x20u8.not();
        }
        self
    }
}

#[derive(Debug)]
pub struct VTerm {
    pub characters: [[VtermCharacter; VTERM_WIDTH as usize]; VTERM_HEIGHT as usize],
    pub cursor_visible: bool,

    /// Internal ID of the vterm
    iid: u32,

    /// The internal representation of the vterm
    style: Style,

    /// The cursor position in layout x,y
    cursor_position: (u32, u32),
    key_buff: Vec<char>,
}

impl Default for VTerm {
    fn default() -> Self {
        VTerm {
            iid: 0,
            characters: [[VtermCharacter {
                character: 0x00 as char,
                char_color: (0xff_ff_ff_ff, 0x00_00_00_00),
                style: Style::default(),
            }; VTERM_WIDTH as usize]; VTERM_HEIGHT as usize],
            cursor_position: (0, 0),
            cursor_visible: true,
            style: Style::default(),
            key_buff: vec![],
        }
    }
}

/// The primitive interface for a vterm
impl VTerm {
    pub fn new() -> Self {
        let mut vterm = VTerm::default();
        let mut vtc = VIRTUAL_TERMINAL_COUNT.load(Ordering::Relaxed);
        vterm.iid = vtc;
        vtc += 1;
        VIRTUAL_TERMINAL_COUNT.store(vtc, Ordering::Relaxed);

        vterm
    }
    /// Set the vterm cursor to the given position
    pub fn set_cursor_position(&mut self, x: u32, y: u32) {
        if x > VTERM_WIDTH {
            self.cursor_position.0 = VTERM_WIDTH;
            error!("Cursor x position out of bounds");
        } else {
            self.cursor_position.0 = x;
        }
        if y > VTERM_HEIGHT {
            error!("Cursor y position out of bounds");
            self.cursor_position.1 = VTERM_HEIGHT;
        } else {
            self.cursor_position.1 = y;
        }
    }

    /// Set the vterm style
    pub fn set_vterm_style(&mut self, style: Style) {
        self.style = style;
    }
}

impl CharacterDevice for VTerm {
    fn can_read(&self) -> bool {
        true
    }

    fn can_write(&self) -> bool {
        true
    }

    fn read_char(&mut self) -> Option<char> {
        if let Some(c) = self.key_buff.pop() {
            return Some(c);
        }
        None
    }

    fn write_char(&mut self, c: char) -> bool {
        match c {
            '\n' => {
                self.cursor_position.1 += 1;
                self.cursor_position.0 = 0;
                true
            }
            '\r' => {
                self.cursor_position.0 = 0;
                true
            }
            '\t' => {
                self.cursor_position.0 += 4;
                true
            }
            '\x08' => {
                self.cursor_position.0 -= 1;
                self.characters[self.cursor_position.1 as usize][self.cursor_position.0 as usize]
                    .character = ' ';
                true
            }
            // This is a form feed, which is used to clear the screen
            '\x0c' => {
                self.characters = [[VtermCharacter {
                    character: ' ',
                    char_color: (0xff_ff_ff_ff, 0x00_00_00_00),
                    style: Style::default(),
                }; VTERM_WIDTH as usize]; VTERM_HEIGHT as usize];
                true
            }

            _ => {
                self.characters[self.cursor_position.1 as usize][self.cursor_position.0 as usize]
                    .character = c;
                self.characters[self.cursor_position.1 as usize][self.cursor_position.0 as usize]
                    .char_color = (0xff_ff_ff_ff, 0x00_00_00_00);
                self.characters[self.cursor_position.1 as usize][self.cursor_position.0 as usize]
                    .style = self.style;

                if self.cursor_position.0 < VTERM_WIDTH {
                    self.cursor_position.0 += 1;
                    true
                } else {
                    self.cursor_position.0 = 0;
                    self.cursor_position.1 += 1;
                    true
                }
            }
        }
    }

    fn reset(&mut self) {
        self.characters = [[VtermCharacter {
            character: ' ',
            char_color: (0xff_ff_ff_ff, 0x00_00_00_00),
            style: Style::default(),
        }; VTERM_WIDTH as usize]; VTERM_HEIGHT as usize];

        self.cursor_position = (0, 0);
        self.cursor_visible = true;
        self.style = Style::default();
    }

    fn initialize(&mut self) -> bool {
        true
    }
}
