#!/usr/bin/env python
"""A module with utility decorators for skipping tests."""

import functools
import types
import unittest

from typing import Any
from typing import Callable
from typing import Text
from typing import Union


def If(condition: Union[Any, Callable[[], Any]], reason: Text):
  """A decorator that skips test evaluation if the condition holds.

  This decorator can be applied either to a test method or a test class (in
  which case, all test methods are going to be skipped if necessary).

  Unlike `unittest.skipIf`, this decorator can also bo provided with a function
  that is not evaluated until the test actually runs. This is useful if the
  condition cannot be determined at the time the module is loaded.

  Args:
    condition: If true, the test is going to be skipped.
    reason: A reason why the test needed to be skipped.

  Returns:
    A decorator that can be applied to test methods or classes.
  """
  if callable(condition):
    return _IfLazy(condition, reason)
  else:
    return unittest.skipIf(condition, reason)


def Unless(condition: Union[Any, Callable[[], Any]], reason: Text):
  """A decorator that skips test evaluation if the condition dot not hold.

  See documentation for the `If` decorator for more information.

  Args:
    condition: If false, the test is going to be skipped.
    reason: A reason why the test needed to be skipped.

  Returns:
    A decorator that can be applied to test method or classes.
  """
  if callable(condition):
    return _UnlessLazy(condition, reason)
  else:
    return unittest.skipUnless(condition, reason)


def _IfLazy(condition: Callable[[], Any], reason: Text):

  def Decorator(test):
    if isinstance(test, type):

      cls_wrapper = type(test.__name__, test.__bases__, dict(test.__dict__))

      def __init__(self, method_name):  # pylint: disable=invalid-name
        super(cls_wrapper, self).__init__(method_name)

        method = getattr(self, method_name)
        setattr(self, method_name, _IfLazy(condition, reason)(method))

      cls_wrapper.__init__ = __init__

      return cls_wrapper

    if isinstance(test, (types.FunctionType, types.MethodType)):

      @functools.wraps(test)
      def MethodWrapper(*args, **kwargs):
        if condition():
          raise unittest.SkipTest(reason)

        return test(*args, **kwargs)

      return MethodWrapper

    raise TypeError("Unexpected test type: {}".format(type(test)))

  return Decorator


def _UnlessLazy(condition: Callable[[], Any], reason: Text):
  return _IfLazy(lambda: not condition(), reason)
