use super::args::CliArgs;
use super::diagnostic::{DiagLevel, Diagnostic};
use super::*;
use crate::{block, logic, sched, syntax, tych};

pub struct PipeIO {
    pub output: Box<dyn Write>,
    pub stat_log: Box<dyn Write>,
}

impl PipeIO {
    pub fn empty() -> PipeIO {
        PipeIO {
            output: Box::new(io::empty()),
            stat_log: Box::new(io::empty()),
        }
    }

    pub fn stdout() -> PipeIO {
        PipeIO {
            output: Box::new(io::stdout()),
            stat_log: Box::new(io::stdout()),
        }
    }
}

pub struct Pipeline<'arg> {
    pub args: &'arg CliArgs,
    pub diags: Vec<Diagnostic>,
}

impl<'arg> Pipeline<'arg> {
    pub fn new(args: &'arg CliArgs) -> Pipeline<'arg> {
        Pipeline {
            args,
            diags: Vec::new(),
        }
    }

    fn emit_diags<D: Into<Diagnostic>>(&mut self, diags: Vec<D>) -> bool {
        let mut flag = false;
        for diag in diags.into_iter() {
            let diag = diag.into();
            if diag.level == DiagLevel::Error
                || (self.args.warn_as_err && diag.level == DiagLevel::Warn)
            {
                flag = true;
            }
            self.diags.push(diag);
        }
        flag
    }

    pub fn run_pipline(
        &mut self,
        src: &str,
        pipe_io: &mut PipeIO,
    ) -> Result<Vec<usize>, io::Error> {
        let mut prog = self.parse_program(src)?;

        self.rename_pass(&mut prog)?;

        self.check_pass(&mut prog)?;

        let prog = self.compile_pass(&prog);

        let res = self.run_backend(&prog, pipe_io);
        Ok(res)
    }

    pub fn parse_program(&mut self, src: &str) -> Result<syntax::ast::Program, io::Error> {
        let (prog, errs) = syntax::parser::parse_program(src);
        if self.emit_diags(errs) {
            return Err(io::Error::other("failed to parse program!"));
        }
        Ok(prog)
    }

    pub fn rename_pass(&mut self, prog: &mut syntax::ast::Program) -> Result<(), io::Error> {
        let errs = tych::rename::rename_pass(prog);
        if self.emit_diags(errs) {
            return Err(io::Error::other("failed in binding analysis pass!"));
        }
        Ok(())
    }

    pub fn check_pass(&mut self, prog: &mut syntax::ast::Program) -> Result<(), io::Error> {
        let errs = tych::check::check_pass(prog);
        if self.emit_diags(errs) {
            return Err(io::Error::other("failed in type checking pass!"));
        }
        Ok(())
    }

    pub fn compile_pass(&mut self, prog: &syntax::ast::Program) -> block::ast::Program {
        let mut prog = logic::transform::logic_translation(prog);

        logic::elab::elab_pass(&mut prog);

        block::compile::compile_dict(&prog)
    }

    pub fn run_backend(&self, prog: &block::ast::Program, pipe_io: &mut PipeIO) -> Vec<usize> {
        let mut res_vec = Vec::new();
        let mut wlk = sched::walker::Walker::new(&prog.preds, pipe_io, self.args.backend);
        for query_decl in &prog.querys {
            wlk.config_reset_default();
            for param in query_decl.params.iter() {
                wlk.config_set_param(param);
            }
            let res = wlk.run_loop(query_decl.entry);
            res_vec.push(res);
        }
        res_vec
    }
}

pub fn run_pipline(args: &CliArgs) -> Result<Vec<usize>, io::Error> {
    let src = std::fs::read_to_string(&args.input)?;

    let mut pipe_io = PipeIO::empty();

    if args.mute_output {
        pipe_io.output = Box::new(std::io::empty());
    } else if let Some(path) = &args.output {
        pipe_io.output = Box::new(File::create(path)?);
    } else {
        pipe_io.output = Box::new(std::io::stdout());
    }

    if args.mute_stat_log {
        pipe_io.stat_log = Box::new(std::io::empty());
    } else if let Some(path) = &args.stat_log {
        pipe_io.stat_log = Box::new(File::create(path)?);
    } else {
        pipe_io.stat_log = Box::new(std::io::stdout());
    }

    let mut pipe = Pipeline::new(args);
    match pipe.run_pipline(&src, &mut pipe_io) {
        Ok(res) => {
            for diag in pipe.diags.into_iter() {
                eprintln!("{}", diag.report(&src, args.verbosity));
            }
            Ok(res)
        }
        Err(err) => {
            for diag in pipe.diags.into_iter() {
                eprintln!("{}", diag.report(&src, args.verbosity));
            }
            Err(err)
        }
    }
}

pub fn run_cli() -> Result<Vec<usize>, io::Error> {
    let args = args::parse_cli_args();
    let res = pipeline::run_pipline(&args)?;
    Ok(res)
}

pub fn run_cli_test(prog_name: PathBuf) -> Result<Vec<usize>, io::Error> {
    let args = args::get_test_cli_args(prog_name);
    let res = pipeline::run_pipline(&args)?;
    Ok(res)
}
