/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * Copyright (C) 2004-2007 Imendio AB
 * Copyright (C) 2010 Collabora Ltd
 *
 * 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
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301  USA
 *
 * Authors: Mikael Hallendal <micke@imendio.com>
 */

#include "config.h"

#include <string.h>

#include <libempathy/empathy-utils.h>
#include "empathy-cell-renderer-text.h"

#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyCellRendererText)
typedef struct {
	gchar    *name;
	TpConnectionPresenceType presence_type;
	gchar    *status;
	gboolean  is_group;

	gboolean  is_valid;
	gboolean  is_selected;

	gchar   **types;

	gboolean  compact;
} EmpathyCellRendererTextPriv;

static void cell_renderer_text_finalize          (GObject                     *object);
static void cell_renderer_text_get_property      (GObject                     *object,
						  guint                        param_id,
						  GValue                      *value,
						  GParamSpec                  *pspec);
static void cell_renderer_text_set_property      (GObject                     *object,
						  guint                        param_id,
						  const GValue                *value,
						  GParamSpec                  *pspec);
static void cell_renderer_text_render            (GtkCellRenderer             *cell,
						  cairo_t *cr,
						  GtkWidget                   *widget,
						  const GdkRectangle          *background_area,
						  const GdkRectangle          *cell_area,
						  GtkCellRendererState         flags);
static void cell_renderer_text_update_text       (EmpathyCellRendererText      *cell,
						  GtkWidget                   *widget,
						  gboolean                     selected);

/* Properties */
enum {
	PROP_0,
	PROP_NAME,
	PROP_PRESENCE_TYPE,
	PROP_STATUS,
	PROP_IS_GROUP,
	PROP_COMPACT,
	PROP_CLIENT_TYPES
};

G_DEFINE_TYPE (EmpathyCellRendererText, empathy_cell_renderer_text, GTK_TYPE_CELL_RENDERER_TEXT);

static void
cell_renderer_text_get_preferred_height_for_width (GtkCellRenderer *renderer,
								GtkWidget *widget,
								gint width,
								gint *minimum_size,
								gint *natural_size)
{
	EmpathyCellRendererText *self = EMPATHY_CELL_RENDERER_TEXT (renderer);
	EmpathyCellRendererTextPriv *priv = GET_PRIV (self);

	/* Only update if not already valid so we get the right size. */
	cell_renderer_text_update_text (self, widget, priv->is_selected);

	GTK_CELL_RENDERER_CLASS (empathy_cell_renderer_text_parent_class)->
			get_preferred_height_for_width (renderer, widget, width,
					minimum_size, natural_size);
}


static void
empathy_cell_renderer_text_class_init (EmpathyCellRendererTextClass *klass)
{
	GObjectClass         *object_class;
	GtkCellRendererClass *cell_class;
	GParamSpec           *spec;

	object_class = G_OBJECT_CLASS (klass);
	cell_class = GTK_CELL_RENDERER_CLASS (klass);

	object_class->finalize = cell_renderer_text_finalize;

	object_class->get_property = cell_renderer_text_get_property;
	object_class->set_property = cell_renderer_text_set_property;

	cell_class->get_preferred_height_for_width = cell_renderer_text_get_preferred_height_for_width;
	cell_class->render = cell_renderer_text_render;

	spec = g_param_spec_string ("name", "Name", "Contact name", NULL,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
	g_object_class_install_property (object_class, PROP_NAME, spec);

	spec = g_param_spec_uint ("presence-type", "TpConnectionPresenceType",
		"The contact's presence type",
		0, G_MAXUINT, /* Telepathy enum, can be extended */
		TP_CONNECTION_PRESENCE_TYPE_UNKNOWN,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
	g_object_class_install_property (object_class, PROP_PRESENCE_TYPE,
		spec);

	spec = g_param_spec_string ("status", "Status message",
		"Contact's custom status message", NULL,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
	g_object_class_install_property (object_class, PROP_STATUS, spec);

	spec = g_param_spec_boolean ("is-group", "Is group",
		"Whether this cell is a group", FALSE,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
	g_object_class_install_property (object_class, PROP_IS_GROUP, spec);

	spec = g_param_spec_boolean ("compact", "Compact",
		"TRUE to show the status alongside the contact name;"
		"FALSE to show it on its own line",
		FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
	g_object_class_install_property (object_class, PROP_COMPACT, spec);

	spec = g_param_spec_boxed ("client-types", "Contact client types",
		"Client types of the contact",
		G_TYPE_STRV, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
	g_object_class_install_property (object_class, PROP_CLIENT_TYPES, spec);

	g_type_class_add_private (object_class, sizeof (EmpathyCellRendererTextPriv));
}

static void
empathy_cell_renderer_text_init (EmpathyCellRendererText *cell)
{
	EmpathyCellRendererTextPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (cell,
		EMPATHY_TYPE_CELL_RENDERER_TEXT, EmpathyCellRendererTextPriv);

	cell->priv = priv;
	g_object_set (cell,
		      "ellipsize", PANGO_ELLIPSIZE_END,
		      NULL);

	priv->name = g_strdup ("");
	priv->status = g_strdup ("");
	priv->compact = FALSE;
}

static void
cell_renderer_text_finalize (GObject *object)
{
	EmpathyCellRendererText     *cell;
	EmpathyCellRendererTextPriv *priv;

	cell = EMPATHY_CELL_RENDERER_TEXT (object);
	priv = GET_PRIV (cell);

	g_free (priv->name);
	g_free (priv->status);
	g_strfreev (priv->types);

	(G_OBJECT_CLASS (empathy_cell_renderer_text_parent_class)->finalize) (object);
}

static void
cell_renderer_text_get_property (GObject    *object,
				 guint       param_id,
				 GValue     *value,
				 GParamSpec *pspec)
{
	EmpathyCellRendererText     *cell;
	EmpathyCellRendererTextPriv *priv;

	cell = EMPATHY_CELL_RENDERER_TEXT (object);
	priv = GET_PRIV (cell);

	switch (param_id) {
	case PROP_NAME:
		g_value_set_string (value, priv->name);
		break;
	case PROP_PRESENCE_TYPE:
		g_value_set_uint (value, priv->presence_type);
		break;
	case PROP_STATUS:
		g_value_set_string (value, priv->status);
		break;
	case PROP_IS_GROUP:
		g_value_set_boolean (value, priv->is_group);
		break;
	case PROP_COMPACT:
		g_value_set_boolean (value, priv->compact);
		break;
	case PROP_CLIENT_TYPES:
		g_value_set_boxed (value, priv->types);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
		break;
	}
}

static void
cell_renderer_text_set_property (GObject      *object,
				 guint         param_id,
				 const GValue *value,
				 GParamSpec   *pspec)
{
	EmpathyCellRendererText     *cell;
	EmpathyCellRendererTextPriv *priv;
	const gchar                *str;

	cell = EMPATHY_CELL_RENDERER_TEXT (object);
	priv = GET_PRIV (cell);

	switch (param_id) {
	case PROP_NAME:
		g_free (priv->name);
		str = g_value_get_string (value);
		priv->name = g_strdup (str ? str : "");
		g_strdelimit (priv->name, "\n\r\t", ' ');
		priv->is_valid = FALSE;
		break;
	case PROP_PRESENCE_TYPE:
		priv->presence_type = g_value_get_uint (value);
		priv->is_valid = FALSE;
		break;
	case PROP_STATUS:
		g_free (priv->status);
		str = g_value_get_string (value);
		priv->status = g_strdup (str ? str : "");
		g_strdelimit (priv->status, "\n\r\t", ' ');
		priv->is_valid = FALSE;
		break;
	case PROP_IS_GROUP:
		priv->is_group = g_value_get_boolean (value);
		priv->is_valid = FALSE;
		break;
	case PROP_COMPACT:
		priv->compact = g_value_get_boolean (value);
		priv->is_valid = FALSE;
		break;
	case PROP_CLIENT_TYPES:
		g_strfreev (priv->types);
		priv->types = g_value_dup_boxed (value);
		priv->is_valid = FALSE;
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
		break;
	}
}

static void
cell_renderer_text_render (GtkCellRenderer      *cell,
			   cairo_t *cr,
			   GtkWidget            *widget,
			   const GdkRectangle   *background_area,
			   const GdkRectangle   *cell_area,
			   GtkCellRendererState  flags)
{
	EmpathyCellRendererText *celltext;

	celltext = EMPATHY_CELL_RENDERER_TEXT (cell);

	cell_renderer_text_update_text (celltext,
					widget,
					(flags & GTK_CELL_RENDERER_SELECTED));

	(GTK_CELL_RENDERER_CLASS (empathy_cell_renderer_text_parent_class)->render) (
		cell, cr,
		widget,
		background_area,
		cell_area,
		flags);
}

static void
cell_renderer_text_update_text (EmpathyCellRendererText *cell,
				GtkWidget              *widget,
				gboolean                selected)
{
	EmpathyCellRendererTextPriv *priv;
	const PangoFontDescription *font_desc;
	PangoAttrList              *attr_list;
	PangoAttribute             *attr_color = NULL, *attr_size;
	GtkStyleContext            *style;
	gchar                      *str;
	gint                        font_size;

	priv = GET_PRIV (cell);

	if (priv->is_valid && priv->is_selected == selected) {
		return;
	}

	if (priv->is_group) {
		g_object_set (cell,
			      "visible", TRUE,
			      "weight", PANGO_WEIGHT_BOLD,
			      "text", priv->name,
			      "attributes", NULL,
			      "xpad", 1,
			      "ypad", 1,
			      NULL);

		priv->is_selected = selected;
		priv->is_valid = TRUE;
		return;
	}

	style = gtk_widget_get_style_context (widget);

	attr_list = pango_attr_list_new ();

	font_desc = gtk_style_context_get_font (style, GTK_STATE_FLAG_NORMAL);
	font_size = pango_font_description_get_size (font_desc);
	attr_size = pango_attr_size_new (font_size / 1.2);
	attr_size->start_index = strlen (priv->name) + 1;
	attr_size->end_index = -1;
	pango_attr_list_insert (attr_list, attr_size);

	if (!selected) {
		GdkRGBA color;

		gtk_style_context_get_color (style, 0, &color);

		attr_color = pango_attr_foreground_new (color.red * 0xffff,
							color.green * 0xffff,
							color.blue * 0xffff);
		attr_color->start_index = attr_size->start_index;
		attr_color->end_index = -1;
		pango_attr_list_insert (attr_list, attr_color);
	}

	if (priv->compact) {
		if (EMP_STR_EMPTY (priv->status)) {
			str = g_strdup (priv->name);
		} else {
			str = g_strdup_printf ("%s %s", priv->name, priv->status);
		}
	} else {
		const gchar *status = priv->status;
		gboolean on_a_phone = FALSE;

		if (EMP_STR_EMPTY (priv->status)) {
			status = empathy_presence_get_default_message (priv->presence_type);
		}

		if (!priv->is_group && priv->types != NULL && g_strv_length (priv->types) > 0
		    && !tp_strdiff (priv->types[0], "phone")) {
			on_a_phone = TRUE;
			/* We want the phone black. */
			if (attr_color)
				attr_color->start_index += 3;
		}

		if (status == NULL)
			str = g_strdup (priv->name);
		else
			str = g_strdup_printf ("%s\n%s%s", priv->name,
					       on_a_phone ? "☎  " : "",
					       status);
	}

	g_object_set (cell,
		      "visible", TRUE,
		      "weight", PANGO_WEIGHT_NORMAL,
		      "text", str,
		      "attributes", attr_list,
		      "xpad", 0,
		      "ypad", 1,
		      NULL);

	g_free (str);
	pango_attr_list_unref (attr_list);

	priv->is_selected = selected;
	priv->is_valid = TRUE;
}

GtkCellRenderer *
empathy_cell_renderer_text_new (void)
{
	return g_object_new (EMPATHY_TYPE_CELL_RENDERER_TEXT, NULL);
}
