from System.Reflection import Assembly
from System.Xml import XmlImplementation
import time

def intersect_dicts(d1, d2):
    intersect_dict = {}
    for key in [k for k in d1 if k in d2]:
        if d1[key].ParameterType != d2[key].ParameterType:
            print '--> %s %s %s %s' % (d1[key].Name, d1[key].ParameterType, d2[key].Name, d2[key].ParameterType)
        intersect_dict[key] = d1[key]

    return intersect_dict

def difference_dicts(d1, d2):
    diff_dict = {}
    for key in [k for k in d1 if k not in d2]:
        diff_dict[key] = d1[key]
    for key in [k for k in d2 if k not in d1]:
        diff_dict[key] = d2[key]

    return diff_dict

def _build_signature_and_doc(paramlists):
    if len(paramlists) == 0:
        return [], '', ''

    # build a list containing dictionaries where each dictionary
    # represents one signature of an identically named method. The
    # dictionary keys are the argument names and the values of
    # .NET ParamInfo classes
    
    dicts = []
    for params in paramlists:
        dicts.append(dict([(param.Name, param) for param in params]))

    # determine the intersection and difference between these argument
    # lists - the intersection is the "required" paramters and the
    # different is the "optional" parameters. This is only an
    # approximation used because Komodo only supports one signature
    # per method name
    
    intersection_dict = dict(dicts[0])
    difference_dict = {}
    
    for d in dicts:
        # XXX These don't actually seem to get built correctly
        # see MessageBox.Show(
        difference_dict = difference_dicts(intersection_dict, d)
        intersection_dict = intersect_dicts(intersection_dict, d)
        
    def sorted(params):
        params = [(p.Position, p.Name, p) for p in params]
        params.sort()
        return [p[2] for p in params]

    doc = ""
    sig = ""

    for param in sorted(intersection_dict.values()):
        if doc != "":
#            assert sig != ""

            doc = doc + ", "
            sig = sig + ", "

        sig = sig + param.Name
        doc = doc + str(param.ParameterType)

    for param in sorted(difference_dict.values()):
        if doc != "":
 #           assert sig != ""

            doc = doc + ", "
            sig = sig + ", "

        sig = '%s%s?' % (sig, param.Name)
        doc = '%s%s?' % (doc, param.ParameterType)
        
    return intersection_dict.keys(), sig, doc


class MethodInfo(object):
    def __init__(self, methodinfo):
        # XXX need to accept a list of methodinfos with the
        # same .Name
        self.name = methodinfo.Name
        self.args, sig, doc = _build_signature_and_doc(
            [methodinfo.GetParameters()])

        self.doc = "%s(%s)" % (self.name, doc)
        self.signature = "%s(%s)" % (self.name, sig)
        
class ClassInfo(object):
    def __init__(self, typ):
        splitname = typ.FullName.split('.')
        self.name = splitname[-1]
        self.namespace = '.'.join(splitname[:-1])

        constructor_param_lists = []
        for ctors in typ.GetConstructors():
            constructor_param_lists.append(ctors.GetParameters())

        self.args, sig, doc = _build_signature_and_doc(constructor_param_lists)
        
        self.doc = "%s(%s)" % (self.name, doc)
        self.signature = "%s(%s)" % (self.name, sig)
        
        self.methodsinfo = []        
        for method in typ.GetMethods():
            self.methodsinfo.append(MethodInfo(method))
            
def create_namespace_from_assembly(assembly):
    namespace = {}
    types = assembly.GetTypes()
    
    for t in types:    
        cls = ClassInfo(t)
        namespace.setdefault(cls.namespace, []).append(cls)

    return namespace

def create_namespace_from_dll(path):
    create_namespace_from_assembly(
        Assembly.LoadFrom(path))
    
def merge_namespace_lists(ns_lists):
    merged_ns = {}

    for ns in ns_lists:    
        for name, contents in ns.items():
            merged_ns.setdefault(name, []).extend(contents) 

    return merged_ns     

def create_ctor_element(document_node, class_info):
    init_element = document_node.CreateElement('function')

    init_element.SetAttribute('attributes', '__ctor__')
    init_element.SetAttribute('name', '__init__')
    init_element.SetAttribute('signature', class_info.signature)

    doc_element = document_node.CreateElement('doc')
    doc_element.AppendChild(document_node.CreateTextNode('XXX not used'))
    init_element.AppendChild(doc_element)

    return init_element

def create_argument_element(document_node, arg_name):
    arg_element = document_node.CreateElement('argument')
    arg_element.SetAttribute('name', arg_name)
    return arg_element

def append_argument_elements(document_node, node, args):
    for arg in args:
        node.AppendChild(create_argument_element(document_node, arg))
        
def create_method_element(document_node, method_info):
    function_element = document_node.CreateElement('function')

    function_element.SetAttribute('name', method_info.name)
    function_element.SetAttribute('signature', method_info.signature)
    
    doc_element = document_node.CreateElement('doc')
    doc_element.AppendChild(document_node.CreateTextNode(method_info.doc))
    function_element.AppendChild(doc_element)

    append_argument_elements(document_node, function_element, method_info.args)
    return function_element

def create_class_element(document_node, class_info):
    class_element = document_node.CreateElement('class')
    
    class_element.SetAttribute('name', class_info.name)
    doc_element = document_node.CreateElement('doc')
    doc_element.AppendChild(document_node.CreateTextNode(class_info.doc))
    class_element.AppendChild(doc_element)
    append_argument_elements(document_node, class_element, class_info.args)

    class_element.AppendChild(
        create_ctor_element(document_node, class_info))  
    
    for method_info in class_info.methodsinfo:
        class_element.AppendChild(
            create_method_element(document_node, method_info))
        
    return class_element

def create_module_element(document_node, module_name, classes):
    module_element = document_node.CreateElement('module')

    module_element.SetAttribute('name', module_name)
    for c in classes:
        module_element.AppendChild(
            create_class_element(document_node, c))

    return module_element

def modify_and_write_dom(document_node, namespace, path):
    file_node = document_node.SelectSingleNode('//file')

    modules = namespace.keys()
    for module_name, classes in namespace.items():
        file_node.AppendChild(
            create_module_element(document_node, module_name, classes))

        """
        XXX is this needed?
        for childnamespace in [n for n in namespaces if namespace in n]:
            importelement = doc.CreateElement('import')
            moduleelement.AppendChild(importelement)
        
            importelement.SetAttribute('alias', modules[childnamespace][0].name)
            importelement.SetAttribute('line', '1')
            importelement.SetAttribute('module', childnamespace)
        """

    document_node.Save(path)
 
    

document_node = XmlImplementation().CreateDocument()
document_node.Load(r'file://C:\Program%20Files\ActiveState%20Komodo%203.0\python\Lib\site-packages\codeintel\python-orig.cix')
assembly = Assembly.LoadFrom(r'C:/WINDOWS/Microsoft.NET/Framework/v1.0.3705/System.Windows.Forms.dll')
namespace = create_namespace_from_assembly(assembly)
# XXX if you want multible assemblies to be loaded, create namespaces for each
# and then use merge_namespace_lists() [not tested]

modify_and_write_dom(
    document_node,
    namespace,
    r'C:\Program Files\ActiveState Komodo 3.0\python\Lib\site-packages\codeintel\python.cix')
