#!/usr/local/cpython-3.6/bin/python3

"""Unit tests for portions of equivs3e."""

import sys
import copy
import functools
import unittest.mock

import equivs3e


@functools.total_ordering
class ComparableInt(object):
    # pylint: disable=too-few-public-methods

    """An int with a compare method."""

    def __init__(self, value):
        """Initialize."""
        self.value = value

    def compare(self, other):
        """Like 2.x's cmp."""
        if self.value < other.value:
            return -1
        elif self.value > other.value:
            return 1
        else:
            return 0

    def __eq__(self, other):
        """Return True iff self.value equals other.value."""
        return self.value == other.value

    def __lt__(self, other):
        """Return True iff self.value is less than other.value."""
        return self.value < other.value

    def __repr__(self):
        """Return a repr."""
        return 'CI({})'.format(self.value)


CI = ComparableInt

def test_double_merge_sort():
    """Test double_merge_sort."""
    all_good = True
    unordered_list = [
        CI(1),
        CI(2),
        CI(3),
        CI(4),
        CI(3),
        CI(5),
        CI(3),
        CI(2),
        CI(6),
    ]
    expected_result = [
        [CI(1)],
        [CI(2), CI(2)],
        [CI(3), CI(3), CI(3)],
        [CI(4)],
        [CI(5)],
        [CI(6)]
    ]
    actual_result = equivs3e.double_merge_sort(verbosity=0, lst=unordered_list)
    if actual_result != expected_result:
        sys.stderr.write('{}: test_double_merge_sort: actual_result {} != expected_result {}\n'.format(
            sys.argv[0],
            actual_result,
            expected_result,
        ))
        all_good = False
    return all_good


def test_merge_case_mosn():
    """Test merge_or_start_new under normal conditions: merge case."""
    all_good = True

    initial_result = [
        [CI(1)],
        [CI(2), CI(2)],
        [CI(3)],
    ]
    actual_result = copy.deepcopy(initial_result)
    bucket = [CI(3)]
    equivs3e.merge_or_start_new(actual_result, bucket)
    expected_result = copy.deepcopy(initial_result)
    expected_result[-1].extend(bucket)

    if actual_result != expected_result:
        sys.stderr.write('{}: test_merge_case_mosn: actual_result {} != expected_result {}\n'.format(
            sys.argv[0],
            actual_result,
            expected_result,
        ))
        all_good = False

    return all_good


def test_append_case_mosn():
    """Test merge_or_start_new under normal conditions: append case."""
    all_good = True

    initial_result = [
        [CI(1)],
        [CI(2), CI(2)],
        [CI(3)],
    ]
    actual_result = copy.deepcopy(initial_result)
    bucket = [CI(4)]
    equivs3e.merge_or_start_new(actual_result, bucket)
    expected_result = copy.deepcopy(initial_result)
    expected_result.append(bucket)

    if actual_result != expected_result:
        sys.stderr.write('{}: test_append_case_mosn: actual_result {} != expected_result {}\n'.format(
            sys.argv[0],
            actual_result,
            expected_result,
        ))
        all_good = False

    return all_good


def test_mosn_left_exc():
    """
    Test merge_or_start_new with a LeftException.

    We only test the merge case, because it's the only part that should be able to raise LeftException or RightException.
    """
    all_good = True

    initial_result = [
        [CI(1)],
        [CI(2), CI(2)],
        [CI(3)],
    ]
    actual_result = copy.deepcopy(initial_result)
    # We'll pretend that the first element of this list has an error during
    # comparison. The other 2 elements, we won't touch, even if they're "no
    # longer good".
    bucket = [CI(3), CI(3), CI(3)]

    expected_result = copy.deepcopy(actual_result)
    # expected_result[-1].append(CI(3))
    # expected_result[-1].append(CI(3))

    with unittest.mock.patch.object(ComparableInt, '__eq__', side_effect=equivs3e.LeftException):
        # This actually should leave bucket unchanged, and actual_result should
        # be extended by 2 CI(3)'s.
        equivs3e.merge_or_start_new(actual_result, bucket)

    if actual_result != expected_result:
        sys.stderr.write('{}: test_mosn_left_exc: actual_result {} != expected_result {}\n'.format(
            sys.argv[0],
            actual_result,
            expected_result,
        ))
        all_good = False

    return all_good


def test_mosn_right_exc_w2():
    """
    Test merge_or_start_new with a RightException with 2 elements in result[-1].

    We only test the merge case, because it's the only part that should be able to raise LeftException or RightException.
    """
    all_good = True

    initial_result = [
        [CI(1)],
        [CI(2), CI(2)],
        [CI(3), CI(3)],
    ]
    actual_result = copy.deepcopy(initial_result)
    bucket = [CI(3)]

    with unittest.mock.patch.object(ComparableInt, '__eq__', side_effect=equivs3e.RightException):
        # This should delete the leftmost CI(3), and add the new one, giving us
        # the same output as input.
        equivs3e.merge_or_start_new(actual_result, bucket)

    if initial_result != actual_result:
        sys.stderr.write('{}: test_mosn_right_exc_w2: initial_result {} != actual_result {}\n'.format(
            sys.argv[0],
            initial_result,
            actual_result,
        ))
        all_good = False

    return all_good


def test_mosn_right_exc_w1():
    """
    Test merge_or_start_new with a RightException with 1 elements in result[-1].

    We only test the merge case, because it's the only part that should be able to raise LeftException or RightException.
    """
    all_good = True

    initial_result = [
        [CI(1)],
        [CI(2), CI(2)],
        [CI(3)],
    ]
    actual_result = copy.deepcopy(initial_result)
    bucket = [CI(3)]

    with unittest.mock.patch.object(ComparableInt, '__eq__', side_effect=equivs3e.RightException):
        # This should delete the leftmost CI(3), and add the new one, giving us
        # the same output as input.
        equivs3e.merge_or_start_new(actual_result, bucket)

    if initial_result != actual_result:
        sys.stderr.write('{}: test_mosn_right_exc_w1: initial_result {} != actual_result {}\n'.format(
            sys.argv[0],
            initial_result,
            actual_result,
        ))
        all_good = False

    return all_good


def main():
    """Main function."""
    all_good = True

    all_good &= test_double_merge_sort()
    all_good &= test_merge_case_mosn()
    all_good &= test_append_case_mosn()
    all_good &= test_mosn_left_exc()
    all_good &= test_mosn_right_exc_w1()
    all_good &= test_mosn_right_exc_w2()

    if all_good:
        sys.stderr.write('{}: All tests passed\n'.format(sys.argv[0]))
        sys.exit(0)
    else:
        sys.stderr.write('{}: One or more tests failed\n'.format(sys.argv[0]))
        sys.exit(1)

main()