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

use libopenbirch::environment::Environment;
use libopenbirch::node::Node;
use libopenbirch::parser::{Lexer, Parser};
#[cfg(feature = "async")]
use termion::AsyncReader;
use termion::color;
use termion::event::Key;
use termion::input::TermReadEventsAndRaw;
use termion::raw::{IntoRawMode, RawTerminal};

pub struct Input {
    #[cfg(not(feature = "async"))]
    stdin: std::io::Stdin,

    stdout: RawTerminal<StdoutLock<'static>>,
    buffer: String,
    current_char: usize,
}
impl Input {
    pub fn new() -> Self {
        #[cfg(not(feature = "async"))]
        let stdin = std::io::stdin();
        let stdout = stdout().lock().into_raw_mode().unwrap();

        Self {
            stdin,
            stdout,
            buffer: "".into(),
            current_char: 0,
        }
    }

    #[inline]
    fn draw_line(
        buffer: &String,
        stdout: &mut RawTerminal<StdoutLock<'static>>,
        current_char: usize,
    ) {
        print!("\r{}> {}", termion::clear::CurrentLine, buffer,);
        let left_diff = buffer.len() - current_char;
        if left_diff > 0 {
            print!("{}", termion::cursor::Left(left_diff.try_into().unwrap()));
        }
        let _ = stdout.flush();
    }

    /// Gets input from `io::stdin`.
    /// Returns `None` when the user presses <C-c> or types `:quit`
    pub fn get(&mut self) -> Result<Option<String>, io::Error> {
        Self::draw_line(&self.buffer, &mut self.stdout, self.current_char);
        loop {
            for b in (&mut self.stdin).events_and_raw() {
                let (event, _slice) = b?;

                match event {
                    termion::event::Event::Key(key) => match key {
                        Key::Char(char) if char == '\n' => {
                            if &self.buffer == ":quit" {
                                println!("\r");
                                return Err(io::Error::from(io::ErrorKind::Interrupted));
                            }

                            let r = self.buffer.clone();
                            self.buffer.clear();
                            self.current_char = 0;
                            print!("\n\r");
                            return Ok(Some(r));
                        }
                        Key::Backspace => {
                            if self.current_char == 0 {
                                continue;
                            }
                            self.current_char -= 1;
                            self.buffer.remove(self.current_char);
                            Self::draw_line(&self.buffer, &mut self.stdout, self.current_char);
                            let _ = self.stdout.flush();
                        }
                        Key::Delete => {
                            if self.current_char == self.buffer.len() {
                                continue;
                            }
                            self.buffer.remove(self.current_char);
                            Self::draw_line(&self.buffer, &mut self.stdout, self.current_char);
                            let _ = self.stdout.flush();
                        }
                        Key::Left => {
                            if self.current_char == 0 {
                                continue;
                            }
                            self.current_char -= 1;
                            print!("{}", termion::cursor::Left(1));
                            let _ = self.stdout.flush();
                        }
                        Key::Right => {
                            if self.current_char == self.buffer.len() {
                                continue;
                            }
                            self.current_char += 1;
                            print!("{}", termion::cursor::Right(1));
                            let _ = self.stdout.flush();
                        }
                        Key::Char(char) => {
                            self.buffer.insert(self.current_char, char);
                            self.current_char += 1;
                            Self::draw_line(&self.buffer, &mut self.stdout, self.current_char);
                        }
                        Key::Ctrl(c) if c == 'c' => {
                            println!("\r");
                            return Err(io::Error::from(io::ErrorKind::Interrupted));
                        }
                        _ => {
                            #[cfg(debug_assertions)]
                            return Err(io::Error::other(format!(
                                "Key {key:?} is not implemented"
                            )));
                        }
                    },
                    termion::event::Event::Mouse(mouse_event) => {}
                    termion::event::Event::Unsupported(items) => {}
                }
            }
        }
    }

    pub fn disable_raw(&mut self) {
        self.stdout.suspend_raw_mode();
    }

    pub fn enable_raw(&mut self) {
        self.stdout.activate_raw_mode();
    }
}

fn print_err(i: usize, exp: String) {
    println!("\r{}{}^", " ".repeat(i + 2), color::Fg(color::Yellow));
    println!(
        "\r{}{}{}",
        color::Fg(color::Red),
        exp,
        color::Fg(color::Reset)
    );
}

fn main() -> Result<(), io::Error> {
    let mut input = Input::new();

    let mut env = Environment::new();

    while let Some(source) = input.get()? {
        let mut lexer = Lexer::new(&source);
        let tokens_result = lexer.lex();

        if tokens_result.is_err() {
            match tokens_result.err().unwrap() {
                libopenbirch::parser::LexerError::UnexpectedChar(i, exp) => print_err(i, exp),
            }
            continue;
        }

        #[cfg(debug_assertions)]
        input.disable_raw();

        let tokens = tokens_result.unwrap();
        let mut parser = Parser::new(tokens);

        #[cfg(debug_assertions)]
        input.enable_raw();

        let nodes = match parser.parse() {
            Ok(nodes) => nodes,
            Err(err) => {
                match err {
                    libopenbirch::parser::ParserError::UnexpectedEndOfTokens(exp) => {
                        print_err(source.len(), exp)
                    }
                    libopenbirch::parser::ParserError::UnexpectedToken(i, exp) => print_err(i, exp),
                }
                continue;
            }
        };

        print!("{}", color::Fg(color::Blue));

        for node in nodes {
            let evaluated = node.evaluate(&mut env);
            match evaluated {
                Ok(result) => println!("\r\t{}", result.as_string(None)),
                Err(exp) => print_err(0, exp),
            }
        }

        print!("{}", color::Fg(color::Reset));

        // println!("\t{}{source}{}", termion::color::Fg(termion::color::Blue), termion::color::Fg(termion::color::Reset));
    }

    Ok(())
}
