/* valagdbusclientmodule.vala
 *
 * Copyright (C) 2010-2011  Jürg Billeter
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.

 * This library 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
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 *
 * Author:
 * 	Jürg Billeter <j@bitron.ch>
 * 	Philip Van Hoof <pvanhoof@gnome.org>
 */

public class Vala.GDBusClientModule : GDBusModule {
	enum CallType {
		SYNC,
		ASYNC,
		FINISH,
		NO_REPLY
	}

	public CCodeConstant get_dbus_timeout (Symbol symbol) {
		int timeout = -1;

		var dbus = symbol.get_attribute ("DBus");
		if (dbus != null && dbus.has_argument ("timeout")) {
			timeout = dbus.get_integer ("timeout");
		} else if (symbol.parent_symbol != null) {
			return get_dbus_timeout (symbol.parent_symbol);
		}

		return new CCodeConstant (timeout.to_string ());
	}

	public override void generate_dynamic_method_wrapper (DynamicMethod method) {
		var func = new CCodeFunction (get_ccode_name (method));
		func.modifiers = CCodeModifiers.STATIC;

		var cparam_map = new HashMap<int,CCodeParameter> (direct_hash, direct_equal);

		generate_cparameters (method, cfile, cparam_map, func);

		push_function (func);

		if (method.dynamic_type.type_symbol == dbus_proxy_type) {
			generate_marshalling (method, CallType.SYNC, null, method.name, -1);
		} else {
			Report.error (method.source_reference, "dynamic methods are not supported for `%s'", method.dynamic_type.to_string ());
		}

		pop_function ();

		cfile.add_function_declaration (func);
		cfile.add_function (func);
	}

	void generate_proxy_interface_init (Interface main_iface, Interface iface) {
		// also generate proxy for prerequisites
		foreach (var prereq in iface.get_prerequisites ()) {
			if (prereq.type_symbol is Interface) {
				generate_proxy_interface_init (main_iface, (Interface) prereq.type_symbol);
			}
		}

		string lower_cname = get_ccode_lower_case_prefix (main_iface) + "proxy";

		var proxy_iface_init = new CCodeFunction (lower_cname + "_" + get_ccode_lower_case_prefix (iface) + "interface_init", "void");
		proxy_iface_init.add_parameter (new CCodeParameter ("iface", get_ccode_name (iface) + "Iface*"));

		push_function (proxy_iface_init);

		foreach (Method m in iface.get_methods ()) {
			if (!m.is_abstract) {
				continue;
			}

			var vfunc_entry = new CCodeMemberAccess.pointer (new CCodeIdentifier ("iface"), get_ccode_vfunc_name (m));
			if (!m.coroutine) {
				ccode.add_assignment (vfunc_entry, new CCodeIdentifier (generate_dbus_proxy_method (main_iface, iface, m)));
			} else {
				ccode.add_assignment (vfunc_entry, new CCodeIdentifier (generate_async_dbus_proxy_method (main_iface, iface, m)));
				vfunc_entry = new CCodeMemberAccess.pointer (new CCodeIdentifier ("iface"), get_ccode_finish_vfunc_name (m));
				ccode.add_assignment (vfunc_entry, new CCodeIdentifier (generate_finish_dbus_proxy_method (main_iface, iface, m)));
			}
		}

		foreach (Property prop in iface.get_properties ()) {
			if (!prop.is_abstract) {
				continue;
			}

			if (prop.get_accessor != null && prop.get_accessor.access != SymbolAccessibility.PRIVATE) {
				var vfunc_entry = new CCodeMemberAccess.pointer (new CCodeIdentifier ("iface"), "get_" + prop.name);
				ccode.add_assignment (vfunc_entry, new CCodeIdentifier (generate_dbus_proxy_property_get (main_iface, iface, prop)));
			}
			if (prop.set_accessor != null && prop.set_accessor.access != SymbolAccessibility.PRIVATE && prop.set_accessor.writable) {
				var vfunc_entry = new CCodeMemberAccess.pointer (new CCodeIdentifier ("iface"), "set_" + prop.name);
				ccode.add_assignment (vfunc_entry, new CCodeIdentifier (generate_dbus_proxy_property_set (main_iface, iface, prop)));
			}
		}

		proxy_iface_init.modifiers = CCodeModifiers.STATIC;
		pop_function ();
		cfile.add_function_declaration (proxy_iface_init);
		cfile.add_function (proxy_iface_init);
	}

	string implement_interface (CCodeFunctionCall define_type, Interface main_iface, Interface iface) {
		string result = "";

		// also implement prerequisites
		foreach (var prereq in iface.get_prerequisites ()) {
			if (prereq.type_symbol is Interface) {
				result += implement_interface (define_type, main_iface, (Interface) prereq.type_symbol);
			}
		}

		string interface_macro;

		if (in_plugin) {
			interface_macro = "G_IMPLEMENT_INTERFACE_DYNAMIC";
		} else {
			interface_macro = "G_IMPLEMENT_INTERFACE";
		}

		result += "%s (%s, %sproxy_%sinterface_init) ".printf (
			interface_macro,
			get_ccode_upper_case_name (iface, "TYPE_"),
			get_ccode_lower_case_prefix (main_iface),
			get_ccode_lower_case_prefix (iface));
		return result;
	}

	public override void generate_interface_declaration (Interface iface, CCodeFile decl_space) {
		base.generate_interface_declaration (iface, decl_space);

		string dbus_iface_name = get_dbus_name (iface);
		if (dbus_iface_name == null) {
			return;
		}

		string get_type_name = "%sproxy_get_type".printf (get_ccode_lower_case_prefix (iface));

		if (add_symbol_declaration (decl_space, iface, get_type_name)) {
			return;
		}

		decl_space.add_type_declaration (new CCodeNewline ());
		var macro = "(%s ())".printf (get_type_name);
		decl_space.add_type_declaration (new CCodeMacroReplacement ("%s_PROXY".printf (get_ccode_type_id (iface)), macro));

		// declare proxy_get_type function
		var proxy_get_type = new CCodeFunction (get_type_name, "GType");
		proxy_get_type.modifiers = CCodeModifiers.CONST | CCodeModifiers.EXTERN;
		requires_vala_extern = true;

		decl_space.add_function_declaration (proxy_get_type);

		if (in_plugin) {
			var proxy_register_type = new CCodeFunction ("%sproxy_register_dynamic_type".printf (get_ccode_lower_case_prefix (iface)));
			proxy_register_type.add_parameter (new CCodeParameter ("module", "GTypeModule*"));
			proxy_register_type.modifiers |= CCodeModifiers.EXTERN;
			requires_vala_extern = true;

			decl_space.add_function_declaration (proxy_register_type);
		}
	}

	public override void visit_interface (Interface iface) {
		base.visit_interface (iface);

		string dbus_iface_name = get_dbus_name (iface);
		if (dbus_iface_name == null) {
			return;
		}

		cfile.add_include ("gio/gio.h");

		// create proxy class
		string cname = get_ccode_name (iface) + "Proxy";
		string lower_cname = get_ccode_lower_case_prefix (iface) + "proxy";

		cfile.add_type_declaration (new CCodeTypeDefinition ("GDBusProxy", new CCodeVariableDeclarator (cname)));
		cfile.add_type_declaration (new CCodeTypeDefinition ("GDBusProxyClass", new CCodeVariableDeclarator (cname + "Class")));

		string type_macro;

		if (in_plugin) {
			type_macro = "G_DEFINE_DYNAMIC_TYPE_EXTENDED";
		} else {
			type_macro = "G_DEFINE_TYPE_EXTENDED";
		}

		var define_type = new CCodeFunctionCall (new CCodeIdentifier (type_macro));
		define_type.add_argument (new CCodeIdentifier (cname));
		define_type.add_argument (new CCodeIdentifier (lower_cname));
		define_type.add_argument (new CCodeIdentifier ("G_TYPE_DBUS_PROXY"));
		define_type.add_argument (new CCodeConstant ("0"));
		define_type.add_argument (new CCodeIdentifier (implement_interface (define_type, iface, iface)));

		cfile.add_type_member_definition (define_type);

		var proxy_class_init = new CCodeFunction (lower_cname + "_class_init", "void");
		proxy_class_init.add_parameter (new CCodeParameter ("klass", cname + "Class*"));
		proxy_class_init.modifiers = CCodeModifiers.STATIC;
		push_function (proxy_class_init);
		var proxy_class = new CCodeFunctionCall (new CCodeIdentifier ("G_DBUS_PROXY_CLASS"));
		proxy_class.add_argument (new CCodeIdentifier ("klass"));
		ccode.add_assignment (new CCodeMemberAccess.pointer (proxy_class, "g_signal"), new CCodeIdentifier (lower_cname + "_g_signal"));
		pop_function ();
		cfile.add_function (proxy_class_init);

		generate_signal_handler_function (iface);

		if (in_plugin) {
			var proxy_class_finalize = new CCodeFunction (lower_cname + "_class_finalize", "void");
			proxy_class_finalize.add_parameter (new CCodeParameter ("klass", cname + "Class*"));
			proxy_class_finalize.modifiers = CCodeModifiers.STATIC;
			cfile.add_function (proxy_class_finalize);

			var proxy_type_init = new CCodeFunction (lower_cname + "_register_dynamic_type", "void");
			proxy_type_init.add_parameter (new CCodeParameter ("module", "GTypeModule*"));
			push_function (proxy_type_init);
			var call_register_type = new CCodeFunctionCall (new CCodeIdentifier (lower_cname + "_register_type"));
			call_register_type.add_argument (new CCodeIdentifier ("module"));
			ccode.add_expression (call_register_type);
			pop_function ();
			cfile.add_function(proxy_type_init);
		}

		var proxy_instance_init = new CCodeFunction (lower_cname + "_init", "void");
		proxy_instance_init.add_parameter (new CCodeParameter ("self", cname + "*"));
		proxy_instance_init.modifiers = CCodeModifiers.STATIC;
		push_function (proxy_instance_init);

		// TODO Replaces setting of "vala-dbus-interface-info"
		var dbus_proxy_cast = new CCodeFunctionCall (new CCodeIdentifier ("G_DBUS_PROXY"));
		dbus_proxy_cast.add_argument (new CCodeIdentifier ("self"));
		var set_interface_info = new CCodeFunctionCall (new CCodeIdentifier ("g_dbus_proxy_set_interface_info"));
		set_interface_info.add_argument (dbus_proxy_cast);
		set_interface_info.add_argument (new CCodeCastExpression (new CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, get_interface_info (iface)), "GDBusInterfaceInfo *"));
		ccode.add_expression (set_interface_info);

		pop_function ();
		cfile.add_function (proxy_instance_init);

		generate_proxy_interface_init (iface, iface);
	}

	public override void visit_method_call (MethodCall expr) {
		var mtype = expr.call.value_type as MethodType;
		bool bus_get_proxy_async = (mtype != null && get_ccode_name (mtype.method_symbol) == "g_bus_get_proxy");
		bool bus_get_proxy_sync = (mtype != null && get_ccode_name (mtype.method_symbol) == "g_bus_get_proxy_sync");
		bool conn_get_proxy_async = (mtype != null && get_ccode_name (mtype.method_symbol) == "g_dbus_connection_get_proxy");
		bool conn_get_proxy_sync = (mtype != null && get_ccode_name (mtype.method_symbol) == "g_dbus_connection_get_proxy_sync");
		if (!bus_get_proxy_async && !bus_get_proxy_sync && !conn_get_proxy_async && !conn_get_proxy_sync) {
			base.visit_method_call (expr);
			return;
		}

		var ma = (MemberAccess) expr.call;
		var type_arg = ma.get_type_arguments ().get (0);

		CCodeExpression proxy_type;
		CCodeExpression dbus_iface_name;

		var object_type = type_arg as ObjectType;
		if (object_type != null) {
			var iface = (Interface) object_type.type_symbol;

			if (get_dbus_name (iface) == null) {
				Report.error (expr.source_reference, "`%s' is not a D-Bus interface", iface.get_full_name ());
				return;
			}

			proxy_type = new CCodeIdentifier ("%s_PROXY".printf (get_ccode_type_id (iface)));
			dbus_iface_name = new CCodeConstant ("\"%s\"".printf (get_dbus_name (iface)));
		} else {
			// use runtime type information for generic methods

			var quark = new CCodeFunctionCall (new CCodeIdentifier ("g_quark_from_static_string"));
			quark.add_argument (new CCodeConstant ("\"vala-dbus-proxy-type\""));

			var get_qdata = new CCodeFunctionCall (new CCodeIdentifier ("g_type_get_qdata"));
			get_qdata.add_argument (get_type_id_expression (type_arg));
			get_qdata.add_argument (quark);

			proxy_type = new CCodeFunctionCall (new CCodeCastExpression (get_qdata, "GType (*) (void)"));

			quark = new CCodeFunctionCall (new CCodeIdentifier ("g_quark_from_static_string"));
			quark.add_argument (new CCodeConstant ("\"vala-dbus-interface-name\""));

			get_qdata = new CCodeFunctionCall (new CCodeIdentifier ("g_type_get_qdata"));
			get_qdata.add_argument (get_type_id_expression (type_arg));
			get_qdata.add_argument (quark);

			dbus_iface_name = get_qdata;
		}

		if (bus_get_proxy_async || conn_get_proxy_async) {
			if (ma.member_name == "end" && ma.inner.symbol_reference == ma.symbol_reference) {
				// method can fail
				current_method_inner_error = true;

				var args = expr.get_argument_list ();
				Expression res = args.get (0);

				var source_var = get_temp_variable (expr.value_type, expr.value_type.value_owned);
				var source_ref = get_variable_cexpression (source_var.name);
				emit_temp_var (source_var);
				var source = new CCodeFunctionCall (new CCodeIdentifier ("g_async_result_get_source_object"));
				source.add_argument (get_cvalue (res));
				ccode.add_assignment (source_ref, source);

				var ccall = new CCodeFunctionCall (new CCodeIdentifier ("g_async_initable_new_finish"));
				ccall.add_argument (new CCodeCastExpression (source_ref, "GAsyncInitable *"));
				ccall.add_argument (get_cvalue (res));
				ccall.add_argument (new CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, get_inner_error_cexpression ()));

				var temp_var = get_temp_variable (expr.value_type, expr.value_type.value_owned);
				var temp_ref = get_variable_cexpression (temp_var.name);
				emit_temp_var (temp_var);
				ccode.add_assignment (temp_ref, new CCodeCastExpression (ccall, get_ccode_name (expr.value_type)));

				// g_async_result_get_source_object transfers ownership, unref after use
				var unref_proxy = new CCodeFunctionCall (new CCodeIdentifier ("g_object_unref"));
				unref_proxy.add_argument (source_ref);
				ccode.add_expression (unref_proxy);

				set_cvalue (expr, temp_ref);

				return;
			}
		}

		var base_arg_index = 0;
		if (bus_get_proxy_async || bus_get_proxy_sync)
			base_arg_index = 1;

		var args = expr.get_argument_list ();
		Expression name = args.get (base_arg_index + 0);
		Expression object_path = args.get (base_arg_index + 1);
		Expression flags = args.get (base_arg_index + 2);
		Expression cancellable = args.get (base_arg_index + 3);

		// method can fail
		current_method_inner_error = true;

		CCodeFunctionCall ccall;
		if (bus_get_proxy_async || conn_get_proxy_async) {
			ccall = new CCodeFunctionCall (new CCodeIdentifier ("g_async_initable_new_async"));
		} else {
			ccall = new CCodeFunctionCall (new CCodeIdentifier ("g_initable_new"));
		}
		ccall.add_argument (proxy_type);
		if (bus_get_proxy_async || conn_get_proxy_async) {
			// I/O priority
			ccall.add_argument (new CCodeConstant ("0"));
		}
		ccall.add_argument (get_cvalue (cancellable));
		if (bus_get_proxy_async || conn_get_proxy_async) {
			if (expr.is_yield_expression) {
				// asynchronous call
				ccall.add_argument (new CCodeIdentifier (generate_ready_function (current_method)));
				ccall.add_argument (new CCodeIdentifier ("_data_"));
			} else {
				// begin
				Expression callback = args.get (base_arg_index + 4);
				ccall.add_argument (get_cvalue (callback));
				ccall.add_argument (get_delegate_target (callback));
			}
		} else {
			ccall.add_argument (new CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, get_inner_error_cexpression ()));
		}
		ccall.add_argument (new CCodeConstant ("\"g-flags\""));
		ccall.add_argument (get_cvalue (flags));
		ccall.add_argument (new CCodeConstant ("\"g-name\""));
		ccall.add_argument (get_cvalue (name));
		if (bus_get_proxy_async || bus_get_proxy_sync) {
			Expression bus_type = args.get (0);
			ccall.add_argument (new CCodeConstant ("\"g-bus-type\""));
			ccall.add_argument (get_cvalue (bus_type));
		} else {
			Expression connection = ma.inner;
			if (ma.member_name == "begin" && ma.inner.symbol_reference == ma.symbol_reference) {
				var inner_ma = (MemberAccess) ma.inner;
				connection = inner_ma.inner;
			}
			ccall.add_argument (new CCodeConstant ("\"g-connection\""));
			ccall.add_argument (get_cvalue (connection));
		}
		ccall.add_argument (new CCodeConstant ("\"g-object-path\""));
		ccall.add_argument (get_cvalue (object_path));
		ccall.add_argument (new CCodeConstant ("\"g-interface-name\""));
		ccall.add_argument (dbus_iface_name);
		ccall.add_argument (new CCodeConstant ("NULL"));

		if (bus_get_proxy_async || conn_get_proxy_async) {
			if (expr.is_yield_expression) {
				int state = emit_context.next_coroutine_state++;

				ccode.add_assignment (new CCodeMemberAccess.pointer (new CCodeIdentifier ("_data_"), "_state_"), new CCodeConstant (state.to_string ()));
				ccode.add_expression (ccall);
				ccode.add_return (new CCodeConstant ("FALSE"));
				ccode.add_label ("_state_%d".printf (state));

				ccall = new CCodeFunctionCall (new CCodeIdentifier ("g_async_initable_new_finish"));
				ccall.add_argument (new CCodeCastExpression (new CCodeMemberAccess.pointer (new CCodeIdentifier ("_data_"), "_source_object_"), "GAsyncInitable *"));
				// pass GAsyncResult stored in closure to finish function
				ccall.add_argument (new CCodeMemberAccess.pointer (new CCodeIdentifier ("_data_"), "_res_"));
				ccall.add_argument (new CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, get_inner_error_cexpression ()));
			} else {
				// begin
				ccode.add_expression (ccall);
				return;
			}
		}

		var temp_var = get_temp_variable (expr.value_type, expr.value_type.value_owned);
		var temp_ref = get_variable_cexpression (temp_var.name);

		emit_temp_var (temp_var);

		ccode.add_assignment (temp_ref, new CCodeCastExpression (ccall, get_ccode_name (expr.value_type)));
		set_cvalue (expr, temp_ref);
	}

	string generate_dbus_signal_handler (Signal sig, ObjectTypeSymbol sym) {
		string wrapper_name = "_dbus_handle_%s_%s".printf (get_ccode_lower_case_name (sym), get_ccode_lower_case_name (sig));

		var function = new CCodeFunction (wrapper_name);
		function.modifiers = CCodeModifiers.STATIC;
		function.add_parameter (new CCodeParameter ("self", get_ccode_name (sym) + "*"));
		function.add_parameter (new CCodeParameter ("parameters", "GVariant*"));

		push_function (function);

		ccode.add_declaration ("GVariantIter", new CCodeVariableDeclarator ("_arguments_iter"));

		var iter_init = new CCodeFunctionCall (new CCodeIdentifier ("g_variant_iter_init"));
		iter_init.add_argument (new CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, new CCodeIdentifier ("_arguments_iter")));
		iter_init.add_argument (new CCodeIdentifier ("parameters"));
		ccode.add_expression (iter_init);

		var ccall = new CCodeFunctionCall (new CCodeIdentifier ("g_signal_emit_by_name"));
		ccall.add_argument (new CCodeIdentifier ("self"));
		ccall.add_argument (get_signal_canonical_constant (sig));

		foreach (Parameter param in sig.get_parameters ()) {
			var param_name = get_variable_cname (param.name);
			var owned_type = param.variable_type.copy ();
			owned_type.value_owned = true;

			ccode.add_declaration (get_ccode_name (owned_type), new CCodeVariableDeclarator.zero (param_name, default_value_for_type (param.variable_type, true)));

			unowned Struct? st = param.variable_type.type_symbol as Struct;
			if (st != null && !st.is_simple_type ()) {
				ccall.add_argument (new CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, new CCodeIdentifier (param_name)));
			} else {
				ccall.add_argument (new CCodeIdentifier (param_name));
			}

			if (param.variable_type is ArrayType) {
				var array_type = (ArrayType) param.variable_type;
				var length_ctype = get_ccode_array_length_type (array_type);

				for (int dim = 1; dim <= array_type.rank; dim++) {
					string length_cname = get_variable_array_length_cname (param, dim);

					ccode.add_declaration (length_ctype, new CCodeVariableDeclarator (length_cname, new CCodeConstant ("0")));
					ccall.add_argument (new CCodeIdentifier (length_cname));
				}
			}

			read_expression (param.variable_type, new CCodeIdentifier ("_arguments_iter"), new CCodeIdentifier (param_name), param);
		}

		ccode.add_expression (ccall);

		foreach (Parameter param in sig.get_parameters ()) {
			var owned_type = param.variable_type.copy ();
			owned_type.value_owned = true;

			if (requires_destroy (owned_type)) {
				// keep local alive (symbol_reference is weak)
				var local = new LocalVariable (owned_type, param.name);
				ccode.add_expression (destroy_local (local));
			}
		}

		pop_function ();

		cfile.add_function_declaration (function);
		cfile.add_function (function);

		return wrapper_name;
	}

	void generate_signal_handler_function (ObjectTypeSymbol sym) {
		var cfunc = new CCodeFunction (get_ccode_lower_case_prefix (sym) + "proxy_g_signal", "void");
		cfunc.add_parameter (new CCodeParameter ("proxy", "GDBusProxy*"));
		cfunc.add_parameter (new CCodeParameter ("sender_name", "const gchar*"));
		cfunc.add_parameter (new CCodeParameter ("signal_name", "const gchar*"));
		cfunc.add_parameter (new CCodeParameter ("parameters", "GVariant*"));

		cfunc.modifiers |= CCodeModifiers.STATIC;

		cfile.add_function_declaration (cfunc);

		push_function (cfunc);

		bool firstif = true;

		foreach (Signal sig in sym.get_signals ()) {
			if (sig.access != SymbolAccessibility.PUBLIC) {
				continue;
			}

			cfile.add_include ("string.h");

			var ccheck = new CCodeFunctionCall (new CCodeIdentifier ("strcmp"));
			ccheck.add_argument (new CCodeIdentifier ("signal_name"));
			ccheck.add_argument (new CCodeConstant ("\"%s\"".printf (get_dbus_name_for_member (sig))));

			var cond = new CCodeBinaryExpression (CCodeBinaryOperator.EQUALITY, ccheck, new CCodeConstant ("0"));
			if (firstif) {
				ccode.open_if (cond);
				firstif = false;
			} else {
				ccode.else_if (cond);
			}

			var ccall = new CCodeFunctionCall (new CCodeIdentifier (generate_dbus_signal_handler (sig, sym)));
			ccall.add_argument (new CCodeCastExpression (new CCodeIdentifier ("proxy"), get_ccode_name (sym) + "*"));
			ccall.add_argument (new CCodeIdentifier ("parameters"));

			ccode.add_expression (ccall);
		}
		if (!firstif) {
			ccode.close ();
		}

		pop_function ();

		cfile.add_function (cfunc);
	}

	void generate_marshalling (Method m, CallType call_type, string? iface_name, string? method_name, int method_timeout) {
		var gdbusproxy = new CCodeCastExpression (new CCodeIdentifier ("self"), "GDBusProxy *");

		var connection = new CCodeFunctionCall (new CCodeIdentifier ("g_dbus_proxy_get_connection"));
		connection.add_argument (gdbusproxy);

		bool uses_fd = dbus_method_uses_file_descriptor (m);
		if (uses_fd) {
			cfile.add_include ("gio/gunixfdlist.h");
			ccode.add_declaration ("GUnixFDList*", new CCodeVariableDeclarator ("_fd_list"));
		}

		bool has_error_argument = m.tree_can_fail;
		CCodeExpression error_argument;
		if (has_error_argument) {
			error_argument = new CCodeIdentifier ("error");
		} else {
			error_argument = new CCodeConstant ("NULL");
		}

		if (call_type != CallType.FINISH) {
			var destination = new CCodeFunctionCall (new CCodeIdentifier ("g_dbus_proxy_get_name"));
			destination.add_argument (gdbusproxy);

			var interface_name = new CCodeFunctionCall (new CCodeIdentifier ("g_dbus_proxy_get_interface_name"));
			interface_name.add_argument (gdbusproxy);

			var object_path = new CCodeFunctionCall (new CCodeIdentifier ("g_dbus_proxy_get_object_path"));
			object_path.add_argument (gdbusproxy);

			CCodeExpression timeout;
			if (method_timeout <= 0) {
				timeout = new CCodeFunctionCall (new CCodeIdentifier ("g_dbus_proxy_get_default_timeout"));
				((CCodeFunctionCall) timeout).add_argument (gdbusproxy);
			} else {
				timeout = new CCodeConstant ("%d".printf (method_timeout));
			}

			// register errors
			var error_types = new ArrayList<DataType> ();
			m.get_error_types (error_types);
			foreach (var error_type in error_types) {
				var errtype = (ErrorType) error_type;
				if (errtype.error_domain != null) {
					ccode.add_expression (new CCodeIdentifier (get_ccode_upper_case_name (errtype.error_domain)));
				}
			}

			// build D-Bus message

			ccode.add_declaration ("GDBusMessage", new CCodeVariableDeclarator ("*_message"));

			var ccall = new CCodeFunctionCall (new CCodeIdentifier ("g_dbus_message_new_method_call"));
			ccall.add_argument (destination);
			ccall.add_argument (object_path);
			if (iface_name != null) {
				ccall.add_argument (new CCodeConstant ("\"%s\"".printf (iface_name)));
			} else {
				ccall.add_argument (interface_name);
			}
			ccall.add_argument (new CCodeConstant ("\"%s\"".printf (method_name)));
			ccode.add_assignment (new CCodeIdentifier ("_message"), ccall);

			ccode.add_declaration ("GVariant", new CCodeVariableDeclarator ("*_arguments"));
			ccode.add_declaration ("GVariantBuilder", new CCodeVariableDeclarator ("_arguments_builder"));

			var builder_init = new CCodeFunctionCall (new CCodeIdentifier ("g_variant_builder_init"));
			builder_init.add_argument (new CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, new CCodeIdentifier ("_arguments_builder")));
			builder_init.add_argument (new CCodeIdentifier ("G_VARIANT_TYPE_TUPLE"));
			ccode.add_expression (builder_init);

			if (uses_fd) {
				ccode.add_assignment (new CCodeIdentifier ("_fd_list"), new CCodeFunctionCall (new CCodeIdentifier ("g_unix_fd_list_new")));
			}

			CCodeExpression cancellable = new CCodeConstant ("NULL");

			foreach (Parameter param in m.get_parameters ()) {
				if (param.direction == ParameterDirection.IN) {
					CCodeExpression expr = new CCodeIdentifier (get_variable_cname (param.name));
					if (param.variable_type.is_real_struct_type ()) {
						expr = new CCodeUnaryExpression (CCodeUnaryOperator.POINTER_INDIRECTION, expr);
					}

					if (param.variable_type is ObjectType && param.variable_type.type_symbol.get_full_name () == "GLib.Cancellable") {
						cancellable = expr;
						continue;
					}

					if (param.variable_type is ObjectType && param.variable_type.type_symbol.get_full_name () == "GLib.BusName") {
						// ignore BusName sender parameters
						continue;
					}

					send_dbus_value (param.variable_type, new CCodeIdentifier ("_arguments_builder"), expr, param);
				}
			}

			var builder_end = new CCodeFunctionCall (new CCodeIdentifier ("g_variant_builder_end"));
			builder_end.add_argument (new CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, new CCodeIdentifier ("_arguments_builder")));
			ccode.add_assignment (new CCodeIdentifier ("_arguments"), builder_end);

			var set_body = new CCodeFunctionCall (new CCodeIdentifier ("g_dbus_message_set_body"));
			set_body.add_argument (new CCodeIdentifier ("_message"));
			set_body.add_argument (new CCodeIdentifier ("_arguments"));
			ccode.add_expression (set_body);

			if (uses_fd) {
				ccall = new CCodeFunctionCall (new CCodeIdentifier ("g_dbus_message_set_unix_fd_list"));
				ccall.add_argument (new CCodeIdentifier ("_message"));
				ccall.add_argument (new CCodeIdentifier ("_fd_list"));
				ccode.add_expression (ccall);

				ccall = new CCodeFunctionCall (new CCodeIdentifier ("g_object_unref"));
				ccall.add_argument (new CCodeIdentifier ("_fd_list"));
				ccode.add_expression (ccall);
			}

			// send D-Bus message

			if (call_type == CallType.SYNC) {
				ccall = new CCodeFunctionCall (new CCodeIdentifier ("g_dbus_connection_send_message_with_reply_sync"));
				ccall.add_argument (connection);
				ccall.add_argument (new CCodeIdentifier ("_message"));
				ccall.add_argument (new CCodeConstant ("G_DBUS_SEND_MESSAGE_FLAGS_NONE"));
				ccall.add_argument (timeout);
				ccall.add_argument (new CCodeConstant ("NULL"));
				ccall.add_argument (cancellable);
				ccall.add_argument (error_argument);
				ccode.add_assignment (new CCodeIdentifier ("_reply_message"), ccall);
			} else if (call_type == CallType.NO_REPLY) {
				var set_flags = new CCodeFunctionCall (new CCodeIdentifier ("g_dbus_message_set_flags"));
				set_flags.add_argument (new CCodeIdentifier ("_message"));
				set_flags.add_argument (new CCodeConstant ("G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED"));
				ccode.add_expression (set_flags);

				ccall = new CCodeFunctionCall (new CCodeIdentifier ("g_dbus_connection_send_message"));
				ccall.add_argument (connection);
				ccall.add_argument (new CCodeIdentifier ("_message"));
				ccall.add_argument (new CCodeConstant ("G_DBUS_SEND_MESSAGE_FLAGS_NONE"));
				ccall.add_argument (new CCodeConstant ("NULL"));
				ccall.add_argument (error_argument);
				ccode.add_expression (ccall);
			} else if (call_type == CallType.ASYNC) {
				var callback_specified = new CCodeBinaryExpression (CCodeBinaryOperator.INEQUALITY, new CCodeIdentifier ("_callback_"), new CCodeConstant ("NULL"));
				ccode.open_if (callback_specified);

				ccall = new CCodeFunctionCall (new CCodeIdentifier ("g_dbus_connection_send_message_with_reply"));
				ccall.add_argument (connection);
				ccall.add_argument (new CCodeIdentifier ("_message"));
				ccall.add_argument (new CCodeConstant ("G_DBUS_SEND_MESSAGE_FLAGS_NONE"));
				ccall.add_argument (timeout);
				ccall.add_argument (new CCodeConstant ("NULL"));
				ccall.add_argument (cancellable);

				CCodeFunctionCall res_wrapper = null;

				// use wrapper as source_object wouldn't be correct otherwise
				ccall.add_argument (new CCodeIdentifier (generate_async_callback_wrapper ()));
				res_wrapper = new CCodeFunctionCall (new CCodeIdentifier ("g_task_new"));
				res_wrapper.add_argument (new CCodeCastExpression (new CCodeIdentifier ("self"), "GObject *"));
				res_wrapper.add_argument (new CCodeConstant ("NULL"));
				res_wrapper.add_argument (new CCodeIdentifier ("_callback_"));
				res_wrapper.add_argument (new CCodeIdentifier ("_user_data_"));
				ccall.add_argument (res_wrapper);

				ccode.add_expression (ccall);

				ccode.add_else ();

				var set_flags = new CCodeFunctionCall (new CCodeIdentifier ("g_dbus_message_set_flags"));
				set_flags.add_argument (new CCodeIdentifier ("_message"));
				set_flags.add_argument (new CCodeConstant ("G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED"));
				ccode.add_expression (set_flags);

				ccall = new CCodeFunctionCall (new CCodeIdentifier ("g_dbus_connection_send_message"));
				ccall.add_argument (connection);
				ccall.add_argument (new CCodeIdentifier ("_message"));
				ccall.add_argument (new CCodeConstant ("G_DBUS_SEND_MESSAGE_FLAGS_NONE"));
				ccall.add_argument (new CCodeConstant ("NULL"));
				ccall.add_argument (new CCodeConstant ("NULL"));
				ccode.add_expression (ccall);

				ccode.close ();
			}

			// free D-Bus message

			ccall = new CCodeFunctionCall (new CCodeIdentifier ("g_object_unref"));
			ccall.add_argument (new CCodeIdentifier ("_message"));
			ccode.add_expression (ccall);
		} else {
			var ccall = new CCodeFunctionCall (new CCodeIdentifier ("g_dbus_connection_send_message_with_reply_finish"));
			ccall.add_argument (connection);

			// unwrap async result
			ccode.add_declaration ("GAsyncResult", new CCodeVariableDeclarator ("*_inner_res"));

			var inner_res = new CCodeFunctionCall (new CCodeIdentifier ("g_task_propagate_pointer"));
			inner_res.add_argument (new CCodeCastExpression (new CCodeIdentifier ("_res_"), "GTask *"));
			inner_res.add_argument (new CCodeConstant ("NULL"));
			ccode.add_assignment (new CCodeIdentifier ("_inner_res"), inner_res);

			ccall.add_argument (new CCodeIdentifier ("_inner_res"));
			ccall.add_argument (error_argument);
			ccode.add_assignment (new CCodeIdentifier ("_reply_message"), ccall);

			// _inner_res is guaranteed to be non-NULL, so just unref it
			var unref_inner_res = new CCodeFunctionCall (new CCodeIdentifier ("g_object_unref"));
			unref_inner_res.add_argument (new CCodeIdentifier ("_inner_res"));
			ccode.add_expression (unref_inner_res);
		}

		if (call_type == CallType.SYNC || call_type == CallType.FINISH) {
			ccode.add_declaration ("GDBusMessage", new CCodeVariableDeclarator ("*_reply_message"));

			var unref_reply = new CCodeFunctionCall (new CCodeIdentifier ("g_object_unref"));
			unref_reply.add_argument (new CCodeIdentifier ("_reply_message"));

			// return on io error
			var reply_is_null = new CCodeUnaryExpression (CCodeUnaryOperator.LOGICAL_NEGATION, new CCodeIdentifier ("_reply_message"));
			ccode.open_if (reply_is_null);
			return_default_value (m.return_type);
			ccode.close ();

			// return on remote error
			var ccall = new CCodeFunctionCall (new CCodeIdentifier ("g_dbus_message_to_gerror"));
			ccall.add_argument (new CCodeIdentifier ("_reply_message"));
			ccall.add_argument (error_argument);
			ccode.open_if (ccall);
			ccode.add_expression (unref_reply);
			return_default_value (m.return_type);
			ccode.close ();

			bool has_result = !(m.return_type is VoidType);

			if (uses_fd) {
				ccode.add_declaration ("gint", new CCodeVariableDeclarator.zero ("_fd_index", new CCodeConstant ("0")));
				ccode.add_declaration ("gint", new CCodeVariableDeclarator ("_fd"));
			}

			foreach (Parameter param in m.get_parameters ()) {
				if (param.direction == ParameterDirection.OUT) {
					has_result = true;
				}
			}

			if (has_result) {
				ccode.add_declaration ("GVariant", new CCodeVariableDeclarator ("*_reply"));
				ccode.add_declaration ("GVariantIter", new CCodeVariableDeclarator ("_reply_iter"));

				ccall = new CCodeFunctionCall (new CCodeIdentifier ("g_dbus_message_get_body"));
				ccall.add_argument (new CCodeIdentifier ("_reply_message"));
				ccode.add_assignment (new CCodeIdentifier ("_reply"), ccall);

				var iter_init = new CCodeFunctionCall (new CCodeIdentifier ("g_variant_iter_init"));
				iter_init.add_argument (new CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, new CCodeIdentifier ("_reply_iter")));
				iter_init.add_argument (new CCodeIdentifier ("_reply"));
				ccode.add_expression (iter_init);

				foreach (Parameter param in m.get_parameters ()) {
					if (param.direction == ParameterDirection.OUT) {
						ccode.add_declaration (get_ccode_name (param.variable_type), new CCodeVariableDeclarator.zero ("_vala_%s".printf (param.name), default_value_for_type (param.variable_type, true)));

						var array_type = param.variable_type as ArrayType;
						if (array_type != null) {
							var length_ctype = get_ccode_array_length_type (array_type);
							for (int dim = 1; dim <= array_type.rank; dim++) {
								ccode.add_declaration (length_ctype, new CCodeVariableDeclarator ("_vala_%s_length%d".printf (param.name, dim), new CCodeConstant ("0")));
							}
						}

						var target = new CCodeIdentifier ("_vala_%s".printf (param.name));
						bool may_fail;

						receive_dbus_value (param.variable_type, new CCodeIdentifier ("_reply_message"), new CCodeIdentifier ("_reply_iter"), target, param, error_argument, out may_fail);

						// TODO check that parameter is not NULL (out parameters are optional)
						// free value if parameter is NULL
						ccode.add_assignment (new CCodeUnaryExpression (CCodeUnaryOperator.POINTER_INDIRECTION, new CCodeIdentifier (get_variable_cname (param.name))), target);

						if (array_type != null) {
							for (int dim = 1; dim <= array_type.rank; dim++) {
								// TODO check that parameter is not NULL (out parameters are optional)
								ccode.add_assignment (new CCodeUnaryExpression (CCodeUnaryOperator.POINTER_INDIRECTION, new CCodeIdentifier ("%s_length%d".printf (param.name, dim))), new CCodeIdentifier ("_vala_%s_length%d".printf (param.name, dim)));
							}
						}

						if (may_fail && has_error_argument) {
							ccode.open_if (new CCodeBinaryExpression (CCodeBinaryOperator.AND, new CCodeIdentifier ("error"), new CCodeUnaryExpression (CCodeUnaryOperator.POINTER_INDIRECTION, new CCodeIdentifier ("error"))));
							ccode.add_expression (unref_reply);
							return_default_value (m.return_type);
							ccode.close ();
						}
					}
				}

				if (!(m.return_type is VoidType)) {
					if (m.return_type.is_real_non_null_struct_type ()) {
						var target = new CCodeUnaryExpression (CCodeUnaryOperator.POINTER_INDIRECTION, new CCodeIdentifier ("result"));
						receive_dbus_value (m.return_type, new CCodeIdentifier ("_reply_message"), new CCodeIdentifier ("_reply_iter"), target, m);
					} else {
						ccode.add_declaration (get_ccode_name (m.return_type), new CCodeVariableDeclarator.zero ("_result", default_value_for_type (m.return_type, true)));

						var array_type = m.return_type as ArrayType;
						if (array_type != null) {
							var length_ctype = get_ccode_array_length_type (array_type);
							for (int dim = 1; dim <= array_type.rank; dim++) {
								ccode.add_declaration (length_ctype, new CCodeVariableDeclarator ("_result_length%d".printf (dim), new CCodeConstant ("0")));
							}
						}

						bool may_fail;
						receive_dbus_value (m.return_type, new CCodeIdentifier ("_reply_message"), new CCodeIdentifier ("_reply_iter"), new CCodeIdentifier ("_result"), m, new CCodeIdentifier ("error"), out may_fail);

						if (array_type != null) {
							for (int dim = 1; dim <= array_type.rank; dim++) {
								// TODO check that parameter is not NULL (out parameters are optional)
								ccode.add_assignment (new CCodeUnaryExpression (CCodeUnaryOperator.POINTER_INDIRECTION, new CCodeIdentifier ("result_length%d".printf (dim))), new CCodeIdentifier ("_result_length%d".printf (dim)));
							}
						}

						if (may_fail) {
							ccode.open_if (new CCodeBinaryExpression (CCodeBinaryOperator.AND, new CCodeIdentifier ("error"), new CCodeUnaryExpression (CCodeUnaryOperator.POINTER_INDIRECTION, new CCodeIdentifier ("error"))));
							ccode.add_expression (unref_reply);
							return_default_value (m.return_type);
							ccode.close ();
						}
					}
				}
			}

			ccode.add_expression (unref_reply);

			if (!(m.return_type is VoidType || m.return_type.is_real_non_null_struct_type ())) {
				ccode.add_return (new CCodeIdentifier ("_result"));
			}
		}
	}

	string generate_dbus_proxy_method (Interface main_iface, Interface iface, Method m) {
		string proxy_name = "%sproxy_%s".printf (get_ccode_lower_case_prefix (main_iface), m.name);

		string dbus_iface_name = get_dbus_name (iface);

		bool no_reply = is_dbus_no_reply (m);

		var function = new CCodeFunction (proxy_name);
		function.modifiers = CCodeModifiers.STATIC;

		var cparam_map = new HashMap<int,CCodeParameter> (direct_hash, direct_equal);

		generate_cparameters (m, cfile, cparam_map, function);

		push_function (function);

		generate_marshalling (m, no_reply ? CallType.NO_REPLY : CallType.SYNC, dbus_iface_name, get_dbus_name_for_member (m), get_dbus_timeout_for_member (m));

		pop_function ();

		cfile.add_function_declaration (function);
		cfile.add_function (function);

		return proxy_name;
	}

	string generate_async_dbus_proxy_method (Interface main_iface, Interface iface, Method m) {
		string proxy_name = "%sproxy_%s_async".printf (get_ccode_lower_case_prefix (main_iface), m.name);

		string dbus_iface_name = get_dbus_name (iface);

		var function = new CCodeFunction (proxy_name, "void");
		function.modifiers = CCodeModifiers.STATIC;

		var cparam_map = new HashMap<int,CCodeParameter> (direct_hash, direct_equal);

		cparam_map.set (get_param_pos (-1), new CCodeParameter ("_callback_", "GAsyncReadyCallback"));
		cparam_map.set (get_param_pos (-0.9), new CCodeParameter ("_user_data_", "gpointer"));

		generate_cparameters (m, cfile, cparam_map, function, null, null, null, 1);

		push_function (function);

		generate_marshalling (m, CallType.ASYNC, dbus_iface_name, get_dbus_name_for_member (m), get_dbus_timeout_for_member (m));

		pop_function ();

		cfile.add_function_declaration (function);
		cfile.add_function (function);

		return proxy_name;
	}

	string generate_finish_dbus_proxy_method (Interface main_iface, Interface iface, Method m) {
		string proxy_name = "%sproxy_%s_finish".printf (get_ccode_lower_case_prefix (main_iface), m.name);

		var function = new CCodeFunction (proxy_name);
		function.modifiers = CCodeModifiers.STATIC;

		var cparam_map = new HashMap<int,CCodeParameter> (direct_hash, direct_equal);

		cparam_map.set (get_param_pos (get_ccode_async_result_pos (m)), new CCodeParameter ("_res_", "GAsyncResult*"));

		generate_cparameters (m, cfile, cparam_map, function, null, null, null, 2);

		push_function (function);

		generate_marshalling (m, CallType.FINISH, null, null, -1);

		pop_function ();

		cfile.add_function_declaration (function);
		cfile.add_function (function);

		return proxy_name;
	}

	string generate_dbus_proxy_property_get (Interface main_iface, Interface iface, Property prop) {
		string proxy_name = "%sdbus_proxy_get_%s".printf (get_ccode_lower_case_prefix (main_iface), prop.name);

		string dbus_iface_name = get_dbus_name (iface);

		var owned_type = prop.get_accessor.value_type.copy ();
		owned_type.value_owned = true;
		if (owned_type.is_disposable () && !prop.get_accessor.value_type.value_owned) {
			Report.error (prop.get_accessor.value_type.source_reference, "Properties used in D-Bus clients require owned get accessor");
		}

		var array_type = prop.get_accessor.value_type as ArrayType;

		var function = new CCodeFunction (proxy_name);
		function.modifiers = CCodeModifiers.STATIC;

		function.add_parameter (new CCodeParameter ("self", "%s*".printf (get_ccode_name (iface))));

		if (prop.property_type.is_real_non_null_struct_type ()) {
			function.add_parameter (new CCodeParameter ("result", "%s*".printf (get_ccode_name (prop.get_accessor.value_type))));
		} else {
			if (array_type != null) {
				var length_ctype = get_ccode_array_length_type (array_type) + "*";
				for (int dim = 1; dim <= array_type.rank; dim++) {
					function.add_parameter (new CCodeParameter ("result_length%d".printf (dim), length_ctype));
				}
			}

			function.return_type = get_ccode_name (prop.get_accessor.value_type);
		}

		push_function (function);

		ccode.add_declaration ("GVariant", new CCodeVariableDeclarator ("*_inner_reply"));

		// first try cached value
		var ccall = new CCodeFunctionCall (new CCodeIdentifier ("g_dbus_proxy_get_cached_property"));
		ccall.add_argument (new CCodeCastExpression (new CCodeIdentifier ("self"), "GDBusProxy *"));
		ccall.add_argument (new CCodeConstant ("\"%s\"".printf (get_dbus_name_for_member (prop))));
		ccode.add_assignment (new CCodeIdentifier ("_inner_reply"), ccall);

		// if not successful, retrieve value via D-Bus
		ccode.open_if (new CCodeUnaryExpression (CCodeUnaryOperator.LOGICAL_NEGATION, new CCodeIdentifier ("_inner_reply")));

		ccode.add_declaration ("GVariant", new CCodeVariableDeclarator ("*_arguments"));
		ccode.add_declaration ("GVariant", new CCodeVariableDeclarator ("*_reply"));
		ccode.add_declaration ("GVariantBuilder", new CCodeVariableDeclarator ("_arguments_builder"));

		var builder_init = new CCodeFunctionCall (new CCodeIdentifier ("g_variant_builder_init"));
		builder_init.add_argument (new CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, new CCodeIdentifier ("_arguments_builder")));
		builder_init.add_argument (new CCodeIdentifier ("G_VARIANT_TYPE_TUPLE"));
		ccode.add_expression (builder_init);

		// interface name
		write_expression (string_type, new CCodeIdentifier ("_arguments_builder"), new CCodeConstant ("\"%s\"".printf (dbus_iface_name)), null);
		// property name
		write_expression (string_type, new CCodeIdentifier ("_arguments_builder"), new CCodeConstant ("\"%s\"".printf (get_dbus_name_for_member (prop))), null);

		var builder_end = new CCodeFunctionCall (new CCodeIdentifier ("g_variant_builder_end"));
		builder_end.add_argument (new CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, new CCodeIdentifier ("_arguments_builder")));
		ccode.add_assignment (new CCodeIdentifier ("_arguments"), builder_end);

		ccall = new CCodeFunctionCall (new CCodeIdentifier ("g_dbus_proxy_call_sync"));
		ccall.add_argument (new CCodeCastExpression (new CCodeIdentifier ("self"), "GDBusProxy *"));
		ccall.add_argument (new CCodeConstant ("\"org.freedesktop.DBus.Properties.Get\""));
		ccall.add_argument (new CCodeIdentifier ("_arguments"));
		ccall.add_argument (new CCodeConstant ("G_DBUS_CALL_FLAGS_NONE"));
		ccall.add_argument (get_dbus_timeout (prop));
		ccall.add_argument (new CCodeConstant ("NULL"));
		ccall.add_argument (new CCodeConstant ("NULL"));

		ccode.add_assignment (new CCodeIdentifier ("_reply"), ccall);

		// return on error
		ccode.open_if (new CCodeUnaryExpression (CCodeUnaryOperator.LOGICAL_NEGATION, new CCodeIdentifier ("_reply")));
		return_default_value (prop.property_type);
		ccode.close ();

		var get_variant = new CCodeFunctionCall (new CCodeIdentifier ("g_variant_get"));
		get_variant.add_argument (new CCodeIdentifier ("_reply"));
		get_variant.add_argument (new CCodeConstant ("\"(v)\""));
		get_variant.add_argument (new CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, new CCodeIdentifier ("_inner_reply")));
		ccode.add_expression (get_variant);

		var unref_reply = new CCodeFunctionCall (new CCodeIdentifier ("g_variant_unref"));
		unref_reply.add_argument (new CCodeIdentifier ("_reply"));
		ccode.add_expression (unref_reply);

		ccode.close ();

		if (prop.property_type.is_real_non_null_struct_type ()) {
			var target = new CCodeUnaryExpression (CCodeUnaryOperator.POINTER_INDIRECTION, new CCodeIdentifier ("result"));
			var result = deserialize_expression (prop.get_accessor.value_type, new CCodeIdentifier ("_inner_reply"), target);
			ccode.add_assignment (target, result);
		} else {
			ccode.add_declaration (get_ccode_name (prop.get_accessor.value_type), new CCodeVariableDeclarator ("_result"));

			if (get_dbus_signature (prop) != null) {
				// raw GVariant
				ccode.add_assignment (new CCodeIdentifier ("_result"), new CCodeIdentifier("_inner_reply"));
			} else {
				if (array_type != null) {
					var length_ctype = get_ccode_array_length_type (array_type);
					for (int dim = 1; dim <= array_type.rank; dim++) {
						ccode.add_declaration (length_ctype, new CCodeVariableDeclarator ("_result_length%d".printf (dim), new CCodeConstant ("0")));
					}
				}

				var result = deserialize_expression (prop.get_accessor.value_type, new CCodeIdentifier ("_inner_reply"), new CCodeIdentifier ("_result"));
				ccode.add_assignment (new CCodeIdentifier ("_result"), result);

				if (array_type != null) {
					for (int dim = 1; dim <= array_type.rank; dim++) {
						// TODO check that parameter is not NULL (out parameters are optional)
						ccode.add_assignment (new CCodeUnaryExpression (CCodeUnaryOperator.POINTER_INDIRECTION, new CCodeIdentifier ("result_length%d".printf (dim))), new CCodeIdentifier ("_result_length%d".printf (dim)));
					}
				}
			}
		}

		if (prop.property_type.is_real_non_null_struct_type () || get_dbus_signature (prop) == null) {
			unref_reply = new CCodeFunctionCall (new CCodeIdentifier ("g_variant_unref"));
			unref_reply.add_argument (new CCodeIdentifier ("_inner_reply"));
			ccode.add_expression (unref_reply);
		}

		if (prop.property_type.is_real_non_null_struct_type ()) {
			ccode.add_return ();
		} else {
			ccode.add_return (new CCodeIdentifier ("_result"));
		}

		pop_function ();

		cfile.add_function_declaration (function);
		cfile.add_function (function);

		return proxy_name;
	}

	string generate_dbus_proxy_property_set (Interface main_iface, Interface iface, Property prop) {
		string proxy_name = "%sdbus_proxy_set_%s".printf (get_ccode_lower_case_prefix (main_iface), prop.name);

		string dbus_iface_name = get_dbus_name (iface);

		var array_type = prop.set_accessor.value_type as ArrayType;

		var function = new CCodeFunction (proxy_name);
		function.modifiers = CCodeModifiers.STATIC;

		function.add_parameter (new CCodeParameter ("self", "%s*".printf (get_ccode_name (iface))));

		if (prop.property_type.is_real_non_null_struct_type ()) {
			function.add_parameter (new CCodeParameter ("value", "%s*".printf (get_ccode_name (prop.set_accessor.value_type))));
		} else {
			function.add_parameter (new CCodeParameter ("value", get_ccode_name (prop.set_accessor.value_type)));

			if (array_type != null) {
				var length_ctype = get_ccode_array_length_type (array_type);
				for (int dim = 1; dim <= array_type.rank; dim++) {
					function.add_parameter (new CCodeParameter ("value_length%d".printf (dim), length_ctype));
				}
			}
		}

		push_function (function);

		ccode.add_declaration ("GVariant", new CCodeVariableDeclarator ("*_arguments"));
		ccode.add_declaration ("GVariant", new CCodeVariableDeclarator ("*_reply"));

		ccode.add_declaration ("GVariantBuilder", new CCodeVariableDeclarator ("_arguments_builder"));

		var builder_init = new CCodeFunctionCall (new CCodeIdentifier ("g_variant_builder_init"));
		builder_init.add_argument (new CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, new CCodeIdentifier ("_arguments_builder")));
		builder_init.add_argument (new CCodeIdentifier ("G_VARIANT_TYPE_TUPLE"));
		ccode.add_expression (builder_init);

		// interface name
		write_expression (string_type, new CCodeIdentifier ("_arguments_builder"), new CCodeConstant ("\"%s\"".printf (dbus_iface_name)), null);
		// property name
		write_expression (string_type, new CCodeIdentifier ("_arguments_builder"), new CCodeConstant ("\"%s\"".printf (get_dbus_name_for_member (prop))), null);

		// property value (as variant)
		var builder_open = new CCodeFunctionCall (new CCodeIdentifier ("g_variant_builder_open"));
		builder_open.add_argument (new CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, new CCodeIdentifier ("_arguments_builder")));
		builder_open.add_argument (new CCodeIdentifier ("G_VARIANT_TYPE_VARIANT"));
		ccode.add_expression (builder_open);

		if (prop.property_type.is_real_non_null_struct_type ()) {
			write_expression (prop.set_accessor.value_type, new CCodeIdentifier ("_arguments_builder"), new CCodeUnaryExpression (CCodeUnaryOperator.POINTER_INDIRECTION, new CCodeIdentifier ("value")), prop);
		} else {
			write_expression (prop.set_accessor.value_type, new CCodeIdentifier ("_arguments_builder"), new CCodeIdentifier ("value"), prop);
		}

		var builder_close = new CCodeFunctionCall (new CCodeIdentifier ("g_variant_builder_close"));
		builder_close.add_argument (new CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, new CCodeIdentifier ("_arguments_builder")));
		ccode.add_expression (builder_close);

		var builder_end = new CCodeFunctionCall (new CCodeIdentifier ("g_variant_builder_end"));
		builder_end.add_argument (new CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, new CCodeIdentifier ("_arguments_builder")));
		ccode.add_assignment (new CCodeIdentifier ("_arguments"), builder_end);

		var ccall = new CCodeFunctionCall (new CCodeIdentifier ("g_dbus_proxy_call_sync"));
		ccall.add_argument (new CCodeCastExpression (new CCodeIdentifier ("self"), "GDBusProxy *"));
		ccall.add_argument (new CCodeConstant ("\"org.freedesktop.DBus.Properties.Set\""));
		ccall.add_argument (new CCodeIdentifier ("_arguments"));
		ccall.add_argument (new CCodeConstant ("G_DBUS_CALL_FLAGS_NONE"));
		ccall.add_argument (get_dbus_timeout (prop));
		ccall.add_argument (new CCodeConstant ("NULL"));
		ccall.add_argument (new CCodeConstant ("NULL"));

		ccode.add_assignment (new CCodeIdentifier ("_reply"), ccall);

		// return on error
		ccode.open_if (new CCodeUnaryExpression (CCodeUnaryOperator.LOGICAL_NEGATION, new CCodeIdentifier ("_reply")));
		ccode.add_return ();
		ccode.close ();

		var unref_reply = new CCodeFunctionCall (new CCodeIdentifier ("g_variant_unref"));
		unref_reply.add_argument (new CCodeIdentifier ("_reply"));
		ccode.add_expression (unref_reply);

		pop_function ();

		cfile.add_function_declaration (function);
		cfile.add_function (function);

		return proxy_name;
	}

	public override void register_dbus_info (CCodeBlock block, ObjectTypeSymbol sym) {
		if (!(sym is Interface)) {
			return;
		}

		string dbus_iface_name = get_dbus_name (sym);
		if (dbus_iface_name == null) {
			return;
		}

		var quark = new CCodeFunctionCall (new CCodeIdentifier ("g_quark_from_static_string"));
		quark.add_argument (new CCodeConstant ("\"vala-dbus-proxy-type\""));

		var proxy_type = new CCodeIdentifier (get_ccode_lower_case_prefix (sym) + "proxy_get_type");

		var set_qdata = new CCodeFunctionCall (new CCodeIdentifier ("g_type_set_qdata"));
		set_qdata.add_argument (new CCodeIdentifier ("%s_type_id".printf (get_ccode_lower_case_name (sym, null))));
		set_qdata.add_argument (quark);
		set_qdata.add_argument (new CCodeCastExpression (proxy_type, "void*"));

		block.add_statement (new CCodeExpressionStatement (set_qdata));

		quark = new CCodeFunctionCall (new CCodeIdentifier ("g_quark_from_static_string"));
		quark.add_argument (new CCodeConstant ("\"vala-dbus-interface-name\""));

		set_qdata = new CCodeFunctionCall (new CCodeIdentifier ("g_type_set_qdata"));
		set_qdata.add_argument (new CCodeIdentifier ("%s_type_id".printf (get_ccode_lower_case_name (sym, null))));
		set_qdata.add_argument (quark);
		set_qdata.add_argument (new CCodeConstant ("\"%s\"".printf (dbus_iface_name)));

		block.add_statement (new CCodeExpressionStatement (set_qdata));

		// TODO Replaced by g_dbus_proxy_set_interface_info() call in *_init
		quark = new CCodeFunctionCall (new CCodeIdentifier ("g_quark_from_static_string"));
		quark.add_argument (new CCodeConstant ("\"vala-dbus-interface-info\""));

		set_qdata = new CCodeFunctionCall (new CCodeIdentifier ("g_type_set_qdata"));
		set_qdata.add_argument (new CCodeIdentifier ("%s_type_id".printf (get_ccode_lower_case_name (sym, null))));
		set_qdata.add_argument (quark);
		set_qdata.add_argument (new CCodeCastExpression (new CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, get_interface_info (sym)), "void*"));

		block.add_statement (new CCodeExpressionStatement (set_qdata));
	}
}
