from collections import OrderedDict
from functools import update_wrapper
from inspect import Signature, signature, Parameter
from typing import get_type_hints

from typeguard import check_argument_types, _CallMemo


__author__ = 'un.def <un.def@ya.ru>'
__version__ = '0.2.0.dev0'


_registry = {}


class PolypieException(Exception):

    pass


class HashableParameter(Parameter):

    def __hash__(self):
        return hash((self.name, self.kind, self.default, self.annotation))

    @classmethod
    def from_parameter(cls, parameter):
        return cls(
            name=parameter.name,
            kind=parameter.kind,
            default=parameter.default,
            annotation=parameter.annotation
        )


def _call_func(func_key, args=None, kwargs=None):
    args = args or ()
    kwargs = kwargs or {}
    for parameters_tuple, func in _registry[func_key].items():
        try:
            Signature(parameters_tuple).bind(*args, **kwargs)
            check_argument_types(_CallMemo(func, args=args, kwargs=kwargs))
        except TypeError:
            continue
        return func(*args, **kwargs)
    raise PolypieException(
        "Matсhing signature for function '{func}' with "
        "args={args} and kwargs={kwargs} not found".format(func=func_key,
                                                           args=args,
                                                           kwargs=kwargs)
    )


def update_dispatcher(dispatcher, func, assign=True):
    assigned = ('__name__', '__qualname__', '__module__') if assign else ()
    dispatcher = update_wrapper(dispatcher, func, assigned)
    del dispatcher.__wrapped__
    return dispatcher


def polymorphic(func):
    global _registry
    func_key = func.__module__ + '.' + func.__qualname__
    parameters = signature(func).parameters
    parameters_tuple = tuple(
        HashableParameter.from_parameter(p) for p in parameters.values())
    if func_key not in _registry:
        def dispatcher(*args, **kwargs):
            return _call_func(func_key, args, kwargs)
        dispatcher = update_dispatcher(dispatcher, func, assign=True)
        signature_mapping = OrderedDict()
        signature_mapping[parameters_tuple] = func
        signature_mapping.dispatcher = dispatcher
        _registry[func_key] = signature_mapping
        return dispatcher
    else:
        if parameters_tuple in _registry[func_key]:
            hints = get_type_hints(func)
            sig_gen = ('{}:{}'.format(p, hints[p]) if p in hints else p
                       for p in parameters)
            raise PolypieException(
                "Function '{func}' with signature ({sig}) "
                "already exists".format(func=func_key,
                                        sig=', '.join(sig_gen))
            )
        else:
            _registry[func_key][parameters_tuple] = func
            dispatcher = _registry[func_key].dispatcher
            return update_dispatcher(dispatcher, func, assign=False)

@@ -86,25 +86,25 @@
  import 
-Signature
+Parameter
 , signat
@@ -110,19 +110,8 @@
 ture
-, Parameter
 %0Afro
@@ -812,26 +812,8 @@
  for
- parameters_tuple,
  fun
@@ -841,12 +841,13 @@
 ey%5D.
-item
+value
 s():
@@ -864,70 +864,8 @@
 ry:%0A
-            Signature(parameters_tuple).bind(*args, **kwargs)%0A
     
@@ -1156,32 +1156,45 @@
 t found%22.format(
+%0A            
 func=func_key,%0A 
@@ -1195,137 +1195,19 @@
 key,
-%0A                                                           args=args,%0A                                                          
+ args=args,
  kwa
@@ -2146,68 +2146,236 @@
   el
-se:%0A        if parameters_tuple in _registry%5Bfunc_key%5D:%0A    
+if parameters_tuple not in _registry%5Bfunc_key%5D:%0A        _registry%5Bfunc_key%5D%5Bparameters_tuple%5D = func%0A        dispatcher = _registry%5Bfunc_key%5D.dispatcher%0A        return update_dispatcher(dispatcher, func, assign=False)%0A    else:%0A
     
@@ -2415,20 +2415,16 @@
         
-    
 sig_gen 
@@ -2426,16 +2426,29 @@
 _gen = (
+%0A            
 '%7B%7D:%7B%7D'.
@@ -2500,27 +2500,16 @@
         
-          
- 
 for p in
@@ -2519,30 +2519,35 @@
 rameters
-)
 %0A     
+   )%0A
        
+ 
 raise Po
@@ -2558,28 +2558,24 @@
 eException(%0A
-    
             
@@ -2630,20 +2630,16 @@
         
-  
-  
 %22already
@@ -2672,279 +2672,39 @@
 key,
-%0A                                        sig=', '.join(sig_gen))%0A            )%0A        else:%0A            _registry%5Bfunc_key%5D%5Bparameters_tuple%5D = func%0A            dispatcher = _registry%5Bfunc_key%5D.dispatcher%0A            return update_dispatcher(dispatcher, func, assign=False
+ sig=', '.join(sig_gen))%0A        
 )%0A

Remove redundant signature check