/*
 * 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/>.
 */
/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 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 Library General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <ctype.h>
#include <string>

#include "linphone/core.h"
#include "linphone/core_utils.h"
#include "linphone/lpconfig.h"
#include "linphone/sipsetup.h"
#include <bctoolbox/defs.h>

#include "mediastreamer2/mediastream.h"

#include "core/core-p.h"
#include "core/core.h"
#include "enum.h"
#include "private.h"

// For migration purpose.
#include "account/account-params.h"
#include "account/account.h"
#include "address/address.h"
#include "c-wrapper/c-wrapper.h"
#include "event/event-publish.h"
#include "linphone/api/c-dial-plan.h"

#include "dial-plan/dial-plan.h"

using namespace LinphonePrivate;

LinphoneProxyConfigAddressComparisonResult linphone_proxy_config_address_equal(const LinphoneAddress *a,
                                                                               const LinphoneAddress *b) {
	std::shared_ptr<Address> aAddr =
	    a ? Address::toCpp(const_cast<LinphoneAddress *>(a))->getSharedFromThis() : nullptr;
	std::shared_ptr<Address> bAddr =
	    b ? Address::toCpp(const_cast<LinphoneAddress *>(b))->getSharedFromThis() : nullptr;
	return (LinphoneProxyConfigAddressComparisonResult)Account::compareLinphoneAddresses(aAddr, bAddr);
}

LinphoneProxyConfigAddressComparisonResult
linphone_proxy_config_is_server_config_changed(const LinphoneProxyConfig *cfg) {
	return (LinphoneProxyConfigAddressComparisonResult)Account::toCpp(cfg->account)->isServerConfigChanged();
}

void linphone_proxy_config_write_all_to_config_file(LinphoneCore *lc) {
	bctbx_list_t *elem;
	int i;
	if (!linphone_core_ready(lc)) return;

	if (linphone_config_is_readonly(lc->config)) {
		lInfo() << "Storage of accounts to config file is skipped.";
		return;
	}

	for (elem = lc->sip_conf.proxies, i = 0; elem != NULL; elem = bctbx_list_next(elem), i++) {
		LinphoneProxyConfig *cfg = (LinphoneProxyConfig *)elem->data;
		linphone_proxy_config_write_to_config_file(lc->config, cfg, i);
	}
	/*to ensure removed configs are erased:*/
	linphone_proxy_config_write_to_config_file(lc->config, NULL, i);
	linphone_config_set_int(lc->config, "sip", "default_proxy", linphone_core_get_default_proxy_config_index(lc));
	L_GET_PRIVATE_FROM_C_OBJECT(lc)->writeNatPolicyConfigurations();
}

static void linphone_proxy_config_init(LinphoneCore *lc, LinphoneProxyConfig *cfg) {
	LinphoneAccountParams *params = linphone_account_params_new(lc);
	cfg->account = linphone_account_new_with_config(lc, params, cfg);
	linphone_account_params_unref(params);
	cfg->edit = NULL;
}

static void _linphone_proxy_config_destroy(LinphoneProxyConfig *cfg);

BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneProxyConfig);

BELLE_SIP_INSTANCIATE_VPTR(LinphoneProxyConfig,
                           belle_sip_object_t,
                           (belle_sip_object_destroy_t)_linphone_proxy_config_destroy,
                           NULL, // clone
                           NULL, // marshal
                           FALSE);

LinphoneProxyConfig *linphone_core_create_proxy_config(LinphoneCore *lc) {
	LinphoneProxyConfig *cfg = belle_sip_object_new(LinphoneProxyConfig);
	linphone_proxy_config_init(lc, cfg);
	return cfg;
}

void _linphone_proxy_config_destroy(LinphoneProxyConfig *cfg) {
	if (cfg->account) linphone_account_unref(cfg->account);
	if (cfg->edit) linphone_account_params_unref(cfg->edit);
}

void _linphone_proxy_config_release(LinphoneProxyConfig *cfg) {
	Account::toCpp(cfg->account)->releaseOps();
	belle_sip_object_unref(cfg);
}

LinphoneProxyConfig *linphone_proxy_config_ref(LinphoneProxyConfig *cfg) {
	belle_sip_object_ref(cfg);
	return cfg;
}

void linphone_proxy_config_unref(LinphoneProxyConfig *cfg) {
	belle_sip_object_unref(cfg);
}

LinphoneStatus linphone_proxy_config_set_server_addr(LinphoneProxyConfig *cfg, const char *server_addr) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	return linphone_account_params_set_server_addr(cfg->edit, server_addr);
}

LinphoneStatus linphone_proxy_config_set_identity_address(LinphoneProxyConfig *cfg, LinphoneAddress *addr) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	return linphone_account_params_set_identity_address(cfg->edit, addr);
}

const char *linphone_proxy_config_get_domain(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);

	const LinphoneAddress *identity_address = linphone_account_params_get_identity_address(params);
	return identity_address ? linphone_address_get_domain(identity_address) : NULL;
}

LinphoneStatus linphone_proxy_config_set_route(LinphoneProxyConfig *cfg, const char *route) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	bctbx_list_t *list = NULL;
	LinphoneStatus ret;

	if (route != NULL && route[0] != '\0') {
		std::string tmp;
		/*try to prepend 'sip:' */
		if (strstr(route, "sip:") == NULL && strstr(route, "sips:") == NULL) {
			tmp.append("sip:");
		}
		tmp.append(route);

		auto *addr = linphone_address_new(tmp.c_str());
		if (addr != NULL) {
			list = bctbx_list_append(list, addr);
		}
	}

	ret = linphone_account_params_set_routes_addresses(cfg->edit, list);

	bctbx_list_free_with_data(list, (bctbx_list_free_func)linphone_address_unref);
	return ret;
}

LinphoneStatus linphone_proxy_config_set_routes(LinphoneProxyConfig *cfg, const bctbx_list_t *routes) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	return AccountParams::toCpp(cfg->edit)->setRoutesFromStringList(routes);
}

void linphone_proxy_config_enableregister(LinphoneProxyConfig *cfg, bool_t val) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	linphone_account_params_set_register_enabled(cfg->edit, val);
}

void linphone_proxy_config_set_expires(LinphoneProxyConfig *cfg, int val) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	linphone_account_params_set_expires(cfg->edit, val);
}

void linphone_proxy_config_enable_publish(LinphoneProxyConfig *cfg, bool_t val) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	linphone_account_params_set_publish_enabled(cfg->edit, val);
}

void linphone_proxy_config_pause_register(LinphoneProxyConfig *cfg) {
	linphone_account_pause_register(cfg->account);
}

void linphone_proxy_config_edit(LinphoneProxyConfig *cfg) {
	if (cfg->edit) {
		ms_message("Proxy config [%p] is already on edit mode", cfg);
		return;
	}

	cfg->edit = linphone_account_params_clone(linphone_account_get_params(cfg->account));
}

void linphone_proxy_config_apply(LinphoneProxyConfig *cfg, LinphoneCore *lc) {
	Account::toCpp(cfg->account)->apply(lc);
}

void linphone_proxy_config_stop_refreshing(LinphoneProxyConfig *cfg) {
	Account::toCpp(cfg->account)->stopRefreshing();
}

void _linphone_proxy_config_unregister(LinphoneProxyConfig *obj) {
	Account::toCpp(obj->account)->unregister();
}

void linphone_proxy_config_refresh_register(LinphoneProxyConfig *cfg) {
	linphone_account_refresh_register(cfg->account);
}

void linphone_proxy_config_set_dial_prefix(LinphoneProxyConfig *cfg, const char *prefix) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	linphone_account_params_set_international_prefix(cfg->edit, prefix);
}

const char *linphone_proxy_config_get_dial_prefix(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_international_prefix(params);
}

void linphone_proxy_config_set_dial_escape_plus(LinphoneProxyConfig *cfg, bool_t val) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	linphone_account_params_set_dial_escape_plus_enabled(cfg->edit, val);
}

bool_t linphone_proxy_config_get_dial_escape_plus(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_dial_escape_plus_enabled(params);
}

void linphone_proxy_config_enable_quality_reporting(LinphoneProxyConfig *cfg, bool_t val) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	linphone_account_params_set_quality_reporting_enabled(cfg->edit, val);
}

bool_t linphone_proxy_config_quality_reporting_enabled(LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params =
	    cfg->edit ? cfg->edit : ((cfg->account) ? linphone_account_get_params(cfg->account) : NULL);
	return (params ? linphone_account_params_get_quality_reporting_enabled(params)
	               : !!linphone_config_get_default_int(linphone_core_get_config(linphone_proxy_config_get_core(cfg)),
	                                                   "proxy", "quality_reporting_enabled", false));
}

void linphone_proxy_config_set_quality_reporting_interval(LinphoneProxyConfig *cfg, int interval) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	linphone_account_params_set_quality_reporting_interval(cfg->edit, interval);
}

int linphone_proxy_config_get_quality_reporting_interval(LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_quality_reporting_interval(params);
}

void linphone_proxy_config_set_quality_reporting_collector(LinphoneProxyConfig *cfg, const char *collector) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	linphone_account_params_set_quality_reporting_collector(cfg->edit, collector);
}

const char *linphone_proxy_config_get_quality_reporting_collector(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_quality_reporting_collector(params);
}

bool_t linphone_proxy_config_normalize_number(LinphoneProxyConfig *proxy,
                                              const char *username,
                                              char *result,
                                              size_t result_len) {
	char *normalized_phone = linphone_proxy_config_normalize_phone_number(proxy, username);
	const char *output = normalized_phone ? normalized_phone : username;
	memset(result, 0, result_len);
	memcpy(result, output, MIN(strlen(output) + 1, result_len));
	ms_free(normalized_phone);
	return output != username;
} // TODO: not used but blacklisted in wrapper

static char *linphone_account_flatten_phone_number(const char *number) {
	char *unescaped_phone_number = belle_sip_username_unescape_unnecessary_characters(number);
	char *result = reinterpret_cast<char *>(ms_malloc0(strlen(unescaped_phone_number) + 1));
	char *w = result;
	const char *r;

	for (r = unescaped_phone_number; *r != '\0'; ++r) {
		if (*r == '+' || isdigit(*r)) {
			*w++ = *r;
		}
	}

	*w++ = '\0';
	belle_sip_free(unescaped_phone_number);
	return result;
}

static char *replace_icp_with_plus(char *phone, const char *icp) {
	return (strstr(phone, icp) == phone) ? ms_strdup_printf("+%s", phone + strlen(icp)) : ms_strdup(phone);
}

char *linphone_proxy_config_normalize_phone_number(LinphoneProxyConfig *proxy, const char *username) {
	// return linphone_account_normalize_phone_number(proxy ? proxy->account : NULL, username);
	LinphoneProxyConfig *tmpproxy = proxy ? proxy : linphone_core_create_proxy_config(NULL);
	char *result = NULL;
	std::shared_ptr<DialPlan> dialplan;
	char *nationnal_significant_number = NULL;
	int ccc = -1;

	if (linphone_account_is_phone_number(NULL, username)) {
		char *flatten = linphone_account_flatten_phone_number(username);
		ms_debug("Flattened number is '%s' for '%s'", flatten, username);

		ccc = DialPlan::lookupCccFromE164(flatten);
		if (ccc > -1) { /*e164 like phone number*/
			dialplan = DialPlan::findByCcc(ccc);
			nationnal_significant_number = strstr(flatten, dialplan->getCountryCallingCode().c_str());
			if (nationnal_significant_number) {
				nationnal_significant_number += strlen(dialplan->getCountryCallingCode().c_str());
			}
		} else if (flatten[0] == '+') {
			ms_message("Unknown ccc for e164 like number [%s]", flatten);
			goto end;
		} else {
			if (linphone_proxy_config_get_dial_prefix(tmpproxy)) {
				dialplan = DialPlan::findByCcc(linphone_proxy_config_get_dial_prefix(tmpproxy)); // copy dial plan;
			} else {
				dialplan = DialPlan::MostCommon;
			}
			if (linphone_proxy_config_get_dial_prefix(tmpproxy)) {
				if (strcmp(linphone_proxy_config_get_dial_prefix(tmpproxy),
				           dialplan->getCountryCallingCode().c_str()) != 0) {
					// probably generic dialplan, preserving proxy dial prefix
					dialplan->setCountryCallingCode(linphone_proxy_config_get_dial_prefix(tmpproxy));
				}

				/*it does not make sens to try replace icp with + if we are not sure from the country we are (I.E
				 * tmpproxy->dial_prefix==NULL)*/
				if (strstr(flatten, dialplan->getInternationalCallPrefix().c_str()) == flatten) {
					char *e164 = replace_icp_with_plus(flatten, dialplan->getInternationalCallPrefix().c_str());
					result = linphone_proxy_config_normalize_phone_number(tmpproxy, e164);
					ms_free(e164);
					goto end;
				}
			}
			nationnal_significant_number = flatten;
		}
		ms_debug("Using dial plan '%s'", dialplan->getCountry().c_str());

		/*if proxy has a dial prefix, modify phonenumber accordingly*/
		if (dialplan->getCountryCallingCode().c_str()[0] != '\0') {
			/* the number already starts with + or international prefix*/
			/*0. keep at most national number significant digits */
			char *nationnal_significant_number_start =
			    nationnal_significant_number +
			    MAX(0, (int)strlen(nationnal_significant_number) - (int)dialplan->getNationalNumberLength());
			ms_debug("Prefix not present. Keeping at most %d digits: %s", dialplan->getNationalNumberLength(),
			         nationnal_significant_number_start);

			/*1. First prepend international calling prefix or +*/
			/*2. Second add prefix*/
			/*3. Finally add user digits */
			result = ms_strdup_printf("%s%s%s",
			                          linphone_proxy_config_get_dial_escape_plus(tmpproxy)
			                              ? dialplan->getInternationalCallPrefix().c_str()
			                              : "+",
			                          dialplan->getCountryCallingCode().c_str(), nationnal_significant_number_start);
			ms_debug("Prepended prefix resulted in %s", result);
		}

	end:
		if (result == NULL) {
			result = flatten;
		} else {
			ms_free(flatten);
		}
	}
	if (proxy == NULL) linphone_proxy_config_unref(tmpproxy);
	return result;
}

static LinphoneAddress *_linphone_core_destroy_addr_if_not_sip(LinphoneAddress *addr) {
	if (linphone_address_is_sip(addr)) {
		return addr;
	} else {
		linphone_address_unref(addr);
		return NULL;
	}
}

LinphoneAddress *linphone_proxy_config_normalize_sip_uri(LinphoneProxyConfig *proxy, const char *username) {
	// return linphone_account_normalize_sip_uri(proxy ? proxy->account : NULL, username);
	enum_lookup_res_t *enumres = NULL;
	char *enum_domain = NULL;
	char *tmpurl;
	LinphoneAddress *uri;

	if (!username || *username == '\0') return NULL;

	if (is_enum(username, &enum_domain)) {
		if (enum_lookup(enum_domain, &enumres) < 0) {
			ms_free(enum_domain);
			return NULL;
		}
		ms_free(enum_domain);
		tmpurl = enumres->sip_address[0];
		uri = linphone_address_new(tmpurl);
		enum_lookup_res_free(enumres);
		return _linphone_core_destroy_addr_if_not_sip(uri);
	}
	/* check if we have a "sip:" or a "sips:" */
	if ((strstr(username, "sip:") == NULL) && (strstr(username, "sips:") == NULL)) {
		/* this doesn't look like a true sip uri */
		if (strchr(username, '@') != NULL) {
			/* seems like sip: is missing !*/
			tmpurl = ms_strdup_printf("sip:%s", username);
			uri = linphone_address_new(tmpurl);
			ms_free(tmpurl);
			if (uri) {
				return _linphone_core_destroy_addr_if_not_sip(uri);
			}
		}

		if (proxy != NULL && linphone_proxy_config_get_identity_address(proxy) != NULL) {
			/* append the proxy domain suffix but remove any custom parameters/headers */
			LinphoneAddress *uri = linphone_address_clone(linphone_proxy_config_get_identity_address(proxy));
			if (uri == NULL) {
				return NULL;
			} else {
				linphone_address_clean(uri);
				linphone_address_set_display_name(uri, NULL);
				// Unescape character if possible
				char *unescaped_username = belle_sip_username_unescape_unnecessary_characters(username);
				linphone_address_set_username(uri, unescaped_username);
				belle_sip_free(unescaped_username);
				return _linphone_core_destroy_addr_if_not_sip(uri);
			}
		} else {
			return NULL;
		}
	}
	uri = linphone_address_new(username);
	if (uri != NULL) {
		return _linphone_core_destroy_addr_if_not_sip(uri);
	}

	return NULL;
}

void linphone_proxy_config_set_etag(LinphoneProxyConfig *cfg, const char *sip_etag) {
	Account::toCpp(cfg->account)->setSipEtag(sip_etag);
}

/**
 * Commits modification made to the proxy configuration.
 **/
LinphoneStatus linphone_proxy_config_done(LinphoneProxyConfig *cfg) {
	if (!cfg->edit) {
		ms_message("Done called before edit on proxy config [%p]", cfg);
		return -1;
	}

	LinphoneStatus ret = linphone_account_set_params(cfg->account, cfg->edit);
	linphone_account_params_unref(cfg->edit);
	cfg->edit = NULL;

	return ret;
}

const char *linphone_proxy_config_get_realm(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_realm(params);
}

void linphone_proxy_config_set_realm(LinphoneProxyConfig *cfg, const char *realm) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	linphone_account_params_set_realm(cfg->edit, realm);
}

int linphone_proxy_config_send_publish(LinphoneProxyConfig *proxy, LinphonePresenceModel *presence) {
	Account::toCpp(proxy->account)->setPresenceModel(presence);
	return Account::toCpp(proxy->account)->sendPublish();
}

void _linphone_proxy_config_unpublish(LinphoneProxyConfig *obj) {
	Account::toCpp(obj->account)->unpublish();
}

const char *linphone_proxy_config_get_route(const LinphoneProxyConfig *cfg) {
	const bctbx_list_t *list = linphone_proxy_config_get_routes(cfg);
	if (list != NULL) {
		const char *route = (const char *)bctbx_list_get_data(list);
		return route;
	}

	return NULL;
}

const bctbx_list_t *linphone_proxy_config_get_routes(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return AccountParams::toCpp(params)->getRoutesCString();
}

const LinphoneAddress *linphone_proxy_config_get_identity_address(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_identity_address(params);
}

const char *linphone_proxy_config_get_identity(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_identity(params);
}

bool_t linphone_proxy_config_publish_enabled(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_publish_enabled(params);
}

const char *linphone_proxy_config_get_server_addr(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_server_addr(params);
}

/**
 * @return the duration of registration.
 **/
int linphone_proxy_config_get_expires(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_expires(params);
}

bool_t linphone_proxy_config_register_enabled(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_register_enabled(params);
}

void linphone_proxy_config_set_contact_parameters(LinphoneProxyConfig *cfg, const char *contact_params) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	linphone_account_params_set_contact_parameters(cfg->edit, contact_params);
}

void linphone_proxy_config_set_contact_uri_parameters(LinphoneProxyConfig *cfg, const char *contact_uri_params) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	linphone_account_params_set_contact_uri_parameters(cfg->edit, contact_uri_params);
}

const char *linphone_proxy_config_get_contact_parameters(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_contact_parameters(params);
}

const char *linphone_proxy_config_get_contact_uri_parameters(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_contact_uri_parameters(params);
}

struct _LinphoneCore *linphone_proxy_config_get_core(const LinphoneProxyConfig *cfg) {
	return linphone_account_get_core(cfg->account);
}

const char *linphone_proxy_config_get_custom_header(LinphoneProxyConfig *cfg, const char *header_name) {
	return linphone_account_get_custom_header(cfg->account, header_name);
}

void linphone_proxy_config_set_custom_header(LinphoneProxyConfig *cfg,
                                             const char *header_name,
                                             const char *header_value) {
	linphone_account_set_custom_header(cfg->account, header_name, header_value);
}

LinphoneProxyConfig *linphone_proxy_config_get_dependency(LinphoneProxyConfig *cfg) {
	LinphoneAccount *dependency = linphone_account_get_dependency(cfg->account);
	return dependency ? Account::toCpp(dependency)->getConfig() : NULL;
}

void linphone_proxy_config_set_dependency(LinphoneProxyConfig *cfg, LinphoneProxyConfig *dependency) {
	linphone_account_set_dependency(cfg->account, dependency->account);
}

const char *linphone_proxy_config_get_idkey(LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_idkey(params);
}

void linphone_proxy_config_set_idkey(LinphoneProxyConfig *cfg, const char *idkey) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	linphone_account_params_set_idkey(cfg->edit, idkey);
}

LinphoneAccount *linphone_proxy_config_get_account(LinphoneProxyConfig *cfg) {
	return cfg->account;
}

LinphoneStatus linphone_core_add_proxy_config(LinphoneCore *lc, LinphoneProxyConfig *cfg) {
	if (cfg->edit) {
		// Use done here to be sure this will use updated parameters
		linphone_proxy_config_done(cfg);
	}

	if (!Account::toCpp(cfg->account)->check()) {
		return -1;
	}
	if (bctbx_list_find(lc->sip_conf.proxies, cfg) != NULL) {
		ms_warning("ProxyConfig already entered, ignored.");
		return 0;
	}
	lc->sip_conf.proxies = bctbx_list_append(lc->sip_conf.proxies, (void *)linphone_proxy_config_ref(cfg));
	lc->sip_conf.accounts = bctbx_list_append(lc->sip_conf.accounts, (void *)linphone_account_ref(cfg->account));
	linphone_proxy_config_apply(cfg, lc);

	linphone_core_notify_account_added(lc, cfg->account);

	return 0;
}

// If an account dependency is removed, restore 'normal' behavior for previously dependent accounts
void linphone_core_remove_dependent_account(LinphoneCore *lc, LinphoneAccount *account) {
	bctbx_list_t *it = lc->sip_conf.accounts;

	for (; it; it = it->next) {
		LinphoneAccount *tmp = static_cast<LinphoneAccount *>(it->data);
		if (tmp != account && linphone_account_get_dependency(tmp) == account) {
			ms_message("Updating dependent account [%p] caused by removal of 'master' account idkey[%s]", tmp,
			           linphone_account_params_get_idkey(linphone_account_get_params(account)));
			linphone_account_set_dependency(tmp, NULL);
			Account::toCpp(account)->setNeedToRegister(true);
			Account::toCpp(tmp)->update();
		}
	}
}

void linphone_core_remove_proxy_config(LinphoneCore *lc, LinphoneProxyConfig *cfg) {
	/* check this proxy config is in the list before doing more*/
	if (bctbx_list_find(lc->sip_conf.proxies, cfg) == NULL) {
		ms_error("linphone_core_remove_proxy_config: LinphoneProxyConfig [%p] is not known by LinphoneCore "
		         "(programming error?)",
		         cfg);
		return;
	}

	lc->sip_conf.proxies = bctbx_list_remove(lc->sip_conf.proxies, cfg);
	/* add to the list of destroyed proxies, so that the possible unREGISTER request can succeed authentication */
	lc->sip_conf.deleted_proxies = bctbx_list_append(lc->sip_conf.deleted_proxies, cfg);

	if (lc->default_proxy == cfg) {
		lc->default_proxy = NULL;
	}

	/* we also need to update the accounts list */
	lc->sip_conf.accounts = bctbx_list_remove(lc->sip_conf.accounts, cfg->account);
	linphone_core_remove_dependent_account(lc, cfg->account);
	/* add to the list of destroyed proxies, so that the possible unREGISTER request can succeed authentication */
	lc->sip_conf.deleted_accounts = bctbx_list_append(lc->sip_conf.deleted_accounts, cfg->account);

	if (lc->default_account == cfg->account) {
		lc->default_account = NULL;
		linphone_core_notify_default_account_changed(lc, NULL);
	}

	linphone_core_notify_account_removed(lc, cfg->account);

	Account::toCpp(cfg->account)->setDeletionDate(ms_time(NULL));
	if (linphone_proxy_config_get_state(cfg) == LinphoneRegistrationOk) {
		/* UNREGISTER */
		linphone_proxy_config_edit(cfg);
		linphone_proxy_config_enable_register(cfg, FALSE);
		linphone_proxy_config_done(cfg);
		linphone_proxy_config_update(cfg);
	} else if (linphone_proxy_config_get_state(cfg) != LinphoneRegistrationNone) {
		linphone_proxy_config_set_state(cfg, LinphoneRegistrationNone, "Registration disabled");
	}
	linphone_proxy_config_write_all_to_config_file(lc);

	// Update the associated linphone specs on the core
	linphone_proxy_config_edit(cfg);
	linphone_proxy_config_set_conference_factory_uri(cfg, NULL);
	linphone_proxy_config_done(cfg);
}

void linphone_core_clear_proxy_config(LinphoneCore *lc) {
	bctbx_list_t *list = bctbx_list_copy(linphone_core_get_proxy_config_list((const LinphoneCore *)lc));
	bctbx_list_t *copy = list;
	for (; list != NULL; list = list->next) {
		linphone_core_remove_proxy_config(lc, (LinphoneProxyConfig *)list->data);
	}
	bctbx_list_free(copy);
	linphone_proxy_config_write_all_to_config_file(lc);
}

int linphone_core_get_default_proxy_config_index(LinphoneCore *lc) {
	int pos = -1;
	if (lc->default_proxy != NULL) {
		pos =
		    bctbx_list_position(lc->sip_conf.proxies, bctbx_list_find(lc->sip_conf.proxies, (void *)lc->default_proxy));
	}
	return pos;
}

LinphoneProxyConfig *linphone_core_get_proxy_config_by_idkey(LinphoneCore *lc, const char *idkey) {
	if (idkey == NULL || lc == NULL) return NULL;
	bctbx_list_t *list = lc->sip_conf.proxies;
	for (; list != NULL; list = list->next) {
		LinphoneProxyConfig *tmp = static_cast<LinphoneProxyConfig *>(list->data);
		if (tmp) {
			const char *proxy_idkey = linphone_proxy_config_get_idkey(tmp);
			if (proxy_idkey && strcmp(idkey, proxy_idkey) == 0) return tmp;
		}
	}
	return NULL;
}

void linphone_core_set_default_proxy_config(LinphoneCore *lc, LinphoneProxyConfig *config) {
	/* check if this proxy is in our list */
	if (config != NULL) {
		if (bctbx_list_find(lc->sip_conf.proxies, config) == NULL) {
			ms_warning("Bad proxy address: it is not in the list !");
			lc->default_proxy = NULL;
			return;
		}
	}
	lc->default_proxy = config;
	lc->default_account = config ? config->account : NULL;
	if (linphone_core_ready(lc)) {
		linphone_config_set_int(lc->config, "sip", "default_proxy", linphone_core_get_default_proxy_config_index(lc));
		/* Invalidate phone numbers in friends maps when default proxy config changes because the new one may have a
		 * different dial prefix */
		linphone_core_invalidate_friends_maps(lc);
	}
}

void linphone_core_set_default_proxy_index(LinphoneCore *lc, int index) {
	if (index < 0) linphone_core_set_default_proxy_config(lc, NULL);
	else
		linphone_core_set_default_proxy_config(
		    lc, static_cast<LinphoneProxyConfig *>(bctbx_list_nth_data(lc->sip_conf.proxies, index)));
}

int linphone_core_get_default_proxy(LinphoneCore *lc, LinphoneProxyConfig **config) {
	if (config != NULL) *config = lc->default_proxy;
	return linphone_core_get_default_proxy_config_index(lc);
}

LinphoneProxyConfig *linphone_core_get_default_proxy_config(const LinphoneCore *lc) {
	return lc->default_proxy;
}

const bctbx_list_t *linphone_core_get_proxy_config_list(const LinphoneCore *lc) {
	return lc->sip_conf.proxies;
}

LinphoneAccountParams *linphone_core_create_account_params(LinphoneCore *core) {
	return linphone_account_params_new(core);
}

LinphoneAccount *linphone_core_create_account(LinphoneCore *core, LinphoneAccountParams *params) {
	return linphone_account_new(core, params);
}

LinphoneStatus linphone_core_add_account(LinphoneCore *lc, LinphoneAccount *account) {
	if (!Account::toCpp(account)->check()) {
		return -1;
	}
	if (bctbx_list_find(lc->sip_conf.accounts, account) != NULL) {
		ms_warning("Account already entered, ignored.");
		return 0;
	}
	lc->sip_conf.accounts = bctbx_list_append(lc->sip_conf.accounts, (void *)linphone_account_ref(account));

	// If there is no back pointer to a proxy config then create a proxy config that will depend on this account
	// to ensure backward compatibility when using only proxy configs
	LinphoneProxyConfig *cfg = Account::toCpp(account)->getConfig();
	if (cfg == NULL) {
		cfg = belle_sip_object_new(LinphoneProxyConfig);
		cfg->account = linphone_account_ref(account);
		Account::toCpp(account)->setConfig(cfg);
		lc->sip_conf.proxies = bctbx_list_append(lc->sip_conf.proxies, (void *)cfg);
	} else {
		lc->sip_conf.proxies = bctbx_list_append(lc->sip_conf.proxies, (void *)linphone_proxy_config_ref(cfg));
	}

	Account::toCpp(account)->apply(lc);

	linphone_core_notify_account_added(lc, account);

	return 0;
}

void linphone_core_clear_accounts(LinphoneCore *core) {
	bctbx_list_t *list = bctbx_list_copy(linphone_core_get_account_list((const LinphoneCore *)core));
	bctbx_list_t *copy = list;
	for (; list != NULL; list = list->next) {
		linphone_core_remove_account(core, (LinphoneAccount *)list->data);
	}
	bctbx_list_free(copy);
	linphone_proxy_config_write_all_to_config_file(core);
}

void linphone_core_remove_account(LinphoneCore *core, LinphoneAccount *account) {
	/* check this proxy config is in the list before doing more*/
	if (bctbx_list_find(core->sip_conf.accounts, account) == NULL) {
		ms_error("linphone_core_remove_account: LinphoneAccount [%p] is not known by LinphoneCore (programming error?)",
		         account);
		return;
	}

	/* we also need to update the accounts list */
	core->sip_conf.accounts = bctbx_list_remove(core->sip_conf.accounts, account);
	linphone_core_remove_dependent_account(core, account);
	/* add to the list of destroyed accounts, so that the possible unREGISTER request can succeed authentication */
	core->sip_conf.deleted_accounts = bctbx_list_append(core->sip_conf.deleted_accounts, account);

	if (core->default_account == account) {
		core->default_account = NULL;
		linphone_core_notify_default_account_changed(core, NULL);
	}

	LinphoneProxyConfig *cfg = Account::toCpp(account)->getConfig();
	core->sip_conf.proxies = bctbx_list_remove(core->sip_conf.proxies, cfg);
	/* add to the list of destroyed proxies, so that the possible unREGISTER request can succeed authentication */
	core->sip_conf.deleted_proxies = bctbx_list_append(core->sip_conf.deleted_proxies, cfg);

	if (core->default_proxy == cfg) {
		core->default_proxy = NULL;
	}

	linphone_core_notify_account_removed(core, account);

	Account::toCpp(account)->setDeletionDate(ms_time(NULL));
	auto state = linphone_account_get_state(account);
	switch (state) {
		case LinphoneRegistrationNone:
		case LinphoneRegistrationOk:
		case LinphoneRegistrationRefreshing:
		case LinphoneRegistrationProgress: {
			LinphoneAccountParams *params = linphone_account_params_clone(linphone_account_get_params(account));
			linphone_account_params_set_register_enabled(params, FALSE);
			linphone_account_set_params(account, params);
			linphone_account_params_unref(params);
			// Account::toCpp(account)->update();
		} break;
		case LinphoneRegistrationFailed:
		case LinphoneRegistrationCleared:
			Account::toCpp(account)->setState(LinphoneRegistrationNone, "Registration disabled");
			break;
	}
	linphone_proxy_config_write_all_to_config_file(core);

	// Update the associated linphone specs on the core
	LinphoneAccountParams *params = linphone_account_params_clone(linphone_account_get_params(account));
	linphone_account_params_set_conference_factory_address(params, NULL);
	linphone_account_set_params(account, params);
	linphone_account_params_unref(params);
}

const bctbx_list_t *linphone_core_get_deleted_account_list(const LinphoneCore *lc) {
	return lc->sip_conf.deleted_accounts;
}

const bctbx_list_t *linphone_core_get_account_list(const LinphoneCore *lc) {
	return lc->sip_conf.accounts;
}

void linphone_core_set_default_account_index(LinphoneCore *lc, int index) {
	if (index < 0) linphone_core_set_default_account(lc, NULL);
	else
		linphone_core_set_default_account(
		    lc, static_cast<LinphoneAccount *>(bctbx_list_nth_data(lc->sip_conf.accounts, index)));
}

LinphoneAccount *linphone_core_get_account_by_idkey(LinphoneCore *lc, const char *idkey) {
	if (idkey == NULL || lc == NULL) return NULL;
	bctbx_list_t *list = lc->sip_conf.accounts;
	for (; list != NULL; list = list->next) {
		LinphoneAccount *tmp = static_cast<LinphoneAccount *>(list->data);
		if (tmp) {
			const char *account_idkey = linphone_account_params_get_idkey(linphone_account_get_params(tmp));
			if (account_idkey && strcmp(idkey, account_idkey) == 0) return tmp;
		}
	}
	return NULL;
}

LinphoneAccount *linphone_core_get_default_account(const LinphoneCore *lc) {
	return lc->default_account;
}

int linphone_core_get_default_account_index(LinphoneCore *lc) {
	int pos = -1;
	if (lc->default_account != NULL) {
		pos = bctbx_list_position(lc->sip_conf.accounts,
		                          bctbx_list_find(lc->sip_conf.accounts, (void *)lc->default_account));
	}
	return pos;
}

void linphone_core_set_default_account(LinphoneCore *lc, LinphoneAccount *account) {
	/* check if this account is in our list */
	if (account != NULL) {
		if (bctbx_list_find(lc->sip_conf.accounts, account) == NULL) {
			ms_warning("Bad account address: it is not in the list !");
			lc->default_account = NULL;
			linphone_core_notify_default_account_changed(lc, NULL);
			return;
		}
	}

	if (lc->default_account == account) {
		ms_warning("This account is already the default one, skipping.");
		return;
	}

	lc->default_account = account;
	lc->default_proxy = account ? Account::toCpp(account)->getConfig() : NULL;
	if (linphone_core_ready(lc)) {
		linphone_config_set_int(lc->config, "sip", "default_proxy", linphone_core_get_default_account_index(lc));
		/* Invalidate phone numbers in friends maps when default account changes because the new one may have a
		 * different dial prefix */
		linphone_core_invalidate_friends_maps(lc);
	}

	linphone_core_notify_default_account_changed(lc, account);
}

void linphone_proxy_config_write_to_config_file(LpConfig *config, LinphoneProxyConfig *cfg, int index) {
	char key[50];

	sprintf(key, "proxy_%i", index);
	linphone_config_clean_section(config, key);
	if (cfg == NULL) {
		return;
	}

	Account::toCpp(cfg->account)->writeToConfigFile(index);
}

LinphoneProxyConfig *linphone_proxy_config_new_from_config_file(LinphoneCore *lc, int index) {
	LinphoneAccountParams *params = linphone_account_params_new_with_config(lc, index);

	// The config do not have the section refered by index if params is NULL
	if (params == NULL) {
		return NULL;
	}

	LinphoneProxyConfig *cfg = belle_sip_object_new(LinphoneProxyConfig);
	cfg->account = linphone_account_new_with_config(lc, params, cfg);

	linphone_account_params_unref(params);
	cfg->edit = NULL;

	return cfg;
}

SipSetup *linphone_proxy_config_get_sip_setup(BCTBX_UNUSED(LinphoneProxyConfig *cfg)) {
	return NULL;
} // TODO: remove this

void linphone_proxy_config_update(LinphoneProxyConfig *cfg) {
	Account::toCpp(cfg->account)->update();
}

void linphone_account_update(LinphoneAccount *account) {
	Account::toCpp(account)->update();
}

void linphone_proxy_config_set_sip_setup(BCTBX_UNUSED(LinphoneProxyConfig *cfg), BCTBX_UNUSED(const char *type)) {

} // TODO: remove this

SipSetupContext *linphone_proxy_config_get_sip_setup_context(BCTBX_UNUSED(LinphoneProxyConfig *cfg)) {
	return NULL;
} // TODO: remove this

void linphone_proxy_config_set_user_data(LinphoneProxyConfig *cfg, void *ud) {
	linphone_account_set_user_data(cfg->account, ud);
}

void *linphone_proxy_config_get_user_data(const LinphoneProxyConfig *cfg) {
	return linphone_account_get_user_data(cfg->account);
}

void linphone_proxy_config_set_state(LinphoneProxyConfig *cfg, LinphoneRegistrationState state, const char *message) {
	Account::toCpp(cfg->account)->setState(state, message);
}

LinphoneRegistrationState linphone_proxy_config_get_state(const LinphoneProxyConfig *cfg) {
	return linphone_account_get_state(cfg->account);
}

LinphoneReason linphone_proxy_config_get_error(const LinphoneProxyConfig *cfg) {
	return linphone_account_get_error(cfg->account);
}

const LinphoneErrorInfo *linphone_proxy_config_get_error_info(const LinphoneProxyConfig *cfg) {
	return linphone_account_get_error_info(cfg->account);
}

const LinphoneAddress *linphone_proxy_config_get_service_route(const LinphoneProxyConfig *cfg) {
	const auto route = Account::toCpp(cfg->account)->getServiceRouteAddress();
	return route ? route->toC() : nullptr;
}

const char *linphone_proxy_config_get_transport(const LinphoneProxyConfig *cfg) {
	return linphone_transport_to_string(linphone_account_get_transport(cfg->account));
}

void linphone_proxy_config_set_privacy(LinphoneProxyConfig *params, LinphonePrivacyMask privacy) {
	if (!params->edit) {
		linphone_proxy_config_edit(params);
	}

	return linphone_account_params_set_privacy(params->edit, privacy);
}

LinphonePrivacyMask linphone_proxy_config_get_privacy(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_privacy(params);
}

void linphone_proxy_config_set_publish_expires(LinphoneProxyConfig *cfg, int expires) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	linphone_account_params_set_publish_expires(cfg->edit, expires);
}

int linphone_proxy_config_get_publish_expires(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_publish_expires(params);
}

bool_t linphone_proxy_config_avpf_enabled(LinphoneProxyConfig *cfg) {
	return linphone_account_is_avpf_enabled(cfg->account);
}

LinphoneAVPFMode linphone_proxy_config_get_avpf_mode(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_avpf_mode(params);
}

void linphone_proxy_config_set_avpf_mode(LinphoneProxyConfig *cfg, LinphoneAVPFMode mode) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	linphone_account_params_set_avpf_mode(cfg->edit, mode);
}

void linphone_proxy_config_set_avpf_rr_interval(LinphoneProxyConfig *cfg, uint8_t interval) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	linphone_account_params_set_avpf_rr_interval(cfg->edit, interval);
}

uint8_t linphone_proxy_config_get_avpf_rr_interval(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_avpf_rr_interval(params);
}

const LinphoneAddress *linphone_proxy_config_get_contact(const LinphoneProxyConfig *cfg) {
	return linphone_account_get_contact_address(cfg->account);
}

LinphoneAddress *linphone_proxy_config_get_transport_contact(LinphoneProxyConfig *cfg) {
	LinphoneAddress *addr;
	int port = 0;
	const char *public_ip;

	if (!linphone_account_get_contact_address(cfg->account) || !Account::toCpp(cfg->account)->getOp()) return NULL;
	public_ip = Account::toCpp(cfg->account)->getOp()->getPublicAddress(&port);
	if (!public_ip) return NULL;

	addr = linphone_address_clone(linphone_account_get_contact_address(cfg->account));
	linphone_address_set_domain(addr, public_ip);
	if (port > 0) linphone_address_set_port(addr, port);
	return addr;
}

const LinphoneAddress *_linphone_proxy_config_get_contact_without_params(const LinphoneProxyConfig *cfg) {
	return Account::toCpp(cfg->account)->getContactAddressWithoutParams()->toC();
}

const struct _LinphoneAuthInfo *linphone_proxy_config_find_auth_info(const LinphoneProxyConfig *cfg) {
	return linphone_account_find_auth_info(cfg->account);
}

const char *linphone_proxy_config_get_ref_key(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_ref_key(params);
}

void linphone_proxy_config_set_ref_key(LinphoneProxyConfig *cfg, const char *refkey) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	linphone_account_params_set_ref_key(cfg->edit, refkey);
}

LinphoneNatPolicy *linphone_proxy_config_get_nat_policy(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_nat_policy(params);
}

void linphone_proxy_config_set_nat_policy(LinphoneProxyConfig *cfg, LinphoneNatPolicy *policy) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	linphone_account_params_set_nat_policy(cfg->edit, policy);
}

void linphone_proxy_config_set_conference_factory_uri(LinphoneProxyConfig *cfg, const char *uri) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	linphone_account_params_set_conference_factory_uri(cfg->edit, uri);
}

const char *linphone_proxy_config_get_conference_factory_uri(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_conference_factory_uri(params);
}

bool_t linphone_proxy_config_is_push_notification_allowed(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_push_notification_allowed(params);
}

void linphone_proxy_config_set_push_notification_allowed(LinphoneProxyConfig *cfg, bool_t is_allowed) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	linphone_account_params_set_push_notification_allowed(cfg->edit, is_allowed);
}

bool_t linphone_proxy_config_is_remote_push_notification_allowed(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_remote_push_notification_allowed(params);
}

void linphone_proxy_config_set_remote_push_notification_allowed(LinphoneProxyConfig *cfg, bool_t allow) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	linphone_account_params_set_remote_push_notification_allowed(cfg->edit, allow);
}

bool_t linphone_proxy_config_is_push_notification_available(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_is_push_notification_available(params);
}

void linphone_proxy_config_set_push_notification_config(LinphoneProxyConfig *cfg,
                                                        LinphonePushNotificationConfig *push_cfg) {
	if (!cfg->edit) {
		linphone_proxy_config_edit(cfg);
	}

	linphone_account_params_set_push_notification_config(cfg->edit, push_cfg);
}

LinphonePushNotificationConfig *linphone_proxy_config_get_push_notification_config(const LinphoneProxyConfig *cfg) {
	const LinphoneAccountParams *params = cfg->edit ? cfg->edit : linphone_account_get_params(cfg->account);
	return linphone_account_params_get_push_notification_config(params);
}

int linphone_proxy_config_get_unread_chat_message_count(const LinphoneProxyConfig *cfg) {
	return linphone_account_get_unread_chat_message_count(cfg->account);
}

LinphoneEvent *linphone_proxy_config_create_publish(LinphoneProxyConfig *cfg, const char *event, int expires) {
	return Account::toCpp(cfg->account)->createPublish(event, expires)->toC();
}

SalRegisterOp *linphone_proxy_config_get_op(const LinphoneProxyConfig *cfg) {
	return Account::toCpp(cfg->account)->getOp();
}

const char *linphone_registration_state_to_string(LinphoneRegistrationState cs) {
	switch (cs) {
		case LinphoneRegistrationCleared:
			return "LinphoneRegistrationCleared";
			break;
		case LinphoneRegistrationNone:
			return "LinphoneRegistrationNone";
			break;
		case LinphoneRegistrationProgress:
			return "LinphoneRegistrationProgress";
			break;
		case LinphoneRegistrationRefreshing:
			return "LinphoneRegistrationRefreshing";
			break;
		case LinphoneRegistrationOk:
			return "LinphoneRegistrationOk";
			break;
		case LinphoneRegistrationFailed:
			return "LinphoneRegistrationFailed";
			break;
	}
	return NULL;
}
