import re
import sys
import xml.etree.ElementTree as _etree

class Doc(object):
    def __init__(self, text):
        object.__init__(self)
        self.text = text

    @staticmethod
    def from_xml(elm):
        return Doc(elm.text)

def _set_unique_attribute(obj, attr, value):
    if getattr(obj, attr) is not None:
        raise RuntimeError("duplicated attribute '%s'" % (attr,))
    setattr(obj, attr, value)

def _set_unique_attribute_bool(obj, attr, value):
    if value.lower() in ('0', 'false', 'no'):
        value = False
    elif value.lower() in ('1', 'true', 'yes'):
        value = True
    else:
        raise RuntimeError("bad value '%s' for boolean attribute '%s'" % (value, attr))
    _set_unique_attribute(obj, attr, value)

class _XmlObject(object):
    def __init__(self):
        object.__init__(self)
        self.doc = None
        self.summary = None
        self.annotations = {}
        self.module = None

    @classmethod
    def from_xml(cls, module, elm, *args):
        obj = cls()
        obj.module = module
        obj._parse_xml(module, elm, *args)
        return obj

    def _parse_xml_element(self, module, elm):
        if elm.tag in ('doc', 'summary'):
            _set_unique_attribute(self, elm.tag, Doc.from_xml(elm))
        else:
            raise RuntimeError('unknown element %s' % (elm.tag,))

    def _parse_attribute(self, attr, value):
        if attr.find('.') >= 0:
            self.annotations[attr] = value
            return True
        else:
            return False

    def _parse_xml(self, module, elm, *args):
        for attr, value in list(elm.items()):
            if not self._parse_attribute(attr, value):
                raise RuntimeError('unknown attribute %s' % (attr,))
        for child in elm.getchildren():
            self._parse_xml_element(module, child)

class _ParamBase(_XmlObject):
    def __init__(self):
        _XmlObject.__init__(self)
        self.type = None
        self.transfer_mode = None

    def _parse_attribute(self, attr, value):
        if attr in ('type', 'transfer_mode'):
            _set_unique_attribute(self, attr, value)
        else:
            return _XmlObject._parse_attribute(self, attr, value)
        return True

class Param(_ParamBase):
    def __init__(self):
        _ParamBase.__init__(self)
        self.name = None
        self.default_value = None
        self.allow_none = None

    def _parse_attribute(self, attr, value):
        if attr in ('default_value', 'name'):
            _set_unique_attribute(self, attr, value)
        elif attr in ('allow_none',):
            _set_unique_attribute_bool(self, attr, value)
        else:
            return _ParamBase._parse_attribute(self, attr, value)
        return True

class Retval(_ParamBase):
    def __init__(self):
        _ParamBase.__init__(self)

class FunctionBase(_XmlObject):
    def __init__(self):
        _XmlObject.__init__(self)
        self.name = None
        self.c_name = None
        self.retval = None
        self.params = []
        self.has_gerror_return = False
        self.kwargs = None

    def _parse_attribute(self, attr, value):
        if attr in ('c_name', 'name'):
            _set_unique_attribute(self, attr, value)
        elif attr in ('kwargs',):
            _set_unique_attribute_bool(self, attr, value)
        else:
            return _XmlObject._parse_attribute(self, attr, value)
        return True

    def _parse_xml_element(self, module, elm):
        if elm.tag == 'retval':
            _set_unique_attribute(self, 'retval', Retval.from_xml(module, elm))
        elif elm.tag == 'param':
            self.params.append(Param.from_xml(module, elm))
        else:
            _XmlObject._parse_xml_element(self, module, elm)

    def _parse_xml(self, module, elm, *args):
        _XmlObject._parse_xml(self, module, elm, *args)
        if not self.name:
            raise RuntimeError('function name missing')
        if not self.c_name:
            raise RuntimeError('function c_name missing')

class Function(FunctionBase):
    def __init__(self):
        FunctionBase.__init__(self)

    def symbol_id(self):
        return self.c_name

class MethodBase(FunctionBase):
    def __init__(self):
        super(MethodBase, self).__init__()

    def _parse_xml(self, module, elm, cls):
        super(MethodBase, self)._parse_xml(module, elm, cls)
        self.cls = cls

class Constructor(MethodBase):
    def __init__(self):
        super(Constructor, self).__init__()

    def symbol_id(self):
        return self.c_name

class StaticMethod(MethodBase):
    def __init__(self):
        super(StaticMethod, self).__init__()

    def symbol_id(self):
        return self.c_name

class Method(MethodBase):
    def __init__(self):
        super(Method, self).__init__()

    def symbol_id(self):
        return self.c_name

class VMethod(MethodBase):
    def __init__(self):
        super(VMethod, self).__init__()
        self.c_name = "fake"

    def symbol_id(self):
        return '%s::%s' % (self.cls.name, self.name)

class Signal(MethodBase):
    def __init__(self):
        super(Signal, self).__init__()
        self.c_name = "fake"

    def symbol_id(self):
        return 'signal:%s:%s' % (self.cls.name, self.name)

class Type(_XmlObject):
    def __init__(self):
        _XmlObject.__init__(self)
        self.name = None
        self.c_name = None
        self.gtype_id = None

    def symbol_id(self):
        return self.name

class BasicType(Type):
    def __init__(self, name):
        Type.__init__(self)
        self.name = name

class ArrayType(Type):
    def __init__(self, elm_type):
        Type.__init__(self)
        self.elm_type = elm_type
        self.name = '%sArray*' % elm_type.name
        self.c_name = '%sArray*' % elm_type.name

class GErrorReturnType(Type):
    def __init__(self):
        Type.__init__(self)
        self.name = 'GError**'

class GTypedType(Type):
    def __init__(self):
        Type.__init__(self)
        self.short_name = None

    def _parse_attribute(self, attr, value):
        if attr in ('short_name', 'name', 'gtype_id'):
            _set_unique_attribute(self, attr, value)
        else:
            return Type._parse_attribute(self, attr, value)
        return True

    def _parse_xml(self, module, elm, *args):
        Type._parse_xml(self, module, elm, *args)
        if self.name is None:
            raise RuntimeError('class name missing')
        if self.short_name is None:
            raise RuntimeError('class short name missing')
        if self.gtype_id is None:
            raise RuntimeError('class gtype missing')

class EnumValue(_XmlObject):
    def __init__(self):
        super(EnumValue, self).__init__()
        self.name = None
        self.short_name = None
        self.enum = None

    def symbol_id(self):
        return self.name

    def _parse_attribute(self, attr, value):
        if attr in ('name'):
            _set_unique_attribute(self, attr, value)
        else:
            return super(EnumValue, self)._parse_attribute(attr, value)
        return True

    def _parse_xml(self, module, elm, *args):
        super(EnumValue, self)._parse_xml(module, elm, *args)
        if self.name is None:
            raise RuntimeError('enum value name missing')
        if not self.short_name:
            self.short_name = self.name
            prefix = module.name.upper() + '_'
            if self.short_name.startswith(prefix):
                self.short_name = self.short_name[len(prefix):]

class EnumBase(GTypedType):
    def __init__(self):
        super(EnumBase, self).__init__()
        self.values = []
        self.__value_hash = {}

    def _parse_xml_element(self, module, elm):
        if elm.tag == 'value':
            value = EnumValue.from_xml(module, elm)
            assert not value.name in self.__value_hash
            self.__value_hash[value.name] = value
            value.enum = self
            self.values.append(value)
        else:
            super(EnumBase, self)._parse_xml_element(module, elm)

class Enum(EnumBase):
    def __init__(self):
        super(Enum, self).__init__()

class Flags(EnumBase):
    def __init__(self):
        super(Flags, self).__init__()

class InstanceType(GTypedType):
    def __init__(self):
        GTypedType.__init__(self)
        self.constructor = None
        self.methods = []
        self.static_methods = []
        self.__method_hash = {}

    def _parse_xml_element(self, module, elm):
        if elm.tag == 'method':
            meth = Method.from_xml(module, elm, self)
            assert not meth.name in self.__method_hash
            self.__method_hash[meth.name] = meth
            self.methods.append(meth)
        elif elm.tag == 'static-method':
            meth = StaticMethod.from_xml(module, elm, self)
            assert not meth.name in self.__method_hash
            self.__method_hash[meth.name] = meth
            self.static_methods.append(meth)
        elif elm.tag == 'constructor':
            assert not self.constructor
            self.constructor = Constructor.from_xml(module, elm, self)
        else:
            GTypedType._parse_xml_element(self, module, elm)

class Pointer(InstanceType):
    def __init__(self):
        InstanceType.__init__(self)

class Boxed(InstanceType):
    def __init__(self):
        InstanceType.__init__(self)

class Class(InstanceType):
    def __init__(self):
        InstanceType.__init__(self)
        self.parent = None
        self.vmethods = []
        self.signals = []
        self.constructable = None
        self.__vmethod_hash = {}
        self.__signal_hash = {}

    def _parse_attribute(self, attr, value):
        if attr in ('parent'):
            _set_unique_attribute(self, attr, value)
        elif attr in ('constructable'):
            _set_unique_attribute_bool(self, attr, value)
        else:
            return InstanceType._parse_attribute(self, attr, value)
        return True

    def _parse_xml_element(self, module, elm):
        if elm.tag == 'virtual':
            meth = VMethod.from_xml(module, elm, self)
            assert not meth.name in self.__vmethod_hash
            self.__vmethod_hash[meth.name] = meth
            self.vmethods.append(meth)
        elif elm.tag == 'signal':
            meth = Signal.from_xml(module, elm, self)
            assert not meth.name in self.__signal_hash
            self.__signal_hash[meth.name] = meth
            self.signals.append(meth)
        else:
            InstanceType._parse_xml_element(self, module, elm)

    def _parse_xml(self, module, elm, *args):
        InstanceType._parse_xml(self, module, elm, *args)
        if self.parent is None:
            raise RuntimeError('class parent name missing')
        if self.constructable and self.constructor:
            raise RuntimeError('both constructor and constructable attributes present')

class Module(object):
    def __init__(self):
        object.__init__(self)
        self.name = None
        self.__classes = []
        self.__boxed = []
        self.__pointers = []
        self.__enums = []
        self.__class_hash = {}
        self.__functions = []
        self.__function_hash = {}
        self.__import_modules = []
        self.__parsing_done = False
        self.__types = {}
        self.__symbols = {}

    def __add_type(self, typ):
        if typ.name in self.__class_hash:
            raise RuntimeError('duplicated type %s' % typ.name)
        self.__class_hash[typ.name] = typ

    def __finish_type(self, typ):
        if isinstance(typ, Type):
            return typ
        if typ in self.__types:
            return self.__types[typ]
        if typ == 'GError**':
            return GErrorReturnType()
        m = re.match(r'(const-)?([\w\d_]+)\*', typ)
        if m:
            name = m.group(2)
            if name in self.__types:
                obj_type = self.__types[name]
                if isinstance(obj_type, InstanceType):
                    return obj_type
        m = re.match(r'Array<([\w\d_]+)>\*', typ)
        if m:
            elm_type = self.__finish_type(m.group(1))
            return ArrayType(elm_type)
        m = re.match(r'([\w\d_]+)Array\*', typ)
        if m:
            elm_type = self.__finish_type(m.group(1))
            return ArrayType(elm_type)
        return BasicType(typ)

    def __finish_parsing_method(self, meth, typ):
        sym_id = meth.symbol_id()
        if self.__symbols.get(sym_id):
            raise RuntimeError('duplicate symbol %s' % sym_id)
        self.__symbols[sym_id] = meth

        for p in meth.params:
            p.type = self.__finish_type(p.type)
        if meth.retval:
            meth.retval.type = self.__finish_type(meth.retval.type)

        meth.has_gerror_return = False
        if meth.params and isinstance(meth.params[-1].type, GErrorReturnType):
            meth.has_gerror_return = True

        if meth.kwargs:
            params = list(meth.params)
            pos_args = []
            kw_args = []
            if meth.has_gerror_return:
                params = params[:-1]
            seen_kwarg = False
            for p in params:
                if p.default_value is not None:
                    seen_kwarg = True
                elif seen_kwarg:
                    raise RuntimeError('in %s: parameter without a default value follows a kwarg one' % sym_id)
                if p.default_value is not None:
                    kw_args.append(p)
                else:
                    pos_args.append(p)

    def __finish_parsing_type(self, typ):
        if hasattr(typ, 'constructor') and typ.constructor is not None:
            self.__finish_parsing_method(typ.constructor, typ)
        for meth in typ.static_methods:
            self.__finish_parsing_method(meth, typ)
        for meth in typ.methods:
            self.__finish_parsing_method(meth, typ)
        if hasattr(typ, 'vmethods'):
            for meth in typ.vmethods:
                self.__finish_parsing_method(meth, typ)
        if hasattr(typ, 'signals'):
            for meth in typ.signals:
                self.__finish_parsing_method(meth, typ)

    def __finish_parsing_enum(self, typ):
        for v in typ.values:
            sym_id = v.symbol_id()
            if self.__symbols.get(sym_id):
                raise RuntimeError('duplicate symbol %s' % sym_id)
            self.__symbols[sym_id] = v

    def __add_type_symbol(self, typ):
        sym_id = typ.symbol_id()
        if self.__symbols.get(sym_id):
            raise RuntimeError('duplicate symbol %s' % sym_id)
        self.__symbols[sym_id] = typ

    def __finish_parsing(self):
        if self.__parsing_done:
            return

        for typ in self.__classes + self.__boxed + self.__pointers + self.__enums:
            self.__add_type_symbol(typ)
            self.__types[typ.name] = typ

        for module in self.__import_modules:
            for typ in module.get_classes() + module.get_boxed() + \
                       module.get_pointers() + module.get_enums():
                self.__types[typ.name] = typ
            for sym in module.__symbols:
                if self.__symbols.get(sym):
                    raise RuntimeError('duplicate symbol %s' % sym)
                self.__symbols[sym] = module.__symbols[sym]

        for typ in self.__classes + self.__boxed + self.__pointers:
            self.__finish_parsing_type(typ)

        for typ in self.__enums:
            self.__finish_parsing_enum(typ)

        for func in self.__functions:
            self.__finish_parsing_method(func, None)

        self.__parsing_done = True

    def get_classes(self):
        self.__finish_parsing()
        return list(self.__classes)

    def get_boxed(self):
        self.__finish_parsing()
        return list(self.__boxed)

    def get_pointers(self):
        self.__finish_parsing()
        return list(self.__pointers)

    def get_enums(self):
        self.__finish_parsing()
        return list(self.__enums)

    def get_functions(self):
        self.__finish_parsing()
        return list(self.__functions)

    def get_symbols(self):
        self.__finish_parsing()
        return dict(self.__symbols)

    def __parse_module_entry(self, elm):
        if elm.tag == 'class':
            cls = Class.from_xml(self, elm)
            self.__add_type(cls)
            self.__classes.append(cls)
        elif elm.tag == 'boxed':
            cls = Boxed.from_xml(self, elm)
            self.__add_type(cls)
            self.__boxed.append(cls)
        elif elm.tag == 'pointer':
            cls = Pointer.from_xml(self, elm)
            self.__add_type(cls)
            self.__pointers.append(cls)
        elif elm.tag == 'enum':
            enum = Enum.from_xml(self, elm)
            self.__add_type(enum)
            self.__enums.append(enum)
        elif elm.tag == 'flags':
            enum = Flags.from_xml(self, elm)
            self.__add_type(enum)
            self.__enums.append(enum)
        elif elm.tag == 'function':
            func = Function.from_xml(self, elm)
            assert not func.name in self.__function_hash
            self.__function_hash[func.name] = func
            self.__functions.append(func)
        else:
            raise RuntimeError('unknown element %s' % (elm.tag,))

    def import_module(self, mod):
        self.__import_modules.append(mod)

    @staticmethod
    def from_xml(filename):
        mod = Module()
        xml = _etree.ElementTree()
        xml.parse(filename)
        root = xml.getroot()
        assert root.tag == 'module'
        mod.name = root.get('name')
        assert mod.name is not None
        for elm in root.getchildren():
            mod.__parse_module_entry(elm)
        return mod
