use std::io::{self, Write};

use ratatui::{
    backend::{ClearType, WindowSize},
    buffer::Cell,
    layout::{Position, Size},
    style::Color,
};

use crate::{Color as TColor, RawTerminal, Term};

fn to_attributes(color: Color) -> Option<(TColor, bool)> {
    Some(match color {
        Color::Reset | Color::Indexed(_) | Color::Rgb(_, _, _) => return None,
        Color::Red => (TColor::Red, false),
        Color::LightRed => (TColor::Red, true),
        Color::Green => (TColor::Green, false),
        Color::LightGreen => (TColor::Green, true),
        Color::Blue => (TColor::Blue, false),
        Color::LightBlue => (TColor::Blue, true),
        Color::Yellow => (TColor::Yellow, false),
        Color::LightYellow => (TColor::Yellow, true),
        Color::Magenta => (TColor::Magenta, false),
        Color::LightMagenta => (TColor::Magenta, true),
        Color::Cyan => (TColor::Cyan, false),
        Color::LightCyan => (TColor::Cyan, true),
        Color::Gray => (TColor::White, false),
        Color::White => (TColor::White, true),
        Color::Black => (TColor::Black, false),
        Color::DarkGray => (TColor::Black, true),
    })
}

impl ratatui::backend::Backend for Term {
    type Error = io::Error;

    fn draw<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
    where
        I: Iterator<Item = (u16, u16, &'a Cell)>,
    {
        let mut fg = Color::Reset;
        let mut bg = Color::Reset;
        let mut last_pos: Option<(u16, u16)> = None;

        for (x, y, cell) in content {
            if !matches!(last_pos, Some(p) if x == p.0 + 1 && y == p.1) {
                self.stdout.raw_move_cursor(y as usize, x as usize)?;
            }
            last_pos = Some((x, y));
            if cell.fg != fg {
                if let Some((color, bold)) = to_attributes(cell.fg) {
                    if bold {
                        self.stdout.raw_set_style(1)?;
                    } else {
                        self.stdout.raw_set_style(22)?;
                    }
                    self.stdout.raw_set_color(3, color)?;
                } else {
                    self.stdout.raw_set_style(0)?;
                }
            }
            if cell.bg != bg || cell.fg != fg {
                if let Some((color, _)) = to_attributes(cell.bg) {
                    self.stdout.raw_set_color(4, color)?;
                } else {
                    self.stdout.raw_set_color(4, TColor::Black)?;
                }
            }

            fg = cell.fg;
            bg = cell.bg;

            self.stdout.write_all(cell.symbol().as_bytes())?;
        }

        self.stdout.raw_set_style(0)?;

        Ok(())
    }

    fn size(&self) -> Result<Size, Self::Error> {
        let (w, h) = self.stdout.raw_size()?;
        Ok(Size {
            width: w as u16,
            height: h as u16 - 1,
        })
    }

    fn window_size(&mut self) -> Result<WindowSize, Self::Error> {
        let (w, h) = self.stdout.raw_size()?;
        let columns_rows = Size {
            width: w as u16,
            height: h as u16 - 1,
        };
        // TODO
        let pixels = columns_rows;
        Ok(WindowSize {
            columns_rows,
            pixels,
        })
    }

    fn clear(&mut self) -> Result<(), Self::Error> {
        self.stdout.raw_clear_all()
    }

    fn clear_region(&mut self, _clear_type: ClearType) -> Result<(), Self::Error> {
        Ok(())
    }

    fn flush(&mut self) -> Result<(), Self::Error> {
        self.stdout.flush()
    }

    fn set_cursor(&mut self, x: u16, y: u16) -> Result<(), Self::Error> {
        self.stdout.raw_move_cursor(y as usize, x as usize)
    }

    fn hide_cursor(&mut self) -> Result<(), Self::Error> {
        self.stdout.raw_set_cursor_visible(false)
    }

    fn show_cursor(&mut self) -> Result<(), Self::Error> {
        self.stdout.raw_set_cursor_visible(true)
    }

    fn append_lines(&mut self, _n: u16) -> Result<(), Self::Error> {
        Ok(())
    }

    fn get_cursor_position(&mut self) -> Result<Position, Self::Error> {
        Ok(Position { x: 0, y: 0 })
    }

    fn get_cursor(&mut self) -> Result<(u16, u16), Self::Error> {
        Ok((0, 0))
    }

    fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> Result<(), Self::Error> {
        let position = position.into();
        self.stdout
            .raw_move_cursor(position.y as usize, position.x as usize)
    }
}
