// Copyright (c) 2018 The predicates-rs Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/license/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Definition of `Predicate`s for comparisons over `Ord` and `Eq` types.

use std::fmt;

use crate::reflection;
use crate::utils;
use crate::Predicate;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum EqOps {
    Equal,
    NotEqual,
}

impl fmt::Display for EqOps {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let op = match *self {
            EqOps::Equal => "==",
            EqOps::NotEqual => "!=",
        };
        write!(f, "{}", op)
    }
}

/// Predicate that returns `true` if `variable` matches the pre-defined `Eq`
/// value, otherwise returns `false`.
///
/// This is created by the `predicate::{eq, ne}` functions.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EqPredicate<T>
where
    T: fmt::Debug + PartialEq,
{
    constant: T,
    op: EqOps,
}

impl<T> Predicate<T> for EqPredicate<T>
where
    T: fmt::Debug + PartialEq,
{
    fn eval(&self, variable: &T) -> bool {
        match self.op {
            EqOps::Equal => variable.eq(&self.constant),
            EqOps::NotEqual => variable.ne(&self.constant),
        }
    }

    fn find_case<'a>(&'a self, expected: bool, variable: &T) -> Option<reflection::Case<'a>> {
        utils::default_find_case(self, expected, variable).map(|case| {
            case.add_product(reflection::Product::new(
                "var",
                utils::DebugAdapter::new(variable).to_string(),
            ))
        })
    }
}

impl<'a, T> Predicate<T> for EqPredicate<&'a T>
where
    T: fmt::Debug + PartialEq + ?Sized,
{
    fn eval(&self, variable: &T) -> bool {
        match self.op {
            EqOps::Equal => variable.eq(self.constant),
            EqOps::NotEqual => variable.ne(self.constant),
        }
    }

    fn find_case<'b>(&'b self, expected: bool, variable: &T) -> Option<reflection::Case<'b>> {
        utils::default_find_case(self, expected, variable).map(|case| {
            case.add_product(reflection::Product::new(
                "var",
                utils::DebugAdapter::new(variable).to_string(),
            ))
        })
    }
}

impl<T> reflection::PredicateReflection for EqPredicate<T> where T: fmt::Debug + PartialEq {}

impl<T> fmt::Display for EqPredicate<T>
where
    T: fmt::Debug + PartialEq,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let palette = crate::Palette::current();
        write!(
            f,
            "{} {} {}",
            palette.var.paint("var"),
            palette.description.paint(self.op),
            palette
                .expected
                .paint(utils::DebugAdapter::new(&self.constant)),
        )
    }
}

/// Creates a new predicate that will return `true` when the given `variable` is
/// equal to a pre-defined value.
///
/// # Examples
///
/// ```
/// use predicates::prelude::*;
///
/// let predicate_fn = predicate::eq(5);
/// assert_eq!(true, predicate_fn.eval(&5));
/// assert_eq!(false, predicate_fn.eval(&10));
///
/// let predicate_fn = predicate::eq("Hello");
/// assert_eq!(true, predicate_fn.eval("Hello"));
/// assert_eq!(false, predicate_fn.eval("Goodbye"));
/// ```
pub fn eq<T>(constant: T) -> EqPredicate<T>
where
    T: fmt::Debug + PartialEq,
{
    EqPredicate {
        constant,
        op: EqOps::Equal,
    }
}

/// Creates a new predicate that will return `true` when the given `variable` is
/// _not_ equal to a pre-defined value.
///
/// # Examples
///
/// ```
/// use predicates::prelude::*;
///
/// let predicate_fn = predicate::ne(5);
/// assert_eq!(false, predicate_fn.eval(&5));
/// assert_eq!(true, predicate_fn.eval(&10));
/// ```
pub fn ne<T>(constant: T) -> EqPredicate<T>
where
    T: PartialEq + fmt::Debug,
{
    EqPredicate {
        constant,
        op: EqOps::NotEqual,
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum OrdOps {
    LessThan,
    LessThanOrEqual,
    GreaterThanOrEqual,
    GreaterThan,
}

impl fmt::Display for OrdOps {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let op = match *self {
            OrdOps::LessThan => "<",
            OrdOps::LessThanOrEqual => "<=",
            OrdOps::GreaterThanOrEqual => ">=",
            OrdOps::GreaterThan => ">",
        };
        write!(f, "{}", op)
    }
}

/// Predicate that returns `true` if `variable` matches the pre-defined `Ord`
/// value, otherwise returns `false`.
///
/// This is created by the `predicate::{gt, ge, lt, le}` functions.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OrdPredicate<T>
where
    T: fmt::Debug + PartialOrd,
{
    constant: T,
    op: OrdOps,
}

impl<T> Predicate<T> for OrdPredicate<T>
where
    T: fmt::Debug + PartialOrd,
{
    fn eval(&self, variable: &T) -> bool {
        match self.op {
            OrdOps::LessThan => variable.lt(&self.constant),
            OrdOps::LessThanOrEqual => variable.le(&self.constant),
            OrdOps::GreaterThanOrEqual => variable.ge(&self.constant),
            OrdOps::GreaterThan => variable.gt(&self.constant),
        }
    }

    fn find_case<'a>(&'a self, expected: bool, variable: &T) -> Option<reflection::Case<'a>> {
        utils::default_find_case(self, expected, variable).map(|case| {
            case.add_product(reflection::Product::new(
                "var",
                utils::DebugAdapter::new(variable).to_string(),
            ))
        })
    }
}

impl<'a, T> Predicate<T> for OrdPredicate<&'a T>
where
    T: fmt::Debug + PartialOrd + ?Sized,
{
    fn eval(&self, variable: &T) -> bool {
        match self.op {
            OrdOps::LessThan => variable.lt(self.constant),
            OrdOps::LessThanOrEqual => variable.le(self.constant),
            OrdOps::GreaterThanOrEqual => variable.ge(self.constant),
            OrdOps::GreaterThan => variable.gt(self.constant),
        }
    }

    fn find_case<'b>(&'b self, expected: bool, variable: &T) -> Option<reflection::Case<'b>> {
        utils::default_find_case(self, expected, variable).map(|case| {
            case.add_product(reflection::Product::new(
                "var",
                utils::DebugAdapter::new(variable).to_string(),
            ))
        })
    }
}

impl<T> reflection::PredicateReflection for OrdPredicate<T> where T: fmt::Debug + PartialOrd {}

impl<T> fmt::Display for OrdPredicate<T>
where
    T: fmt::Debug + PartialOrd,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let palette = crate::Palette::current();
        write!(
            f,
            "{} {} {}",
            palette.var.paint("var"),
            palette.description.paint(self.op),
            palette
                .expected
                .paint(utils::DebugAdapter::new(&self.constant)),
        )
    }
}

/// Creates a new predicate that will return `true` when the given `variable` is
/// less than a pre-defined value.
///
/// # Examples
///
/// ```
/// use predicates::prelude::*;
///
/// let predicate_fn = predicate::lt(5);
/// assert_eq!(true, predicate_fn.eval(&4));
/// assert_eq!(false, predicate_fn.eval(&6));
///
/// let predicate_fn = predicate::lt("b");
/// assert_eq!(true, predicate_fn.eval("a"));
/// assert_eq!(false, predicate_fn.eval("c"));
/// ```
pub fn lt<T>(constant: T) -> OrdPredicate<T>
where
    T: fmt::Debug + PartialOrd,
{
    OrdPredicate {
        constant,
        op: OrdOps::LessThan,
    }
}

/// Creates a new predicate that will return `true` when the given `variable` is
/// less than or equal to a pre-defined value.
///
/// # Examples
///
/// ```
/// use predicates::prelude::*;
///
/// let predicate_fn = predicate::le(5);
/// assert_eq!(true, predicate_fn.eval(&4));
/// assert_eq!(true, predicate_fn.eval(&5));
/// assert_eq!(false, predicate_fn.eval(&6));
/// ```
pub fn le<T>(constant: T) -> OrdPredicate<T>
where
    T: PartialOrd + fmt::Debug,
{
    OrdPredicate {
        constant,
        op: OrdOps::LessThanOrEqual,
    }
}

/// Creates a new predicate that will return `true` when the given `variable` is
/// greater than or equal to a pre-defined value.
///
/// # Examples
///
/// ```
/// use predicates::prelude::*;
///
/// let predicate = predicate::ge(5);
/// assert_eq!(false, predicate.eval(&4));
/// assert_eq!(true, predicate.eval(&5));
/// assert_eq!(true, predicate.eval(&6));
/// ```
pub fn ge<T>(constant: T) -> OrdPredicate<T>
where
    T: PartialOrd + fmt::Debug,
{
    OrdPredicate {
        constant,
        op: OrdOps::GreaterThanOrEqual,
    }
}

/// Creates a new predicate that will return `true` when the given `variable` is
/// greater than a pre-defined value.
///
/// # Examples
///
/// ```
/// use predicates::prelude::*;
///
/// let predicate_fn = predicate::gt(5);
/// assert_eq!(false, predicate_fn.eval(&4));
/// assert_eq!(false, predicate_fn.eval(&5));
/// assert_eq!(true, predicate_fn.eval(&6));
/// ```
pub fn gt<T>(constant: T) -> OrdPredicate<T>
where
    T: PartialOrd + fmt::Debug,
{
    OrdPredicate {
        constant,
        op: OrdOps::GreaterThan,
    }
}
