"""Robust apply mechanism

Provides a function "call", which can sort out
what arguments a given callable object can take,
and subset the given arguments to match only
those which are acceptable.
"""
import sys

if sys.hexversion >= 0x3000000:
    im_func = "__func__"
    im_self = "__self__"
    im_code = "__code__"
    func_code = "__code__"
else:
    im_func = "im_func"
    im_self = "im_self"
    im_code = "im_code"
    func_code = "func_code"


def function(receiver):
    """Get function-like callable object for given receiver

    returns (function_or_method, codeObject, fromMethod)

    If fromMethod is true, then the callable already
    has its first argument bound
    """
    if hasattr(receiver, "__call__"):
        # Reassign receiver to the actual method that will be called.
        if hasattr(receiver.__call__, im_func) or hasattr(receiver.__call__, im_code):
            receiver = receiver.__call__
    if hasattr(receiver, im_func):
        # an instance-method...
        return receiver, getattr(getattr(receiver, im_func), func_code), 1
    elif not hasattr(receiver, func_code):
        raise ValueError("unknown receiver type %s %s" % (receiver, type(receiver)))
    return receiver, getattr(receiver, func_code), 0


def robustApply(receiver, *arguments, **named):
    """Call receiver with arguments and an appropriate subset of named"""
    receiver, codeObject, startIndex = function(receiver)
    acceptable = codeObject.co_varnames[
        startIndex + len(arguments) : codeObject.co_argcount
    ]
    for name in codeObject.co_varnames[startIndex : startIndex + len(arguments)]:
        if name in named:
            raise TypeError(
                """Argument %r specified both positionally and as a keyword"""
                """ for calling %r""" % (name, receiver)
            )
    if not (codeObject.co_flags & 8):
        # fc does not have a **kwds type parameter, therefore
        # remove unacceptable arguments.
        for arg in list(named):
            if arg not in acceptable:
                del named[arg]
    return receiver(*arguments, **named)
