/* format.c - attempts to emulate excel's number formatting ability.
 * Copyright (C) 1998 Chris Lahey, Miguel de Icaza
 *
 * Redid the format parsing routine to make it accept more of the Excel
 * formats.  The number rendeing code from Chris has not been touched,
 * that routine is pretty good. 
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <config.h>
#include <gnome.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include <locale.h>
#include <ctype.h>
#include "gnumeric.h"
#include "format.h"
#include "dates.h"

/* Points to the locale information for number display */
static struct lconv *lc;

static void style_entry_free (gpointer data, gpointer user_data);


/*
 * The returned string is newly allocated.
 *
 * Current format is an optional date specification followed by an
 * optional number specification.
 *
 * A date specification is an arbitrary sequence of characters (other
 * than '#', '0', '?', or '.') which is copied to the output.  The
 * standard date fields are substituted for.  If it ever finds an a or
 * a p it lists dates in 12 hour time, otherwise, it lists dates in 24
 * hour time.
 *
 * A number specification is as described in the relavent portions of
 * the excel formatting information.  Commas can currently only appear
 * at the end of the number specification.  Fractions are not yet
 * supported.  
 */


static void
do_roundup (GString *string)
{
	int i;
  
	for (i = string->len - 1; string->str [i] == '9'; i--)
		string->str[i] = '0';

	if (string->str [i] == '.')
	{
		/* FIXME */
	}
	else
	{
		string->str [i]++;
	}
}

/*
 * Parses the year field at the beginning of the format.  Returns the
 * number of characters used.
 */
static int
append_year (GString *string, gchar *format, struct tm *time_split)
{
	char temp [5];
	
	if (tolower (format [1]) != 'y'){
		g_string_append_c (string, 'y');
		return 1;
	}
	
	if (tolower (format [2]) != 'y' || tolower (format [3]) != 'y'){
		sprintf (temp, "%02d", time_split->tm_year % 100);
		g_string_append (string, temp);
		return 2;
	}

	sprintf (temp, "%04d", time_split->tm_year + 1900);
	g_string_append (string, temp);

	return 4;
}

/*
 * Parses the month field at the beginning of the format.  Returns the
 * number of characters used.
 */
static int
append_month (GString *string, gchar *format, struct tm *time_split)
{
	char temp [3];
	
	if (tolower (format [1]) != 'm'){
		sprintf (temp, "%d", time_split->tm_mon+1);
		g_string_append( string, temp);
		return 1;
	}
	
	if (tolower (format [2]) != 'm')
	{
		sprintf (temp, "%02d", time_split->tm_mon+1);
		g_string_append (string, temp);
		return 2;
	}
	
	if (tolower (format [3]) != 'm'){
		g_string_append (string, _(month_short [time_split->tm_mon])+1);
		return 3;
	}

	g_string_append (string, _(month_long [time_split->tm_mon]));
	return 4;
}

/*
 * Parses the hour field at the beginning of the format.  Returns the
 * number of characters used.
 */
static int
append_hour (GString *string, gchar *format, struct tm *time_split, int timeformat)
{
	char temp[3];

	if (tolower (format [1]) != 'h'){
	    sprintf (temp, "%d", timeformat ? (time_split->tm_hour % 12) : time_split->tm_hour);
	    g_string_append (string, temp);
	    return 1;
	}
	sprintf (temp, "%02d", timeformat ? (time_split->tm_hour % 12) : time_split->tm_hour);
	g_string_append (string, temp);
	return 2;
}

/*
 * Parses the day field at the beginning of the format.  Returns the
 * number of characters used.
 */
static int
append_day (GString *string, gchar *format, struct tm *time_split)
{
	char temp[3];

	if (tolower (format [1]) != 'd'){
		sprintf (temp, "%d", time_split->tm_mday);
		g_string_append (string, temp);
		return 1;
	}

	if (tolower (format [2]) != 'd'){
		sprintf (temp, "%02d", time_split->tm_mday);
		g_string_append (string, temp);
		return 2;
	}

	if (tolower (format [3]) != 'd'){
		g_string_append (string, _(day_short[time_split->tm_wday])+1);
		return 3;
	}

	g_string_append (string, _(day_long[time_split->tm_wday]));

	return 4;
}

/*
 * Parses the minute field at the beginning of the format.  Returns the
 * number of characters used.
 */
static int
append_minute (GString *string, gchar *format, struct tm *time_split)
{
	char temp [3];

	if (tolower (format [1]) != 'm'){
		sprintf (temp, "%d", time_split->tm_min);
		g_string_append (string, temp);
		return 1;
	}

	sprintf (temp, "%02d", time_split->tm_min);
	g_string_append (string, temp);

	return 2;
}

/*
 * Parses the second field at the beginning of the format.  Returns the
 * number of characters used.
 */
static int
append_second (GString *string, gchar *format, struct tm *time_split)
{
	char temp[3];
	
	if (tolower (format [1]) != 's'){
		sprintf (temp, "%d", time_split->tm_sec);
		g_string_append (string, temp);
		return 1;
	}

	sprintf (temp, "%02d", time_split->tm_sec);
	g_string_append (string, temp);

	return 2;
}

/*
 * Parses the day part field at the beginning of the format.  Returns
 * the number of characters used.
 */
static int
append_half (GString *string, gchar *format, struct tm *time_split)
{
	if (time_split->tm_hour <= 11){
		if (tolower (format [0]) == 'a' || tolower (format [0]) == 'p')
			g_string_append_c (string, 'a');
		else
			g_string_append_c (string, 'A');
	}
	else {
		if (tolower (format [0]) == 'a' || tolower (format [0]) == 'p')
			g_string_append_c (string, 'p');
		else
			g_string_append_c (string, 'P');
	}
	
	if (tolower (format [1]) == 'm'){
		g_string_append_c (string, format [1]);
		return 2;
	} else
		return 1;
}

/*
 * Since the Excel formating codes contain a number of ambiguities,
 * this routine does some analisis on the format first.
 */
static void
pre_parse_format (StyleFormatEntry *style)
{
	char *format;

	style->want_am_pm = 0;
	for (format = style->format; *format; format++){
		switch (*format){
		case '"':
			for (format++; *format && *format != '"'; format++)
				;
			if (*format)
				format++;
			break;
			
		case '\\':
			if (*(format+1))
				format++;
			else
				return;
			break;

		case 'a':
		case 'p':
		case 'A':
		case 'P':
			if (tolower (*(format+1) == 'm'))
				style->want_am_pm = 1;
			break;
		}
	}
}

typedef struct
{
	int decimal;
	int timeformat;
	int hasnumbers;
} xformat_info;

/*
 * This routine should always return, it can not fail, in the worst
 * case it should just downgrade to simplistic formatting
 */
void
format_compile (StyleFormat *format)
{
	GString *string = g_string_new ("");
	int i;
	int which = 0;
	int length = strlen (format->format);
	StyleFormatEntry standard_entries[4];
	StyleFormatEntry *temp;
	
	g_list_free (format->format_list);
	format->format_list = 0;
	
	/* g_string_maybe_expand (string, length); */
	
	for (i = 0; i < length; i++){

		switch (format->format[i]){

		case ';':
			if (which < 4){
				standard_entries [which].format = g_malloc0 (string->len + 1);
				strncpy (standard_entries[which].format, string->str, string->len);
				standard_entries [which].format[string->len] = 0;
				standard_entries [which].restriction_type = '*';
				which++;
			}
			string = g_string_truncate (string, 0);
			break;
		default:
			string = g_string_append_c (string, format->format [i]);
			break;
		}
	}

	if (which < 4){
		standard_entries[which].format = g_malloc0 (string->len + 1);
		strncpy (standard_entries[which].format, string->str, string->len);
		standard_entries[which].format[string->len] = 0;
		standard_entries[which].restriction_type = '*';
		which++;
	}
	
	/* Set up restriction types. */
	standard_entries[1].restriction_type = '<';
	standard_entries[1].restriction_value = 0;
	switch (which){

	case 4:
		standard_entries[3].restriction_type = '@';
		/* Fall through. */
	case 3:
		standard_entries[2].restriction_type = '=';
		standard_entries[2].restriction_value = 0;
		standard_entries[0].restriction_type = '>';
		standard_entries[0].restriction_value = 0;
		break;
	case 2:
		standard_entries[0].restriction_type = '.';  /* . >= */
		standard_entries[0].restriction_value = 0;
		break;
	}

	for (i = 0; i < which; i++){
		temp = g_new (StyleFormatEntry, 1);
		*temp = standard_entries[i];
		pre_parse_format (temp);
		format->format_list = g_list_append (format->format_list, temp);
	}

	g_string_free (string, TRUE);
}

static void
style_entry_free(gpointer data, gpointer user_data)
{
	StyleFormatEntry *entry = data;
	
	g_free (entry->format);
	g_free (entry);
}

/*
 * This routine is invoked when the last user of the
 * format is gone (ie, refcount has reached zero) just
 * before the StyleFormat structure is actually released.
 *
 * resources allocated in format_compile should be disposed here
 */
void
format_destroy (StyleFormat *format)
{
	g_list_foreach (format->format_list, style_entry_free, NULL);
	g_list_free (format->format_list);
	format->format_list = NULL;  
}

static struct {
	char       *name;
	StyleColor *color;
} format_colors [] = {
	{ N_("black")   },
	{ N_("blue")    },
	{ N_("cyan")    },
	{ N_("green")   },
	{ N_("magenta") },
	{ N_("red")     },
	{ N_("white")   },
	{ N_("yellow")  },
	{ NULL, NULL }
};

void
format_color_init (void)
{
	int i;

	for (i = 0; format_colors [i].name; i++){
		StyleColor *sc;
		GdkColor c;
		
		gdk_color_parse (format_colors [i].name, &c);
		sc = style_color_new (c.red, c.green, c.blue);
		
		format_colors [i].color = sc;
	}
}

void
format_color_shutdown (void)
{
	int i;

	for (i = 0; format_colors [i].name; i++)
		style_color_unref (format_colors [i].color);
}

static StyleColor *
lookup_color (char *str, char *end)
{
	int i;

	for (i = 0; format_colors [i].name; i++){
		int len = strlen (format_colors [i].name);

		if ((strncasecmp (format_colors [i].name, str, len) == 0) ||
		    (strncasecmp (_(format_colors [i].name), str, len) == 0)){
			style_color_ref (format_colors [i].color);
			return format_colors [i].color;
		}
	}
	return NULL;
}

static GString *
render_number (gdouble number,
	       int left_req,
	       int right_req,
	       int left_spaces,
	       int right_spaces,
	       int right_allowed,
	       int use_thousand_sep,
	       int negative,
	       int supress_minus,
	       int decimal,
	       char *show_decimal)
{
	GString *number_string = g_string_new ("");
	gint zero_count, nine_count;
	gdouble temp;
	int group = 0;
	
	for (temp = number; temp >= 1.0; temp /= 10.0){

		double r = floor (temp);
		int digit;
				  
		if (use_thousand_sep){
			group++;
			if (group == 4){
				int c;
				
				group = 1;
				if (lc->thousands_sep [0] == 0)
					c = ',';
				else
					c = lc->thousands_sep [0];
				
				g_string_prepend_c (number_string, c);
			}
		}
		
		digit = r - floor (r / 10) * 10;
		g_string_prepend_c (number_string, (digit) + '0');
		if (left_req > 0)
			left_req --;
		if (left_spaces > 0)
			left_spaces --;
		
	}
      
	for (; left_req > 0; left_req--, left_spaces--)
		g_string_prepend_c (number_string, '0');

	for (; left_spaces > 0; left_spaces--)
		g_string_prepend_c (number_string, ' ');

	if (negative && !supress_minus)
		g_string_prepend_c (number_string, '-');

	if (decimal > 0)
		g_string_append (number_string, lc->decimal_point);
	else
		g_string_append (number_string, show_decimal);

      temp = number - floor (number);

      for (; right_req > 0; right_req --, right_allowed --, right_spaces --)
      {
	      gint digit;
	      temp *= 10.0;
	      digit = floor (temp);
	      temp -= floor (temp);
	      if (right_allowed == 1 && floor (temp * 10.0) >= 5)
	      {
		      if (digit < 9)
			      digit ++;
		      else
		      {
			      digit = 0;
			      do_roundup (number_string);
		      }
	      }
	      g_string_append_c (number_string, digit + '0');
      }
      
      zero_count = 0;
      nine_count = 0;
      
      for (; right_allowed > 0; right_allowed --)
      {
	      gint digit;
	      temp *= 10.0;
	      digit = floor (temp);
	      temp -= floor (temp);
	      
	      if (right_allowed == 1 && floor (temp * 10.0) >= 5)
	      {
		      if (digit < 9)
			      digit ++;
		      else
		      {
			      digit = 0;
			      right_spaces -= zero_count;
			      zero_count = nine_count;
			      right_spaces += zero_count;
			      do_roundup (number_string);
		      }
	      }
	      if (digit == 0)
		      zero_count ++;
	      else
	      {
		      right_spaces -= zero_count + 1;
		      zero_count = 0;
	      }
	      if (digit == 9)
		      nine_count ++;
	      else
		      nine_count = 0;
	      
	      g_string_append_c (number_string, digit + '0');
      }
      
      g_string_truncate (number_string, number_string->len - zero_count);
      
      for (; right_spaces > 0; right_spaces--)
      {
	      g_string_append_c (number_string, ' ');
      }

      return number_string;
}

typedef struct {
	char *decimal_point, *append_after_number;
	int  right_optional, right_spaces, right_req, right_allowed;
	int  left_spaces, left_req;
	int  scientific;
	int  scientific_shows_plus;
	int  scientific_exp;
	int  rendered;
	int  negative;
	int  decimal_separator_seen;
	int  supress_minus;
	int  comma_separator_seen;
} format_info_t;

static char *
do_render_number (gdouble number, format_info_t *info)
{
	GString *res;
	char *result;
	char decimal_point [2];
	
	info->rendered = 1;

	/*
	 * If the format contains only "#"s to the left of the decimal
	 * point, number in the [0.0,1.0] range are prefixed with a
	 * decimal point
	 */
	if (number > 0.0 && number < 1.0 && info->right_allowed == 0 && info->right_optional > 0){
		decimal_point [0] = lc->decimal_point [0];
		decimal_point [1] = 0;
	} else
		decimal_point [0] = 0;

#if 0
	printf ("Rendering: %g with:\n", number);
	printf ("left_req:    %d\n"
		"right_req:   %d\n"
		"left_spaces: %d\n"
		"right_spaces:%d\n"
		"right_allow: %d\n"
		"negative:    %d\n"
		"supress:     %d\n"
		"decimalseen: %d\n"
		"decimalp:    %s\n",
		info->left_req,
		info->right_req,
		info->left_spaces,
		info->right_spaces,
		info->right_allowed + info->right_optional,
		info->negative,
		info->supress_minus,
		info->decimal_separator_seen,
		decimal_point);
#endif
	
	res = render_number (
		number,
		info->left_req,
		info->right_req,
		info->left_spaces,
		info->right_spaces,
		info->right_allowed + info->right_optional,
		info->comma_separator_seen,
		info->negative,
		info->supress_minus,
		info->decimal_separator_seen,
		decimal_point);

	if (info->append_after_number)
		g_string_append (res, info->append_after_number);
	
	result = g_strdup (res->str);
	g_string_free (res, TRUE);
	
	return result;
}

/*
 * Microsoft Excel has a bug in the handling of year 1900,
 * I quote from http://catless.ncl.ac.uk/Risks/19.64.html#subj9.1
 *
 * > Microsoft EXCEL version 6.0 ("Office 95 version") and version 7.0 ("Office
 * > 97 version") believe that year 1900 is a leap year.  The extra February 29
 * > cause the following problems.
 * > 
 * > 1)  All day-of-week before March 1, 1900 are incorrect;
 * > 2)  All date sequence (serial number) on and after March 1, 1900 are incorrect.
 * > 3)  Calculations of number of days across March 1, 1900 are incorrect.
 * > 
 * > The risk of the error will cause must be little.  Especially case 1.
 * > However, import or export date using serial date number will be a problem.
 * > If no one noticed anything wrong, it must be that no one did it that way.
 */
static struct tm *
split_time (gdouble number)
{
	static struct tm tm;
	double secs;

	GDate* date = g_date_new_serial (number);
	
	g_date_to_struct_tm (date, &tm);

	secs = (number - floor (number)) * 86400.0;
	tm.tm_hour = secs / 3600;
	secs -= tm.tm_hour * 3600;
	tm.tm_min  = secs / 60;
	secs -= tm.tm_min * 60;
	tm.tm_sec  = floor (secs);

	return &tm;
}

static gchar *
format_number (gdouble number, StyleFormatEntry *style_format_entry)
{
	GString *result = g_string_new ("");
	char *format = style_format_entry->format;
	format_info_t info;
	int can_render_number = 0;
	int hour_seen = 0;
	struct tm *time_split = 0;
	char *res;
	
	memset (&info, 0, sizeof (info));
	if (number < 0.0){
		info.negative = TRUE;
		number = -number;
	}

	if (!lc)
		lc = localeconv ();
		
	while (*format){
		switch (*format){
		case '#':
			can_render_number = 1;
			if (info.decimal_separator_seen)
				info.right_optional++;
			break;
			
		case '?':
			can_render_number = 1;
			if (info.decimal_separator_seen)
				info.right_spaces++;
			else
				info.left_spaces++;
			break;
			
		case '0':
			can_render_number = 1;
			if (info.decimal_separator_seen){
				info.right_req++;
				info.right_allowed++;
				info.right_spaces++;
			} else {
				info.left_spaces++;
				info.left_req++;
			}
			break;
			
		case ',': case '.': {
			if (*format == lc->decimal_point [0]){
				int c = *(format+1);
				
				can_render_number = 1;
				if (c && (c != '0' && c != '#' && c != '?'))
					number /= 1000;
				else
					info.decimal_separator_seen = TRUE;
				break;
			} else {
				info.comma_separator_seen = TRUE;
				break;
			}
		}
		
		case 'E': case 'e':
			can_render_number = 1;
			info.scientific = TRUE;
			format++;
			for (format++; *format;){
				if (*format == '+'){
					info.scientific_shows_plus = TRUE;
					format++;
				} else if (*format == '-')
					format++;
				else if (*format == '0'){
					info.scientific_exp++;
					format++;
				} else
					break;
			}
			/* FIXME: this is a gross hack */
			{
				char buffer [40];
				sprintf (buffer, "%g", number);
				
				g_string_append (result, buffer);
				goto finish;
			}
			
		/* percent */
		case '%':
			can_render_number = 1;
			number *= 100;
			info.append_after_number = "%";
			break;

		case '\\':
			if (*(format+1)){
				if (can_render_number && !info.rendered)
					g_string_append (result, do_render_number (number, &info));
				g_string_append_c (result, *format);
			}
			break;

		case '"': {
			if (can_render_number && !info.rendered)
				g_string_append (result, do_render_number (number, &info));

			for (format++; *format && *format != '"'; format++)
				g_string_append_c (result, *format);
			break;

		}

		case '-':
		case '/':
		case '(':
		case '+':
		case ' ':
		case ':':
			info.supress_minus = TRUE;
			/* fall down */
			
		case '$':
			g_string_append_c (result, *format);
			break;
			
		case ')':
			if (can_render_number && !info.rendered)
				g_string_append (result, do_render_number (number, &info));
			g_string_append_c (result, *format);
			break;

		case '_':
			if (*(format+1))
				format++;
			g_string_append_c (result, ' ');
			break;

		case 'M':
		case 'm':
			if (!time_split)
				time_split = split_time (number);
			if (hour_seen)
				format += append_minute (result, format, time_split) - 1;
			else
				format += append_month (result, format, time_split) - 1;
			break;

		case 'D':
		case 'd':
			if (!time_split)
				time_split = split_time (number);
			format += append_day (result, format, time_split) -1;
			break;

		case 'Y':
		case 'y':
			if (!time_split)
				time_split = split_time (number);
			format += append_year (result, format, time_split) - 1;
			break;

		case 'S':
		case 's':
			if (!time_split)
				time_split = split_time (number);
			format += append_second (result, format, time_split) - 1;
			break;

		case '*':
			g_warning ("REPEAT FORMAT NOT YET SUPPORTED\n");
			break;

		case 'H':
		case 'h':
			if (!time_split)
				time_split = split_time (number);
			format += append_hour (result, format, time_split, style_format_entry->want_am_pm) - 1;
			hour_seen = TRUE;
			break;
			
		case 'A':
		case 'a':
			if (!time_split)
				time_split = split_time (number);
			if (time_split->tm_hour < 12){
				g_string_append_c (result, *format);
				format++;
				if (*format == 'm' || *format == 'M'){
					g_string_append_c (result, *format);
					if (*(format+1) == '/')
						format++;
				}
			} else {
				if (*(format+1) == 'm' || *(format+1) == 'M')
					format++;
				if (*(format+1) == '/')
					format++;
			}
			break;
			
		case 'P': case 'p':
			if (!time_split)
				time_split = split_time (number);
			if (time_split->tm_hour >= 12){
				g_string_append_c (result, *format);
				if (*(format+1) == 'm' || *(format+1) == 'M'){
					format++;
					g_string_append_c (result, *format);
				}
			} else {
				if (*(format+1) == 'm' || *(format+1) == 'M')
					format++;
			}
			break;
			
		default:
			break;
		}
		format++;
	}
	if (!info.rendered && can_render_number){
		char *rendered_string = do_render_number (number, &info);
		
		g_string_append (result, rendered_string);
		g_free (rendered_string);
	}
 finish:
	res = g_strdup (result->str);
	g_string_free (result, TRUE);
	return res;
}

static gboolean
check_valid (StyleFormatEntry *entry, Value *value)
{
	switch (value->type){

	case VALUE_STRING:
		return entry->restriction_type == '@';

	case VALUE_FLOAT:
		switch (entry->restriction_type){

		case '*': 
			return TRUE;
		case '<':
			return value->v.v_float < entry->restriction_value;
		case '>':
			return value->v.v_float > entry->restriction_value;
		case '=':
			return value->v.v_float == entry->restriction_value;
		case ',':
			return value->v.v_float <= entry->restriction_value;
		case '.':
			return value->v.v_float >= entry->restriction_value;
		case '+':
			return value->v.v_float != entry->restriction_value;
		default:
			return FALSE;
		}
		
	case VALUE_INTEGER:
		switch (entry->restriction_type){

		case '*': 
			return TRUE;
		case '<':
			return value->v.v_int < entry->restriction_value;
		case '>':
			return value->v.v_int > entry->restriction_value;
		case '=':
			return value->v.v_int == entry->restriction_value;
		case ',':
			return value->v.v_int <= entry->restriction_value;
		case '.':
			return value->v.v_int >= entry->restriction_value;
		case '+':
			return value->v.v_int != entry->restriction_value;
		default:
			return FALSE;
		}
		
	default:
		return FALSE;
	}
}

gchar *
format_value (StyleFormat *format, Value *value, StyleColor **color)
{
	char *v = NULL;
	StyleFormatEntry entry;
	GList *list;
	int is_general = 0;
	
	if (color)
		*color = NULL;
	
	/* get format */
	for (list = format->format_list; list; list = g_list_next (list))
		if (check_valid (list->data, value))
			break;
	
	if (list)
		entry = *(StyleFormatEntry *)(list->data);
	else
		entry.format = format->format;

	/* Try to parse a color specification */
	if (entry.format [0] == '['){
		char *end = strchr (entry.format, ']');
      
		if (end){
			if (color)
				*color = lookup_color (&entry.format [1], end);
			entry.format = end+1;
		}
	}

	if (entry.format [0] == 0)
		is_general = 1;
	
	if (strcmp (entry.format, "General") == 0){
		entry.format += 7;
		is_general = 1;
	} 
	
	switch (value->type){
	case VALUE_FLOAT:
		if (is_general){
			if (floor (value->v.v_float) == value->v.v_float)
				entry.format = "0";
			else
				entry.format = "0.0########";
		}
		if (finite (value->v.v_float))
			v = format_number (value->v.v_float, &entry);
		else
			return g_strdup ("#VAL");
		break;
		
	case VALUE_INTEGER:
		if (is_general)
			entry.format = "0";
		v = format_number (value->v.v_int, &entry);
		break;
		
	case VALUE_STRING:
		return g_strdup (value->v.str->str);
		
	default:
		return g_strdup ("Internal error");
	}
	
	/* Format error, return a default value */
	if (v == NULL)
		return value_string (value);
	
	return v;
}

