"""Runtime registration data."""

import inspect
import types

from turbojson import jsonify

class typedproperty(property):
    def __init__(self, type, *args, **kw):
        self.type = type
        super(typedproperty, self).__init__(*args, **kw)

class unsigned(object):
    """Represents an unsigned integer to the outside world. Within
    Python, it just creates an int."""
    def __new__(cls, value=None):
        if value is not None:
            if value < 0:
                # avoid import loop
                from tgwebservices.base import ClientError
                raise ClientError("Value of %s is not an unsigned integer" %
                                  value)
            return int(value)
        else:
            return int()
        
# These are the primitive types that are generally understood equally well
# by the various protocols.
primitives = set([int, basestring, str, unicode, float, bool, list, dict,
                  long, typedproperty, unsigned])

class FunctionInfo(object):
    """Keeps track of information about a function that is available for
    web services use.
    """
    
    return_type = basestring
    _params = None
    input_types = {}
    
    def __init__(self, params, defaults):
        self.params = params
        
        if defaults:
            # the optional parameters are the ones that are listed in defaults
            # the only optional parameters are at the *end* of the argument
            # list
            optional = [params[i] for i in 
                            range(len(params) - len(defaults), len(params))]
            self.optional = set(optional)
        else:
            self.optional = []
        self.complex_types = set()
    
    def _set_params(self, value):
        # remove the "self" parameter, because it's not relevant for web services
        # declarations
        if value and value[0] == "self":
            value = value[1:]
        self._params = value
    
    params = property(lambda self: self._params, _set_params)
    
    def __repr__(self):
        return "Return type: %s, params: %s" % (self.return_type, self.params)
    
def register(func):
    """Registers a function as available for web services. You can call this
    repeatedly and will get back the same FunctionInfo object each time.
    
    @param func Function to register
    @return a FunctionInfo object with the information saved for this
            function."""
    if not hasattr(func, "_ws_func_info"):
        argspec = inspect.getargspec(func)
        func._ws_func_info = FunctionInfo(argspec[0], argspec[3])
    
    return func._ws_func_info

def ctvalues(cls):
    """Inspects a class for the defined default values. This is used to
    figure out the types of attributes that instances of the class will
    be expected to have (to provide useful information for statically
    typed languages). It will strip out any attributes that start with _
    and any methods.
    
    @param cls class to inspect
    @return list of keys that are likely holders of useful data"""
    return [key for key in dir(cls) if not key.startswith("_") and not 
            isinstance(getattr(cls, key), types.MethodType)]

def _setup_new_type(cls):
    cls._type_dependents = 0
    
    # simplify types that we know about
    @jsonify.jsonify.when("isinstance(obj, cls)")
    def jsonify_cls(obj):
        simplified = dict()
        for key in ctvalues(cls):
            val = getattr(obj, key)
            if val is not None and type(val) not in primitives:
                val = jsonify.jsonify(val)
            simplified[key] = val
        return simplified

def register_complex_type(fi, cls):
    """Remembers a complex type (user-defined class) that has been discovered and 
    also discovers any complex types that are referred  to be the given type.
    Additionally, this will keep track of how many classes depend on a given
    class in the _type_dependencies class variable. This is used as a crude
    mechanism for determining an appropriate order for types to appear in when
    being defined for consumption by clients.
    
    @param fi the FunctionInfo object that will hold the complex types
    @param cls The class to add and inspect"""
    if not cls or cls in fi.complex_types:
        return
    if not hasattr(cls, "_type_dependents"):
        _setup_new_type(cls)
    fi.complex_types.add(cls)
    for key in ctvalues(cls):
        checkitem = getattr(cls, key)
        
        if isinstance(checkitem, list):
            checkitem = checkitem[0]
        if not isinstance(checkitem, type):
            checkitem = checkitem.__class__
        # figure out of this is a complex type. If so, increase
        # its dependents count
        if checkitem != types.NoneType and checkitem not in primitives:
            if not hasattr(checkitem, "_type_dependents"):
                _setup_new_type(checkitem)
            checkitem._type_dependents += 1
            register_complex_type(fi, checkitem)
        