# xctest_checker/compare.py - Compares two files line by line -*- python -*-
#
# This source file is part of the Swift.org open source project
#
# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
# Licensed under Apache License v2.0 with Runtime Library Exception
#
# See http://swift.org/LICENSE.txt for license information
# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors

import re
try:
    from itertools import zip_longest
except ImportError:
    from itertools import izip_longest as zip_longest

from .error import XCTestCheckerError
from .line import replace_offsets


def _actual_lines(file_handle):
    """
    Returns a generator that yields each line in the file.
    """
    for line in file_handle:
        yield line


def _expected_lines_and_line_numbers(path, check_prefix):
    """
    Returns a generator that yields each line in the file at the given path
    that begins with the given prefix.
    """
    with open(path) as f:
        for index, line in enumerate(f):
            if 'RUN:' in line:
                # Ignore lit directives, which may include a call to
                # xctest_checker that specifies a check prefix.
                continue

            # Note that line numbers are not zero-indexed; we must add one to
            # the loop index.
            line_number = index + 1

            components = line.split(check_prefix)
            if len(components) == 2:
                yield (replace_offsets(components[1].strip(), line_number),
                       line_number)
            elif len(components) > 2:
                # Include a newline, then the file name and line number in the
                # exception in order to have it appear as an inline failure in
                # Xcode.
                raise XCTestCheckerError(
                    path, line_number,
                    'Usage violation: prefix "{}" appears twice in the same '
                    'line.'.format(check_prefix))


def _add_whitespace_leniency(original_regex):
    return "^ *" + original_regex + " *$"


def compare(actual, expected, check_prefix):
    """
    Compares each line in the two given files.
    If any line in the 'actual' file doesn't match the regex in the 'expected'
    file, raises an AssertionError. Also raises an AssertionError if the number
    of lines in the two files differ.
    """
    for actual_line, expected_line_and_number in zip_longest(
            _actual_lines(actual),
            _expected_lines_and_line_numbers(expected, check_prefix)):

        if expected_line_and_number is None:
            raise XCTestCheckerError(
                expected, 1,
                'The actual output contained more lines of text than the '
                'expected output. First unexpected line: {}'.format(
                    repr(actual_line)))

        (expected_line, expectation_line_number) = expected_line_and_number

        if actual_line is None:
            raise XCTestCheckerError(
                expected, expectation_line_number,
                'There were more lines expected to appear than there were '
                'lines in the actual input. Unmet expectation: {}'.format(
                    repr(expected_line)))

        if not re.match(_add_whitespace_leniency(expected_line), actual_line):
            raise XCTestCheckerError(
                expected, expectation_line_number,
                'Actual line did not match the expected regular expression.\n'
                'Actual: {}\nExpected: {}'.format(
                    repr(actual_line), repr(expected_line)))
