#!/usr/bin/python

import os, sys
import pprint
import xml.dom.minidom
from xml.dom.minidom import Node


class Type(object):
    def __init__(self, node):
        self.node = node
        self.name = node.getAttribute("name") # for classes and records
        self.is_array = False
        self.array_size = 0
        self.is_vararg = False
        
        self._extract_info()


    def _extract_info(self):
        for t in self.node.getElementsByTagName("type"):
            self.name = t.getAttribute("name")

        if len(self.node.getElementsByTagName("callback")) > 0:
            self.name = "THISSHOULDBEAPROTOTYPE"
        elif len(self.node.getElementsByTagName("array")) > 0:
            self.is_array = True
            try:
                for n in self.node.getElementsByTagName("array"):
                    self.array_size = n.getAttribute("fixed-size")
            except:
                # array is not fixed-size -> therefore a pointer
                self.is_array = False
                self.name = "any"
        elif len(self.node.getElementsByTagName("varargs")) > 0:
            self.is_vararg = True



    def __repr__(self):
        return self.name



class Parameter(object):
    def __init__(self, node):
        self.node = node
        self.name = node.getAttribute("name")
        self.type = Type(node)

    def __repr__(self):
        return self.name + " : " + self.type
        

class Function(object):
    def __init__(self, node):
        self.node = node

        self.attributes = {}
        self._extract_attributes()
        
        if "c:identifier" in self.attributes:
            self.identifier = self.attributes["c:identifier"]
        else:
            self.identifier = self.attributes["name"]
            
        self.return_type = self._extract_return_type()
        self.parameters = self._extract_parameters()

    def _extract_attributes(self):
        self.attributes.update(self.node.attributes.items())
        
        for att in self.node.getElementsByTagName("attribute"):
            att_name = att.attributes["name"].value
            self.attributes[att_name] = att.attributes["value"].value


    def _extract_return_type(self):
        for rv in self.node.getElementsByTagName("return-value"):
            return Type(rv)
            #  for rt in self.node.getElementsByTagName("type"):
            #    return rt.getAttribute("name")

    def _extract_parameters(self):
        result = list()
        for pars in self.node.getElementsByTagName("parameters"):
            for ps in pars.getElementsByTagName("parameter"):
                result.append(Parameter(ps))
                
        return result

    def __repr__(self):
        ps = ", ".join(map(str, self.parameters))
        return self.identifier + "(" + ps + ") : " + self.return_type


class Constant(object):
    def __init__(self, node):
        self.node = node
        self.attributes = {}

        self._extract_attributes()

        if "c:identifier" in self.attributes:
            self.name = self.attributes["c:identifier"]
        else:
            self.name = self.attributes["name"]
        self.value = self.attributes["value"]#node.getAttribute("value")

        self.type = Type(node)

    def _extract_attributes(self):
        self.attributes.update(self.node.attributes.items())
        
        for att in self.node.getElementsByTagName("attribute"):
            att_name = att.attributes["name"].value
            self.attributes[att_name] = att.attributes["value"].value


    def __repr__(self):
        return self.name + " = " + self.value


class Field(object):
    def __init__(self, node):
        self.node = node
        self.name = node.getAttribute("name")

        self.type = Type(node)
        

    def __repr__(self):
        return self.name + " : " + self.type


class Record(object):
    def __init__(self, node):
        self.node = node
        self.name = node.getAttribute("name")
        self.fields = self._extract_fields()

        if len(node.getElementsByTagName("union")) > 0:
            self.fields = list()
            self.name = self.name + "_NOT_SUPPORTED_UNION"

    def __repr__(self):
        return self.name + " = {" + ", ".join(map(str, self.fields)) + "}"
        
    
    def _extract_fields(self):
        result = list()
        for f in self.node.getElementsByTagName("field"):
            result.append(Field(f))

        return result
            
        

class Namespace(object):
    def __init__(self, node):
        self.node = node
        self.name = node.getAttribute("name")
        self.library = node.getAttribute("shared-library")
        
        self.functions = self._extract_functions()
        self.constants = self._extract_constants()
        self.enumerations = self._extract_enumerations() # dict() = {name: [Constant]}
        self.aliases = self._extract_aliases() # dict() = {name: target}
        self.records = self._extract_records()

    def __repr__(self):
        return self.name + " in " + self.library

    def _extract_enumerations(self):
        result = dict()
        for e in self.node.getElementsByTagName("enumeration"):
            name = e.getAttribute("name")
            
            members = list()
            for const in e.getElementsByTagName("member"):
                members.append(Constant(const))

            result[name] = members

            #print name + " = {" + ", ".join(map(str, members)) + "}"

        return result

    def _extract_records(self):
        result = list()
        for r in self.node.getElementsByTagName("record"):
            result.append(Record(r))

        return result

    def _extract_aliases(self):
        result = dict()
        for alias in self.node.getElementsByTagName("alias"):
            name = alias.getAttribute("name")
            target = alias.getAttribute("target")
            result[name] = target

            #print "Found alias:", name, "->", target

        return result

    def _work_around_lower_upper_cases(self, consts):
        temp = dict()
        for c in consts:
            c.name = c.name.lower()
            if c.name not in temp:
                temp[c.name] = c
            
        return temp.values()
        
        
    def _extract_constants(self):
        result = list()
        for const in self.node.getElementsByTagName("constant"):
            if len(const.getAttribute("name")) > 0 and \
                   const.getAttribute("value").isdigit():
                c = Constant(const)
                c.name = self.name.upper()+"_"+c.name
                result.append(c)
                
            #print str(Constant(const))
        
        return result #self._work_around_lower_upper_cases(result)


    def _extract_functions(self):
        result = list()
        
        for _class in self.node.getElementsByTagName("class") + \
                self.node.getElementsByTagName("record") +\
                self.node.getElementsByTagName("interface"):
            class_name = _class.getAttribute("name")
            
            for f in _class.getElementsByTagName("constructor"): 
                result.append(Function(f))

            for f in _class.getElementsByTagName("method"):
                func = Function(f)
                param = Parameter(_class)
                param.type.name = param.name
                param.name = param.name.lower()
                
                func.parameters.insert(0, param)
                result.append(func)


        for f in self.node.getElementsByTagName("function"):
            result.append(Function(f))
            
        return result

    
                

class Collector(object):
    
    def __init__(self, filename):
        self.filename = filename
        self.doc = xml.dom.minidom.parse(filename)
        self.namespaces = list()
        
        
    def collect(self):
        for ns in self.doc.getElementsByTagName("namespace"):
            self.namespaces.append(Namespace(ns))
            



class TypeTranslator(object):
    def __init__(self, enums, aliases):
        self.enums = enums
        self.aliases = aliases
        self.c_table = {"utf8":"p-utf8", "int8":"b", "int16":"w", "int32":"l",
                        "int64":"q", "size_t":"i", "float":"f", "double":"d",
                        "uint8":"a", "uint16":"u", "char_t":"c", "int":"l",
                        "short":"w", "short int":"w", "long int":"i", "long":"i",
                        "bool":"l", "boolean":"l", "long long":"q", "any":"*",
                        "uint32":"l", "uint64":"q", "ulong":"i", "none":"",
                        "uint":"l", "ushort":"u", "ubyte":"a", "gulong":"i",
                        "guint8":"a", "guint16":"u", "guint32":"l",
                        "guint64":"q", "gint8":"b", "gint16":"w", "gint32":"l",
                        "gint64":"q", "gfloat":"f", "gdouble":"d", "gbool":"l",
                        "gboolean":"l", "gchar":"a", "guchar":"a",
                        "gint":"l", "guint":"l", "gshort":"w", "gushort":"u",
                        "glong":"i", "gulong":"i", "gssize":"i",
                        "goffset":"q"}

    def _translate(self, name):
        """
        translates a given type to a PB type
        returning "*" means pointer, like unknown types
        returning "" means void
        """
        done = False
        while not done:
            if name in self.c_table:
                name = self.c_table[name]
                done = True
            elif name in self.aliases:
                name = self.aliases[name]
            elif name in self.enums:
                name = "l"
                done = True
            else:
                name = "*"
                done = True
                #print "WARNING: Could not translate type", name

        return name

    def _mangle_pb_keywords(self, name):
        if name in ["data", "read", "restore", "end", "debug", "structure", "from", "to", "as",
                    "default", "repeat", "until", "step"]:
            return name + "_"
        return name

    def _clean_identifier(self, name):
        name = name.replace(" ", "_")
        name = name.replace("\t", "_")
        name = name.replace("-", "_")
        if len(name) > 0 and not name[0].isalpha() and not name[0] == "_":
            name = "_" + name
        return name
    
    def decorate(self, identifier, type, in_struct = False, is_func = False):
        t_type = self._translate(type.name)
        #fields = fields + "\n" +type+"\n"
        result = ""
        identifier = self._clean_identifier(identifier)
        identifier = self._mangle_pb_keywords(identifier)
        
        if not is_func:
            if in_struct and t_type == "p-utf8":
                t_type = "*"

            if t_type == "*" or t_type == "":
                result = "*" + identifier
            else:
                result = identifier + "." + t_type

            if type.is_array and in_struct:
                result = result + "[" + type.array_size + "]"
        else:
            if t_type == "*" or t_type == "p-utf8":
                result = identifier + ".i"
            elif t_type != "":
                result = identifier + "." + t_type
            else:
                result = identifier

        return result




class Exporter(object):
    def __init__(self, collector):
        self.collector = collector
        self.namespaces = self.collector.namespaces
        self.out = ""

    def export(self):
        self.out = """
; ATTENTION: instances of gulong/ulong/long will be translated to
; a INTEGER/.i. This is right on Linux but probably should be a
; LONG/.l on Windows. If you are brave enough, take a look at
; the TypeTranslator class and the attribute c_types, which
; contains the translation table. There you can fix it.

"""
        self._export_namespaces()
        
        return self.out

    def _export_namespaces(self):
        for ns in self.namespaces:
            self.out = self.out + ";- NAMESPACE: " + ns.name + " from \"" + ns.library + "\"\n"
            translator = TypeTranslator(ns.enumerations, ns.aliases)
            
            self._export_constants(ns, translator)
            self._export_enumerations(ns, translator)
            self._export_records(ns, translator)
            self._export_functions(ns, translator)
            

    def _export_constants(self, ns, translator):
        for c in ns.constants:
            self.out = self.out + "#" + translator._clean_identifier(c.name) + " = " + c.value + "\n"

    def _export_enumerations(self, ns, translator):
        for e in ns.enumerations:
            members = ""
            for m in ns.enumerations[e]:
                name = translator._clean_identifier(m.name)
                members = members + "\t#" + name + " = " + m.value + "\n"
                
            self.out = self.out + "\nEnumeration ; " + e + "\n" + members + "EndEnumeration\n"
    
    def _export_records(self, ns, translator):
        for r in ns.records:
            fields = ""
            for f in r.fields:
                if f.name !=  "": # some bugs in girs?
                    field = translator.decorate(f.name, f.type, in_struct = True)
                    fields = fields + "\t" + field + "\n"

            name = ns.name  + r.name
            self.out = self.out + "\nCompilerIf Defined(" + name + ", #PB_Structure)\n"
            self.out = self.out + "CompilerElse\n"
            self.out = self.out + "Structure " + name + "\n" + fields + "EndStructure\n"
            self.out = self.out + "CompilerEndIf\n"
            
    
    def _export_functions(self, ns, translator):
        if ns.library != "":
            self.out = self.out + "ImportC \"/usr/lib/" + ns.library + "\"\n"
        else:
            self.out = self.out + "ImportC \"" + ns.library + "\"\n"

        for f in ns.functions:
            params = list()
            for p in f.parameters:
                if p.name != "": # some bugs in girs?
                    param = translator.decorate(p.name, p.type, in_struct = False)
                    params.append(param)
                elif p.type.is_vararg:
                    param = "*_=0,*__=0,*___=0,*____=0,*_____=0,*______=0"
                    params.append(param)

            func = translator.decorate(f.identifier, f.return_type, in_struct = False, is_func = True)
            self.out = self.out + "\t" + func + "(" + ", ".join(params) + ")\n"
            
        self.out = self.out + "EndImport\n\n" 
    


## doc = xml.dom.minidom.parse("books.xml")
 
## mapping = {}
 
## for node in doc.getElementsByTagName("book"):
##   isbn = node.getAttribute("isbn")
##   L = node.getElementsByTagName("title")
##   for node2 in L:
##     title = ""
##     for node3 in node2.childNodes:
##       if node3.nodeType == Node.TEXT_NODE:
##         title += node3.data
##     mapping[isbn] = title
 
## # mapping now has the same value as in the SAX example:
## pprint.pprint(mapping)

if __name__ == "__main__":
        
    if len(sys.argv) < 2:
        path = "/usr/share/gir-1.0/"
        files = list(os.walk(path))[0][2]
        files = ["/usr/share/gir-1.0/Gdk-2.0.gir"]
        print "Run with .gir files as arguments"
    else:
        files = list()
        for arg in sys.argv: 
            files.append(arg)
        del files[0]

    for file in files:
        print ";-------------- FILE:", file, "--------------"
        collector = Collector(file)
        collector.collect()

        exporter = Exporter(collector)
        out = exporter.export()
        print out
        


