/*
 * Purple - Internet Messaging Library
 * Copyright (C) Pidgin Developers <devel@pidgin.im>
 *
 * Purple is the legal property of its developers, whose names are too numerous
 * to list here. Please refer to the COPYRIGHT file distributed with this
 * source distribution.
 *
 * This library 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 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 General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this library; if not, see <https://www.gnu.org/licenses/>.
 */

#include <birb.h>

#include "purplecontactinfo.h"

#include "purpleenums.h"
#include "util.h"

typedef struct  {
	char *id;

	char *display_name;
	char *alias;
	char *color;
	char *email;
	char *phone_number;
	GTimeZone *time_zone;
	char *note;
	char *sid;
	gboolean favorite;
	gboolean external;
	GDateTime *created_time;

	char *name_for_display;

	PurpleImage *avatar;
	PurpleBadges *badges;

	PurplePresence *presence;

	PurpleTags *tags;

	PurplePerson *person;

	PurpleContactInfoPermission permission;
} PurpleContactInfoPrivate;

enum {
	PROP_0,
	PROP_ID,
	PROP_DISPLAY_NAME,
	PROP_ALIAS,
	PROP_COLOR,
	PROP_CREATED_TIME,
	PROP_EMAIL,
	PROP_PHONE_NUMBER,
	PROP_TIME_ZONE,
	PROP_NOTE,
	PROP_AVATAR,
	PROP_AVATAR_FOR_DISPLAY,
	PROP_BADGES,
	PROP_PRESENCE,
	PROP_TAGS,
	PROP_PERSON,
	PROP_PERMISSION,
	PROP_SID,
	PROP_FAVORITE,
	PROP_NAME_FOR_DISPLAY,
	PROP_EXTERNAL,
	N_PROPERTIES,
};
static GParamSpec *properties[N_PROPERTIES] = {NULL, };

enum {
	SIG_PRESENCE_CHANGED,
	SIG_POPULATE_MENU,
	N_SIGNALS,
};
static guint signals[N_SIGNALS] = {0, };

G_DEFINE_TYPE_WITH_PRIVATE(PurpleContactInfo, purple_contact_info,
                           G_TYPE_OBJECT)

/******************************************************************************
 * Helpers
 *****************************************************************************/
static void
purple_contact_info_update_name_for_display(PurpleContactInfo *info) {
	PurpleContactInfoPrivate *priv = NULL;
	const char *name_for_display = NULL;

	priv = purple_contact_info_get_instance_private(info);

	/* If the info has an alias set, use it. */
	if(name_for_display == NULL && !birb_str_is_empty(priv->alias)) {
		name_for_display = priv->alias;
	}

	/* If info is associated with a PurplePerson that has an alias set, use the
	 * alias of that PurplePerson.
	 */
	if(name_for_display == NULL && priv->person != NULL) {
		const char *alias = purple_person_get_alias(priv->person);

		if(!birb_str_is_empty(alias)) {
			name_for_display = alias;
		}
	}

	/* If the info has a display name set, use it. */
	if(name_for_display == NULL && !birb_str_is_empty(priv->display_name)) {
		name_for_display = priv->display_name;
	}

	/* Finally, in a last ditch effort, use the id of the info. */
	if(name_for_display == NULL) {
		name_for_display = priv->id;
	}

	if(g_set_str(&priv->name_for_display, name_for_display)) {
		g_object_notify_by_pspec(G_OBJECT(info),
		                         properties[PROP_NAME_FOR_DISPLAY]);
	}
}

/******************************************************************************
 * Callbacks
 *****************************************************************************/
static void
purple_contact_info_person_alias_changed_cb(G_GNUC_UNUSED GObject *obj,
                                            G_GNUC_UNUSED GParamSpec *pspec,
                                            gpointer data)
{
	purple_contact_info_update_name_for_display(data);
}

/*
 * This is a notify callback on the presence for a contact info, it is used
 * to emit the presence-changed signal.
 */
static void
purple_contact_info_presence_notify_cb(GObject *source, GParamSpec *pspec,
                                       gpointer data)
{
	g_signal_emit(data, signals[SIG_PRESENCE_CHANGED],
	              g_param_spec_get_name_quark(pspec),
	              source, pspec);
}

/******************************************************************************
 * GObject Implementation
 *****************************************************************************/
static void
purple_contact_info_get_property(GObject *obj, guint param_id, GValue *value,
                                 GParamSpec *pspec)
{
	PurpleContactInfo *info = PURPLE_CONTACT_INFO(obj);

	switch(param_id) {
		case PROP_ID:
			g_value_set_string(value, purple_contact_info_get_id(info));
			break;
		case PROP_DISPLAY_NAME:
			g_value_set_string(value,
			                   purple_contact_info_get_display_name(info));
			break;
		case PROP_ALIAS:
			g_value_set_string(value, purple_contact_info_get_alias(info));
			break;
		case PROP_COLOR:
			g_value_set_string(value, purple_contact_info_get_color(info));
			break;
		case PROP_CREATED_TIME:
			g_value_set_boxed(value,
			                  purple_contact_info_get_created_time(info));
			break;
		case PROP_EMAIL:
			g_value_set_string(value, purple_contact_info_get_email(info));
			break;
		case PROP_PHONE_NUMBER:
			g_value_set_string(value,
			                   purple_contact_info_get_phone_number(info));
			break;
		case PROP_TIME_ZONE:
			g_value_set_boxed(value, purple_contact_info_get_time_zone(info));
			break;
		case PROP_NOTE:
			g_value_set_string(value, purple_contact_info_get_note(info));
			break;
		case PROP_AVATAR:
			g_value_set_object(value, purple_contact_info_get_avatar(info));
			break;
		case PROP_AVATAR_FOR_DISPLAY:
			g_value_set_object(value, purple_contact_info_get_avatar_for_display(info));
			break;
		case PROP_BADGES:
			g_value_set_object(value, purple_contact_info_get_badges(info));
			break;
		case PROP_PRESENCE:
			g_value_set_object(value, purple_contact_info_get_presence(info));
			break;
		case PROP_TAGS:
			g_value_set_object(value, purple_contact_info_get_tags(info));
			break;
		case PROP_PERSON:
			g_value_set_object(value, purple_contact_info_get_person(info));
			break;
		case PROP_PERMISSION:
			g_value_set_enum(value, purple_contact_info_get_permission(info));
			break;
		case PROP_NAME_FOR_DISPLAY:
			g_value_set_string(value,
			                   purple_contact_info_get_name_for_display(info));
			break;
		case PROP_SID:
			g_value_set_string(value, purple_contact_info_get_sid(info));
			break;
		case PROP_FAVORITE:
			g_value_set_boolean(value, purple_contact_info_get_favorite(info));
			break;
		case PROP_EXTERNAL:
			g_value_set_boolean(value, purple_contact_info_get_external(info));
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
			break;
	}
}

static void
purple_contact_info_set_property(GObject *obj, guint param_id,
                                 const GValue *value, GParamSpec *pspec)
{
	PurpleContactInfo *info = PURPLE_CONTACT_INFO(obj);

	switch(param_id) {
		case PROP_ID:
			purple_contact_info_set_id(info, g_value_get_string(value));
			break;
		case PROP_DISPLAY_NAME:
			purple_contact_info_set_display_name(info,
			                                     g_value_get_string(value));
			break;
		case PROP_ALIAS:
			purple_contact_info_set_alias(info, g_value_get_string(value));
			break;
		case PROP_COLOR:
			purple_contact_info_set_color(info, g_value_get_string(value));
			break;
		case PROP_CREATED_TIME:
			purple_contact_info_set_created_time(info,
			                                     g_value_get_boxed(value));
			break;
		case PROP_EMAIL:
			purple_contact_info_set_email(info, g_value_get_string(value));
			break;
		case PROP_PHONE_NUMBER:
			purple_contact_info_set_phone_number(info,
			                                     g_value_get_string(value));
			break;
		case PROP_TIME_ZONE:
			purple_contact_info_set_time_zone(info, g_value_get_boxed(value));
			break;
		case PROP_NOTE:
			purple_contact_info_set_note(info, g_value_get_string(value));
			break;
		case PROP_AVATAR:
			purple_contact_info_set_avatar(info, g_value_get_object(value));
			break;
		case PROP_PERSON:
			purple_contact_info_set_person(info, g_value_get_object(value));
			break;
		case PROP_PERMISSION:
			purple_contact_info_set_permission(info, g_value_get_enum(value));
			break;
		case PROP_SID:
			purple_contact_info_set_sid(info, g_value_get_string(value));
			break;
		case PROP_FAVORITE:
			purple_contact_info_set_favorite(info, g_value_get_boolean(value));
			break;
		case PROP_EXTERNAL:
			purple_contact_info_set_external(info, g_value_get_boolean(value));
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
			break;
	}
}

static void
purple_contact_info_dispose(GObject *obj) {
	PurpleContactInfo *info = PURPLE_CONTACT_INFO(obj);
	PurpleContactInfoPrivate *priv = NULL;

	priv = purple_contact_info_get_instance_private(info);

	g_clear_object(&priv->avatar);
	g_clear_object(&priv->badges);
	g_clear_object(&priv->presence);
	g_clear_object(&priv->tags);
	g_clear_object(&priv->person);
	g_clear_pointer(&priv->created_time, g_date_time_unref);

	G_OBJECT_CLASS(purple_contact_info_parent_class)->dispose(obj);
}

static void
purple_contact_info_finalize(GObject *obj) {
	PurpleContactInfo *info = PURPLE_CONTACT_INFO(obj);
	PurpleContactInfoPrivate *priv = NULL;

	priv = purple_contact_info_get_instance_private(info);

	g_clear_pointer(&priv->id, g_free);
	g_clear_pointer(&priv->display_name, g_free);
	g_clear_pointer(&priv->alias, g_free);
	g_clear_pointer(&priv->color, g_free);
	g_clear_pointer(&priv->email, g_free);
	g_clear_pointer(&priv->phone_number, g_free);
	g_clear_pointer(&priv->time_zone, g_time_zone_unref);
	g_clear_pointer(&priv->note, g_free);
	g_clear_pointer(&priv->sid, g_free);
	g_clear_pointer(&priv->name_for_display, g_free);

	G_OBJECT_CLASS(purple_contact_info_parent_class)->finalize(obj);
}

static void
purple_contact_info_constructed(GObject *obj) {
	PurpleContactInfo *info = NULL;
	PurpleContactInfoPrivate *priv = NULL;

	G_OBJECT_CLASS(purple_contact_info_parent_class)->constructed(obj);

	info = PURPLE_CONTACT_INFO(obj);
	priv = purple_contact_info_get_instance_private(info);

	if(birb_str_is_empty(priv->id)) {
		purple_contact_info_set_id(info, NULL);
	}

	purple_contact_info_update_name_for_display(info);
}

static void
purple_contact_info_init(PurpleContactInfo *info) {
	PurpleContactInfoPrivate *priv = NULL;

	priv = purple_contact_info_get_instance_private(info);

	priv->badges = purple_badges_new();
	priv->tags = purple_tags_new();

	priv->presence = g_object_new(PURPLE_TYPE_PRESENCE, NULL);
	g_signal_connect_object(priv->presence, "notify",
	                        G_CALLBACK(purple_contact_info_presence_notify_cb),
	                        info, 0);
}

static void
purple_contact_info_class_init(PurpleContactInfoClass *klass) {
	GObjectClass *obj_class = G_OBJECT_CLASS(klass);

	obj_class->constructed = purple_contact_info_constructed;
	obj_class->dispose = purple_contact_info_dispose;
	obj_class->finalize = purple_contact_info_finalize;
	obj_class->get_property = purple_contact_info_get_property;
	obj_class->set_property = purple_contact_info_set_property;

	/**
	 * PurpleContactInfo:id:
	 *
	 * The protocol specific id for the contact.
	 *
	 * Since: 3.0
	 */
	properties[PROP_ID] = g_param_spec_string(
		"id", NULL, NULL,
		NULL,
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleContactInfo:display-name:
	 *
	 * The display name for this contact. This is generally set by the person
	 * the contact is representing and controlled via the protocol plugin.
	 *
	 * Since: 3.0
	 */
	properties[PROP_DISPLAY_NAME] = g_param_spec_string(
		"display-name", NULL, NULL,
		NULL,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleContactInfo:alias:
	 *
	 * The alias for this contact. This is controlled by the libpurple user and
	 * may be used by the protocol if it allows for aliasing.
	 *
	 * Since: 3.0
	 */
	properties[PROP_ALIAS] = g_param_spec_string(
		"alias", NULL, NULL,
		NULL,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleContactInfo:color:
	 *
	 * The color for this contact. This is an RGB hex code that user interfaces
	 * can use when rendering the contact. This may also be controlled via a
	 * protocol plugin in the event that the protocol allows people to set a
	 * highlight/branding color.
	 *
	 * Since: 3.0
	 */
	properties[PROP_COLOR] = g_param_spec_string(
		"color", NULL, NULL,
		NULL,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleContactInfo:email:
	 *
	 * The primary email address for the contact. This may also be controlled
	 * via a protocol plugin in the event that the protocol makes it available.
	 *
	 * Since: 3.0
	 */
	properties[PROP_EMAIL] = g_param_spec_string(
		"email", NULL, NULL,
		NULL,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleContactInfo:phone-number:
	 *
	 * The primary phone number for the contact. This may also be set via a
	 * protocol plugin in the event that the protocol knows it.
	 *
	 * Since: 3.0
	 */
	properties[PROP_PHONE_NUMBER] = g_param_spec_string(
		"phone-number", NULL, NULL,
		NULL,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleContactInfo:time-zone:
	 *
	 * The time zone for this contact. This is typically controlled by the
	 * protocol and should only be read by others.
	 *
	 * Since: 3.0
	 */
	properties[PROP_TIME_ZONE] = g_param_spec_boxed(
		"time-zone", NULL, NULL,
		G_TYPE_TIME_ZONE,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleContactInfo:note:
	 *
	 * A note for this contact. Many protocols will allow you to set a note on
	 * a contact and store it server side. This property is where that is kept
	 * track of.
	 *
	 * Since: 3.0
	 */
	properties[PROP_NOTE] = g_param_spec_string(
		"note", NULL, NULL,
		NULL,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleContactInfo:avatar:
	 *
	 * The avatar for this contact. This is typically controlled by the
	 * protocol and should only be read by others.
	 *
	 * Since: 3.0
	 */
	properties[PROP_AVATAR] = g_param_spec_object(
		"avatar", NULL, NULL,
		PURPLE_TYPE_IMAGE,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleContactInfo:avatar-for-display:
	 *
	 * The avatar that should be displayed for this contact info.
	 *
	 * Since: 3.0
	 */
	properties[PROP_AVATAR_FOR_DISPLAY] = g_param_spec_object(
		"avatar-for-display", NULL, NULL,
		PURPLE_TYPE_IMAGE,
		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleContactInfo:badges:
	 *
	 * Badges for this contact.
	 *
	 * Since: 3.0
	 */
	properties[PROP_BADGES] = g_param_spec_object(
		"badges", NULL, NULL,
		PURPLE_TYPE_BADGES,
		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleContactInfo:presence:
	 *
	 * The [class@Purple.Presence] for this contact. This is typically
	 * controlled by the protocol and should only be read by others.
	 *
	 * Since: 3.0
	 */
	properties[PROP_PRESENCE] = g_param_spec_object(
		"presence", NULL, NULL,
		PURPLE_TYPE_PRESENCE,
		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleContactInfo:tags:
	 *
	 * The [class@Purple.Tags] for this contact.
	 *
	 * Since: 3.0
	 */
	properties[PROP_TAGS] = g_param_spec_object(
		"tags", NULL, NULL,
		PURPLE_TYPE_TAGS,
		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleContactInfo:person:
	 *
	 * The [class@Purple.Person] that this contact belongs to.
	 *
	 * Since: 3.0
	 */
	properties[PROP_PERSON] = g_param_spec_object(
		"person", NULL, NULL,
		PURPLE_TYPE_PERSON,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleContactInfo:permission:
	 *
	 * The permission level for the contact.
	 *
	 * Since: 3.0
	 */
	properties[PROP_PERMISSION] = g_param_spec_enum(
		"permission", NULL, NULL,
		PURPLE_TYPE_CONTACT_INFO_PERMISSION,
		PURPLE_CONTACT_INFO_PERMISSION_UNSET,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleContactInfo:sid:
	 *
	 * The sid, or secondary id, is an additional identifier field for a
	 * contact.
	 *
	 * This could be used to hold the `ident` for an IRC contact, the
	 * `resource` for an XMPP contact, or something similar.
	 *
	 * Since: 3.0
	 */
	properties[PROP_SID] = g_param_spec_string(
		"sid", NULL, NULL,
		NULL,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleContactInfo:favorite:
	 *
	 * Whether or not the contact info has been marked as a favorite or stared.
	 *
	 * Since: 3.0
	 */
	properties[PROP_FAVORITE] = g_param_spec_boolean(
		"favorite", NULL, NULL,
		FALSE,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleContactInfo:name-for-display:
	 *
	 * The name that the user interface should display for this contact info.
	 *
	 * This will first check [property@Purple.ContactInfo:alias] and return
	 * that if it is set.
	 *
	 * Next, if the [property@Purple.ContactInfo:person] points to a valid
	 * [class@Purple.Person], the alias of [class@Purple.Person] will be
	 * returned if it is set.
	 *
	 * Otherwise, this will be set to the first set property from the following
	 * list:
	 *
	 *  * [property@Purple.ContactInfo:display-name]
	 *  * [property@Purple.ContactInfo:id]
	 *
	 * Since: 3.0
	 */
	properties[PROP_NAME_FOR_DISPLAY] = g_param_spec_string(
		"name-for-display", NULL, NULL,
		NULL,
		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleContactInfo:external:
	 *
	 * Whether or not the contact is an external contact.
	 *
	 * Some chat networks like Microsoft Teams and Slack have a concept of
	 * external contacts that are not members of the organization but can
	 * access some portion of the network. This flag represents that.
	 *
	 * Since: 3.0
	 */
	properties[PROP_EXTERNAL] = g_param_spec_boolean(
		"external", NULL, NULL,
		FALSE,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleContactInfo:created-time:
	 *
	 * The time when the contact info was created on the protocol/network.
	 *
	 * Many protocols expose when an account was created for moderation or
	 * other purposes which is what this property exposes.
	 *
	 * Since: 3.0
	 */
	properties[PROP_CREATED_TIME] = g_param_spec_boxed(
		"created-time", NULL, NULL,
		G_TYPE_DATE_TIME,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);

	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);

	/**
	 * PurpleContactInfo::presence-changed:
	 * @info: The instance.
	 * @presence: The presence that was changed.
	 * @pspec: The [class@GObject.ParamSpec] for the property that changed.
	 *
	 * This is a propagation of the notify signal from @presence. This means
	 * that your callback will be called when anything in the presence changes.
	 *
	 * This also supports details, so you can specify the signal name as
	 * something like `presence-changed::message` and your callback will only
	 * be called when the message property of @presence has been changed.
	 *
	 * Since: 3.0
	 */
	signals[SIG_PRESENCE_CHANGED] = g_signal_new_class_handler(
		"presence-changed",
		G_OBJECT_CLASS_TYPE(klass),
		G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
		NULL,
		NULL,
		NULL,
		NULL,
		G_TYPE_NONE,
		2,
		PURPLE_TYPE_PRESENCE,
		G_TYPE_PARAM);

	/**
	 * PurpleContactInfo::populate-menu:
	 * @info: The instance.
	 * @menu: The [class@Birb.ActionMenu] to be displayed.
	 *
	 * Emitted in response to [method@PurpleContactInfo.get_menu] being called,
	 * so that plugins can add additional items to @menu.
	 *
	 * The user interface is responsible for displaying @menu which means it
	 * can add additional items, hide items, and lay them out however it
	 * pleases.
	 *
	 * Since: 3.0
	 */
	signals[SIG_POPULATE_MENU] = g_signal_new_class_handler(
		"populate-menu",
		G_OBJECT_CLASS_TYPE(klass),
		G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
		NULL,
		NULL,
		NULL,
		NULL,
		G_TYPE_NONE,
		1,
		BIRB_TYPE_ACTION_MENU);
}

/******************************************************************************
 * Public API
 *****************************************************************************/
PurpleContactInfo *
purple_contact_info_new(const gchar *id) {
	return g_object_new(
		PURPLE_TYPE_CONTACT_INFO,
		"id", id,
		NULL);
}

const gchar *
purple_contact_info_get_id(PurpleContactInfo *info) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), NULL);

	priv = purple_contact_info_get_instance_private(info);

	return priv->id;
}

void
purple_contact_info_set_id(PurpleContactInfo *info, const gchar *id) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_if_fail(PURPLE_IS_CONTACT_INFO(info));

	priv = purple_contact_info_get_instance_private(info);

	if(g_set_str(&priv->id, id)) {
		g_object_freeze_notify(G_OBJECT(info));

		g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_ID]);

		purple_contact_info_update_name_for_display(info);

		g_object_thaw_notify(G_OBJECT(info));
	}
}

const gchar *
purple_contact_info_get_display_name(PurpleContactInfo *info) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), NULL);

	priv = purple_contact_info_get_instance_private(info);

	return priv->display_name;
}

void
purple_contact_info_set_display_name(PurpleContactInfo *info,
                                     const gchar *display_name)
{
	PurpleContactInfoPrivate *priv = NULL;

	g_return_if_fail(PURPLE_IS_CONTACT_INFO(info));

	priv = purple_contact_info_get_instance_private(info);

	if(g_set_str(&priv->display_name, display_name)) {
		g_object_freeze_notify(G_OBJECT(info));

		g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_DISPLAY_NAME]);

		purple_contact_info_update_name_for_display(info);

		g_object_thaw_notify(G_OBJECT(info));
	}
}

const gchar *
purple_contact_info_get_alias(PurpleContactInfo *info) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), NULL);

	priv = purple_contact_info_get_instance_private(info);

	return priv->alias;
}

void
purple_contact_info_set_alias(PurpleContactInfo *info, const gchar *alias) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_if_fail(PURPLE_IS_CONTACT_INFO(info));

	priv = purple_contact_info_get_instance_private(info);

	if(g_set_str(&priv->alias, alias)) {
		g_object_freeze_notify(G_OBJECT(info));

		g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_ALIAS]);

		purple_contact_info_update_name_for_display(info);

		g_object_thaw_notify(G_OBJECT(info));
	}
}

const char *
purple_contact_info_get_color(PurpleContactInfo *info) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), NULL);

	priv = purple_contact_info_get_instance_private(info);

	return priv->color;
}

void
purple_contact_info_set_color(PurpleContactInfo *info, const char *color) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_if_fail(PURPLE_IS_CONTACT_INFO(info));

	priv = purple_contact_info_get_instance_private(info);

	if(g_set_str(&priv->color, color)) {
		g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_COLOR]);
	}
}

const char *
purple_contact_info_get_email(PurpleContactInfo *info) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), NULL);

	priv = purple_contact_info_get_instance_private(info);

	return priv->email;
}

void
purple_contact_info_set_email(PurpleContactInfo *info, const char *email) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_if_fail(PURPLE_IS_CONTACT_INFO(info));

	priv = purple_contact_info_get_instance_private(info);

	if(g_set_str(&priv->email, email)) {
		g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_EMAIL]);
	}
}

const char *
purple_contact_info_get_phone_number(PurpleContactInfo *info) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), NULL);

	priv = purple_contact_info_get_instance_private(info);

	return priv->phone_number;
}

void
purple_contact_info_set_phone_number(PurpleContactInfo *info,
                                     const char *phone_number)
{
	PurpleContactInfoPrivate *priv = NULL;

	g_return_if_fail(PURPLE_IS_CONTACT_INFO(info));

	priv = purple_contact_info_get_instance_private(info);

	if(g_set_str(&priv->phone_number, phone_number)) {
		g_object_notify_by_pspec(G_OBJECT(info),
		                         properties[PROP_PHONE_NUMBER]);
	}
}

GTimeZone *
purple_contact_info_get_time_zone(PurpleContactInfo *info) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), NULL);

	priv = purple_contact_info_get_instance_private(info);

	return priv->time_zone;
}

void
purple_contact_info_set_time_zone(PurpleContactInfo *info,
                                  GTimeZone *time_zone)
{
	PurpleContactInfoPrivate *priv = NULL;
	GTimeZone *old = NULL;

	g_return_if_fail(PURPLE_IS_CONTACT_INFO(info));

	priv = purple_contact_info_get_instance_private(info);
	old = priv->time_zone;

	priv->time_zone = NULL;
	if(time_zone != NULL) {
		priv->time_zone = g_time_zone_ref(time_zone);
	}

	if(old != NULL) {
		g_time_zone_unref(old);
	}

	g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_TIME_ZONE]);
}

const char *
purple_contact_info_get_note(PurpleContactInfo *info) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), NULL);

	priv = purple_contact_info_get_instance_private(info);

	return priv->note;
}

void
purple_contact_info_set_note(PurpleContactInfo *info, const char *note) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_if_fail(PURPLE_IS_CONTACT_INFO(info));

	priv = purple_contact_info_get_instance_private(info);

	if(g_set_str(&priv->note, note)) {
		g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_NOTE]);
	}
}

PurpleImage *
purple_contact_info_get_avatar(PurpleContactInfo *info) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), NULL);

	priv = purple_contact_info_get_instance_private(info);

	return priv->avatar;
}

PurpleImage *
purple_contact_info_get_avatar_for_display(PurpleContactInfo *info) {
	PurpleContactInfoPrivate *priv = NULL;
	PurpleImage *candidate = NULL;

	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), NULL);

	priv = purple_contact_info_get_instance_private(info);

	if(PURPLE_IS_PERSON(priv->person)) {
		candidate = purple_person_get_avatar_for_display(priv->person);
	}

	if(!PURPLE_IS_IMAGE(candidate)) {
		candidate = priv->avatar;
	}

	return candidate;
}

PurpleBadges *
purple_contact_info_get_badges(PurpleContactInfo *info) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), NULL);

	priv = purple_contact_info_get_instance_private(info);

	return priv->badges;
}

void
purple_contact_info_set_avatar(PurpleContactInfo *info, PurpleImage *avatar) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_if_fail(PURPLE_IS_CONTACT_INFO(info));

	priv = purple_contact_info_get_instance_private(info);

	if(g_set_object(&priv->avatar, avatar)) {
		GObject *obj = G_OBJECT(info);

		g_object_freeze_notify(obj);
		g_object_notify_by_pspec(obj, properties[PROP_AVATAR]);
		g_object_notify_by_pspec(obj, properties[PROP_AVATAR_FOR_DISPLAY]);
		g_object_thaw_notify(obj);
	}
}

PurplePresence *
purple_contact_info_get_presence(PurpleContactInfo *info) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), NULL);

	priv = purple_contact_info_get_instance_private(info);

	return priv->presence;
}

PurpleTags *
purple_contact_info_get_tags(PurpleContactInfo *info) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), NULL);

	priv = purple_contact_info_get_instance_private(info);

	return priv->tags;
}

void
purple_contact_info_set_person(PurpleContactInfo *info, PurplePerson *person) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_if_fail(PURPLE_IS_CONTACT_INFO(info));

	priv = purple_contact_info_get_instance_private(info);

	if(g_set_object(&priv->person, person)) {
		/* If we got a new person, we need to connect to the notify::alias
		 * signal.
		 */
		if(PURPLE_IS_PERSON(priv->person)) {
			g_signal_connect_object(priv->person, "notify::alias",
			                        G_CALLBACK(purple_contact_info_person_alias_changed_cb),
			                        info, 0);
		}

		/* Freeze notifications as the person update could change the
		 * name-for-display property.
		 */
		g_object_freeze_notify(G_OBJECT(info));

		g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_PERSON]);
		g_object_notify_by_pspec(G_OBJECT(info),
		                         properties[PROP_AVATAR_FOR_DISPLAY]);

		/* Update the name-for-display property */
		purple_contact_info_update_name_for_display(info);

		g_object_thaw_notify(G_OBJECT(info));
	}
}

PurplePerson *
purple_contact_info_get_person(PurpleContactInfo *info) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), NULL);

	priv = purple_contact_info_get_instance_private(info);

	return priv->person;
}

PurpleContactInfoPermission
purple_contact_info_get_permission(PurpleContactInfo *info) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info),
	                     PURPLE_CONTACT_INFO_PERMISSION_UNSET);

	priv = purple_contact_info_get_instance_private(info);

	return priv->permission;
}

void
purple_contact_info_set_permission(PurpleContactInfo *info,
                                   PurpleContactInfoPermission permission)
{
	PurpleContactInfoPrivate *priv = NULL;

	g_return_if_fail(PURPLE_IS_CONTACT_INFO(info));

	priv = purple_contact_info_get_instance_private(info);

	priv->permission = permission;

	g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_PERMISSION]);
}

const char *
purple_contact_info_get_sid(PurpleContactInfo *info) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), NULL);

	priv = purple_contact_info_get_instance_private(info);

	return priv->sid;
}

void
purple_contact_info_set_sid(PurpleContactInfo *info, const char *sid) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_if_fail(PURPLE_IS_CONTACT_INFO(info));

	priv = purple_contact_info_get_instance_private(info);

	if(g_set_str(&priv->sid, sid)) {
		g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_SID]);
	}
}

gboolean
purple_contact_info_get_favorite(PurpleContactInfo *info) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), FALSE);

	priv = purple_contact_info_get_instance_private(info);

	return priv->favorite;
}

void
purple_contact_info_set_favorite(PurpleContactInfo *info, gboolean favorite) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_if_fail(PURPLE_IS_CONTACT_INFO(info));

	priv = purple_contact_info_get_instance_private(info);

	if(priv->favorite != favorite) {
		priv->favorite = favorite;

		g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_FAVORITE]);
	}
}

const char *
purple_contact_info_get_name_for_display(PurpleContactInfo *info) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), NULL);

	priv = purple_contact_info_get_instance_private(info);

	return priv->name_for_display;
}

int
purple_contact_info_compare(PurpleContactInfo *a, PurpleContactInfo *b) {
	PurplePerson *person_a = NULL;
	PurplePerson *person_b = NULL;
	const char *name_a = NULL;
	const char *name_b = NULL;

	/* Check for NULL values. */
	if(a != NULL && b == NULL) {
		return -1;
	} else if(a == NULL && b != NULL) {
		return 1;
	} else if(a == NULL && b == NULL) {
		return 0;
	}

	/* Check if the contacts have persons associated with them. */
	person_a = purple_contact_info_get_person(a);
	person_b = purple_contact_info_get_person(b);

	if(person_a != NULL && person_b == NULL) {
		return -1;
	} else if(person_a == NULL && person_b != NULL) {
		return 1;
	}

	/* Finally get the names for the displaying and compare those. */
	name_a = purple_contact_info_get_name_for_display(a);
	name_b = purple_contact_info_get_name_for_display(b);

	return purple_utf8_strcasecmp(name_a, name_b);
}

gboolean
purple_contact_info_equal(PurpleContactInfo *a, PurpleContactInfo *b) {
	return purple_contact_info_compare(a, b) == 0;
}

gboolean
purple_contact_info_matches(PurpleContactInfo *info, const char *needle) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), FALSE);

	if(birb_str_is_empty(needle)) {
		return TRUE;
	}

	priv = purple_contact_info_get_instance_private(info);

	if(!birb_str_is_empty(priv->id)) {
		if(birb_str_matches(needle, priv->id)) {
			return TRUE;
		}
	}

	if(!birb_str_is_empty(priv->alias)) {
		if(birb_str_matches(needle, priv->alias)) {
			return TRUE;
		}
	}

	if(!birb_str_is_empty(priv->display_name)) {
		if(birb_str_matches(needle, priv->display_name)) {
			return TRUE;
		}
	}

	/* Nothing matched, so return FALSE. */
	return FALSE;
}

BirbActionMenu *
purple_contact_info_get_menu(PurpleContactInfo *info) {
	BirbActionMenu *menu = NULL;

	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), NULL);

	menu = birb_action_menu_new();

	g_signal_emit(info, signals[SIG_POPULATE_MENU], 0, menu);

	return menu;
}

gboolean
purple_contact_info_get_external(PurpleContactInfo *info) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), FALSE);

	priv = purple_contact_info_get_instance_private(info);

	return priv->external;
}

void
purple_contact_info_set_external(PurpleContactInfo *info, gboolean external) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_if_fail(PURPLE_IS_CONTACT_INFO(info));

	priv = purple_contact_info_get_instance_private(info);
	if(priv->external != external) {
		priv->external = external;

		g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_EXTERNAL]);
	}
}

GDateTime *
purple_contact_info_get_created_time(PurpleContactInfo *info) {
	PurpleContactInfoPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), NULL);

	priv = purple_contact_info_get_instance_private(info);

	return priv->created_time;
}

void
purple_contact_info_set_created_time(PurpleContactInfo *info,
                                     GDateTime *created_time)
{
	PurpleContactInfoPrivate *priv = NULL;

	g_return_if_fail(PURPLE_IS_CONTACT_INFO(info));

	priv = purple_contact_info_get_instance_private(info);

	if(birb_date_time_set(&priv->created_time, created_time)) {
		g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_CREATED_TIME]);
	}
}

