use std::{io::BufReader, marker::PhantomData, process::ExitCode};

use crate::{
    builtin::{self, Envs},
    error::Error,
    exec::{exec_pipeline, wait_for_pipeline, Execution, InheritStdout, Input, Outcome, Output},
    syntax::parse::{BinaryOperator, ConditionalExpression, Expression},
};

use super::{env::Environment, ExpandedPipeline, ExpandedPipelineElement};

pub fn evaluate_pipeline(
    pipeline: &ExpandedPipeline,
    env: &mut Environment,
) -> Result<(Outcome, Option<ExitCode>), Error> {
    let mut executions = vec![];
    let mut stdins = vec![];
    let mut stdouts = vec![];

    let pipeline_stdin = Input::Inherit;
    let pipeline_stdout = Output::<InheritStdout>::Inherit(PhantomData);

    stdins.push(pipeline_stdin);
    for _ in 1..pipeline.elements.len() {
        let (read, write) = std::pipe::pipe().unwrap();
        stdins.push(Input::Pipe(BufReader::new(read)));
        stdouts.push(Output::Pipe(write));
    }
    stdouts.push(pipeline_stdout);

    assert_eq!(stdins.len(), stdouts.len());
    assert_eq!(pipeline.elements.len(), stdouts.len());

    let io = Iterator::zip(stdins.drain(..), stdouts.drain(..));

    for (command, (stdin, stdout)) in pipeline.elements.iter().zip(io) {
        let (program, arguments) = command.words.split_first().unwrap();
        // let stderr = stderr.try_clone()?;
        let envs = command
            .envs
            .iter()
            .map(|(a, b)| (a.clone(), b.clone()))
            .collect();
        let execution = Execution {
            program: program.to_owned(),
            arguments: arguments.to_vec(),
            envs,
            stdin,
            stdout,
            stderr: Output::Inherit(PhantomData),
        };
        executions.push(execution);
    }

    let handles = exec_pipeline(executions.into_iter(), env)?;
    let (status, exit) = wait_for_pipeline(handles)?;

    Ok((status, exit))
}

fn evaluate_conditional_body(
    mut body: &Expression,
    env: &mut Environment,
) -> (Outcome, Option<ExitCode>) {
    while let Expression::Not(inner) = body {
        body = inner;
    }
    let status = match body {
        Expression::Binary(BinaryOperator::Equal, lhs, rhs)
            if let (Some(lhs), Some(rhs)) = (lhs.as_word(), rhs.as_word()) =>
        {
            let lhs = env.expand(lhs);
            let rhs = env.expand(rhs);
            Some(lhs == rhs)
        }
        Expression::Binary(BinaryOperator::NotEqual, lhs, rhs)
            if let (Some(lhs), Some(rhs)) = (lhs.as_word(), rhs.as_word()) =>
        {
            let lhs = env.expand(lhs);
            let rhs = env.expand(rhs);
            Some(lhs != rhs)
        }
        Expression::Pipeline(pipeline)
            if let Some(element) = pipeline
                .as_single_command()
                .map(|element| ExpandedPipelineElement::from_syntax(env, element)) =>
        {
            let io = builtin::Args {
                stdin: Input::Inherit,
                stdout: Output::Inherit(PhantomData),
                stderr: Output::Inherit(PhantomData),
            };
            return (builtin::b_test(io, element.words, Envs::from(vec![])), None);
        }
        _ => None,
    };

    match status {
        Some(true) => (Outcome::ok(), None),
        Some(false) => (Outcome::err(), None),
        None => {
            eprintln!("test: invalid expression: {:?}", body);
            (Outcome::err(), None)
        }
    }
}

pub fn evaluate_conditional(
    conditional: &ConditionalExpression,
    env: &mut Environment,
) -> (Outcome, Option<ExitCode>) {
    evaluate_conditional_body(&conditional.body, env)
}

pub fn evaluate(expression: &Expression, env: &mut Environment) -> (Outcome, Option<ExitCode>) {
    match expression {
        Expression::Pipeline(pipeline) => {
            let mut expanded = ExpandedPipeline::from_syntax(env, pipeline);
            let mut setenvs = vec![];
            expanded.elements.retain_mut(|e| {
                if e.words.is_empty() {
                    setenvs.append(&mut e.envs);
                }
                !e.words.is_empty()
            });

            for (key, value) in setenvs {
                env.insert_current_literal(key, value);
            }

            if expanded.elements.is_empty() {
                return (Outcome::ok(), None);
            }

            match evaluate_pipeline(&expanded, env) {
                Ok(res) => res,
                Err(error) => {
                    eprintln!("{error}");
                    (Outcome::err(), None)
                }
            }
        }
        Expression::Conditional(cond) => evaluate_conditional(cond, env),
        // TODO redirects
        Expression::If(if_expression) => {
            let (condition, exit) = evaluate(&if_expression.condition, env);
            if exit.is_some() {
                return (condition, exit);
            }
            if condition.is_success() {
                // Execute true branch
                evaluate_list(&if_expression.if_true, env)
            } else if let Some(if_false) = if_expression.if_false.as_ref() {
                evaluate_list(if_false, env)
            } else {
                (Outcome::ok(), None)
            }
        }
        // TODO redirects
        Expression::While(while_expression) => loop {
            let (condition, exit) = evaluate(&while_expression.condtion, env);
            if exit.is_some() || !condition.is_success() {
                return (Outcome::ok(), exit);
            }
            let (_, exit) = evaluate_list(&while_expression.body, env);
            if exit.is_some() {
                return (Outcome::ok(), exit);
            }
        },
        // TODO redirects
        Expression::For(for_expression) => {
            let list = ExpandedPipelineElement::from_syntax(env, &for_expression.list);
            env.push_environment();
            for word in list.words {
                env.insert_current_literal(for_expression.variable, word);
                let (_, exit) = evaluate_list(&for_expression.body, env);
                if exit.is_some() {
                    env.pop_environment();
                    return (Outcome::ok(), exit);
                }
            }
            env.pop_environment();
            (Outcome::ok(), None)
        }
        Expression::Binary(BinaryOperator::And, lhs, rhs) => {
            let (lhs, exit) = evaluate(lhs, env);
            if exit.is_some() {
                return (lhs, exit);
            }
            if lhs.is_success() {
                evaluate(rhs, env)
            } else {
                (lhs, exit)
            }
        }
        Expression::Binary(BinaryOperator::Or, lhs, rhs) => {
            let (lhs, exit) = evaluate(lhs, env);
            if exit.is_some() {
                return (lhs, exit);
            }
            if !lhs.is_success() {
                evaluate(rhs, env)
            } else {
                (lhs, exit)
            }
        }
        _ => todo!(),
    }
}

pub fn evaluate_list<'a, I: IntoIterator<Item = &'a Expression<'a>>>(
    expressions: I,
    env: &mut Environment,
) -> (Outcome, Option<ExitCode>) {
    let mut status = Outcome::ok();
    for expression in expressions {
        let (outcome, exit) = evaluate(expression, env);
        if exit.is_some() {
            return (outcome, exit);
        }
        status = outcome;
    }
    (status, None)
}
