/*
 * Style.c: Style resource management
 *
 * Author:
 *  Miguel de Icaza (miguel@gnu.org)
 *  (C) 1998 Miguel de Icaza
 */
#include <config.h>
#include <gnome.h>
#include <string.h>
#include "gnumeric.h"
#include "format.h"
#include "color.h"

#define DEFAULT_FONT      "-adobe-helvetica-medium-r-normal--*-120-*-*-*-*-*-*"
#define DEFAULT_BOLD_FONT "-adobe-helvetica-bold-r-normal--*-120-*-*-*-*-*-*"
#define DEFAULT_ITALIC_FONT "-adobe-helvetica-medium-o-normal--*-120-*-*-*-*-*-*"
#define DEFAULT_SIZE 12

static GHashTable *style_format_hash;
static GHashTable *style_font_hash;
static GHashTable *style_border_hash;
static GHashTable *style_color_hash;

StyleFont *gnumeric_default_font;
StyleFont *gnumeric_default_bold_font;
StyleFont *gnumeric_default_italic_font;

StyleFormat *
style_format_new (char *name)
{
	StyleFormat *format;

	g_return_val_if_fail (name != NULL, NULL);
	
	format = (StyleFormat *) g_hash_table_lookup (style_format_hash, name);
	
	if (!format){
		format = g_new0 (StyleFormat, 1);
		format->format = g_strdup (name);
		format_compile (format);
		g_hash_table_insert (style_format_hash, name, format);
	}
	format->ref_count++;

	return format;
}

void
style_format_ref (StyleFormat *sf)
{
	g_return_if_fail (sf != NULL);

	sf->ref_count++;
}

void
style_format_unref (StyleFormat *sf)
{
	g_return_if_fail (sf->ref_count > 0);

	sf->ref_count--;
	if (sf->ref_count != 0)
		return;

	g_hash_table_remove (style_format_hash, sf->format);

	format_destroy (sf);
	g_free (sf->format);
	g_free (sf);
}

static void
font_compute_hints (StyleFont *font)
{
	char *p = font->font_name;
	int hyphens = 0;
	
	font->hint_is_bold = 0;
	font->hint_is_italic = 0;

	for (;*p; p++){
		if (*p == '-'){
			hyphens++;

			if (hyphens == 3 && (strncmp (p+1, "bold", 4) == 0))
				font->hint_is_bold = 1;

			if (hyphens == 4){
				if (*(p+1) == 'o' || *(p+1) == 'i')
					font->hint_is_italic = 1;
			}

			if (hyphens > 5)
				break;
		}
	}
}

StyleFont *
style_font_new_simple (char *font_name, int units)
{
	StyleFont *font;
	StyleFont key;

	g_return_val_if_fail (font_name != NULL, NULL);
	g_return_val_if_fail (units != 0, NULL);

	key.font_name = font_name;
	key.units    = units;
	
	font = (StyleFont *) g_hash_table_lookup (style_font_hash, &key);
	if (!font){
		GdkFont *gdk_font;

		gdk_font = gdk_font_load (font_name);
		
		if (!gdk_font)
			return NULL;
		
		font = g_new0 (StyleFont, 1);
		font->font_name = g_strdup (font_name);
		font->units    = units;
		font->font     = gdk_font_load (font_name);

		font_compute_hints (font);
		
		g_hash_table_insert (style_font_hash, font, font);
	}
	font->ref_count++;

	return font;
}

StyleFont *
style_font_new (char *font_name, int units)
{
	StyleFont *font;

	g_return_val_if_fail (font_name != NULL, NULL);
	g_return_val_if_fail (units != 0, NULL);

	font = style_font_new_simple (font_name, units);
	if (!font){
		font = gnumeric_default_font;
		style_font_ref (font);
	}

	return font;
}

void
style_font_ref (StyleFont *sf)
{
	g_return_if_fail (sf != NULL);

	sf->ref_count++;
}

void
style_font_unref (StyleFont *sf)
{
	g_return_if_fail (sf != NULL);
	g_return_if_fail (sf->ref_count > 0);

	sf->ref_count--;
	if (sf->ref_count != 0)
		return;

	g_hash_table_remove (style_font_hash, sf);
	g_free (sf->font_name);
	g_free (sf);
}

StyleBorder *
style_border_new (StyleBorderType  border_type  [4],
		  StyleColor      *border_color [4])

{
	StyleBorder key, *border;
	int lp ;

 	memcpy (&key.type, border_type, sizeof(key.type)) ;
 	for (lp = 0; lp < 4; lp++){
		if (border_color [lp])
			key.color [lp] = border_color [lp] ;
		else
 			key.color [lp] = NULL ;
 	}
	
	border = (StyleBorder *) g_hash_table_lookup (style_border_hash,
						      &key);
	if (!border){
		border = g_new0 (StyleBorder, 1);
		*border = key;
		g_hash_table_insert (style_border_hash, border, border);
		border->ref_count = 0;
	}
	border->ref_count++;

	return border;
}

void
style_border_ref (StyleBorder *sb)
{
	g_return_if_fail (sb != NULL);

	sb->ref_count++;
}

void
style_border_unref (StyleBorder *sb)
{
	g_return_if_fail (sb != NULL);
	g_return_if_fail (sb->ref_count > 0);

	sb->ref_count--;
	if (sb->ref_count != 0)
		return;

	g_hash_table_remove (style_border_hash, sb);
	g_free (sb);
}

StyleBorder *
style_border_new_plain (void)
{
 	StyleBorderType style [4] = { BORDER_NONE, BORDER_NONE, BORDER_NONE, BORDER_NONE } ;
 	StyleColor *color [4] = { NULL, NULL, NULL, NULL } ;

	return style_border_new (style, color) ;
}

StyleColor *
style_color_new (gushort red, gushort green, gushort blue)
{
	StyleColor *sc;
	StyleColor key;

	key.color.red   = red;
	key.color.green = green;
	key.color.blue  = blue;

	sc = g_hash_table_lookup (style_color_hash, &key);
	if (!sc){
		sc = g_new (StyleColor, 1);
		sc->color = key.color;
		sc->color.pixel = color_alloc (red, green, blue);

		g_hash_table_insert (style_color_hash, sc, sc);
		sc->ref_count = 0;
	}
	sc->ref_count++;
	
	return sc;
}

void
style_color_ref (StyleColor *sc)
{
	g_return_if_fail (sc != NULL);

	sc->ref_count++;
}

void
style_color_unref (StyleColor *sc)
{
	g_return_if_fail (sc != NULL);
	g_return_if_fail (sc->ref_count > 0);

	sc->ref_count--;
	if (sc->ref_count != 0)
		return;

	/*
	 * There is no need to deallocate colors, as they come from
	 * the GDK Color Context
	 */
	g_hash_table_remove (style_color_hash, sc);
	g_free (sc);
}

Style *
style_new (void)
{
	Style *style;

	style = g_new0 (Style, 1);

	style->valid_flags = STYLE_ALL;
	
	style->format      = style_format_new ("General");
	style->font        = style_font_new (DEFAULT_FONT, DEFAULT_SIZE);
	style->border      = style_border_new_plain ();
	style->fore_color  = style_color_new (0, 0, 0);
	style->back_color  = style_color_new (0xffff, 0xffff, 0xffff);
	style->halign      = HALIGN_GENERAL;
	style->valign      = VALIGN_CENTER;
	style->orientation = ORIENT_HORIZ;

	return style;
}

Style *
style_new_empty (void)
{
	Style *style;

	style = g_new0 (Style, 1);

	style->valid_flags = 0;
	
	return style;
}

void
style_destroy (Style *style)
{
	g_return_if_fail (style != NULL);

	if (style->valid_flags & STYLE_FORMAT)
		style_format_unref (style->format);

	if (style->valid_flags & STYLE_FONT)
		style_font_unref (style->font);

	if (style->valid_flags & STYLE_BORDER)
		style_border_unref (style->border);

	if (style->valid_flags & STYLE_FORE_COLOR)
		if (style->fore_color)
			style_color_unref (style->fore_color);

	if (style->valid_flags & STYLE_BACK_COLOR)
		if (style->back_color)
			style_color_unref (style->back_color);

	g_free (style);
}


Style *
style_duplicate (Style *original)
{
	Style *style;

	style = g_new0 (Style, 1);

	/* Bit copy */
	*style = *original;

	/* Do the rest of the copy */
	if (style->valid_flags & STYLE_FORMAT)
		style_format_ref (style->format);
	else
		style->format = NULL;

	if (style->valid_flags & STYLE_FONT)
		style_font_ref (style->font);
	else
		style->font = NULL;

	if (style->valid_flags & STYLE_BORDER)
		style_border_ref (style->border);
	else
		style->border = NULL;

	if (style->valid_flags & STYLE_FORE_COLOR)
		style_color_ref (style->fore_color);
	else
		style->fore_color = NULL;

	if (style->valid_flags & STYLE_BACK_COLOR)
		style_color_ref (style->back_color);
	else
		style->back_color = NULL;
	
	return style;
}

/*
 * The routines used to hash and compare the different styles
 */
static gint
font_equal (gconstpointer v, gconstpointer v2)
{
	StyleFont *k1 = (StyleFont *) v;
	StyleFont *k2 = (StyleFont *) v2;

	if (k1->units != k2->units)
		return 0;
	
	return !strcmp (k1->font_name, k2->font_name);
}

static guint
font_hash (gconstpointer v)
{
	StyleFont *k = (StyleFont *) v;

	return k->units + g_str_hash (k->font_name);
}

static gint
border_equal (gconstpointer v, gconstpointer v2)
{
	StyleBorder *k1 = (StyleBorder *) v;
	StyleBorder *k2 = (StyleBorder *) v2;
	int lp ;

 	for (lp = 0; lp < 4; lp++)
 	{
 		if (k1->type [lp] != k2->type [lp])
 			return 0 ;
 		if (k1->type [lp] != BORDER_NONE &&
		    k1->color [lp] != k2->color [lp])
			return 0;
	}
	
	return 1;
}

static guint
border_hash (gconstpointer v)
{
	StyleBorder *k = (StyleBorder *) v;

 	return (k->type [STYLE_LEFT] << 12) | (k->type [STYLE_RIGHT] << 8) |
	       (k->type [STYLE_TOP] << 4)   | (k->type [STYLE_BOTTOM]) ;

}

static gint
color_equal (gconstpointer v, gconstpointer v2)
{
	StyleColor *k1 = (StyleColor *) v;
	StyleColor *k2 = (StyleColor *) v2;

	if (k1->color.red   == k2->color.red &&
	    k1->color.green == k2->color.green &&
	    k1->color.blue  == k2->color.blue)
		return 1;
	
	return 0;
}

static guint
color_hash (gconstpointer v)
{
	StyleColor *k = (StyleColor *)v;

	return (k->color.red << 16) | (k->color.green << 8) | (k->color.blue);
}

void
style_merge_to (Style *target, Style *source)
{
	if (!(target->valid_flags & STYLE_FORMAT))
		if (source->valid_flags & STYLE_FORMAT){
			target->valid_flags |= STYLE_FORMAT;
			target->format = source->format;
			style_format_ref (target->format);
		}

	if (!(target->valid_flags & STYLE_FONT))
		if (source->valid_flags & STYLE_FONT){
			target->valid_flags |= STYLE_FONT;
			target->font = source->font;
			style_font_ref (target->font);
		}

	if (!(target->valid_flags & STYLE_BORDER))
		if (source->valid_flags & STYLE_BORDER){
			target->valid_flags |= STYLE_BORDER;
			target->border = source->border;
			style_border_ref (target->border);
		}

	if (!(target->valid_flags & STYLE_ALIGN))
		if (source->valid_flags & STYLE_ALIGN){
			target->valid_flags |= STYLE_ALIGN;
			target->halign      = source->halign;
			target->valign      = source->valign;
			target->orientation = source->orientation;
		}

	if (!(target->valid_flags & STYLE_FORE_COLOR))
		if (source->valid_flags & STYLE_FORE_COLOR){
			target->valid_flags |= STYLE_FORE_COLOR;
			target->fore_color = source->fore_color;
			if (target->fore_color)
				style_color_ref (target->fore_color);
		}

	if (!(target->valid_flags & STYLE_BACK_COLOR))
		if (source->valid_flags & STYLE_BACK_COLOR){
			target->valid_flags |= STYLE_BACK_COLOR;
			target->back_color = source->back_color;
			if (target->back_color)
				style_color_ref (target->back_color);
		}

	if (!(target->valid_flags & STYLE_PATTERN))
		if (source->valid_flags & STYLE_PATTERN){
			target->valid_flags |= STYLE_PATTERN;
			target->pattern = source->pattern;
		}
}

static void
font_init (void)
{
	gnumeric_default_font = style_font_new_simple (DEFAULT_FONT, DEFAULT_SIZE);

	if (!gnumeric_default_font)
		gnumeric_default_font = style_font_new_simple ("fixed", DEFAULT_SIZE);

	if (!gnumeric_default_font)
		g_error ("Could not load the default font");

	gnumeric_default_bold_font = style_font_new (DEFAULT_BOLD_FONT, DEFAULT_SIZE);
	gnumeric_default_italic_font = style_font_new (DEFAULT_ITALIC_FONT, DEFAULT_SIZE);
}

void
style_init (void)
{
	style_format_hash = g_hash_table_new (g_str_hash, g_str_equal);
	style_font_hash   = g_hash_table_new (font_hash, font_equal);
	style_border_hash = g_hash_table_new (border_hash, border_equal);
	style_color_hash  = g_hash_table_new (color_hash, color_equal);

	font_init ();
}
