#!/usr/bin/env python

r"""
This module provides argument manipulation functions like pop_arg.
"""

import gen_print as gp
import collections


def pop_arg(default=None, *args, **kwargs):
    r"""
    Pop a named argument from the args/kwargs and return a tuple consisting of
    the argument value, the modified args and the modified kwargs.

    The name of the argument is determined automatically by this function by
    examining the source code which calls it (see examples below).  If no
    suitable argument can be found, the default value passed to this function
    will be returned as the argument value.  This function is useful for
    wrapper functions that wish to process arguments in some way before
    calling subordinate function.

    Examples:

    Given this code:

    def func1(*args, **kwargs):

        last_name, args, kwargs = pop_arg('Doe', *args, **kwargs)
        some_function(last_name.capitalize(), *args, **kwargs)

    Consider this call to func1:

    func1('Johnson', ssn='111-11-1111')

    The pop_arg in func1 would return the following:

        'Johnson', [], {'ssn': "111-11-1111"}

    Notice that the 'args' value returned is an empty list. Since last_name
    was assumed to be the first positional argument, it was popped from args.

    Now consider this call to func1:

    func1(last_name='Johnson', ssn='111-11-1111')

    The pop_arg in func1 would return the same last_name value as in the
    previous example.  The only difference being that the last_name value was
    popped from kwargs rather than from args.

    Description of argument(s):
    default                         The value to return if the named argument
                                    is not present in args/kwargs.
    args                            The positional arguments passed to the
                                    calling function.
    kwargs                          The keyword arguments passed to the
                                    calling function.
    """

    # Retrieve the argument name by examining the source code.
    arg_name = gp.get_arg_name(None, arg_num=-3, stack_frame_ix=2)
    if arg_name in kwargs:
        arg_value = kwargs.pop(arg_name)
    else:
        # Convert args from a tuple to a list.
        args = list(args)
        if args:
            arg_value = args.pop(0)
        else:
            arg_value = default

    return arg_value, args, kwargs


def source_to_object(value):
    r"""
    Evaluate string value as python source code and return the resulting
    object.

    If value is NOT a string or can not be interpreted as a python source
    object definition, simply return value.

    The idea is to convert python object definition source code (e.g. for
    lists, dictionaries, tuples, etc.) into an object.

    Example:

    Note that this first example is a special case in that it is a short-cut
    for specifying a collections.OrderedDict.

    result = source_to_object("[('one', 1), ('two', 2), ('three', 3)]")

    The result is a collections.OrderedDict object:

    result:
      [one]:                     1
      [two]:                     2
      [three]:                   3

    This is a short-cut for the long form shown here:

    result = source_to_object("collections.OrderedDict([
        ('one', 1),
        ('two', 2),
        ('three', 3)])")

    Also note that support for this special-case short-cut precludes the
    possibility of interpreting such a string as a list of tuples.

    Example:

    In this example, the result will be a list:

    result = source_to_object("[1, 2, 3]")

    result:
      result[0]:                 1
      result[1]:                 2
      result[2]:                 3

    Example:

    In this example, the value passed to this function is not a string, so it
    is simply returned.

    result = source_to_object(1)

    More examples:
    result = source_to_object("dict(one=1, two=2, three=3)")
    result = source_to_object("{'one':1, 'two':2, 'three':3}")
    result = source_to_object(True)
    etc.

    Description of argument(s):
    value                           If value is a string, it will be evaluated
                                    as a python statement.  If the statement
                                    is valid, the resulting object will be
                                    returned.  In all other cases, the value
                                    will simply be returned.
    """

    if type(value) not in gp.get_string_types():
        return value

    # Strip white space prior to attempting to interpret the string as python
    # code.
    value = value.strip()

    # Try special case of collections.OrderedDict which accepts a list of
    # tuple pairs.
    if value.startswith("[("):
        try:
            return eval("collections.OrderedDict(" + value + ")")
        except (TypeError, NameError, ValueError):
            pass

    try:
        return eval(value)
    except (NameError, SyntaxError):
        pass

    return value


def args_to_objects(args):
    r"""
    Run source_to_object() on each element in args and return the result.

    Description of argument(s):
    args                            A type of dictionary, list, set, tuple or
                                    simple object whose elements are to be
                                    converted via a call to source_to_object().
    """

    type_of_dict = gp.is_dict(args)
    if type_of_dict:
        if type_of_dict == gp.dict_type():
            return {k: source_to_object(v) for (k, v) in args.items()}
        elif type_of_dict == gp.ordered_dict_type():
            return collections.OrderedDict((k, v) for (k, v) in args.items())
        elif type_of_dict == gp.dot_dict_type():
            return DotDict((k, v) for (k, v) in args.items())
        elif type_of_dict == gp.normalized_dict_type():
            return NormalizedDict((k, v) for (k, v) in args.items())
    # Assume args is list, tuple or set.
    if type(args) in (list, set):
        return [source_to_object(arg) for arg in args]
    elif type(args) is tuple:
        return tuple([source_to_object(arg) for arg in args])

    return source_to_object(args)
