use std::{
    borrow::Cow,
    io::{BufRead, Write, stdin, stdout},
    path::PathBuf,
    rc::Rc,
};

use crate::{
    compile::{
        Compile, CompileContext, CompileOptions,
        syntax::{Expression, FunctionBody, ParseError},
    },
    error::{MachineError, MachineErrorAt, ReadError},
    parse::{self, parse_value},
    util::Either,
    vm::{
        env::Environment,
        instruction::Instruction,
        machine::Machine,
        value::{BytecodeFunction, IdentifierValue, Value},
    },
};

pub trait Reader {
    type Error: Into<MachineErrorAt>;

    fn read(&mut self) -> Result<Option<Value>, Self::Error>;
}

pub struct InteractiveReader {
    buffer: String,
    prompt_empty: Cow<'static, str>,
    prompt_continuation: Cow<'static, str>,
}

pub struct FileReader<R: BufRead> {
    reader: R,
    buffer: String,
}

pub struct ModuleReader<R: BufRead> {
    reader: FileReader<R>,
    path: PathBuf,
    macro_machine: Machine,
}

impl InteractiveReader {
    pub fn new<T: Into<Cow<'static, str>>, U: Into<Cow<'static, str>>>(
        prompt_empty: T,
        prompt_continuation: U,
    ) -> Self {
        Self {
            buffer: String::new(),
            prompt_empty: prompt_empty.into(),
            prompt_continuation: prompt_continuation.into(),
        }
    }

    pub fn reset(&mut self) {
        self.buffer.clear();
    }
}

impl Reader for InteractiveReader {
    type Error = MachineErrorAt;

    fn read(&mut self) -> Result<Option<Value>, Self::Error> {
        let stdin = stdin();
        read_inner(
            &mut stdin.lock(),
            &mut self.buffer,
            Some((
                self.prompt_empty.as_ref(),
                self.prompt_continuation.as_ref(),
            )),
        )
    }
}

impl<R: BufRead> FileReader<R> {
    pub fn new(reader: R) -> Self {
        Self {
            buffer: String::new(),
            reader,
        }
    }
}

impl<R: BufRead> Reader for FileReader<R> {
    type Error = MachineErrorAt;

    fn read(&mut self) -> Result<Option<Value>, Self::Error> {
        read_inner(&mut self.reader, &mut self.buffer, None)
    }
}

impl<R: BufRead> ModuleReader<R> {
    pub fn new<P: Into<PathBuf>>(reader: R, path: P, trace_macros: bool) -> Self {
        let mut macro_machine = Machine::default();
        macro_machine.trace_macros = trace_macros;
        Self {
            reader: FileReader::new(reader),
            path: path.into(),
            macro_machine,
        }
    }

    pub fn read_expression(
        &mut self,
        options: &CompileOptions,
        env: &Rc<Environment>,
    ) -> Result<Option<Rc<Expression>>, Either<MachineErrorAt, Vec<ParseError>>> {
        loop {
            let value =
                read(&mut self.reader, &mut self.macro_machine, env).map_err(Either::Left)?;
            let Some(value) = value else {
                return Ok(None);
            };
            let expression = Expression::parse(&value).map_err(Either::Right)?;
            if let Expression::Defmacro(_) = expression.as_ref() {
                self.macro_machine
                    .evaluate_value(options.clone(), Some("defmacro".into()), env, value)
                    .map_err(Either::Left)?;
                continue;
            }
            return Ok(Some(expression));
        }
    }

    pub fn compile(
        mut self,
        module_name: Option<IdentifierValue>,
        options: &CompileOptions,
        env: &Rc<Environment>,
    ) -> Result<Rc<BytecodeFunction>, Either<MachineErrorAt, Vec<ParseError>>> {
        let mut cx =
            CompileContext::new(options.clone(), module_name, Some(self.path.clone().into()));
        let mut body = FunctionBody {
            head: vec![],
            tail: Rc::new(Expression::Nil),
        };

        let mut syntax_errors = vec![];

        loop {
            let expression = match self.read_expression(options, env) {
                Ok(Some(expression)) => expression,
                Ok(None) => break,
                Err(Either::Left(error)) => return Err(Either::Left(error)),
                Err(Either::Right(errors)) => {
                    syntax_errors.extend(errors);
                    continue;
                }
            };

            body.head.push(expression);
        }

        if !syntax_errors.is_empty() {
            return Err(Either::Right(syntax_errors));
        }

        let value = body
            .compile(&mut cx)
            .map_err(MachineError::Compile)
            .map_err(MachineErrorAt::at_unknown)
            .map_err(Either::Left)?;
        cx.push(value)
            .map_err(MachineError::Compile)
            .map_err(MachineErrorAt::at_unknown)
            .map_err(Either::Left)?;
        cx.emit(Instruction::Return);

        let function = cx
            .to_bytecode()
            .map_err(MachineError::Compile)
            .map_err(MachineErrorAt::at_unknown)
            .map_err(Either::Left)?;

        Ok(function)
    }
}

fn read_inner<R: BufRead>(
    reader: &mut R,
    buffer: &mut String,
    prompt: Option<(&str, &str)>,
) -> Result<Option<Value>, MachineErrorAt> {
    loop {
        let mut incomplete = None;
        let mut i = buffer.trim_start();

        while !i.is_empty() {
            i = match parse::skip_comment_and_whitespace(i) {
                Ok((i, _)) => i,
                Err(_error) => {
                    buffer.clear();
                    i = buffer.trim_start();
                    continue;
                }
            };
            if i.is_empty() {
                buffer.clear();
                break;
            }
            let result = parse_value(i);
            let (tail, value) = match result {
                Ok(r) => r,
                Err(nom::Err::Incomplete(error)) => {
                    incomplete = Some(error);
                    break;
                }
                Err(error) => {
                    let error = ReadError::Lexical(error.map_input(|i| i.into()));
                    let error = MachineError::Read(error);
                    buffer.clear();
                    return Err(error.at_unknown());
                }
            };

            *buffer = tail.trim_start().into();
            return Ok(Some(value));
        }

        *buffer = buffer.trim_start().into();

        if let Some((prompt_empty, prompt_continuation)) = prompt {
            if buffer.is_empty() {
                print!("{prompt_empty}");
            } else {
                print!("{prompt_continuation}");
            }
            stdout().flush().ok();
        }

        let len = reader
            .read_line(buffer)
            .map_err(ReadError::Io)
            .map_err(MachineError::Read)
            .map_err(MachineErrorAt::at_unknown)?;
        if len == 0 {
            return if let Some(incomplete) = incomplete {
                let error = ReadError::Lexical(nom::Err::Incomplete(incomplete));
                let error = MachineError::Read(error);
                Err(error.at_unknown())
            } else {
                assert!(buffer.is_empty());
                Ok(None)
            };
        }
    }
}

pub fn read<R: Reader>(
    reader: &mut R,
    vm: &mut Machine,
    env: &Rc<Environment>,
) -> Result<Option<Value>, MachineErrorAt> {
    let raw_value = reader.read().map_err(Into::into)?;
    let Some(raw_value) = raw_value else {
        return Ok(None);
    };
    let exp_value = vm.macro_expand(env, &raw_value)?;
    Ok(Some(exp_value))
}
