use logical_expressions::{LogicalExpression, ParseError};

mod condition;
use condition::{Condition, InvalidCharacter};

fn test(expression: &str, expected: LogicalExpression<Condition>) {
    let result = LogicalExpression::parse(expression);
    assert_eq!(result, Ok(expected));
}

fn test_err(expression: &str, expected: ParseError<InvalidCharacter>) {
    let result: Result<LogicalExpression<Condition>, ParseError<InvalidCharacter>> =
        LogicalExpression::parse(expression);
    assert_eq!(result, Err(expected));
}

#[test]
fn test_condition() {
    test("a", LogicalExpression::Condition(Condition::new("a")));
    test(" a ", LogicalExpression::Condition(Condition::new("a")));
}

#[test]
fn test_simple() {
    test(
        "a & b",
        LogicalExpression::And(vec![
            LogicalExpression::Condition(Condition::new("a")),
            LogicalExpression::Condition(Condition::new("b")),
        ]),
    );

    test(
        "a | b",
        LogicalExpression::Or(vec![
            LogicalExpression::Condition(Condition::new("a")),
            LogicalExpression::Condition(Condition::new("b")),
        ]),
    );
}

#[test]
fn test_empty_condition() {
    test_err("", ParseError::EmptyCondition);
    test_err("   ", ParseError::EmptyCondition);
}

#[test]
fn test_invalid_characters() {
    test_err("a*", ParseError::ConditionParsing(InvalidCharacter('*')));
    test_err("*a", ParseError::ConditionParsing(InvalidCharacter('*')));
    test_err("*a-", ParseError::ConditionParsing(InvalidCharacter('*')));
    test_err("-a*", ParseError::ConditionParsing(InvalidCharacter('-')));
}

#[test]
fn test_one_sided_operators() {
    test_err("a&", ParseError::TrailingOperator);
    test_err("&a", ParseError::LeadingOperator);
    test_err("a|", ParseError::TrailingOperator);
    test_err("|a", ParseError::LeadingOperator);

    test_err("(a&)", ParseError::TrailingOperator);
    test_err("(&a)", ParseError::LeadingOperator);
    test_err("(a|)", ParseError::TrailingOperator);
    test_err("(|a)", ParseError::LeadingOperator);

    test_err("&a&b", ParseError::LeadingOperator);
    test_err("|a|b", ParseError::LeadingOperator);
    test_err("a&b&", ParseError::TrailingOperator);
    test_err("a|b|", ParseError::TrailingOperator);
}

#[test]
fn test_and_or_combinations() {
    test(
        "a & b | c",
        LogicalExpression::Or(vec![
            LogicalExpression::And(vec![
                LogicalExpression::Condition(Condition::new("a")),
                LogicalExpression::Condition(Condition::new("b")),
            ]),
            LogicalExpression::Condition(Condition::new("c")),
        ]),
    );

    test(
        "a | b & c",
        LogicalExpression::Or(vec![
            LogicalExpression::Condition(Condition::new("a")),
            LogicalExpression::And(vec![
                LogicalExpression::Condition(Condition::new("b")),
                LogicalExpression::Condition(Condition::new("c")),
            ]),
        ]),
    );
}

#[test]
fn test_missing_operators_around_brackets() {
    test_err("a (b)", ParseError::ConditionBeforeOpeningBracket);
    test_err("(a) b", ParseError::ConditionAfterClosingBracket);
    test_err("a & b (c)", ParseError::ConditionBeforeOpeningBracket);
    test_err("(a) b | c", ParseError::ConditionAfterClosingBracket);
    test_err("(a (b & c))", ParseError::ConditionBeforeOpeningBracket);
    test_err("((a & b) c)", ParseError::ConditionAfterClosingBracket);
    test_err("a (b) c", ParseError::ConditionBeforeOpeningBracket);
}

#[test]
fn test_parentheses() {
    test("(a)", LogicalExpression::Condition(Condition::new("a")));

    test(
        "(a & b)",
        LogicalExpression::And(vec![
            LogicalExpression::Condition(Condition::new("a")),
            LogicalExpression::Condition(Condition::new("b")),
        ]),
    );

    test_err("(", ParseError::NoMatchingClosingBracket);
    test_err(")", ParseError::NoMatchingOpeningBracket);

    test_err("(a", ParseError::NoMatchingClosingBracket);
    test_err("a)", ParseError::NoMatchingOpeningBracket);
    test_err(")a(", ParseError::NoMatchingOpeningBracket);

    test_err("()a", ParseError::EmptyCondition);
}

#[test]
fn test_nested_parentheses() {
    test(
        "((a) & (b))",
        LogicalExpression::And(vec![
            LogicalExpression::Condition(Condition::new("a")),
            LogicalExpression::Condition(Condition::new("b")),
        ]),
    );

    test(
        "(a & (b | c))",
        LogicalExpression::And(vec![
            LogicalExpression::Condition(Condition::new("a")),
            LogicalExpression::Or(vec![
                LogicalExpression::Condition(Condition::new("b")),
                LogicalExpression::Condition(Condition::new("c")),
            ]),
        ]),
    );
}

#[test]
fn test_multiple_operators() {
    test_err("a && b", ParseError::MultipleOperators);
    test_err("a || b", ParseError::MultipleOperators);
    test_err("a &&& b", ParseError::MultipleOperators);
    test_err("a ||| b", ParseError::MultipleOperators);

    test_err("a &| b", ParseError::MultipleOperators);
    test_err("a |& b", ParseError::MultipleOperators);
    test_err("a &|& b", ParseError::MultipleOperators);
    test_err("a |&| b", ParseError::MultipleOperators);

    test_err("(a && b)", ParseError::MultipleOperators);
    test_err("(a || b)", ParseError::MultipleOperators);
    test_err("a && (b)", ParseError::MultipleOperators);
    test_err("a || (b)", ParseError::MultipleOperators);
    test_err("(a) && b", ParseError::MultipleOperators);
    test_err("(a) || b", ParseError::MultipleOperators);
}

#[test]
fn test_whitespace() {
    test(
        "a&b",
        LogicalExpression::And(vec![
            LogicalExpression::Condition(Condition::new("a")),
            LogicalExpression::Condition(Condition::new("b")),
        ]),
    );

    test(
        "a  &  b",
        LogicalExpression::And(vec![
            LogicalExpression::Condition(Condition::new("a")),
            LogicalExpression::Condition(Condition::new("b")),
        ]),
    );

    test(
        "a & b | c",
        LogicalExpression::Or(vec![
            LogicalExpression::And(vec![
                LogicalExpression::Condition(Condition::new("a")),
                LogicalExpression::Condition(Condition::new("b")),
            ]),
            LogicalExpression::Condition(Condition::new("c")),
        ]),
    );

    test(
        "a  &  b  |  c",
        LogicalExpression::Or(vec![
            LogicalExpression::And(vec![
                LogicalExpression::Condition(Condition::new("a")),
                LogicalExpression::Condition(Condition::new("b")),
            ]),
            LogicalExpression::Condition(Condition::new("c")),
        ]),
    );

    test_err("a  &  &  b", ParseError::MultipleOperators);
    test_err("a  |  |  b", ParseError::MultipleOperators);
    test_err("a  &  |  b", ParseError::MultipleOperators);
    test_err("a  |  &  b", ParseError::MultipleOperators);
}
