# Copyright (C) 2018 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.

"""This module has components that are used for testing Python Fire."""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import collections
import enum
import functools

import six

if six.PY3:
  from ..fire import test_components_py3 as py3  # pylint: disable=unused-import,no-name-in-module,g-import-not-at-top


def identity(arg1, arg2, arg3=10, arg4=20, *arg5, **arg6):  # pylint: disable=keyword-arg-before-vararg
  return arg1, arg2, arg3, arg4, arg5, arg6

identity.__annotations__ = {'arg2': int, 'arg4': int}


def multiplier_with_docstring(num, rate=2):
  """Multiplies num by rate.

  Args:
    num (int): the num you want to multiply
    rate (int): the rate for multiplication
  Returns:
    Multiplication of num by rate
  """
  return num * rate


def function_with_help(help=True):  # pylint: disable=redefined-builtin
  return help


class Empty(object):
  pass


class OldStyleEmpty:  # pylint: disable=old-style-class,no-init
  pass


class WithInit(object):

  def __init__(self):
    pass


class ErrorInConstructor(object):

  def __init__(self, value='value'):
    self.value = value
    raise ValueError('Error in constructor')


class WithHelpArg(object):
  """Test class for testing when class has a help= arg."""

  def __init__(self, help=True):  # pylint: disable=redefined-builtin
    self.has_help = help
    self.dictionary = {'__help': 'help in a dict'}


class NoDefaults(object):

  def double(self, count):
    return 2 * count

  def triple(self, count):
    return 3 * count


class WithDefaults(object):
  """Class with functions that have default arguments."""

  def double(self, count=0):
    """Returns the input multiplied by 2.

    Args:
      count: Input number that you want to double.

    Returns:
      A number that is the double of count.
    """
    return 2 * count

  def triple(self, count=0):
    return 3 * count

  def text(
      self,
      string=('0001020304050607080910111213141516171819'
              '2021222324252627282930313233343536373839')
  ):
    return string


class OldStyleWithDefaults:  # pylint: disable=old-style-class,no-init

  def double(self, count=0):
    return 2 * count

  def triple(self, count=0):
    return 3 * count


class MixedDefaults(object):

  def ten(self):
    return 10

  def sum(self, alpha=0, beta=0):
    return alpha + 2 * beta

  def identity(self, alpha, beta='0'):
    return alpha, beta


class SimilarArgNames(object):

  def identity(self, bool_one=False, bool_two=False):
    return bool_one, bool_two

  def identity2(self, a=None, alpha=None):
    return a, alpha


class CapitalizedArgNames(object):

  def sum(self, Delta=1.0, Gamma=2.0):  # pylint: disable=invalid-name
    return Delta + Gamma


class Annotations(object):

  def double(self, count=0):
    return 2 * count

  def triple(self, count=0):
    return 3 * count

  double.__annotations__ = {'count': float}
  triple.__annotations__ = {'count': float}


class TypedProperties(object):
  """Test class for testing Python Fire with properties of various types."""

  def __init__(self):
    self.alpha = True
    self.beta = (1, 2, 3)
    self.charlie = WithDefaults()
    self.delta = {
        'echo': 'E',
        'nest': {
            0: 'a',
            1: 'b',
        },
    }
    self.echo = ['alex', 'bethany']
    self.fox = ('carry', 'divide')
    self.gamma = 'myexcitingstring'


class VarArgs(object):
  """Test class for testing Python Fire with a property with varargs."""

  def cumsums(self, *items):
    total = None
    sums = []
    for item in items:
      if total is None:
        total = item
      else:
        total += item
      sums.append(total)
    return sums

  def varchars(self, alpha=0, beta=0, *chars):  # pylint: disable=keyword-arg-before-vararg
    return alpha, beta, ''.join(chars)


class Underscores(object):

  def __init__(self):
    self.underscore_example = 'fish fingers'

  def underscore_function(self, underscore_arg):
    return underscore_arg


class BoolConverter(object):

  def as_bool(self, arg=False):
    return bool(arg)


class ReturnsObj(object):

  def get_obj(self, *items):
    del items  # Unused
    return BoolConverter()


class NumberDefaults(object):

  def reciprocal(self, divisor=10.0):
    return 1.0 / divisor

  def integer_reciprocal(self, divisor=10):
    return 1.0 / divisor


class InstanceVars(object):

  def __init__(self, arg1, arg2):
    self.arg1 = arg1
    self.arg2 = arg2

  def run(self, arg1, arg2):
    return (self.arg1, self.arg2, arg1, arg2)


class Kwargs(object):

  def props(self, **kwargs):
    return kwargs

  def upper(self, **kwargs):
    return ' '.join(sorted(kwargs.keys())).upper()

  def run(self, positional, named=None, **kwargs):
    return (positional, named, kwargs)


class ErrorRaiser(object):

  def fail(self):
    raise ValueError('This error is part of a test.')


class NonComparable(object):

  def __eq__(self, other):
    raise ValueError('Instances of this class cannot be compared.')

  def __ne__(self, other):
    raise ValueError('Instances of this class cannot be compared.')


class EmptyDictOutput(object):

  def totally_empty(self):
    return {}

  def nothing_printable(self):
    return {'__do_not_print_me': 1}


class CircularReference(object):

  def create(self):
    x = {}
    x['y'] = x
    return x


class OrderedDictionary(object):

  def empty(self):
    return collections.OrderedDict()

  def non_empty(self):
    ordered_dict = collections.OrderedDict()
    ordered_dict['A'] = 'A'
    ordered_dict[2] = 2
    return ordered_dict


class NamedTuple(object):
  """Functions returning named tuples used for testing."""

  def point(self):
    """Point example straight from Python docs."""
    # pylint: disable=invalid-name
    Point = collections.namedtuple('Point', ['x', 'y'])
    return Point(11, y=22)

  def matching_names(self):
    """Field name equals value."""
    # pylint: disable=invalid-name
    Point = collections.namedtuple('Point', ['x', 'y'])
    return Point(x='x', y='y')


class CallableWithPositionalArgs(object):
  """Test class for supporting callable."""

  TEST = 1

  def __call__(self, x, y):
    return x + y

  def fn(self, x):
    return x + 1


NamedTuplePoint = collections.namedtuple('NamedTuplePoint', ['x', 'y'])


class SubPoint(NamedTuplePoint):
  """Used for verifying subclasses of namedtuples behave as intended."""

  def coordinate_sum(self):
    return self.x + self.y


class CallableWithKeywordArgument(object):
  """Test class for supporting callable."""

  def __call__(self, **kwargs):
    for key, value in kwargs.items():
      print('%s: %s' % (key, value))

  def print_msg(self, msg):
    print(msg)


CALLABLE_WITH_KEYWORD_ARGUMENT = CallableWithKeywordArgument()


class ClassWithDocstring(object):
  """Test class for testing help text output.

  This is some detail description of this test class.
  """

  def __init__(self, message='Hello!'):
    """Constructor of the test class.

    Constructs a new ClassWithDocstring object.

    Args:
      message: The default message to print.
    """
    self.message = message

  def print_msg(self, msg=None):
    """Prints a message."""
    if msg is None:
      msg = self.message
    print(msg)


class ClassWithMultilineDocstring(object):
  """Test class for testing help text output with multiline docstring.

  This is a test class that has a long docstring description that spans across
  multiple lines for testing line breaking in help text.
  """

  @staticmethod
  def example_generator(n):
    """Generators have a ``Yields`` section instead of a ``Returns`` section.

    Args:
        n (int): The upper limit of the range to generate, from 0 to `n` - 1.

    Yields:
        int: The next number in the range of 0 to `n` - 1.

    Examples:
        Examples should be written in doctest format, and should illustrate how
        to use the function.

        >>> print([i for i in example_generator(4)])
        [0, 1, 2, 3]

    """
    for i in range(n):
      yield i


def simple_set():
  return {1, 2, 'three'}


def simple_frozenset():
  return frozenset({1, 2, 'three'})


class Subdict(dict):
  """A subclass of dict, for testing purposes."""


# An example subdict.
SUBDICT = Subdict({1: 2, 'red': 'blue'})


class Color(enum.Enum):
  RED = 1
  GREEN = 2
  BLUE = 3


class HasStaticAndClassMethods(object):
  """A class with a static method and a class method."""

  CLASS_STATE = 1

  def __init__(self, instance_state):
    self.instance_state = instance_state

  @staticmethod
  def static_fn(args):
    return args

  @classmethod
  def class_fn(cls, args):
    return args + cls.CLASS_STATE


def function_with_varargs(arg1, arg2, arg3=1, *varargs):  # pylint: disable=keyword-arg-before-vararg
  """Function with varargs.

  Args:
    arg1: Position arg docstring.
    arg2: Position arg docstring.
    arg3: Flags docstring.
    *varargs: Accepts unlimited positional args.
  Returns:
    The unlimited positional args.
  """
  del arg1, arg2, arg3  # Unused.
  return varargs


def function_with_keyword_arguments(arg1, arg2=3, **kwargs):
  del arg2  # Unused.
  return arg1, kwargs


def fn_with_code_in_docstring():
  """This has code in the docstring.



  Example:
    x = fn_with_code_in_docstring()
    indentation_matters = True



  Returns:
    True.
  """
  return True


class BinaryCanvas(object):
  """A canvas with which to make binary art, one bit at a time."""

  def __init__(self, size=10):
    self.pixels = [[0] * size for _ in range(size)]
    self._size = size
    self._row = 0  # The row of the cursor.
    self._col = 0  # The column of the cursor.

  def __str__(self):
    return '\n'.join(
        ' '.join(str(pixel) for pixel in row) for row in self.pixels)

  def show(self):
    print(self)
    return self

  def move(self, row, col):
    self._row = row % self._size
    self._col = col % self._size
    return self

  def on(self):
    return self.set(1)

  def off(self):
    return self.set(0)

  def set(self, value):
    self.pixels[self._row][self._col] = value
    return self


class DefaultMethod(object):

  def double(self, number):
    return 2 * number

  def __getattr__(self, name):
    def _missing():
      return 'Undefined function'
    return _missing


class InvalidProperty(object):

  def double(self, number):
    return 2 * number

  @property
  def prop(self):
    raise ValueError('test')


def simple_decorator(f):
  @functools.wraps(f)
  def wrapper(*args, **kwargs):
    return f(*args, **kwargs)
  return wrapper


@simple_decorator
def decorated_method(name='World'):
  return 'Hello %s' % name


# pylint: disable=g-doc-args,g-doc-return-or-yield
def fn_with_kwarg(arg1, arg2, **kwargs):
  """Function with kwarg.

  :param arg1: Description of arg1.
  :param arg2: Description of arg2.
  :key arg3: Description of arg3.
  """
  del arg1, arg2
  return kwargs.get('arg3')


def fn_with_kwarg_and_defaults(arg1, arg2, opt=True, **kwargs):
  """Function with kwarg and defaults.

  :param arg1: Description of arg1.
  :param arg2: Description of arg2.
  :key arg3: Description of arg3.
  """
  del arg1, arg2, opt
  return kwargs.get('arg3')
# pylint: enable=g-doc-args,g-doc-return-or-yield

def fn_with_multiple_defaults(first='first', last='last', late='late'):
  """Function with kwarg and defaults.

  :key first: Description of first.
  :key last: Description of last.
  :key late: Description of late.
  """
  del last, late
  return first
