use std::rc::Rc;

use crate::environment::Environment;

use super::{if_else::Bool, Error, Node, NodeEnum, Precedence};

#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct Greater {
    left: Rc<NodeEnum>,
    right: Vec<Rc<NodeEnum>>,
}

#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct Less {
    left: Rc<NodeEnum>,
    right: Vec<Rc<NodeEnum>>,
}

#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct GreaterEquals {
    left: Rc<NodeEnum>,
    right: Vec<Rc<NodeEnum>>,
}

#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct LessEquals {
    left: Rc<NodeEnum>,
    right: Vec<Rc<NodeEnum>>,
}

impl Node for Greater {
    fn evaluate(&self, env: &mut Environment) -> Result<Rc<NodeEnum>, Error> {
        let mut expr = self.left.evaluate(env)?;
        for e in &self.right {
            let e = e.evaluate(env)?;
            let evaluation = expr.partial_cmp(&e);
            match evaluation {
                Some(cmp) if cmp != std::cmp::Ordering::Greater => {
                    return Ok(Rc::new(Bool::False.into()));
                }
                None => {
                    return self.abort(env);
                }
                _ => {}
            }
            expr = e;
        }
        return Ok(Rc::new(Bool::True.into()));
    }

    fn as_string(&self, env: Option<&Environment>) -> String {
        format!(
            "{} > {}",
            self.left.as_string(env),
            self.right
                .iter()
                .map(|x| x.as_string(env))
                .reduce(|a, b| a + " > " + &b)
                .unwrap()
        )
    }

    fn precedence(&self) -> Precedence {
        Precedence::Compare
    }
}

impl Node for Less {
    fn evaluate(&self, env: &mut Environment) -> Result<Rc<NodeEnum>, Error> {
        let mut expr = self.left.evaluate(env)?;
        for e in &self.right {
            let e = e.evaluate(env)?;
            let evaluation = expr.partial_cmp(&e);
            match evaluation {
                Some(cmp) if cmp != std::cmp::Ordering::Less => {
                    return Ok(Rc::new(Bool::False.into()));
                }
                None => {
                    return self.abort(env);
                }
                _ => {}
            }
            expr = e;
        }
        return Ok(Rc::new(Bool::True.into()));
    }

    fn as_string(&self, env: Option<&Environment>) -> String {
        format!(
            "{} < {}",
            self.left.as_string(env),
            self.right
                .iter()
                .map(|x| x.as_string(env))
                .reduce(|a, b| a + " < " + &b)
                .unwrap()
        )
    }

    fn precedence(&self) -> Precedence {
        Precedence::Compare
    }
}

impl Node for GreaterEquals {
    fn evaluate(&self, env: &mut Environment) -> Result<Rc<NodeEnum>, Error> {
        let mut expr = self.left.evaluate(env)?;
        for e in &self.right {
            let e = e.evaluate(env)?;
            let evaluation = expr.partial_cmp(&e);
            match evaluation {
                Some(cmp)
                    if cmp != std::cmp::Ordering::Equal && cmp != std::cmp::Ordering::Greater =>
                {
                    return Ok(Rc::new(Bool::False.into()));
                }
                None => {
                    return self.abort(env);
                }
                _ => {}
            }
            expr = e;
        }
        return Ok(Rc::new(Bool::True.into()));
    }

    fn as_string(&self, env: Option<&Environment>) -> String {
        format!(
            "{} >= {}",
            self.left.as_string(env),
            self.right
                .iter()
                .map(|x| x.as_string(env))
                .reduce(|a, b| a + " >= " + &b)
                .unwrap()
        )
    }

    fn precedence(&self) -> Precedence {
        Precedence::Compare
    }
}

impl Node for LessEquals {
    fn evaluate(&self, env: &mut Environment) -> Result<Rc<NodeEnum>, Error> {
        let mut expr = self.left.evaluate(env)?;
        for e in &self.right {
            let e = e.evaluate(env)?;
            let evaluation = expr.partial_cmp(&e);
            match evaluation {
                Some(cmp)
                    if cmp != std::cmp::Ordering::Equal && cmp != std::cmp::Ordering::Less =>
                {
                    return Ok(Rc::new(Bool::False.into()));
                }
                None => {
                    return self.abort(env);
                }
                _ => {}
            }
            expr = e;
        }
        return Ok(Rc::new(Bool::True.into()));
    }

    fn as_string(&self, env: Option<&Environment>) -> String {
        format!(
            "{} <= {}",
            self.left.as_string(env),
            self.right
                .iter()
                .map(|x| x.as_string(env))
                .reduce(|a, b| a + " <= " + &b)
                .unwrap()
        )
    }

    fn precedence(&self) -> Precedence {
        Precedence::Compare
    }
}

impl Greater {
    pub fn new(left: Rc<NodeEnum>, right: Vec<Rc<NodeEnum>>) -> Rc<NodeEnum> {
        Rc::new(Self { left, right }.into())
    }

    fn abort(&self, env: &mut Environment) -> Result<Rc<NodeEnum>, Error> {
        let mut arguments = vec![];
        for value in self.right.iter() {
            arguments.push(value.evaluate(env)?);
        }
        Ok(Self::new(self.left.evaluate(env)?, arguments))
    }
}

impl Less {
    pub fn new(left: Rc<NodeEnum>, right: Vec<Rc<NodeEnum>>) -> Rc<NodeEnum> {
        Rc::new(Self { left, right }.into())
    }

    fn abort(&self, env: &mut Environment) -> Result<Rc<NodeEnum>, Error> {
        let mut arguments = vec![];
        for value in self.right.iter() {
            arguments.push(value.evaluate(env)?);
        }
        Ok(Self::new(self.left.evaluate(env)?, arguments))
    }
}

impl LessEquals {
    pub fn new(left: Rc<NodeEnum>, right: Vec<Rc<NodeEnum>>) -> Rc<NodeEnum> {
        Rc::new(Self { left, right }.into())
    }

    fn abort(&self, env: &mut Environment) -> Result<Rc<NodeEnum>, Error> {
        let mut arguments = vec![];
        for value in self.right.iter() {
            arguments.push(value.evaluate(env)?);
        }
        Ok(Self::new(self.left.evaluate(env)?, arguments))
    }
}

impl GreaterEquals {
    pub fn new(left: Rc<NodeEnum>, right: Vec<Rc<NodeEnum>>) -> Rc<NodeEnum> {
        Rc::new(Self { left, right }.into())
    }

    fn abort(&self, env: &mut Environment) -> Result<Rc<NodeEnum>, Error> {
        let mut arguments = vec![];
        for value in self.right.iter() {
            arguments.push(value.evaluate(env)?);
        }
        Ok(Self::new(self.left.evaluate(env)?, arguments))
    }
}
