#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os, rustc_private))]

use std::{
    fmt,
    io::{self, stdout, Stdout, Write},
    os::fd::{AsRawFd, RawFd},
};

pub use cross::term::TermKey;
pub use sys::RawMode;

use cross::term::TerminalInput;

#[cfg(any(target_os = "yggdrasil", rust_analyzer))]
mod yggdrasil;
#[cfg(any(target_os = "yggdrasil", rust_analyzer))]
use yggdrasil as sys;

#[cfg(any(unix, rust_analyzer))]
mod unix;
#[cfg(any(unix, rust_analyzer))]
use unix as sys;

#[cfg(any(feature = "tui", rust_analyzer))]
mod tui;

pub mod escape;

pub trait RawTerminal {
    fn raw_enter_alternate_mode(&mut self) -> io::Result<()>;
    fn raw_leave_alternate_mode(&mut self) -> io::Result<()>;
    fn raw_clear_all(&mut self) -> io::Result<()>;
    fn raw_clear_line(&mut self, what: u32) -> io::Result<()>;
    fn raw_report_cursor_position(&mut self) -> io::Result<()>;
    fn raw_move_cursor(&mut self, row: usize, column: usize) -> io::Result<()>;
    fn raw_set_cursor_style(&mut self, style: CursorStyle) -> io::Result<()>;
    fn raw_set_color(&mut self, fgbg: u32, color: Color) -> io::Result<()>;
    fn raw_set_style(&mut self, what: u32) -> io::Result<()>;
    fn raw_set_cursor_visible(&mut self, visible: bool) -> io::Result<()>;
    fn raw_size(&self) -> io::Result<(usize, usize)>;
}

pub struct Term {
    stdin: TerminalInput,
    stdout: Stdout,
}

#[derive(Debug, Clone, Copy)]
pub enum CursorStyle {
    Default,
    Line,
}

#[derive(Debug, Clone, Copy)]
#[repr(u32)]
pub enum Color {
    Black = 0,
    Red = 1,
    Green = 2,
    Yellow = 3,
    Blue = 4,
    Magenta = 5,
    Cyan = 6,
    White = 7,
    Default = 9,
}

#[derive(Debug, Clone, Copy)]
pub enum Clear {
    All,
    LineToEnd,
}

impl RawTerminal for Stdout {
    fn raw_enter_alternate_mode(&mut self) -> io::Result<()> {
        self.write_all(b"\x1B[?1049h")
    }

    fn raw_leave_alternate_mode(&mut self) -> io::Result<()> {
        self.write_all(b"\x1B[?1049l")
    }

    fn raw_clear_all(&mut self) -> io::Result<()> {
        self.write_all(b"\x1B[2J")
    }

    fn raw_clear_line(&mut self, what: u32) -> io::Result<()> {
        write!(self, "\x1B[{what}K")
    }

    fn raw_report_cursor_position(&mut self) -> io::Result<()> {
        write!(self, "\x1B[6n")
    }

    fn raw_move_cursor(&mut self, row: usize, column: usize) -> io::Result<()> {
        write!(self, "\x1B[{};{}f", row + 1, column + 1)
    }

    fn raw_set_cursor_style(&mut self, style: CursorStyle) -> io::Result<()> {
        match style {
            CursorStyle::Default => self.write_all(b"\x1B[0 q")?,
            CursorStyle::Line => self.write_all(b"\x1B[6 q")?,
        }
        Ok(())
    }

    fn raw_set_color(&mut self, fgbg: u32, color: Color) -> io::Result<()> {
        write!(self, "\x1B[{}{}m", fgbg, color as u32)
    }

    fn raw_set_style(&mut self, what: u32) -> io::Result<()> {
        write!(self, "\x1B[{what}m")
    }

    fn raw_set_cursor_visible(&mut self, visible: bool) -> io::Result<()> {
        if visible {
            write!(self, "\x1B[?25h")
        } else {
            write!(self, "\x1B[?25l")
        }
    }

    fn raw_size(&self) -> io::Result<(usize, usize)> {
        unsafe { sys::terminal_size(self) }
    }
}

impl Term {
    pub fn open() -> io::Result<Self> {
        let stdin = TerminalInput::open()?;
        let mut stdout = stdout();

        stdout.raw_enter_alternate_mode()?;
        stdout.raw_clear_all()?;
        stdout.raw_move_cursor(0, 0)?;

        Ok(Self { stdin, stdout })
    }

    pub fn report_cursor_position(&mut self) -> io::Result<Option<(u32, u32)>> {
        self.stdout.raw_report_cursor_position()?;
        self.stdout.flush()?;
        self.stdin.read_cursor_position_report()
    }

    pub fn set_cursor_position(&mut self, row: usize, column: usize) -> io::Result<()> {
        self.stdout.raw_move_cursor(row, column)
    }

    pub fn set_cursor_visible(&mut self, visible: bool) -> io::Result<()> {
        self.stdout.raw_set_cursor_visible(visible)
    }
    pub fn set_cursor_style(&mut self, style: CursorStyle) -> io::Result<()> {
        self.stdout.raw_set_cursor_style(style)
    }
    pub fn size(&self) -> io::Result<(usize, usize)> {
        self.stdout.raw_size()
    }

    pub fn set_foreground(&mut self, color: Color) -> io::Result<()> {
        self.stdout.raw_set_color(3, color)
    }
    pub fn set_background(&mut self, color: Color) -> io::Result<()> {
        self.stdout.raw_set_color(4, color)
    }
    pub fn set_bright(&mut self, bright: bool) -> io::Result<()> {
        if bright {
            self.stdout.raw_set_style(2)
        } else {
            self.stdout.raw_set_style(22)
        }
    }
    pub fn reset_style(&mut self) -> io::Result<()> {
        self.stdout.raw_set_style(0)
    }
    pub fn clear(&mut self, clear: Clear) -> io::Result<()> {
        match clear {
            Clear::All => self.stdout.raw_clear_all(),
            Clear::LineToEnd => self.stdout.raw_clear_line(0),
        }
    }

    pub fn read_key(&mut self) -> io::Result<TermKey> {
        self.stdin.read_key()
    }

    pub fn flush(&mut self) -> io::Result<()> {
        self.stdout.flush()
    }
}

impl AsRawFd for Term {
    fn as_raw_fd(&self) -> RawFd {
        self.stdin.as_raw_fd()
    }
}

impl fmt::Write for Term {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        self.stdout.write_all(s.as_bytes()).map_err(|_| fmt::Error)
    }
}

impl Drop for Term {
    fn drop(&mut self) {
        self.stdout.raw_leave_alternate_mode().ok();
    }
}
