use crossterm::{cursor, queue, style};
use std::io::{Stdout, Write};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Cell {
    pub character: char,
    pub foreground: crossterm::style::Color,
    pub background: crossterm::style::Color,
}

impl Default for Cell {
    fn default() -> Self {
        Self {
            character: ' ',
            foreground: crossterm::style::Color::White,
            background: crossterm::style::Color::Black,
        }
    }
}

pub struct Renderer {
    pub width: u16,
    pub height: u16,
    front_buffer: Vec<Cell>,
    back_buffer: Vec<Cell>,
    first_frame: bool,
}

impl Renderer {
    pub fn new(width: u16, height: u16) -> Self {
        let size = (width as usize) * (height as usize);
        Self {
            width,
            height,
            front_buffer: vec![Cell::default(); size],
            back_buffer: vec![Cell::default(); size],
            first_frame: true,
        }
    }

    pub fn resize(&mut self, width: u16, height: u16) {
        self.width = width;
        self.height = height;
        let size = (width as usize) * (height as usize);
        self.front_buffer = vec![Cell::default(); size];
        self.back_buffer = vec![Cell::default(); size];
        self.first_frame = true;
    }

    pub fn clear(&mut self) {
        for cell in &mut self.back_buffer {
            *cell = Cell::default();
        }
    }

    pub fn set_cell(&mut self, column: u16, row: u16, cell: Cell) {
        if column < self.width && row < self.height {
            let index = (row as usize) * (self.width as usize) + (column as usize);
            self.back_buffer[index] = cell;
        }
    }

    pub fn present(&mut self, stdout: &mut Stdout) -> std::io::Result<()> {
        let mut last_foreground: Option<crossterm::style::Color> = None;
        let mut last_background: Option<crossterm::style::Color> = None;

        for row in 0..self.height {
            for column in 0..self.width {
                let index = (row as usize) * (self.width as usize) + (column as usize);
                let back = self.back_buffer[index];

                if self.first_frame || self.front_buffer[index] != back {
                    queue!(stdout, cursor::MoveTo(column, row))?;

                    if last_foreground != Some(back.foreground) {
                        queue!(stdout, style::SetForegroundColor(back.foreground))?;
                        last_foreground = Some(back.foreground);
                    }

                    if last_background != Some(back.background) {
                        queue!(stdout, style::SetBackgroundColor(back.background))?;
                        last_background = Some(back.background);
                    }

                    queue!(stdout, style::Print(back.character))?;
                }
            }
        }

        stdout.flush()?;

        std::mem::swap(&mut self.front_buffer, &mut self.back_buffer);
        self.first_frame = false;

        Ok(())
    }
}
