"""
Comparison utilities for STIX pattern observation expressions.
"""
from stix2.equivalence.patterns.compare import generic_cmp, iter_lex_cmp
from stix2.equivalence.patterns.compare.comparison import (
    comparison_expression_cmp, generic_constant_cmp,
)
from stix2.patterns import (
    AndObservationExpression, FollowedByObservationExpression,
    ObservationExpression, OrObservationExpression,
    QualifiedObservationExpression, RepeatQualifier, StartStopQualifier,
    WithinQualifier, _CompoundObservationExpression,
)

_OBSERVATION_EXPRESSION_TYPE_ORDER = (
    ObservationExpression, AndObservationExpression, OrObservationExpression,
    FollowedByObservationExpression, QualifiedObservationExpression,
)


_QUALIFIER_TYPE_ORDER = (
    RepeatQualifier, WithinQualifier, StartStopQualifier,
)


def repeats_cmp(qual1, qual2):
    """
    Compare REPEATS qualifiers.  This orders by repeat count.
    """
    return generic_constant_cmp(qual1.times_to_repeat, qual2.times_to_repeat)


def within_cmp(qual1, qual2):
    """
    Compare WITHIN qualifiers.  This orders by number of seconds.
    """
    return generic_constant_cmp(
        qual1.number_of_seconds, qual2.number_of_seconds,
    )


def startstop_cmp(qual1, qual2):
    """
    Compare START/STOP qualifiers.  This lexicographically orders by start time,
    then stop time.
    """
    return iter_lex_cmp(
        (qual1.start_time, qual1.stop_time),
        (qual2.start_time, qual2.stop_time),
        generic_constant_cmp,
    )


_QUALIFIER_COMPARATORS = {
    RepeatQualifier: repeats_cmp,
    WithinQualifier: within_cmp,
    StartStopQualifier: startstop_cmp,
}


def observation_expression_cmp(expr1, expr2):
    """
    Compare two observation expression ASTs.  This is sensitive to the order of
    the expressions' sub-components.  To achieve an order-insensitive
    comparison, the ASTs must be canonically ordered first.

    :param expr1: The first observation expression
    :param expr2: The second observation expression
    :return: <0, 0, or >0 depending on whether the first arg is less, equal or
        greater than the second
    """
    type1 = type(expr1)
    type2 = type(expr2)

    type1_idx = _OBSERVATION_EXPRESSION_TYPE_ORDER.index(type1)
    type2_idx = _OBSERVATION_EXPRESSION_TYPE_ORDER.index(type2)

    if type1_idx != type2_idx:
        result = generic_cmp(type1_idx, type2_idx)

    # else, both exprs are of same type.

    # If they're simple, use contained comparison expression order
    elif type1 is ObservationExpression:
        result = comparison_expression_cmp(
            expr1.operand, expr2.operand,
        )

    elif isinstance(expr1, _CompoundObservationExpression):
        # Both compound, and of same type (and/or/followedby): sort according
        # to contents.
        result = iter_lex_cmp(
            expr1.operands, expr2.operands, observation_expression_cmp,
        )

    else:  # QualifiedObservationExpression
        # Both qualified.  Check qualifiers first; if they are the same,
        # use order of the qualified expressions.
        qual1_type = type(expr1.qualifier)
        qual2_type = type(expr2.qualifier)

        qual1_type_idx = _QUALIFIER_TYPE_ORDER.index(qual1_type)
        qual2_type_idx = _QUALIFIER_TYPE_ORDER.index(qual2_type)

        result = generic_cmp(qual1_type_idx, qual2_type_idx)

        if result == 0:
            # Same qualifier type; compare qualifier details
            qual_cmp = _QUALIFIER_COMPARATORS.get(qual1_type)
            if qual_cmp:
                result = qual_cmp(expr1.qualifier, expr2.qualifier)
            else:
                raise TypeError(
                    "Can't compare qualifier type: " + qual1_type.__name__,
                )

        if result == 0:
            # Same qualifier type and details; use qualified expression order
            result = observation_expression_cmp(
                expr1.observation_expression, expr2.observation_expression,
            )

    return result
