//! The conditions module holds the structures for composing a low level rule definition

/// A ConditionField describes when a Fact element matches
///
/// The simplest match is a constant match. Constants are checked in the alphamemories, and
/// must match exactly against the field. They are used as the most basic rules, and will be
/// checked before any further processing occurs.
///
/// Variable fields are more complex. They are used to compare between different facts. A Variable
/// on a single fact does nothing. Only when it is compared against another variable does it
/// affect the matching. Variables are used to construct the more complex rules.
///
#[derive(Eq, PartialEq, Debug, Clone)]
pub enum ConditionField {
    /// A Const must match exactly.
    Const(String),
    /// A Variable is named, and matches anything. It will be compared against an other Variable.
    Variable(String),
    /// This field is irrelevant for the matching rules.
    Any,
}

/// The ConditionBody defines what fields in a fact are relevant to the matching, and in which way.
///
/// When a fact enters the network, we will consider each field of the fact. The fields that need
/// to match a Const expression are evaluated first. Then the fields that need to match other
/// fields, in the same or in an other fact, are compared against those fields.
///
/// This struct defines how a single fact is of interest to the network.
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct ConditionBody {
    pub class: String,
    pub f1: ConditionField,
    pub f2: ConditionField,
    pub f3: ConditionField,
}

/// The NccCondition checks a child-rule, and inverts the results of that match.
///
/// The NccCondition is used to define a chain of conditions to match, and inverts the outcome of
/// that match. This way, we can recursively define rules inside a rule.
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct NccCondition {
    pub children: Vec<Condition>
}

/// A Condition defines a single rule in a chain of rules that need to be fullfilled to find a match.
///
/// A Condition can be:
///   - Some fact must occur
///   - Some fact must not occur
///   - A certain combination of Conditions must not occur
///
/// The Ncc condition is a negated recursive rule. It defines a combination of facts that are
/// considered a match, and then negates the result.
#[derive(Eq, PartialEq, Debug, Clone)]
pub enum Condition {
    /// A certain fact must occur, which can be compared against consts and previous matches.
    Positive(ConditionBody),
    /// A certain fact must not occur, which can be compared against consts and previous matches.
    Negative(ConditionBody),
    /// A certain combination of facts must not occur.
    Ncc(NccCondition),
}

/// The ConditionBuilder is a helper to create Conditions.
///
/// # Examples
/// We create a rule for the presence of a thing that is green and not small, and the abscence
/// of an other thing, which is blue and large. Both condition must apply before this rule matches.
/// ```
/// # use phreak_engine::condition::*;
///
/// let mut conditions: Vec<Condition> = Vec::new();
/// let mut subconditions: Vec<Condition> = Vec::new();
///
/// let condition =
///     ConditionBuilder::new(String::from("car"))
///         // default is positive()
///         .variable_field1(String::from("a"))
///         .const_field2(String::from("is"))
///         .const_field3(String::from("green"))
///         .build();
/// conditions.push(condition);
///
/// let condition =
///     ConditionBuilder::new(String::from("car"))
///         .negative(String::from("car"))
///         .variable_field1(String::from("a"))
///         .const_field2(String::from("is"))
///         .const_field3(String::from("small"))
///         .build();
/// conditions.push(condition);
///
/// let subcondition =
///     ConditionBuilder::new(String::from("car"))
///         .variable_field1(String::from("b"))
///         .const_field2(String::from("is"))
///         .const_field3(String::from("blue"))
///         .build();
/// subconditions.push(subcondition);
///
/// let subcondition =
///     ConditionBuilder::new(String::from("car"))
///         .variable_field1(String::from("b"))
///         .const_field2(String::from("is"))
///         .const_field3(String::from("large"))
///         .build();
/// subconditions.push(subcondition);
///
/// let condition =
///     ConditionBuilder::new(String::from("car"))
///         .ncc(subconditions)
///         .build();
/// conditions.push(condition);
/// ```
pub struct ConditionBuilder {
    data: Condition,
}

impl ConditionBuilder {
    #[inline]
    pub fn new(class: String) -> ConditionBuilder {
        ConditionBuilder {
            data: Condition::Positive(
                ConditionBody {
                    class,
                    f1: ConditionField::Any,
                    f2: ConditionField::Any,
                    f3: ConditionField::Any,
                }
            ),
        }
    }

    #[inline]
    pub fn positive(mut self, class: String) -> ConditionBuilder {
        self.data = Condition::Positive(
            ConditionBody {
                class,
                f1: ConditionField::Any,
                f2: ConditionField::Any,
                f3: ConditionField::Any,
            }
        );
        self
    }

    #[inline]
    pub fn negative(mut self, class: String) -> ConditionBuilder {
        self.data = Condition::Negative(
            ConditionBody {
                class,
                f1: ConditionField::Any,
                f2: ConditionField::Any,
                f3: ConditionField::Any,
            }
        );
        self
    }

    #[inline]
    pub fn ncc(mut self, tests: Vec<Condition>) -> ConditionBuilder {
        self.data = Condition::Ncc(
            NccCondition {
                children: tests
            }
        );
        self
    }

    #[inline]
    pub fn field1(mut self, a_field: ConditionField) -> ConditionBuilder {
        match &mut self.data {
            Condition::Positive(cond) |
            Condition::Negative(cond) => {
                cond.f1 = a_field;
            }
            Condition::Ncc(_) => { unimplemented!("impossible") }
        }

        self
    }

    #[inline]
    pub fn field2(mut self, a_field: ConditionField) -> ConditionBuilder {
        match &mut self.data {
            Condition::Positive(cond) |
            Condition::Negative(cond) => {
                cond.f2 = a_field;
            }
            Condition::Ncc(_) => { unimplemented!("impossible") }
        }
        self
    }

    #[inline]
    pub fn field3(mut self, a_field: ConditionField) -> ConditionBuilder {
        match &mut self.data {
            Condition::Positive(cond) |
            Condition::Negative(cond) => {
                cond.f3 = a_field;
            }
            Condition::Ncc(_) => { unimplemented!("impossible") }
        }

        self
    }

    #[inline]
    pub fn const_field1(mut self, a_field: String) -> ConditionBuilder {
        match &mut self.data {
            Condition::Positive(cond) |
            Condition::Negative(cond) => {
                cond.f1 = ConditionField::Const(a_field);
            }
            Condition::Ncc(_) => { unimplemented!("impossible") }
        }

        self
    }

    #[inline]
    pub fn const_field2(mut self, a_field: String) -> ConditionBuilder {
        match &mut self.data {
            Condition::Positive(cond) |
            Condition::Negative(cond) => {
                cond.f2 = ConditionField::Const(a_field);
            }
            Condition::Ncc(_) => { unimplemented!("impossible") }
        }

        self
    }

    #[inline]
    pub fn const_field3(mut self, a_field: String) -> ConditionBuilder {
        match &mut self.data {
            Condition::Positive(cond) |
            Condition::Negative(cond) => {
                cond.f3 = ConditionField::Const(a_field);
            }
            Condition::Ncc(_) => { unimplemented!("impossible") }
        }

        self
    }

    #[inline]
    pub fn variable_field1(mut self, a_field: String) -> ConditionBuilder {
        match &mut self.data {
            Condition::Positive(cond) |
            Condition::Negative(cond) => {
                cond.f1 = ConditionField::Variable(a_field);
            }
            Condition::Ncc(_) => { unimplemented!("impossible") }
        }

        self
    }

    #[inline]
    pub fn variable_field2(mut self, a_field: String) -> ConditionBuilder {
        match &mut self.data {
            Condition::Positive(cond) |
            Condition::Negative(cond) => {
                cond.f2 = ConditionField::Variable(a_field);
            }
            Condition::Ncc(_) => { unimplemented!("impossible") }
        }

        self
    }

    #[inline]
    pub fn variable_field3(mut self, a_field: String) -> ConditionBuilder {
        match &mut self.data {
            Condition::Positive(cond) |
            Condition::Negative(cond) => {
                cond.f3 = ConditionField::Variable(a_field);
            }
            Condition::Ncc(_) => { unimplemented!("impossible") }
        }

        self
    }

    #[inline]
    pub fn build(self) -> Condition {
        self.data
    }
}
