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

use std::{
    io::{Read, Write},
    mem,
    os::fd::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
    process::{Child, Command, ExitCode, Stdio},
    sync::{
        atomic::{AtomicBool, Ordering},
        Arc, LazyLock, Mutex,
    },
};

use config::Config;
use cross::{
    io::{Poll, PtyMaster, TerminalOptions, TerminalOptionsImpl, TerminalSize},
    process::CommandSpawnExt,
};
use error::Error;
use font::{Font, Fonts};
use libcolors::{
    application::{
        window::{EventOutcome, Window},
        Application,
    },
    event::KeyModifiers,
    geometry::{Cast, Rectangle},
    input::Key,
    surface::{MappedSurface, Surface},
};
use libterm::escape::CursorStyle;
use state::{Cursor, GridCell, State};

pub mod attr;
pub mod config;
pub mod error;
pub mod escape;
pub mod font;
pub mod state;

struct DrawState {
    width: usize,
    force_redraw: bool,
    focus_changed: bool,
    focused: bool,
    old_cursor: Cursor,
    old_cursor_style: CursorStyle,

    fonts: Fonts,
}

pub struct Terminal {
    poll: Poll,
    conn_fd: RawFd,
    application: Application,
    state: Arc<Mutex<State>>,

    pty_master: Arc<Mutex<PtyMaster>>,
    pty_master_fd: RawFd,
    #[allow(unused)]
    shell: Child,
}

impl DrawState {
    pub fn new(fonts: Fonts, width: usize) -> Self {
        Self {
            width,
            fonts,
            force_redraw: true,
            focus_changed: false,
            focused: true,
            old_cursor_style: CursorStyle::Block(true),
            old_cursor: Cursor { row: 0, col: 0 },
        }
    }

    #[allow(clippy::too_many_arguments)]
    fn draw_character<S: Surface>(
        surface: &mut S,
        cx: usize,
        cy: usize,
        fw: usize,
        fh: usize,
        cell: &GridCell,
        fonts: &Fonts,
        invert: bool,
    ) {
        fn blend(x: u8, y: u8, f: f32) -> u8 {
            let v = f * (x as f32) + (1.0 - f) * (y as f32);
            v as u8
        }

        let mut bg = cell.attrs.bg;
        let mut fg = cell.attrs.fg;
        if invert {
            mem::swap(&mut bg, &mut fg);
        }

        // Fill cell
        let cell_rect = Rectangle::new(cx, cy, fw, fh);
        surface.fill_rect(cell_rect.cast(), bg.to_u32());

        if cell.char == '\0' {
            return;
        }

        let font = match (cell.attrs.bold, cell.attrs.italic) {
            (true, true) => &fonts.bold_italic,
            (false, true) => &fonts.italic,
            (true, false) => &fonts.bold,
            (false, false) => &fonts.regular,
        };

        font.map_glyph(cell.char, |x, y, v| {
            let v = v.min(1.0);
            let r = blend(fg.r, bg.r, v);
            let g = blend(fg.g, bg.g, v);
            let b = blend(fg.b, bg.b, v);
            let color = (b as u32) | ((g as u32) << 8) | ((r as u32) << 16) | 0xFF000000;

            surface.set_pixel((x + cx) as u32, (y + cy) as u32, color);
        });

        if cell.attrs.underlined {
            surface.fill_rect(Rectangle::new(cx, cy + fh - 1, fw, 1).cast(), fg.to_u32());
        }
        if cell.attrs.strikethrough {
            surface.fill_rect(Rectangle::new(cx, cy + fh / 2, fw, 1).cast(), fg.to_u32());
        }
    }

    pub fn draw(&mut self, surface: &mut MappedSurface, state: &mut State) {
        let default_bg = state.default_attributes.bg.to_u32();
        let font_layout = self.fonts.regular.layout();
        let fw = font_layout.width;
        let fh = font_layout.height;

        let cursor_dirty = (self.old_cursor != state.cursor)
            || (self.old_cursor_style != state.cursor_style)
            || state.cursor_dirty;
        state.cursor_dirty = false;

        if self.force_redraw {
            surface.fill(default_bg);
        }

        if cursor_dirty {
            state.buffer.set_row_dirty(self.old_cursor.row);
        }

        let scroll = state.adjust_scroll();
        let cursor_visible = scroll == 0 && state.cursor_visible;

        state.buffer.visible_rows_mut(scroll, |i, row| {
            let cy = i * fh;
            for (j, cell) in row.cells().enumerate() {
                let cx = j * fw;
                Self::draw_character(surface, cx, cy, fw, fh, cell, &self.fonts, false);
            }
        });

        if cursor_visible && cursor_dirty {
            let cx = state.cursor.col * fw;
            let cy = state.cursor.row * fh;

            // Character under cursor
            let cell = state.buffer.cell(state.cursor.row, state.cursor.col);

            let fg = cell.attrs.fg.to_u32();

            match state.cursor_style {
                CursorStyle::Bar(_) if self.focused => {
                    surface.fill_rect(Rectangle::new(cx, cy, 1, fh).cast(), fg);
                }
                CursorStyle::Underline(_) if self.focused => {
                    surface.fill_rect(Rectangle::new(cx, cy + fh - 1, fw, 1).cast(), fg);
                }
                // Block, focused
                CursorStyle::Block(_) if self.focused => {
                    Self::draw_character(surface, cx, cy, fw, fh, cell, &self.fonts, true);
                }
                // Block, not focused (default not focused)
                _ => {
                    Self::draw_character(surface, cx, cy, fw, fh, cell, &self.fonts, false);

                    // Draw outline
                    for y in 0..fh {
                        for x in 0..fw {
                            if y == 0 || y == fh - 1 || x == 0 || x == fw - 1 {
                                surface.set_pixel(x as u32, y as u32, fg);
                            }
                        }
                    }
                }
            }

            self.old_cursor = state.cursor;
        }

        self.force_redraw = false;
        self.focus_changed = false;
    }
}

impl Terminal {
    pub fn new() -> Result<Self, Error> {
        let fonts = Fonts::from_config()?;

        let mut app = Application::new()?;
        let mut window = Window::new(&app)?;
        let mut poll = Poll::new()?;

        let width = window.width() as usize;
        let height = window.height() as usize;

        let font_layout = fonts.regular.layout();

        let rows = height / font_layout.height;
        let columns = width / font_layout.width;

        let termios = TerminalOptionsImpl::normal();
        // let termios = TerminalOptions::default();
        let (pty_master, pty_slave) = cross::io::open_pty(
            &termios,
            TerminalSize {
                rows: rows as _,
                columns: columns as _,
                x_pixels: 0,
                y_pixels: 0,
            },
        )?; // create_pty(Some(termios), TerminalSize { rows, columns })?;
        let pty_master_fd = pty_master.as_raw_fd();

        // TODO I hate this
        let pty_master = Arc::new(Mutex::new(pty_master));
        let state = Arc::new(Mutex::new(State::new(columns, rows)));
        let draw_state = Arc::new(Mutex::new(DrawState::new(fonts, width)));

        let state_c = state.clone();
        let draw_state_c = draw_state.clone();
        let pty_master_c = pty_master.clone();
        window.set_on_resized(move |width, height| {
            let mut s = state_c.lock().unwrap();
            let mut ds = draw_state_c.lock().unwrap();
            let mut pty_master = pty_master_c.lock().unwrap();

            let width = width as usize;
            let height = height as usize;
            let font_layout = ds.fonts.regular.layout();
            let rows = height / font_layout.height;
            let columns = width / font_layout.width;

            pty_master
                .resize_terminal(TerminalSize {
                    rows: rows as _,
                    columns: columns as _,
                    x_pixels: width as _,
                    y_pixels: height as _,
                })
                .expect("terminal resize");

            s.resize(width / font_layout.width, height / font_layout.height);
            ds.width = width;
            ds.force_redraw = true;

            EventOutcome::Redraw
        });

        let state_c = state.clone();
        let pty_master_c = pty_master.clone();
        window.set_on_key_input(move |ev| {
            let mut s = state_c.lock().unwrap();
            let mut pty_master = pty_master_c.lock().unwrap();
            let mut need_redraw = false;

            // TODO error reporting from handlers
            if let Some(mut input) = ev.input {
                if input == '\n' {
                    input = '\x0D';
                }
                pty_master.write_all(&[input as u8]).unwrap();
                need_redraw = s.scroll_end();
            } else {
                match (ev.modifiers, ev.key) {
                    // terminal control
                    (KeyModifiers::CTRL, Key::Char(b'l')) => {
                        if !s.alternate {
                            s.clear();
                        }
                        pty_master.write_all(&[0x0C]).unwrap();
                        need_redraw = !s.alternate;
                    }
                    (KeyModifiers::SHIFT, Key::PageUp) => {
                        need_redraw = s.scroll_up();
                    }
                    (KeyModifiers::SHIFT, Key::PageDown) => {
                        need_redraw = s.scroll_down();
                    }
                    (KeyModifiers::SHIFT, Key::Home) => {
                        need_redraw = s.scroll_home();
                    }
                    (KeyModifiers::SHIFT, Key::End) => {
                        need_redraw = s.scroll_end();
                    }
                    // termios chars
                    (KeyModifiers::NONE, Key::Backspace) => {
                        pty_master.write_all(&[termios.erase_char()]).ok();
                        need_redraw = s.scroll_end();
                    }
                    (KeyModifiers::NONE, Key::Tab) => {
                        pty_master.write_all(b"\t").ok();
                        need_redraw = s.scroll_end();
                    }
                    (KeyModifiers::CTRL, Key::Char(ch)) if ch.is_ascii_lowercase() => {
                        let byte = ch - 0x60;
                        pty_master.write_all(&[byte]).unwrap();
                        need_redraw = s.scroll_end();
                        // pty_master.write_all(&[termios.interrupt_char()]).unwrap();
                        // need_redraw = s.scroll_end();
                    }
                    // (KeyModifiers::CTRL, Key::Char(b'd')) => {
                    //     pty_master.write_all(&[termios.eof_char()]).unwrap();
                    //     need_redraw = s.scroll_end();
                    // }
                    // other sequences
                    (m, k) if let Some(data) = escape::map(m, k) => {
                        pty_master.write_all(data).unwrap();
                        need_redraw = s.scroll_end();
                    }
                    _ => (),
                }
            }

            if need_redraw {
                EventOutcome::Redraw
            } else {
                EventOutcome::None
            }
        });

        let draw_state_c = draw_state.clone();
        window.set_on_focus_changed(move |focused| {
            let mut ds = draw_state_c.lock().unwrap();

            ds.focused = focused;
            ds.focus_changed = true;

            EventOutcome::Redraw
        });

        let state_c = state.clone();
        window.set_on_redraw_requested(move |surface| {
            let mut s = state_c.lock().unwrap();
            let mut ds = draw_state.lock().unwrap();

            ds.draw(surface, &mut s);
        });

        app.add_window(window);

        let conn_fd = app.connection().lock().unwrap().as_raw_fd();

        poll.add(&conn_fd)?;
        poll.add(&pty_master_fd)?;

        let pty_slave_stdin = pty_slave.into_raw_fd();
        let pty_slave_stdout = cross::io::clone_fd(pty_slave_stdin)?;
        let pty_slave_stderr = cross::io::clone_fd(pty_slave_stdin)?;

        log::debug!("stdin = {pty_slave_stdin:?}, stdout = {pty_slave_stdout:?}, stderr = {pty_slave_stderr:?}");

        let shell = unsafe {
            Command::new("/bin/sh")
                .arg("-l")
                .stdin(Stdio::from_raw_fd(pty_slave_stdin))
                .stdout(Stdio::from_raw_fd(pty_slave_stdout))
                .stderr(Stdio::from_raw_fd(pty_slave_stderr))
                .create_session()?
                .create_process_group()?
                .spawn()?
        };

        Ok(Self {
            poll,
            conn_fd,
            state,
            application: app,

            pty_master,
            pty_master_fd,
            shell,
        })
    }

    fn handle_shell(&mut self) -> Result<bool, Error> {
        let mut buf = [0; 8192];
        let mut pty = self.pty_master.lock().unwrap();
        let len = pty.read(&mut buf)?;

        if len == 0 {
            ABORT.store(true, Ordering::Release);
            return Ok(false);
        }

        let mut needs_redraw = false;
        let mut s = self.state.lock().unwrap();

        for &ch in &buf[..len] {
            needs_redraw |= s.handle_shell_output(ch);
        }

        Ok(needs_redraw)
    }

    fn run_inner(mut self) -> Result<ExitCode, Error> {
        while !ABORT.load(Ordering::Acquire) {
            match self.poll.wait(None)? {
                Some(fd) if fd == self.conn_fd => {
                    self.application.poll_events()?;
                }
                Some(fd) if fd == self.pty_master_fd => {
                    let needs_redraw = self.handle_shell()?;

                    if needs_redraw {
                        self.application.redraw()?;
                    }
                }
                Some(_) => (),
                None => (),
            }
            // match self.poll.wait(None, true)? {
            //     Some((fd, Ok(_))) if fd == self.conn_fd => {
            //         self.application.poll_events()?;
            //     }
            //     Some((fd, Ok(_))) if fd == self.pty_master_fd => {
            //     }
            //     Some((_, Ok(_))) => {
            //         todo!()
            //     }
            //     Some((_, Err(error))) => return Err(Error::from(error)),
            //     None => (),
            // }
        }

        Ok(ExitCode::SUCCESS)
    }

    pub fn run(self) -> ExitCode {
        match self.run_inner() {
            Ok(code) => code,
            Err(error) => {
                eprintln!("Error: {}", error);
                ExitCode::FAILURE
            }
        }
    }
}

static ABORT: AtomicBool = AtomicBool::new(false);
static CONFIG: LazyLock<Config> = LazyLock::new(|| Config::load_or_default("/etc/term.conf"));

fn run() -> Result<ExitCode, Error> {
    LazyLock::force(&CONFIG);
    let term = Terminal::new()?;
    Ok(term.run())
}

fn main() -> ExitCode {
    logsink::setup_logging(false);

    match run() {
        Ok(code) => code,
        Err(error) => {
            log::error!("{error}");
            ExitCode::FAILURE
        }
    }
}
