# Copyright 2016 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import re
import unittest

from mobly import signals

# Have an instance of unittest.TestCase so we could reuse some logic
# from python's own unittest.
_pyunit_proxy = unittest.TestCase()
_pyunit_proxy.maxDiff = None


def _call_unittest_assertion(assertion_method,
                             *args,
                             msg=None,
                             extras=None,
                             **kwargs):
  """Wrapper for converting a unittest assertion into a Mobly one.

  Args:
    assertion_method: unittest.TestCase assertion method to call.
    *args: Positional arguments for the assertion call.
    msg: A string that adds additional info about the failure.
    extras: An optional field for extra information to be included in
      test result.
    **kwargs: Keyword arguments for the assertion call.
  """
  my_msg = None
  try:
    assertion_method(*args, **kwargs)
  except AssertionError as e:
    my_msg = str(e)
    if msg:
      my_msg = f'{my_msg} {msg}'

  # This raise statement is outside of the above except statement to
  # prevent Python3's exception message from having two tracebacks.
  if my_msg is not None:
    raise signals.TestFailure(my_msg, extras=extras)


def assert_equal(first, second, msg=None, extras=None):
  """Asserts the equality of objects, otherwise fail the test.

  Error message is "first != second" by default. Additional explanation can
  be supplied in the message.

  Args:
    first: The first object to compare.
    second: The second object to compare.
    msg: A string that adds additional info about the failure.
    extras: An optional field for extra information to be included in
      test result.
  """
  _call_unittest_assertion(_pyunit_proxy.assertEqual,
                           first,
                           second,
                           msg=msg,
                           extras=extras)


def assert_not_equal(first, second, msg=None, extras=None):
  """Asserts that first is not equal (!=) to second."""
  _call_unittest_assertion(_pyunit_proxy.assertNotEqual,
                           first,
                           second,
                           msg=msg,
                           extras=extras)


def assert_almost_equal(first,
                        second,
                        places=None,
                        msg=None,
                        delta=None,
                        extras=None):
  """Asserts that first is almost equal to second.

  Fails if the two objects are unequal as determined by their difference
  rounded to the given number of decimal places (default 7) and
  comparing to zero, or by comparing that the difference between the two
  objects is more than the given delta.
  If the two objects compare equal then they automatically compare
  almost equal.

  Args:
    first: The first value to compare.
    second: The second value to compare.
    places: How many decimal places to take into account for comparison.
      Note that decimal places (from zero) are usually not the same
      as significant digits (measured from the most significant digit).
    msg: A string that adds additional info about the failure.
    delta: Delta to use for comparison instead of decimal places.
    extras: An optional field for extra information to be included in
      test result.
  """
  _call_unittest_assertion(_pyunit_proxy.assertAlmostEqual,
                           first,
                           second,
                           places=places,
                           msg=msg,
                           delta=delta,
                           extras=extras)


def assert_not_almost_equal(first,
                            second,
                            places=None,
                            msg=None,
                            delta=None,
                            extras=None):
  """Asserts that first is not almost equal to second.

  Args:
    first: The first value to compare.
    second: The second value to compare.
    places: How many decimal places to take into account for comparison.
      Note that decimal places (from zero) are usually not the same
      as significant digits (measured from the most significant digit).
    msg: A string that adds additional info about the failure.
    delta: Delta to use for comparison instead of decimal places.
    extras: An optional field for extra information to be included in
      test result.
  """
  _call_unittest_assertion(_pyunit_proxy.assertNotAlmostEqual,
                           first,
                           second,
                           places=places,
                           msg=msg,
                           delta=delta,
                           extras=extras)


def assert_in(member, container, msg=None, extras=None):
  """Asserts that member is in container."""
  _call_unittest_assertion(_pyunit_proxy.assertIn,
                           member,
                           container,
                           msg=msg,
                           extras=extras)


def assert_not_in(member, container, msg=None, extras=None):
  """Asserts that member is not in container."""
  _call_unittest_assertion(_pyunit_proxy.assertNotIn,
                           member,
                           container,
                           msg=msg,
                           extras=extras)


def assert_is(expr1, expr2, msg=None, extras=None):
  """Asserts that expr1 is expr2."""
  _call_unittest_assertion(_pyunit_proxy.assertIs,
                           expr1,
                           expr2,
                           msg=msg,
                           extras=extras)


def assert_is_not(expr1, expr2, msg=None, extras=None):
  """Asserts that expr1 is not expr2."""
  _call_unittest_assertion(_pyunit_proxy.assertIsNot,
                           expr1,
                           expr2,
                           msg=msg,
                           extras=extras)


def assert_count_equal(first, second, msg=None, extras=None):
  """Asserts that two iterables have the same element count.

  Element order does not matter.
  Similar to assert_equal(Counter(list(first)), Counter(list(second))).

  Args:
    first: The first iterable to compare.
    second: The second iterable to compare.
    msg: A string that adds additional info about the failure.
    extras: An optional field for extra information to be included in