#![feature(let_chains, rustc_private)]
#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os))]
#![allow(clippy::new_without_default)]

use std::{
    env,
    fmt::Write,
    io::{self, IsTerminal},
    path::Path,
};

use libterm::{Clear, Color, Term, TermKey};

use buffer::{Buffer, Mode, SetMode};
use config::Config;
use error::Error;
use keymap::{KeySeq, PrefixNode};

pub mod buffer;
pub mod command;
pub mod config;
pub mod error;
pub mod keymap;
pub mod line;

#[derive(Clone, Copy, PartialEq)]
pub enum TopMode {
    Normal,
    Command,
}

pub struct State {
    term: Term,
    buffer: Buffer,
    command: String,
    message: Option<String>,
    status: Option<String>,
    key_seq: KeySeq,
    top_mode: TopMode,
    config: Config,
    running: bool,
    number_width: usize,
}

impl State {
    pub fn open<P: AsRef<Path>>(path: Option<P>) -> Result<Self, Error> {
        let config = Config::default();
        let mut buffer = match path {
            Some(path) => Buffer::open(path).unwrap(),
            None => Buffer::empty(),
        };
        let term = Term::open().map_err(Error::TerminalError)?;

        let (w, h) = term.size().map_err(Error::TerminalError)?;
        if config.number {
            let nw = buffer.number_width() + 2;
            buffer.resize(&config, nw, w - nw - 1, h - 2);
        } else {
            buffer.resize(&config, 0, w - 1, h - 2);
        }

        Ok(Self {
            number_width: buffer.number_width(),
            top_mode: TopMode::Normal,
            message: None,
            status: None,
            command: String::new(),
            key_seq: KeySeq::empty(),
            running: true,
            buffer,
            term,
            config,
        })
    }

    pub fn buffer(&self) -> &Buffer {
        &self.buffer
    }

    pub fn buffer_mut(&mut self) -> &mut Buffer {
        &mut self.buffer
    }

    pub fn exit(&mut self) {
        self.running = false;
    }

    pub fn set_status<S: Into<String>>(&mut self, status: S) {
        self.status.replace(status.into());
    }

    fn display_number(&mut self) -> Result<(), Error> {
        let start = self.buffer.row_offset();
        let end = self.buffer.len();

        for i in 0.. {
            self.term
                .set_cursor_position(i, 0)
                .map_err(Error::TerminalError)?;

            if i + start == self.buffer.cursor_row() {
                self.term.set_bright(true).map_err(Error::TerminalError)?;
                self.term
                    .set_foreground(Color::Yellow)
                    .map_err(Error::TerminalError)?;
            }

            if i + start < end {
                write!(self.term, " {0:1$} ", i + start + 1, self.number_width)
                    .map_err(Error::TerminalFmtError)?;
            }

            if i == self.buffer.height() {
                break;
            }

            if i + start == self.buffer.cursor_row() {
                self.term.reset_style().map_err(Error::TerminalError)?;
            }
        }

        self.term.reset_style().map_err(Error::TerminalError)?;

        Ok(())
    }

    fn display_modeline(&mut self) -> Result<(), Error> {
        self.term
            .set_cursor_position(self.buffer.height(), 0)
            .map_err(Error::TerminalError)?;

        let bg = match (self.top_mode, self.buffer.mode()) {
            (TopMode::Normal, Mode::Normal) => Color::Yellow,
            (TopMode::Normal, Mode::Insert) => Color::Cyan,
            (TopMode::Command, _) => Color::Green,
        };

        self.term.set_background(bg).map_err(Error::TerminalError)?;
        self.term
            .set_foreground(Color::Black)
            .map_err(Error::TerminalError)?;

        match self.top_mode {
            TopMode::Normal => {
                write!(self.term, " {} ", self.buffer.mode().as_str())
                    .map_err(Error::TerminalFmtError)?;

                if self.buffer.is_modified() {
                    self.term
                        .set_background(Color::Magenta)
                        .map_err(Error::TerminalError)?;
                    self.term
                        .set_foreground(Color::Default)
                        .map_err(Error::TerminalError)?;
                } else {
                    self.term
                        .set_foreground(Color::Green)
                        .map_err(Error::TerminalError)?;
                    self.term
                        .set_background(Color::Default)
                        .map_err(Error::TerminalError)?;
                }
            }
            TopMode::Command => {
                write!(self.term, " COMMAND ").map_err(Error::TerminalFmtError)?;

                self.term
                    .set_foreground(Color::Green)
                    .map_err(Error::TerminalError)?;
                self.term
                    .set_background(Color::Default)
                    .map_err(Error::TerminalError)?;
            }
        }

        let name = self
            .buffer
            .name()
            .map(String::as_str)
            .unwrap_or("<unnamed>");
        write!(self.term, " {}", name).map_err(Error::TerminalFmtError)?;
        self.term
            .clear(Clear::LineToEnd)
            .map_err(Error::TerminalError)?;
        self.term
            .set_cursor_position(self.buffer.height(), self.buffer.width() - 10)
            .map_err(Error::TerminalError)?;
        self.term
            .set_foreground(Color::White)
            .map_err(Error::TerminalError)?;
        write!(self.term, "{}", self.key_seq).map_err(Error::TerminalFmtError)?;

        self.term.reset_style().map_err(Error::TerminalError)?;

        Ok(())
    }

    fn display(&mut self) -> Result<(), Error> {
        if self.buffer.is_dirty() {
            self.term.clear(Clear::All).map_err(Error::TerminalError)?;
        }

        if self.config.number && self.buffer.is_dirty() {
            self.display_number()?;
        }

        self.buffer.display(&self.config, &mut self.term)?;

        if self.top_mode != TopMode::Command {
            if let Some(status) = &self.status {
                self.term
                    .set_cursor_position(self.buffer().height() + 1, 0)
                    .map_err(Error::TerminalError)?;
                self.term
                    .write_str(status.as_str())
                    .map_err(Error::TerminalFmtError)?;
            }
        }

        if let Some(msg) = &self.message {
            self.term
                .set_cursor_position(self.buffer.height(), 0)
                .map_err(Error::TerminalError)?;
            self.term.write_str(msg).map_err(Error::TerminalFmtError)?;
            self.term.flush().map_err(Error::TerminalError)?;
            return Ok(());
        }

        self.display_modeline()?;

        match self.top_mode {
            TopMode::Normal => {
                self.buffer
                    .set_terminal_cursor(&self.config, &mut self.term)?;
            }
            TopMode::Command => {
                self.term
                    .set_cursor_position(self.buffer.height() + 1, 0)
                    .map_err(Error::TerminalError)?;
                write!(self.term, ":{}", self.command.as_str()).map_err(Error::TerminalFmtError)?;
            }
        }
        self.term.flush().map_err(Error::TerminalError)?;
        Ok(())
    }

    fn handle_command(&mut self) -> Result<(), Error> {
        let cmd = self.command.clone();
        command::execute(self, cmd)
    }

    fn handle_command_key(&mut self, key: TermKey) -> Result<(), Error> {
        match key {
            TermKey::Char('\n') | TermKey::Char('\x0D') => {
                self.top_mode = TopMode::Normal;
                self.handle_command()?;
            }
            TermKey::Char('\x7F') => {
                if self.command.is_empty() {
                    self.top_mode = TopMode::Normal;
                } else {
                    self.command.pop();
                }
            }
            TermKey::Escape => {
                self.top_mode = TopMode::Normal;
            }
            TermKey::Char(c) if c.is_ascii_graphic() || c == ' ' => self.command.push(c),
            _ => (),
        }

        Ok(())
    }

    fn handle_mode_key(&mut self, mode: Mode, key: TermKey) -> Result<(), Error> {
        let buffer = &mut self.buffer;

        self.key_seq.push(key);

        match self.config.key_seq(mode, &self.key_seq) {
            Some(PrefixNode::Leaf(actions)) => {
                self.key_seq.clear();

                for &action in actions {
                    command::perform(buffer, &self.config, action)?;
                }
            }
            Some(PrefixNode::Prefix(_)) => {}
            None => {
                self.key_seq.clear();
            }
        }

        if self.buffer().mode() != Mode::Normal {
            self.status = None;
        }

        Ok(())
    }

    fn handle_normal_key(&mut self, key: TermKey) -> Result<(), Error> {
        match key {
            TermKey::Escape => {
                self.key_seq.clear();
                self.buffer.set_mode(&self.config, SetMode::Normal);
                Ok(())
            }
            TermKey::Char(':') => {
                self.key_seq.clear();
                self.command.clear();
                self.status = None;
                self.top_mode = TopMode::Command;
                Ok(())
            }
            _ => self.handle_mode_key(Mode::Normal, key),
        }
    }

    fn handle_insert_key(&mut self, key: TermKey) -> Result<(), Error> {
        match key {
            TermKey::Escape => {
                self.buffer.set_mode(&self.config, SetMode::Normal);
                Ok(())
            }
            TermKey::Char(key)
                if !key.is_ascii() || key == ' ' || key == '\t' || key.is_ascii_graphic() =>
            {
                self.buffer.insert(&self.config, key);
                Ok(())
            }
            _ => self.handle_mode_key(Mode::Insert, key),
        }
    }

    pub fn update(&mut self) -> Result<(), Error> {
        if self.config.number {
            let nw = self.buffer.number_width();
            if nw != self.number_width {
                self.number_width = nw;
                let nw = nw + 2;
                let (w, h) = self.term.size().map_err(Error::TerminalError)?;
                self.buffer.resize(&self.config, nw, w - nw - 1, h - 2);
            }
        }

        self.display()?;

        let key = self.term.read_key().map_err(Error::TerminalError)?;

        if self.message.is_some() {
            self.message = None;
            if key != TermKey::Char(':') {
                return Ok(());
            }
        }

        let result = match (self.top_mode, self.buffer.mode()) {
            (TopMode::Normal, Mode::Normal) => self.handle_normal_key(key),
            (TopMode::Normal, Mode::Insert) => self.handle_insert_key(key),
            (TopMode::Command, _) => self.handle_command_key(key),
        };

        match result {
            Ok(()) => Ok(()),
            Err(e) => {
                self.message = Some(format!("Error: {}", e));
                Ok(())
            }
        }
    }

    pub fn cleanup(&mut self) {
        self.term.clear(Clear::All).ok();
    }
}

fn main() {
    let args = env::args().collect::<Vec<_>>();
    if args.len() > 2 {
        eprintln!("Usage: red [FILE]");
        return;
    }

    if !io::stdin().is_terminal() {
        eprintln!("Not a tty");
        return;
    }

    let path = args.get(1);
    let mut state = State::open(path).unwrap();
    let error = loop {
        if !state.running {
            break None;
        }

        if let Err(error) = state.update() {
            break Some(error);
        }
    };
    state.cleanup();

    if let Some(error) = error {
        eprintln!("Error: {:?}", error);
    }
}
