# Copyright (c) 2010-2022 Belledonne Communications SARL.
#
# This file is part of Liblinphone 
# (see https://gitlab.linphone.org/BC/public/liblinphone).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.



import re
import six
import sys


def strip_leading_linphone(s):
	if s.lower().startswith('linphone'):
		return s[8:]
	else:
		return s

def remove_useless_enum_prefix(senum, svalue):
	lenum = re.findall('[A-Z][^A-Z]*', senum)
	lvalue = re.findall('[A-Z][^A-Z]*', svalue)
	if len(lenum) == 0 or len(lvalue) == 0:
		return svalue
	if lenum[0] == lvalue[0]:
		i = 0
		while i < len(lenum) and lenum[i] == lvalue[i]:
			i += 1
		svalue = ''.join(lvalue[i:])
	if svalue == "None":
		return "_None"
	return svalue

def is_callback(s):
	return s.startswith('Linphone') and s.endswith('Cb')

def compute_event_name(s, className):
	s = strip_leading_linphone(s)
	s = s[len(className):-2] # Remove leading class name and tailing 'Cb'
	event_name = ''
	first = True
	for l in s:
		if l.isupper() and not first:
			event_name += '_'
		event_name += l.lower()
		first = False
	return event_name

def is_const_from_complete_type(complete_type):
	splitted_type = complete_type.split(' ')
	return 'const' in splitted_type


class HandWrittenCode:
	def __init__(self, _class, name, func_list, doc = ''):
		self._class = _class
		self.name = name
		self.func_list = func_list
		self.doc = doc

class HandWrittenInstanceMethod(HandWrittenCode):
	def __init__(self, _class, name, cfunction, doc = ''):
		HandWrittenCode.__init__(self, _class, name, [cfunction], doc)

class HandWrittenClassMethod(HandWrittenCode):
	def __init__(self, _class, name, cfunction, doc = ''):
		HandWrittenCode.__init__(self, _class, name, [cfunction], doc)

class HandWrittenDeallocMethod(HandWrittenCode):
	def __init__(self, _class, cfunction):
		HandWrittenCode.__init__(self, _class, 'dealloc', [cfunction], '')

class HandWrittenProperty(HandWrittenCode):
	def __init__(self, _class, name, getter_cfunction = None, setter_cfunction = None, doc = ''):
		func_list = []
		if getter_cfunction is not None:
			func_list.append(getter_cfunction)
		if setter_cfunction is not None:
			func_list.append(setter_cfunction)
		HandWrittenCode.__init__(self, _class, name, func_list, doc)
		self.getter_cfunction = getter_cfunction
		self.setter_cfunction = setter_cfunction


class UnknownTypeException(Exception):
	def __init__(self, typename):
		self.typename = typename
	def __str__(self):
		return "Unknown type " + self.typename

class ArgumentType:
	def __init__(self, basic_type, complete_type, contained_type, linphone_module):
		if not basic_type in linphone_module.known_types:
			raise UnknownTypeException(basic_type)
		self.basic_type = basic_type
		self.complete_type = complete_type
		self.contained_type = contained_type
		self.linphone_module = linphone_module
		self.type_str = None
		self.check_condition = None
		self.convert_code = None
		self.convert_from_func = None
		self.free_convert_result_func = None
		self.fmt_str = 'O'
		self.cfmt_str = '%p'
		self.cnativefmt_str = '%p'
		self.use_native_pointer = False
		self.cast_convert_func_result = True
		self.is_linphone_object = False
		self.__compute()
		if (self.basic_type == 'MSList' or self.basic_type == 'bctbx_list_t') and self.contained_type is not None and self.contained_type != 'const char *':
			self.linphone_module.bctbxlist_types.add(self.contained_type)

	def __compute(self):
		splitted_type = self.complete_type.split(' ')
		if self.basic_type == 'char':
			if '*' in splitted_type:
				self.type_str = 'string'
				self.check_condition = "!PyString_Check({arg_name})"
				self.convert_code = "{result_name}{result_suffix} = {cast}PyString_AsString({arg_name});\n"
				self.fmt_str = 'z'
				self.cfmt_str = '\\"%s\\"'
			else:
				self.type_str = 'int'
				self.check_condition = "!PyInt_Check({arg_name}) && !PyLong_Check({arg_name})"
				self.convert_code = "{result_name}{result_suffix} = {cast}PyInt_AsLong({arg_name});\n"
				self.fmt_str = 'b'
				self.cfmt_str = '%08x'
		elif self.basic_type == 'int':
			if 'unsigned' in splitted_type:
				self.type_str = 'unsigned int'
				self.check_condition = "!PyInt_Check({arg_name})"
				self.convert_code = "{result_name}{result_suffix} = {cast}PyInt_AsUnsignedLongMask({arg_name});\n"
				self.fmt_str = 'I'
				self.cfmt_str = '%u'
			else:
				self.type_str = 'int'
				self.check_condition = "!PyInt_Check({arg_name})"
				self.convert_code = "{result_name}{result_suffix} = {cast}PyInt_AS_LONG({arg_name});\n"
				self.fmt_str = 'i'
				self.cfmt_str = '%d'
		elif self.basic_type in ['int8_t', 'int16_t' 'int32_t']:
			self.type_str = 'int'
			self.check_condition = "!PyInt_Check({arg_name})"
			self.convert_code = "{result_name}{result_suffix} = {cast}PyInt_AS_LONG({arg_name});\n"
			if self.basic_type == 'int8_t':
					self.fmt_str = 'c'
			elif self.basic_type == 'int16_t':
					self.fmt_str = 'h'
			elif self.basic_type == 'int32_t':
					self.fmt_str = 'l'
			self.cfmt_str = '%d'
		elif self.basic_type in ['uint8_t', 'uint16_t', 'uint32_t']:
			self.type_str = 'unsigned int'
			self.check_condition = "!PyInt_Check({arg_name})"
			self.convert_code = "{result_name}{result_suffix} = {cast}PyInt_AsUnsignedLongMask({arg_name});\n"
			if self.basic_type == 'uint8_t':
				self.fmt_str = 'b'
			elif self.basic_type == 'uint16_t':
				self.fmt_str = 'H'
			elif self.basic_type == 'uint32_t':
				self.fmt_str = 'k'
			self.cfmt_str = '%u'
		elif self.basic_type == 'int64_t':
			self.type_str = '64bits int'
			self.check_condition = "!PyInt_Check({arg_name}) && !PyLong_Check({arg_name})"
			self.convert_code = \
"""if (PyInt_Check({arg_name})) {result_name}{result_suffix} = {cast}(PY_LONG_LONG)PyInt_AsLong({arg_name});
	else if (PyLong_Check({arg_name})) {result_name}{result_suffix} = {cast}PyLong_AsLongLong({arg_name});
"""
			self.fmt_str = 'L'
			self.cfmt_str = '%ld'
		elif self.basic_type == 'uint64_t':
			self.type_str = '64bits unsigned int'
			self.check_condition = "!PyLong_Check({arg_name})"
			self.convert_code = "{result_name}{result_suffix} = {cast}PyLong_AsUnsignedLongLong({arg_name});\n"
			self.fmt_str = 'K'
			self.cfmt_str = '%lu'
		elif self.basic_type == 'size_t':
			self.type_str = 'int'
			self.check_condition = "!PyInt_Check({arg_name}) && !PyLong_Check({arg_name})"
			self.convert_code = \
"""if (PyInt_Check({arg_name})) {result_name}{result_suffix} = {cast}(size_t)PyInt_AsSsize_t({arg_name});
	else if (PyLong_Check({arg_name})) {result_name}{result_suffix} = {cast}(size_t)PyLong_AsSsize_t({arg_name});
"""
			self.fmt_str = 'n'
			self.cfmt_str = '%lu'
		elif self.basic_type == 'float':
			self.type_str = 'float'
			self.check_condition = "!PyFloat_Check({arg_name})"
			self.convert_code = \
"""if (PyInt_Check({arg_name})) {result_name}{result_suffix} = {cast}(float)PyInt_AsLong({arg_name});
	else if (PyLong_Check({arg_name})) {result_name}{result_suffix} = {cast}(float)PyLong_AsLong({arg_name});
	else if (PyFloat_Check({arg_name})) {result_name}{result_suffix} = {cast}(float)PyFloat_AsDouble({arg_name});
"""
			self.fmt_str = 'f'
			self.cfmt_str = '%f'
		elif self.basic_type == 'double':
			self.type_str = 'float'
			self.check_condition = "!PyFloat_Check({arg_name})"
			self.convert_code = \
"""if (PyInt_Check({arg_name})) {result_name}{result_suffix} = {cast}(double)PyInt_AsLong({arg_name});
	else if (PyLong_Check({arg_name})) {result_name}{result_suffix} = {cast}(double)PyLong_AsLong({arg_name});
	else if (PyFloat_Check({arg_name})) {result_name}{result_suffix} = {cast}(double)PyFloat_AsDouble({arg_name});
"""
			self.fmt_str = 'd'
			self.cfmt_str = '%f'
		elif self.basic_type == 'bool_t' or self.basic_type == 'LinphoneStatus':
			self.type_str = 'bool'
			self.check_condition = "!PyBool_Check({arg_name})"
			self.convert_code = "{result_name}{result_suffix} = {cast}PyObject_IsTrue({arg_name});\n"
			self.convert_from_func = 'PyBool_FromLong'
			self.fmt_str = 'O'
			self.cfmt_str = '%p'
			self.cnativefmt_str = '%u'
		elif self.basic_type == 'time_t':
			self.type_str = 'DateTime'
			self.check_condition = "!PyDateTime_Check({arg_name})"
			self.convert_code = "{result_name}{result_suffix} = {cast}PyDateTime_As_time_t({arg_name});\n"
			self.convert_from_func = 'PyDateTime_From_time_t'
			self.fmt_str = 'O'
			self.cfmt_str = '%p'
			self.cnativefmt_str = '%ld'
		elif self.basic_type == 'MSList' or self.basic_type == 'bctbx_list_t':
			if self.contained_type == 'const char *':
				self.type_str = 'list of string'
				self.convert_code = "{result_name}{result_suffix} = {cast}PyList_AsBctbxListOfString({arg_name});\n"
				self.convert_from_func = 'PyList_FromBctbxListOfString'
			else:
				self.type_str = 'list of linphone.' + self.contained_type
				self.convert_code = "{result_name}{result_suffix} = {cast}PyList_AsBctbxListOf" + self.contained_type + "({arg_name});\n"
				self.convert_from_func = 'PyList_FromBctbxListOf' + self.contained_type
			if not is_const_from_complete_type(self.complete_type):
				self.free_convert_result_func = "pylinphone_bctbx_list_free"
			self.check_condition = "!PyList_Check({arg_name})"
			self.fmt_str = 'O'
			self.cfmt_str = '%p'
		elif self.basic_type == 'MSVideoSize':
			self.type_str = 'linphone.VideoSize'
			self.check_condition = "!PyLinphoneVideoSize_Check({arg_name})"
			self.convert_code = "{result_name}{result_suffix} = {cast}PyLinphoneVideoSize_AsMSVideoSize({arg_name});\n"
			self.convert_from_func = 'PyLinphoneVideoSize_FromMSVideoSize'
			self.fmt_str = 'O'
			self.cfmt_str = '%p'
			self.cast_convert_func_result = False
		elif self.basic_type == 'LCSipTransports':
			self.type_str = 'linphone.SipTransports'
			self.check_condition = "!PyLinphoneSipTransports_Check({arg_name})"
			self.convert_code = "{result_name}{result_suffix} = {cast}PyLinphoneSipTransports_AsLCSipTransports({arg_name});\n"
			self.convert_from_func = 'PyLinphoneSipTransports_FromLCSipTransports'
			self.fmt_str = 'O'
			self.cfmt_str = '%p'
			self.cast_convert_func_result = False
		else:
			if strip_leading_linphone(self.basic_type) in self.linphone_module.enum_names:
				self.type_str = 'int'
				self.check_condition = "!PyInt_Check({arg_name})"
				self.convert_code = "{result_name}{result_suffix} = {cast}PyInt_AsLong({arg_name});\n"
				self.fmt_str = 'i'
				self.cfmt_str = '%d'
			elif is_callback(self.complete_type):
				self.type_str = 'callable'
				self.check_condition = "!PyCallable_Check({arg_name})"
				self.cnativefmt_str = None
			elif '*' in splitted_type:
				self.type_str = 'linphone.' + strip_leading_linphone(self.basic_type)
				self.use_native_pointer = True
				self.is_linphone_object = True
			else:
				self.type_str = 'linphone.' + strip_leading_linphone(self.basic_type)
				self.is_linphone_object = True


class MethodDefinition:
	def __init__(self, linphone_module, class_, method_name = "", method_node = None):
		self.body = ''
		self.arg_names = []
		self.parse_tuple_format = ''
		self.build_value_format = ''
		self.return_type = 'void'
		self.return_complete_type = 'void'
		self.return_contained_type = None
		self.method_name = method_name
		self.method_node = method_node
		self.class_ = class_
		self.linphone_module = linphone_module
		self.self_arg = None
		self.xml_method_return = None
		self.xml_method_args = []
		self.method_type = 'instancemethod'

	def format_local_variables_definition(self):
		body = self.format_local_return_variables_definition()
		if self.self_arg is not None:
			body += "\t" + self.self_arg.get('completetype') + "native_ptr;\n"
		for xml_method_arg in self.xml_method_args:
			arg_name = "_" + xml_method_arg.get('name')
			arg_type = xml_method_arg.get('type')
			arg_complete_type = xml_method_arg.get('completetype')
			arg_contained_type = xml_method_arg.get('containedtype')
			argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self.linphone_module)
			self.parse_tuple_format += argument_type.fmt_str
			if is_callback(arg_complete_type):
				body += "\tPyObject * {arg_name};\n".format(arg_name=arg_name)
			elif argument_type.fmt_str == 'O' and argument_type.use_native_pointer:
				body += "\tPyObject * " + arg_name + ";\n"
				body += "\t" + arg_complete_type + " " + arg_name + "_native_ptr = NULL;\n"
			elif argument_type.fmt_str == 'O' and argument_type.convert_code is not None:
				body += "\tPyObject * " + arg_name + ";\n"
				body += "\t" + arg_complete_type + " " + arg_name + "_native_obj;\n"
			elif strip_leading_linphone(arg_complete_type) in self.linphone_module.enum_names:
				body += "\tint " + arg_name + ";\n"
			else:
				body += "\t" + arg_complete_type + " " + arg_name + ";\n"
			self.arg_names.append(arg_name)
		return body

	def format_deprecation_warning(self):
		if self.method_node is not None and self.method_node.get('deprecated') == 'true':
			print(self.class_['class_name'] + "." + self.method_name + " is deprecated")
			return "\tPyErr_WarnEx(PyExc_DeprecationWarning, \"{msg}\", 1);\n".format(msg="{class_name}.{method_name} is deprecated".format(class_name=self.class_['class_name'], method_name=self.method_name))
		return ""

	def format_arguments_parsing(self):
		class_native_ptr_check_code = ''
		if self.self_arg is not None:
			class_native_ptr_check_code = self.format_class_native_pointer_check(False)
		parse_tuple_code = ''
		if len(self.arg_names) > 0:
			parse_tuple_code = \
"""if (!PyArg_ParseTuple(args, "{fmt}", {args})) {{
		return NULL;
	}}
""".format(fmt=self.parse_tuple_format, args=', '.join(map(lambda a: '&' + a, self.arg_names)))
		args_conversion_code = ''
		for xml_method_arg in self.xml_method_args:
			arg_name = "_" + xml_method_arg.get('name')
			arg_type = xml_method_arg.get('type')
			arg_complete_type = xml_method_arg.get('completetype')
			arg_contained_type = xml_method_arg.get('containedtype')
			argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self.linphone_module)
			if argument_type.fmt_str == 'O' and argument_type.convert_code is not None:
				args_conversion_code += argument_type.convert_code.format(result_name=arg_name, result_suffix='_native_obj', cast='', arg_name=arg_name)
		return \
"""	{class_native_ptr_check_code}
	{parse_tuple_code}
	{args_type_check_code}
	{args_native_ptr_check_code}
	{args_conversion_code}
""".format(class_native_ptr_check_code=class_native_ptr_check_code,
		parse_tuple_code=parse_tuple_code,
		args_type_check_code=self.format_args_type_check(),
		args_native_ptr_check_code=self.format_args_native_pointer_check(),
		args_conversion_code=args_conversion_code)

	def format_enter_trace(self):
		fmt = ''
		args = []
		if self.self_arg is not None:
			fmt += "%p [%p]"
			args += ["self", "native_ptr"]
		for xml_method_arg in self.xml_method_args:
			arg_name = "_" + xml_method_arg.get('name')
			arg_type = xml_method_arg.get('type')
			arg_complete_type = xml_method_arg.get('completetype')
			arg_contained_type = xml_method_arg.get('containedtype')
			if fmt != '':
				fmt += ', '
			argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self.linphone_module)
			fmt += argument_type.cfmt_str
			args.append(arg_name)
			if argument_type.fmt_str == 'O' and argument_type.cnativefmt_str is not None:
				fmt += ' [' + argument_type.cnativefmt_str + ']'
				if argument_type.use_native_pointer:
					args.append(arg_name + '_native_ptr')
				else:
					args.append(arg_name + '_native_obj')
		args = ', '.join(args)
		if args != '':
			args = ', ' + args
		return "\tpylinphone_trace(1, \"[PYLINPHONE] >>> %s({fmt})\", __FUNCTION__{args});\n".format(fmt=fmt, args=args)

	def format_c_function_call(self):
		arg_names = []
		c_function_call_code = ''
		cfree_argument_code = ''
		python_ref_code = ''
		for xml_method_arg in self.xml_method_args:
			arg_name = "_" + xml_method_arg.get('name')
			arg_type = xml_method_arg.get('type')
			arg_complete_type = xml_method_arg.get('completetype')
			arg_contained_type = xml_method_arg.get('containedtype')
			argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self.linphone_module)
			if argument_type.fmt_str == 'O' and argument_type.use_native_pointer:
				arg_names.append(arg_name + "_native_ptr")
			elif argument_type.fmt_str == 'O' and argument_type.convert_code is not None:
				arg_names.append(arg_name + "_native_obj")
				if argument_type.free_convert_result_func is not None and not is_const_from_complete_type(arg_complete_type):
					cfree_argument_code = \
"""{free_func}({arg_name}_native_obj);
""".format(free_func=argument_type.free_convert_result_func, arg_name=arg_name)
			else:
				arg_names.append(arg_name)
		if is_callback(self.return_complete_type):
			c_function_call_code = "pyresult = ((pylinphone_{class_name}Object *)self)->{callback_name};".format(class_name=self.class_['class_name'], callback_name=compute_event_name(self.return_complete_type, self.class_['class_name']))
		else:
			if self.return_complete_type != 'void':
				c_function_call_code += "cresult = "
			c_function_call_code += self.method_node.get('name') + "("
			if self.self_arg is not None:
				c_function_call_code += "native_ptr"
				if len(arg_names) > 0:
					c_function_call_code += ', '
			c_function_call_code += ', '.join(arg_names) + ");"
		if self.method_name == 'add_callbacks':
			python_ref_code = "Py_INCREF(_cbs);"
		elif self.method_name == 'remove_callbacks':
			python_ref_code = "Py_XDECREF(_cbs);"
		from_native_pointer_code = ''
		convert_from_code = ''
		build_value_code = ''
		cfree_code = ''
		result_variable = ''
		take_native_ref = 'TRUE'
		if self.return_complete_type != 'void':
			if self.build_value_format == 'O':
				stripped_return_type = strip_leading_linphone(self.return_type)
				return_type_class = self.find_class_definition(self.return_type)
				if return_type_class is not None:
					if self.method_name.startswith('create'):
						take_native_ref = 'FALSE'
					from_native_pointer_code = "pyresult = pylinphone_{return_type}_from_native_ptr(&pylinphone_{return_type}Type, cresult, {take_native_ref});\n".format(return_type=stripped_return_type, take_native_ref=take_native_ref)
				else:
					return_argument_type = ArgumentType(self.return_type, self.return_complete_type, self.return_contained_type, self.linphone_module)
					if return_argument_type.convert_from_func is not None:
						convert_from_code = \
"""pyresult = {convert_func}(cresult);
""".format(convert_func=return_argument_type.convert_from_func)
					if return_argument_type.free_convert_result_func is not None:
						cfree_code = \
"""{free_func}(cresult);
""".format(free_func=return_argument_type.free_convert_result_func)
				result_variable = 'pyresult'
			else:
				result_variable = 'cresult'
		if result_variable != '':
			build_value_code = "pyret = Py_BuildValue(\"{fmt}\", {result_variable});".format(fmt=self.build_value_format, result_variable=result_variable)
			if take_native_ref == 'FALSE':
				build_value_code += """
	Py_XDECREF(pyresult);"""
		if self.return_complete_type == 'char *':
			cfree_code = 'ms_free(cresult);';
		body = \
"""	{c_function_call_code}
	{cfree_argument_code}
	{python_ref_code}
	pylinphone_dispatch_messages();
	{from_native_pointer_code}
	{convert_from_code}
	{build_value_code}
	{cfree_code}
""".format(c_function_call_code=c_function_call_code,
		cfree_argument_code=cfree_argument_code,
		python_ref_code=python_ref_code,
		from_native_pointer_code=from_native_pointer_code,
		convert_from_code=convert_from_code,
		build_value_code=build_value_code,
		cfree_code=cfree_code)
		return body

	def format_return_trace(self):
		if self.return_complete_type != 'void':
			return "\tpylinphone_trace(-1, \"[PYLINPHONE] <<< %s -> %p\", __FUNCTION__, pyret);\n"
		else:
			return "\tpylinphone_trace(-1, \"[PYLINPHONE] <<< %s -> None\", __FUNCTION__);\n"

	def format_return_result(self):
		if self.return_complete_type != 'void':
			return "\treturn pyret;"
		return "\tPy_RETURN_NONE;"

	def format_return_none_trace(self):
		return "\tpylinphone_trace(-1, \"[PYLINPHONE] <<< %s -> None\", __FUNCTION__);\n"

	def format_class_native_pointer_check(self, return_int):
		return_value = "NULL"
		if return_int:
			return_value = "-1"
		return \
"""native_ptr = pylinphone_{class_name}_get_native_ptr(self);
	if (native_ptr == NULL) {{
		PyErr_SetString(PyExc_TypeError, "Invalid linphone.{class_name} instance");
		return {return_value};
	}}
""".format(class_name=self.class_['class_name'], return_value=return_value)

	def format_args_type_check(self):
		body = ''
		for xml_method_arg in self.xml_method_args:
			arg_name = "_" + xml_method_arg.get('name')
			arg_type = xml_method_arg.get('type')
			arg_complete_type = xml_method_arg.get('completetype')
			arg_contained_type = xml_method_arg.get('containedtype')
			argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self.linphone_module)
			if argument_type.fmt_str == 'O':
				if argument_type.use_native_pointer:
					body += \
"""	if (({arg_name} != Py_None) && !PyObject_IsInstance({arg_name}, (PyObject *)&pylinphone_{arg_type}Type)) {{
		PyErr_SetString(PyExc_TypeError, "The '{arg_name}' argument must be a {type_str} instance.");
		return NULL;
	}}
""".format(arg_name=arg_name, arg_type=strip_leading_linphone(arg_type), type_str=argument_type.type_str)
				else:
					body += \
"""	if ({check_condition}) {{
		PyErr_SetString(PyExc_TypeError, "The '{arg_name}' argument must be a {type_str} instance.");
		return NULL;
	}}
""".format(arg_name=arg_name, check_condition=argument_type.check_condition.format(arg_name=arg_name), type_str=argument_type.type_str)
		if body != '':
			body = body[1:] # Remove leading '\t'
		return body

	def format_args_native_pointer_check(self):
		body = ''
		for xml_method_arg in self.xml_method_args:
			arg_name = "_" + xml_method_arg.get('name')
			arg_type = xml_method_arg.get('type')
			arg_complete_type = xml_method_arg.get('completetype')
			arg_contained_type = xml_method_arg.get('containedtype')
			argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self.linphone_module)
			if argument_type.fmt_str == 'O' and argument_type.use_native_pointer:
				body += \
"""	if (({arg_name} != NULL) && ({arg_name} != Py_None)) {{
		if (({arg_name}_native_ptr = pylinphone_{arg_type}_get_native_ptr({arg_name})) == NULL) {{
			return NULL;
		}}
	}}
""".format(arg_name=arg_name, arg_type=strip_leading_linphone(arg_type))
		if body != '':
			body = body[1:] # Remove leading '\t'
		return body

	def format_local_return_variables_definition(self):
		body = ''
		if self.xml_method_return is not None:
			self.return_type = self.xml_method_return.get('type')
			self.return_complete_type = self.xml_method_return.get('completetype')
			self.return_contained_type = self.xml_method_return.get('containedtype')
		if is_callback(self.return_complete_type):
			body += "\tPyObject * pyresult;\n"
			body += "\tPyObject * pyret;\n"
			argument_type = ArgumentType(self.return_type, self.return_complete_type, self.return_contained_type, self.linphone_module)
			self.build_value_format = argument_type.fmt_str
		elif self.return_complete_type != 'void':
			body += "\t" + self.return_complete_type + " cresult;\n"
			argument_type = ArgumentType(self.return_type, self.return_complete_type, self.return_contained_type, self.linphone_module)
			self.build_value_format = argument_type.fmt_str
			if self.build_value_format == 'O':
				body += "\tPyObject * pyresult;\n"
			body += "\tPyObject * pyret;\n"
		return body

	def parse_method_node(self):
		if self.method_node is not None:
			self.xml_method_return = self.method_node.find('./return')
			self.xml_method_args = self.method_node.findall('./arguments/argument')
			self.method_type = self.method_node.tag
		if self.method_type != 'classmethod' and len(self.xml_method_args) > 0:
			self.self_arg = self.xml_method_args[0]
			self.xml_method_args = self.xml_method_args[1:]

	def find_class_definition(self, basic_type):
		basic_type = strip_leading_linphone(basic_type)
		for c in self.linphone_module.classes:
			if c['class_name'] == basic_type:
				return c
		return None

	def find_property_definition(self, basic_type, property_name):
		class_definition = self.find_class_definition(basic_type)
		if class_definition is None:
			return None
		for p in class_definition['class_properties']:
			if p['property_name'] == property_name:
				return p
		return None

	def format(self):
		self.parse_method_node()
		body = self.format_local_variables_definition()
		body += self.format_deprecation_warning()
		body += self.format_arguments_parsing()
		body += self.format_enter_trace()
		body += self.format_c_function_call()
		body += self.format_return_trace()
		body += self.format_return_result()
		return body

class NewMethodDefinition(MethodDefinition):
	def __init__(self, linphone_module, class_, method_node = None):
		MethodDefinition.__init__(self, linphone_module, class_, "new", method_node)

	def format_local_variables_definition(self):
		return "\tpylinphone_{class_name}Object *self = (pylinphone_{class_name}Object *)type->tp_alloc(type, 0);\n".format(class_name=self.class_['class_name'])

	def format_arguments_parsing(self):
		return ''

	def format_enter_trace(self):
		return "\tpylinphone_trace(1, \"[PYLINPHONE] >>> %s()\", __FUNCTION__);\n"

	def format_c_function_call(self):
		return ''

	def format_return_trace(self):
		return "\tpylinphone_trace(-1, \"[PYLINPHONE] <<< %s -> %p\", __FUNCTION__, self);\n"

	def format_return_result(self):
		return "\treturn (PyObject *)self;"

class InitMethodDefinition(MethodDefinition):
	def __init__(self, linphone_module, class_, method_node = None):
		MethodDefinition.__init__(self, linphone_module, class_, "init", method_node)

	def format_local_variables_definition(self):
		return "\tpylinphone_{class_name}Object *self_obj = (pylinphone_{class_name}Object *)self;\n".format(class_name=self.class_['class_name'])

	def format_arguments_parsing(self):
		return ''

	def format_enter_trace(self):
		return "\tpylinphone_trace(1, \"[PYLINPHONE] >>> %s()\", __FUNCTION__);\n"

	def format_c_function_call(self):
		specific_member_initialization_code = ''
		for member in self.class_['class_object_members']:
			specific_member_initialization_code += "\tself_obj->{member} = NULL;\n".format(member=member)
		return \
"""	self_obj->native_ptr = NULL;
	self_obj->user_data = NULL;
{specific_member_initialization_code}
""".format(specific_member_initialization_code=specific_member_initialization_code)

	def format_return_trace(self):
		return "\tpylinphone_trace(-1, \"[PYLINPHONE] <<< %s -> %p\", __FUNCTION__, self);\n"

	def format_return_result(self):
		return "\treturn 0;"

class FromNativePointerMethodDefinition(MethodDefinition):
	def __init__(self, linphone_module, class_):
		MethodDefinition.__init__(self, linphone_module, class_, "from_native_pointer", None)

	def format_local_variables_definition(self):
		return "\tpylinphone_{class_name}Object *self = NULL;\n".format(class_name=self.class_['class_name'])

	def format_arguments_parsing(self):
		return ''

	def format_enter_trace(self):
		return "\tpylinphone_trace(1, \"[PYLINPHONE] >>> %s(%p)\", __FUNCTION__, native_ptr);\n"

	def format_c_function_call(self):
		get_user_data_func_call = ''
		set_user_data_func_call = ''
		if self.class_['class_has_user_data']:
			get_user_data_func_call = "self = (pylinphone_{class_name}Object *){function_prefix}get_user_data(native_ptr);".format(class_name=self.class_['class_name'], function_prefix=self.class_['class_c_function_prefix'])
			set_user_data_func_call = "{function_prefix}set_user_data(self->native_ptr, self);".format(function_prefix=self.class_['class_c_function_prefix'])
		ref_native_pointer_code = ''
		if self.class_['class_refcountable']:
			ref_native_pointer_code = "if (take_native_ref == TRUE) {func}(self->native_ptr);".format(func=self.class_['class_c_function_prefix'] + "ref")
		return \
"""	if (native_ptr == NULL) {{
	{none_trace}
		Py_RETURN_NONE;
	}}
	{get_user_data_func_call}
	if (self == NULL) {{
		self = (pylinphone_{class_name}Object *)PyObject_CallObject((PyObject *)&pylinphone_{class_name}Type, NULL);
		if (self == NULL) {{
		{none_trace}
			Py_RETURN_NONE;
		}}
		self->native_ptr = ({class_cname} *)native_ptr;
		{set_user_data_func_call}
		{ref_native_pointer_code}
	}}
""".format(class_name=self.class_['class_name'], class_cname=self.class_['class_cname'],
		none_trace=self.format_return_none_trace(),
		get_user_data_func_call=get_user_data_func_call, set_user_data_func_call=set_user_data_func_call,
		ref_native_pointer_code=ref_native_pointer_code)

	def format_return_trace(self):
		return "\tpylinphone_trace(-1, \"[PYLINPHONE] <<< %s -> %p\", __FUNCTION__, self);\n"

	def format_return_result(self):
		return "\treturn (PyObject *)self;"

class DeallocMethodDefinition(MethodDefinition):
	def __init__(self, linphone_module, class_, method_node = None):
		MethodDefinition.__init__(self, linphone_module, class_, "dealloc", method_node)

	def format_local_variables_definition(self):
		func = "pylinphone_{class_name}_get_native_ptr".format(class_name=self.class_['class_name'])
		return \
"""	{arg_type} * native_ptr = {func}(self);
""".format(arg_type=self.class_['class_cname'], func=func)

	def format_arguments_parsing(self):
		# Check that the dealloc is not called a second time because of reentrancy
		return "\tif (Py_REFCNT(self) < 0) return;\n"

	def format_enter_trace(self):
		return "\tpylinphone_trace(1, \"[PYLINPHONE] >>> %s(%p [%p])\", __FUNCTION__, self, native_ptr);\n"

	def format_c_function_call(self):
		reset_user_data_code = ''
		if self.class_['class_name'] != 'Core' and self.class_['class_has_user_data']:
			reset_user_data_code += \
"""if (native_ptr != NULL) {{
		{function_prefix}set_user_data(native_ptr, NULL);
	}}
""".format(function_prefix=self.class_['class_c_function_prefix'])
		native_ptr_dealloc_code = ''
		specific_member_decref_code = ''
		if self.class_['class_refcountable']:
			native_ptr_dealloc_code += \
"""	if (native_ptr != NULL) {{
		{function_prefix}unref(native_ptr);
	}}
""".format(function_prefix=self.class_['class_c_function_prefix'])
		elif self.class_['class_destroyable']:
			native_ptr_dealloc_code += \
"""	if (native_ptr != NULL) {{
		{function_prefix}destroy(native_ptr);
	}}
""".format(function_prefix=self.class_['class_c_function_prefix'])
		for member in self.class_['class_object_members']:
			specific_member_decref_code += "\tPy_XDECREF(((pylinphone_{class_name}Object *)self)->{member});\n".format(class_name=self.class_['class_name'], member=member)
		return \
"""	{reset_user_data_code}
	{native_ptr_dealloc_code}
	pylinphone_dispatch_messages();
	Py_XDECREF(((pylinphone_{class_name}Object *)self)->user_data);
{specific_member_decref_code}
	self->ob_type->tp_free(self);
""".format(class_name=self.class_['class_name'], reset_user_data_code=reset_user_data_code, native_ptr_dealloc_code=native_ptr_dealloc_code, specific_member_decref_code=specific_member_decref_code)

	def format_return_trace(self):
		return "\tpylinphone_trace(-1, \"[PYLINPHONE] <<< %s\", __FUNCTION__);"

	def format_return_result(self):
		return ''

	def format(self):
		return \
"""static void pylinphone_{class_name}_dealloc(PyObject *self) {{
{method_body}
}}""".format(class_name=self.class_['class_name'], method_body=MethodDefinition.format(self))

class GetterMethodDefinition(MethodDefinition):
	def __init__(self, linphone_module, class_, method_name = "", method_node = None):
		MethodDefinition.__init__(self, linphone_module, class_, method_name, method_node)

class SetterMethodDefinition(MethodDefinition):
	def __init__(self, linphone_module, class_, method_name = "", method_node = None):
		MethodDefinition.__init__(self, linphone_module, class_, method_name, method_node)

	def format_arguments_parsing(self):
		if self.first_argument_type.check_condition is None:
			attribute_type_check_code = \
"""if ((value != Py_None) && !PyObject_IsInstance(value, (PyObject *)&pylinphone_{class_name}Type)) {{
		PyErr_SetString(PyExc_TypeError, "The '{attribute_name}' attribute value must be a linphone.{class_name} instance.");
		return -1;
	}}
""".format(class_name=self.first_arg_class, attribute_name=self.attribute_name)
		else:
			checknotnone = ''
			if self.first_argument_type.type_str == 'string':
				checknotnone = "(value != Py_None) && "
			attribute_type_check_code = \
"""if ({checknotnone}{check_condition}) {{
		PyErr_SetString(PyExc_TypeError, "The '{attribute_name}' attribute value must be a {type_str}.");
		return -1;
	}}
""".format(checknotnone=checknotnone, check_condition=self.first_argument_type.check_condition.format(arg_name='value'), attribute_name=self.attribute_name, type_str=self.first_argument_type.type_str)
		attribute_conversion_code = ''
		callback_setting_code = ''
		if is_callback(self.first_argument_type.complete_type):
			callback_setting_code = \
"""Py_XDECREF(((pylinphone_{class_name}Object *)self)->{callback_name});
	Py_INCREF(value);
	((pylinphone_{class_name}Object *)self)->{callback_name} = value;
""".format(class_name=self.class_['class_name'], callback_name=compute_event_name(self.first_arg_complete_type, self.class_['class_name']))
		if (self.first_argument_type.convert_code is None) or \
			(self.first_argument_type.fmt_str == 'O' and self.first_argument_type.convert_code is not None):
			attribute_conversion_code += "{arg_name} = value;\n".format(arg_name="_" + self.first_arg_name)
		if self.first_argument_type.convert_code is not None:
			cast_code = ''
			suffix = ''
			if self.first_argument_type.cast_convert_func_result:
				cast_code = "({arg_type})".format(arg_type=self.first_arg_complete_type)
			if self.first_argument_type.fmt_str == 'O' and self.first_argument_type.convert_code is not None:
				suffix = '_native_obj'
			attribute_conversion_code += self.first_argument_type.convert_code.format(result_name="_" + self.first_arg_name, result_suffix=suffix, cast=cast_code, arg_name='value')
		attribute_native_ptr_check_code = ''
		if self.first_argument_type.use_native_pointer:
			attribute_native_ptr_check_code = \
"""if ({arg_name} != Py_None) {{
		if (({arg_name}_native_ptr = pylinphone_{arg_class}_get_native_ptr({arg_name})) == NULL) {{
			PyErr_SetString(PyExc_TypeError, "Invalid linphone.{arg_class} instance.");
			return -1;
		}}
	}}
""".format(arg_name="_" + self.first_arg_name, arg_class=self.first_arg_class)
		return \
"""	{native_ptr_check_code}
	if (value == NULL) {{
		PyErr_SetString(PyExc_TypeError, "Cannot delete the '{attribute_name}' attribute.");
		return -1;
	}}
	{attribute_type_check_code}
	{attribute_conversion_code}
	{callback_setting_code}
	{attribute_native_ptr_check_code}
""".format(attribute_name=self.attribute_name,
		native_ptr_check_code=self.format_class_native_pointer_check(True),
		attribute_type_check_code=attribute_type_check_code,
		attribute_conversion_code=attribute_conversion_code,
		callback_setting_code=callback_setting_code,
		attribute_native_ptr_check_code=attribute_native_ptr_check_code)

	def format_c_function_call(self):
		if is_callback(self.first_argument_type.complete_type):
			return \
"""	{method_name}(native_ptr, pylinphone_{class_name}_callback_{callback_name});
	pylinphone_dispatch_messages();
""".format(method_name=self.method_node.get('name'), class_name=self.class_['class_name'], callback_name=compute_event_name(self.first_argument_type.complete_type, self.class_['class_name']))
		cfree_argument_code = ''
		suffix = ''
		if self.first_argument_type.fmt_str == 'O' and self.first_argument_type.use_native_pointer:
			suffix = '_native_ptr'
		elif self.first_argument_type.fmt_str == 'O' and self.first_argument_type.convert_code is not None:
			suffix = '_native_obj'
			if self.first_argument_type.free_convert_result_func is not None and not is_const_from_complete_type(self.first_argument_type.complete_type):
					cfree_argument_code = \
"""{free_func}({arg_name}_native_obj);
""".format(free_func=self.first_argument_type.free_convert_result_func, arg_name="_" + self.first_arg_name)
		return \
"""	{method_name}(native_ptr, {arg_name}{suffix});
	{cfree_argument_code}
	pylinphone_dispatch_messages();
""".format(arg_name="_" + self.first_arg_name, method_name=self.method_node.get('name'), suffix=suffix, cfree_argument_code=cfree_argument_code)

	def format_return_trace(self):
		return "\tpylinphone_trace(-1, \"[PYLINPHONE] <<< %s -> 0\", __FUNCTION__);\n"

	def format_return_result(self):
		return "\treturn 0;"

	def parse_method_node(self):
		MethodDefinition.parse_method_node(self)
		# Force return value type of setter function to prevent declaring useless local variables
		# TODO: Investigate. Maybe we should decide that setters must always return an int value.
		self.xml_method_return = None
		self.attribute_name = self.method_node.get('property_name')
		self.first_arg_type = self.xml_method_args[0].get('type')
		self.first_arg_complete_type = self.xml_method_args[0].get('completetype')
		self.first_arg_contained_type = self.xml_method_args[0].get('containedtype')
		self.first_arg_name = self.xml_method_args[0].get('name')
		self.first_argument_type = ArgumentType(self.first_arg_type, self.first_arg_complete_type, self.first_arg_contained_type, self.linphone_module)
		self.first_arg_class = strip_leading_linphone(self.first_arg_type)

class EventCallbackMethodDefinition(MethodDefinition):
	def __init__(self, linphone_module, class_, method_name = "", method_node = None):
		MethodDefinition.__init__(self, linphone_module, class_, method_name, method_node)

	def format_local_variables_definition(self):
		class_name = self.class_['event_class']
		nocallbacks_class_name = class_name
		if class_name.endswith('Cbs'):
			nocallbacks_class_name = class_name[:-3]
		has_current_callbacks = self.find_property_definition(nocallbacks_class_name, 'current_callbacks')
		if has_current_callbacks is not None:
			get_callbacks_funcname = 'get_current_callbacks'
		else:
			get_callbacks_funcname = 'get_callbacks'
		returnvars = self.format_local_return_variables_definition()
		common = \
"""	pylinphone_{class_name}Object *pyself = (pylinphone_{class_name}Object *){function_prefix}get_user_data(self);
	PyObject *func;
	PyObject *args;
	PyGILState_STATE pygil_state;""".format(class_name=nocallbacks_class_name, function_prefix=self.find_class_definition(nocallbacks_class_name)['class_c_function_prefix'])
		if class_name.endswith('Cbs'):
			common += """
	pylinphone_{class_name}Object *pycbs = (pylinphone_{class_name}Object *){cbs_function_prefix}get_user_data({function_prefix}{get_callbacks_funcname}(self));
""".format(class_name=class_name, cbs_function_prefix=self.find_class_definition(class_name)['class_c_function_prefix'], function_prefix=self.find_class_definition(nocallbacks_class_name)['class_c_function_prefix'], get_callbacks_funcname=get_callbacks_funcname)
		specific = ''
		for xml_method_arg in self.xml_method_args:
			arg_name = xml_method_arg.get('name')
			arg_type = xml_method_arg.get('type')
			arg_complete_type = xml_method_arg.get('completetype')
			arg_contained_type = xml_method_arg.get('containedtype')
			argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self.linphone_module)
			if argument_type.fmt_str == 'O':
				specific += "\tPyObject * py" + arg_name + " = NULL;\n"
		return "{returnvars}\n{common}\n{specific}".format(returnvars=returnvars, common=common, specific=specific)

	def format_arguments_parsing(self):
		return_str = ''
		if self.return_complete_type == 'int':
			return_str = '-1'
		elif self.return_complete_type == 'bool_t':
			return_str = 'FALSE'
		elif self.return_complete_type != 'void':
			argument_type = ArgumentType(self.return_type, self.return_complete_type, self.return_contained_type, self.linphone_module)
			if argument_type.fmt_str == 'O':
				return_str = 'NULL'
		return \
"""	if (Py_REFCNT(pyself) <= 0) return {return_str};
	func = pycbs->{event_name};
	pygil_state = PyGILState_Ensure();
""".format(event_name=self.class_['event_name'], return_str=return_str)

	def format_enter_trace(self):
		fmt = '%p'
		args = ['self']
		for xml_method_arg in self.xml_method_args:
			arg_name = xml_method_arg.get('name')
			arg_type = xml_method_arg.get('type')
			arg_complete_type = xml_method_arg.get('completetype')
			arg_contained_type = xml_method_arg.get('containedtype')
			if fmt != '':
				fmt += ', '
			argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self.linphone_module)
			fmt += argument_type.cfmt_str
			args.append(arg_name)
		args=', '.join(args)
		if args != '':
			args = ', ' + args
		return "\tpylinphone_trace(1, \"[PYLINPHONE] >>> %s({fmt})\", __FUNCTION__{args});\n".format(fmt=fmt, args=args)

	def format_c_function_call(self):
		create_python_objects_code = ''
		convert_python_result_code = ''
		fmt = 'O'
		args = ['pyself']
		for xml_method_arg in self.xml_method_args:
			arg_name = xml_method_arg.get('name')
			arg_type = xml_method_arg.get('type')
			arg_complete_type = xml_method_arg.get('completetype')
			arg_contained_type = xml_method_arg.get('containedtype')
			argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self.linphone_module)
			fmt += argument_type.fmt_str
			if argument_type.fmt_str == 'O':
				args.append('py' + arg_name)
			else:
				args.append(arg_name)
			if argument_type.fmt_str == 'O':
				if argument_type.type_str == "bool":
					create_python_objects_code += "\t\tpy{name} = {convert_from_func}({name});\n".format(name=arg_name, convert_from_func=argument_type.convert_from_func)
				else:
					type_class = self.find_class_definition(arg_type)
					create_python_objects_code += "\t\tpy{name} = pylinphone_{arg_type}_from_native_ptr(&pylinphone_{arg_type}Type, {name}, TRUE);\n".format(name=arg_name, arg_type=strip_leading_linphone(arg_type))
		args=', '.join(args)
		if self.return_complete_type != 'void':
			argument_type = ArgumentType(self.return_type, self.return_complete_type, self.return_contained_type, self.linphone_module)
			if argument_type.is_linphone_object:
				convert_python_result_code = \
"""		if ((pyresult != Py_None) && !PyObject_IsInstance(pyresult, (PyObject *)&pylinphone_{class_name}Type)) {{
			PyErr_SetString(PyExc_TypeError, "The return value must be a linphone.{class_name} instance.");
			return NULL;
		}}
		if ((cresult = pylinphone_{class_name}_get_native_ptr(pyresult)) == NULL) {{
			return NULL;
		}}
""".format(class_name=strip_leading_linphone(self.return_type))

			else:
				convert_python_result_code = '\t\t' + argument_type.convert_code.format(result_name='cresult', result_suffix='', cast='', arg_name='pyresult')
		return \
"""	if ((func != NULL) && PyCallable_Check(func)) {{
{create_python_objects_code}
		args = Py_BuildValue("{fmt}", {args});
		pyresult = PyEval_CallObject(func, args);
		if (pyresult == NULL) {{
			PyErr_Print();
		}}
		Py_DECREF(args);
{convert_python_result_code}
	}}
""".format(fmt=fmt, args=args, create_python_objects_code=create_python_objects_code, convert_python_result_code=convert_python_result_code)

	def format_return_trace(self):
		return "\tpylinphone_trace(-1, \"[PYLINPHONE] <<< %s\", __FUNCTION__);\n"

	def format_return_result(self):
		s = '\tPyGILState_Release(pygil_state);'
		if self.return_complete_type != 'void':
			s += '\n\treturn cresult;'
		return s

	def format_local_return_variables_definition(self):
		body = "\tPyObject * pyresult;"
		if self.xml_method_return is not None:
			self.return_type = self.xml_method_return.get('type')
			self.return_complete_type = self.xml_method_return.get('completetype')
			self.return_contained_type = self.xml_method_return.get('containedtype')
		if self.return_complete_type != 'void':
			body += "\n\t" + self.return_complete_type + " cresult;"
		return body

	def format(self):
		body = MethodDefinition.format(self)
		class_name = self.class_['event_class']
		nocallbacks_class_name = class_name
		if class_name.endswith('Cbs'):
			nocallbacks_class_name = class_name[:-3]
		arguments = ['Linphone' + nocallbacks_class_name + ' * self']
		for xml_method_arg in self.xml_method_args:
			arg_name = xml_method_arg.get('name')
			arg_type = xml_method_arg.get('type')
			arg_complete_type = xml_method_arg.get('completetype')
			arguments.append(arg_complete_type + ' ' + arg_name)
		definition = \
"""static {returntype} pylinphone_{class_name}_callback_{event_name}({arguments}) {{
{body}
}}
""".format(returntype=self.return_complete_type, class_name=class_name, event_name=self.class_['event_name'], arguments=', '.join(arguments), body=body)
		return definition


class LinphoneModule:
	def __init__(self, tree, blacklisted_classes, blacklisted_events, blacklisted_functions, hand_written_codes):
		self.known_types = ['char', 'int', 'int8_t', 'int16_t', 'int32_t', 'int64_t', 'uint8_t', 'uint16_t', 'uint32_t', 'uint64_t', 'bool_t', 'float', 'double', 'size_t', 'time_t', 'MSList', 'bctbx_list_t', 'MSVideoSize', 'LCSipTransports', 'LinphoneStatus']
		self.internal_instance_method_names = ['destroy', 'ref', 'unref']
		self.internal_property_names = ['user_data']
		self.bctbxlist_types = set([])
		self.enums = []
		self.enum_names = []
		self.cfunction2methodmap = {}
		hand_written_functions = []
		for hand_written_code in hand_written_codes:
			hand_written_functions += hand_written_code.func_list
		xml_enums = tree.findall("./enums/enum")
		for xml_enum in xml_enums:
			e = {}
			e['enum_cname'] = xml_enum.get('name')
			e['enum_name'] = strip_leading_linphone(e['enum_cname'])
			e['enum_doc'] = self.__format_doc_content(xml_enum.find('briefdescription'), xml_enum.find('detaileddescription'))
			e['enum_doc'] = self.__replace_doc_special_chars(e['enum_doc'])
			e['enum_doc'] += """

.. csv-table::
   :delim: |
   :widths: 30, 70
   :header: Value,Description

"""
			e['enum_values'] = []
			e['enum_deprecated_values'] = []
			xml_enum_values = xml_enum.findall("./values/value")
			for xml_enum_value in xml_enum_values:
				v = {}
				v['enum_value_cname'] = xml_enum_value.get('name')
				valname = strip_leading_linphone(v['enum_value_cname'])
				v['enum_value_name'] = remove_useless_enum_prefix(e['enum_name'], valname)
				v['enum_value_doc'] = self.__format_doc(xml_enum_value.find('briefdescription'), xml_enum_value.find('detaileddescription'))
				e['enum_doc'] += '   ' + v['enum_value_name'] + '|' + v['enum_value_doc'] + '\n'
				e['enum_values'].append(v)
				if v['enum_value_name'] != valname:
					# TODO: To remove. Add deprecated value name.
					v = {}
					v['enum_value_cname'] = xml_enum_value.get('name')
					v['enum_value_name'] = strip_leading_linphone(v['enum_value_cname'])
					v['enum_value_doc'] = self.__format_doc(xml_enum_value.find('briefdescription'), xml_enum_value.find('detaileddescription'))
					e['enum_deprecated_values'].append(v)
			e['enum_doc'] = self.__replace_doc_special_chars(e['enum_doc'])
			e['enum_doc'] = e['enum_doc'].encode('unicode_escape')
			self.enums.append(e)
			self.enum_names.append(e['enum_name'])
			self.known_types.append(e['enum_cname'])
		self.core_events = []
		self.classes = []
		xml_classes = tree.findall("./classes/class")
		for xml_class in xml_classes:
			if xml_class.get('name') in blacklisted_classes:
				continue
			c = {}
			c['class_xml_node'] = xml_class
			c['class_cname'] = xml_class.get('name')
			c['class_name'] = strip_leading_linphone(c['class_cname'])
			c['class_c_function_prefix'] = xml_class.get('cfunctionprefix')
			c['class_doc'] = self.__format_doc(xml_class.find('briefdescription'), xml_class.find('detaileddescription'))
			c['class_doc'] = c['class_doc'].encode('unicode_escape')
			c['class_refcountable'] = (xml_class.get('refcountable') == 'true')
			c['class_destroyable'] = (xml_class.get('destroyable') == 'true')
			c['class_has_user_data'] = False
			c['class_type_methods'] = []
			c['class_type_hand_written_methods'] = []
			c['class_instance_hand_written_methods'] = []
			c['class_hand_written_properties'] = []
			c['class_object_members'] = []
			c['class_object_members_code'] = ''
			c['class_events'] = []
			xml_events = xml_class.findall("./events/event")
			for xml_event in xml_events:
				if xml_event.get('name') in blacklisted_events:
						continue
				ev = {}
				ev['event_class'] = c['class_name']
				ev['event_xml_node'] = xml_event
				ev['event_cname'] = xml_event.get('name')
				ev['event_name'] = compute_event_name(ev['event_cname'], c['class_name'])
				ev['event_doc'] = self.__format_doc(xml_event.find('briefdescription'), xml_event.find('detaileddescription'))
				ev['event_doc'] = ev['event_doc'].encode('unicode_escape')
				c['class_events'].append(ev)
				self.known_types.append(ev['event_cname'])
				c['class_object_members'].append(ev['event_name'])
				c['class_object_members_code'] += "\tPyObject *" + ev['event_name'] + ";\n"
			for hand_written_code in hand_written_codes:
				if hand_written_code._class == c['class_name']:
					if isinstance(hand_written_code, HandWrittenClassMethod):
						m = {}
						m['method_name'] = hand_written_code.name
						m['method_doc'] = self.__replace_doc_special_chars(hand_written_code.doc)
						m['method_doc'] = m['method_doc'].encode('unicode_escape')
						c['class_type_hand_written_methods'].append(m)
					elif isinstance(hand_written_code, HandWrittenInstanceMethod):
						m = {}
						m['method_name'] = hand_written_code.name
						m['method_doc'] = self.__replace_doc_special_chars(hand_written_code.doc)
						m['method_doc'] = m['method_doc'].encode('unicode_escape')
						c['class_instance_hand_written_methods'].append(m)
					elif isinstance(hand_written_code, HandWrittenDeallocMethod):
						c['class_has_hand_written_dealloc'] = True
					elif isinstance(hand_written_code, HandWrittenProperty):
						p = {}
						p['property_name'] = hand_written_code.name
						if hand_written_code.getter_cfunction is None:
							p['getter_reference'] = 'NULL'
						else:
							p['getter_reference'] = '(getter)pylinphone_' + c['class_name'] + '_get_' + p['property_name']
						if hand_written_code.setter_cfunction is None:
							p['setter_reference'] = 'NULL'
						else:
							p['setter_reference'] = '(setter)pylinphone_' + c['class_name'] + '_set_' + p['property_name']
						p['property_doc'] = self.__replace_doc_special_chars(hand_written_code.doc)
						p['property_doc'] = p['property_doc'].encode('unicode_escape')
						c['class_hand_written_properties'].append(p)
			xml_type_methods = xml_class.findall("./classmethods/classmethod")
			for xml_type_method in xml_type_methods:
				method_name = xml_type_method.get('name')
				if method_name in blacklisted_functions:
					continue
				m = {}
				m['method_name'] = method_name.replace(c['class_c_function_prefix'], '')
				if method_name not in hand_written_functions:
					m['method_xml_node'] = xml_type_method
					self.cfunction2methodmap[method_name] = ':py:meth:`linphone.' + c['class_name'] + '.' + m['method_name'] + '`'
					c['class_type_methods'].append(m)
			c['class_instance_methods'] = []
			xml_instance_methods = xml_class.findall("./instancemethods/instancemethod")
			for xml_instance_method in xml_instance_methods:
				method_name = xml_instance_method.get('name')
				if method_name in blacklisted_functions:
					continue
				if method_name.replace(c['class_c_function_prefix'], '') in self.internal_instance_method_names:
					continue
				m = {}
				m['method_name'] = method_name.replace(c['class_c_function_prefix'], '')
				if method_name not in hand_written_functions:
					m['method_xml_node'] = xml_instance_method
					self.cfunction2methodmap[method_name] = ':py:meth:`linphone.' + c['class_name'] + '.' + m['method_name'] + '`'
					c['class_instance_methods'].append(m)
			c['class_properties'] = []
			xml_properties = xml_class.findall("./properties/property")
			for xml_property in xml_properties:
				property_name = xml_property.get('name')
				if property_name == 'user_data':
					c['class_has_user_data'] = True
				if property_name in self.internal_property_names:
					continue
				p = {}
				p['property_name'] = property_name
				xml_property_getter = xml_property.find("./getter")
				xml_property_setter = xml_property.find("./setter")
				if xml_property_getter is not None:
					if xml_property_getter.get('name') in blacklisted_functions or xml_property_getter.get('name') in hand_written_functions:
						continue
				if xml_property_setter is not None:
					if xml_property_setter.get('name') in blacklisted_functions or xml_property_setter.get('name') in hand_written_functions:
						continue
				if xml_property_getter is not None:
					xml_property_getter.set('property_name', property_name)
					p['getter_name'] = xml_property_getter.get('name').replace(c['class_c_function_prefix'], '')
					p['getter_xml_node'] = xml_property_getter
					p['getter_reference'] = "(getter)pylinphone_" + c['class_name'] + "_" + p['getter_name']
					p['getter_definition_begin'] = "static PyObject * pylinphone_" + c['class_name'] + "_" + p['getter_name'] + "(PyObject *self, void *closure) {"
					p['getter_definition_end'] = "}"
					self.cfunction2methodmap[xml_property_getter.get('name')] = ':py:attr:`linphone.' + c['class_name'] + '.' + property_name + '`'
				else:
					p['getter_reference'] = "NULL"
				if xml_property_setter is not None:
					xml_property_setter.set('property_name', property_name)
					p['setter_name'] = xml_property_setter.get('name').replace(c['class_c_function_prefix'], '')
					p['setter_xml_node'] = xml_property_setter
					p['setter_reference'] = "(setter)pylinphone_" + c['class_name'] + "_" + p['setter_name']
					p['setter_definition_begin'] = "static int pylinphone_" + c['class_name'] + "_" + p['setter_name'] + "(PyObject *self, PyObject *value, void *closure) {"
					p['setter_definition_end'] = "}"
					self.cfunction2methodmap[xml_property_setter.get('name')] = ':py:attr:`linphone.' + c['class_name'] + '.' + property_name + '`'
				else:
					p['setter_reference'] = "NULL"
				c['class_properties'].append(p)
			self.classes.append(c)
			self.known_types.append(c['class_cname'])
		# Format events definitions
		for c in self.classes:
			for ev in c['class_events']:
				ev['event_callback_definition'] = EventCallbackMethodDefinition(self, ev, ev['event_name'], ev['event_xml_node']).format()
		# Format methods' bodies
		for c in self.classes:
			xml_new_method = c['class_xml_node'].find("./classmethods/classmethod[@name='" + c['class_c_function_prefix'] + "new']")
			try:
				c['new_body'] = NewMethodDefinition(self, c, xml_new_method).format()
			except (UnknownTypeException) as e:
				print(e)
				c['blacklisted'] = True
			except (Exception) as e:
				e.args += (c['class_name'], 'new_body')
				raise
			try:
				c['init_body'] = InitMethodDefinition(self, c, xml_new_method).format()
			except (UnknownTypeException) as e:
				print(e)
				c['blacklisted'] = True
			except (Exception) as e:
				e.args += (c['class_name'], 'init_body')
				raise
			try:
				c['from_native_pointer_body'] = FromNativePointerMethodDefinition(self, c).format()
			except (UnknownTypeException) as e:
				print(e)
				c['blacklisted'] = True
			except (Exception) as e:
				e.args += (c['class_name'], 'from_native_pointer_body')
				raise
			for m in c['class_type_methods']:
				try:
					m['method_body'] = MethodDefinition(self, c, m['method_name'], m['method_xml_node']).format()
					m['method_doc'] = self.__format_method_doc(m['method_xml_node'])
					m['method_doc'] = m['method_doc'].encode('unicode_escape')
				except (UnknownTypeException) as e:
					print(e)
					m['blacklisted'] = True
				except (Exception) as e:
					e.args += (c['class_name'], m['method_name'])
					raise
			for m in c['class_instance_methods']:
				try:
					m['method_body'] = MethodDefinition(self, c, m['method_name'], m['method_xml_node']).format()
					m['method_doc'] = self.__format_method_doc(m['method_xml_node'])
					m['method_doc'] = m['method_doc'].encode('unicode_escape')
				except (UnknownTypeException) as e:
					print(e)
					m['blacklisted'] = True
				except (Exception) as e:
					e.args += (c['class_name'], m['method_name'])
					raise
			for p in c['class_properties']:
				p['property_doc'] = ''
				if 'setter_xml_node' in p:
					try:
						p['setter_body'] = SetterMethodDefinition(self, c, p['property_name'], p['setter_xml_node']).format()
						p['property_doc'] = self.__format_setter_doc(p['setter_xml_node'])
					except (UnknownTypeException) as e:
						print(e)
						p['blacklisted'] = True
					except (Exception) as e:
						e.args += (c['class_name'], p['property_name'])
						raise
				if 'getter_xml_node' in p:
					try:
						p['getter_body'] = GetterMethodDefinition(self, c, p['property_name'], p['getter_xml_node']).format()
						if p['property_doc'] == '':
							p['property_doc'] = self.__format_getter_doc(p['getter_xml_node'])
					except (UnknownTypeException) as e:
						print(e)
						p['blacklisted'] = True
					except (Exception) as e:
						e.args += (c['class_name'], p['property_name'])
						raise
				p['property_doc'] = p['property_doc'].encode('unicode_escape')
			if not 'class_has_hand_written_dealloc' in c:
				try:
					if c['class_refcountable']:
						xml_instance_method = c['class_xml_node'].find("./instancemethods/instancemethod[@name='" + c['class_c_function_prefix'] + "unref']")
						c['dealloc_definition'] = DeallocMethodDefinition(self, c, xml_instance_method).format()
					elif c['class_destroyable']:
						xml_instance_method = c['class_xml_node'].find("./instancemethods/instancemethod[@name='" + c['class_c_function_prefix'] + "destroy']")
						c['dealloc_definition'] = DeallocMethodDefinition(self, c, xml_instance_method).format()
					else:
						c['dealloc_definition'] = DeallocMethodDefinition(self, c).format()
				except (UnknownTypeException) as e:
					print(e)
					c['blacklisted'] = True
				except (Exception) as e:
					e.args += (c['class_name'], 'dealloc_body')
					raise
		# Remove blacklisted classes and methods
		self.classes = [c for c in self.classes if not 'blacklisted' in c]
		for c in self.classes:
			c['class_type_methods'] = [m for m in c['class_type_methods'] if not 'blacklisted' in m]
			c['class_instance_methods'] = [m for m in c['class_instance_methods'] if not 'blacklisted' in m]
			c['class_properties'] = [m for m in c['class_properties'] if not 'blacklisted' in m]
		# Convert bctbxlist_types to a list of dictionaries for the template
		d = []
		for bctbxlist_type in self.bctbxlist_types:
			t = {}
			t['c_contained_type'] = bctbxlist_type
			t['python_contained_type'] = strip_leading_linphone(bctbxlist_type)
			d.append(t)
		self.bctbxlist_types = d

	def __format_doc_node(self, node):
		desc = ''
		if node.tag == 'para':
			if node.text is not None:
				desc += node.text.strip()
			for n in list(node):
				desc += self.__format_doc_node(n)
		elif node.tag == 'note':
			if node.text is not None:
				desc += node.text.strip()
			for n in list(node):
				desc += self.__format_doc_node(n)
		elif node.tag == 'ref':
			if node.text is not None:
				desc += ' ' + node.text.strip() + ' '
		tail = node.tail.strip()
		if tail != '':
			if node.tag != 'ref':
				desc += '\n'
			desc += tail
		if node.tag == 'para':
			desc += '\n'
		return desc

	def __format_doc_content(self, brief_description, detailed_description):
		doc = ''
		if brief_description is None:
			brief_description = ''
		else:
			brief_description = brief_description.text
			
		if detailed_description is None:
			detailed_description = ''
		else:
			desc = ''
			for node in list(detailed_description):
				desc += self.__format_doc_node(node) + '\n'
			detailed_description = desc.strip()
		brief_description = brief_description.strip()
		doc += brief_description
		if detailed_description != '':
			if doc != '':
				doc += '\n\n'
			doc += detailed_description
		return doc

	def __replace_doc_special_chars(self, doc):
		return doc.replace('"', '') #.encode('utf-8') #.encode('unicode_escape')

	def __replace_doc_cfunction_by_method(self, doc):
		for cfunction, method in six.iteritems(self.cfunction2methodmap):
			doc = doc.replace(cfunction + '()', method)
		for cfunction, method in six.iteritems(self.cfunction2methodmap):
			doc = doc.replace(cfunction, method)
		return doc

	def __replace_doc_keywords(self, doc):
		return doc.replace('NULL', 'None')

	def __format_doc(self, brief_description, detailed_description):
		doc = self.__format_doc_content(brief_description, detailed_description)
		doc = self.__replace_doc_cfunction_by_method(doc)
		doc = self.__replace_doc_keywords(doc)
		doc = self.__replace_doc_special_chars(doc)
		return doc

	def __format_method_doc(self, xml_node):
		doc = self.__format_doc_content(xml_node.find('briefdescription'), xml_node.find('detaileddescription'))
		xml_method_return = xml_node.find('./return')
		xml_method_args = xml_node.findall('./arguments/argument')
		method_type = xml_node.tag
		if method_type != 'classmethod' and len(xml_method_args) > 0:
			xml_method_args = xml_method_args[1:]
		doc += '\n'
		if len(xml_method_args) > 0:
			for xml_method_arg in xml_method_args:
				arg_name = xml_method_arg.get('name')
				arg_type = xml_method_arg.get('type')
				arg_complete_type = xml_method_arg.get('completetype')
				arg_contained_type = xml_method_arg.get('containedtype')
				argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self)
				arg_doc = self.__format_doc_content(None, xml_method_arg.find('description'))
				doc += '\n:param ' + arg_name + ':'
				if arg_doc != '':
					doc += ' ' + arg_doc
				doc += '\n:type ' + arg_name + ': ' + argument_type.type_str
		if xml_method_return is not None:
			return_type = xml_method_return.get('type')
			return_complete_type = xml_method_return.get('completetype')
			return_contained_type = xml_method_return.get('containedtype')
			if return_complete_type != 'void':
				return_doc = self.__format_doc_content(None, xml_method_return.find('description'))
				return_argument_type = ArgumentType(return_type, return_complete_type, return_contained_type, self)
				doc += '\n:returns: ' + return_doc
				doc += '\n:rtype: ' + return_argument_type.type_str
		doc = self.__replace_doc_cfunction_by_method(doc)
		doc = self.__replace_doc_keywords(doc)
		doc = self.__replace_doc_special_chars(doc)
		return doc

	def __format_setter_doc(self, xml_node):
		xml_method_arg = xml_node.findall('./arguments/argument')[1]
		arg_type = xml_method_arg.get('type')
		arg_complete_type = xml_method_arg.get('completetype')
		arg_contained_type = xml_method_arg.get('containedtype')
		argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self)
		doc = self.__format_doc_content(xml_node.find('briefdescription'), xml_node.find('detaileddescription'))
		doc = '[' + argument_type.type_str + '] ' + doc
		doc = self.__replace_doc_cfunction_by_method(doc)
		doc = self.__replace_doc_keywords(doc)
		doc = self.__replace_doc_special_chars(doc)
		return doc

	def __format_getter_doc(self, xml_node):
		xml_method_return = xml_node.find('./return')
		return_type = xml_method_return.get('type')
		return_complete_type = xml_method_return.get('completetype')
		return_contained_type = xml_method_return.get('containedtype')
		return_argument_type = ArgumentType(return_type, return_complete_type, return_contained_type, self)
		doc = self.__format_doc_content(xml_node.find('briefdescription'), xml_node.find('detaileddescription'))
		doc = '[' + return_argument_type.type_str + '] ' + doc
		doc = self.__replace_doc_cfunction_by_method(doc)
		doc = self.__replace_doc_keywords(doc)
		doc = self.__replace_doc_special_chars(doc)
		return doc
