/* General-purpose monthly calendar canvas item for GNOME
 *
 * Copyright (C) 1998 Red Hat Software, Inc.
 *
 * Author: Federico Mena <federico@nuclecu.unam.mx>
 */

#include <config.h>
#include <math.h>
#include <time.h>
#include <gnome.h>
#include "gnome-month-item.h"


#define DEFAULT_FONT "-*-helvetica-medium-r-normal--10-*-*-*-p-*-*-*"


/* Number of days in a month, for normal and leap years */
static const int days_in_month[2][12] = {
	{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
	{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
};

/* The weird month of September 1752, where 3 Sep through 13 Sep were eliminated due to the
 * Gregorian reformation.
 */
static const int sept_1752[42] = {
	 0,  0,  1,  2, 14, 15, 16,
	17, 18, 19, 20, 21, 22, 23,
	24, 25, 26, 27, 28, 29, 30,
	 0,  0,  0,  0,  0,  0,  0,
	 0,  0,  0,  0,  0,  0,  0,
	 0,  0,  0,  0,  0,  0,  0
};

#define REFORMATION_DAY 639787		/* First day of the reformation, counted from 1 Jan 1 */
#define MISSING_DAYS 11			/* They corrected out 11 days */
#define THURSDAY 4			/* First day of reformation */
#define SATURDAY 6			/* Offset value; 1 Jan 1 was a Saturday */
#define SEPT_1752_START 2		/* Start day within month */
#define SEPT_1752_END 20		/* End day within month */


enum {
	ARG_0,
	ARG_YEAR,
	ARG_MONTH,
	ARG_X,
	ARG_Y,
	ARG_WIDTH,
	ARG_HEIGHT,
	ARG_ANCHOR,
	ARG_HEAD_PADDING,
	ARG_DAY_PADDING,
	ARG_DAY_NAMES,
	ARG_HEADING_HEIGHT,
	ARG_HEADING_ANCHOR,
	ARG_DAY_ANCHOR,
	ARG_START_ON_MONDAY,
	ARG_HEAD_FONT,
	ARG_HEAD_FONT_GDK,
	ARG_DAY_FONT,
	ARG_DAY_FONT_GDK,
	ARG_HEAD_COLOR,
	ARG_HEAD_COLOR_GDK,
	ARG_OUTLINE_COLOR,
	ARG_OUTLINE_COLOR_GDK,
	ARG_DAY_BOX_COLOR,
	ARG_DAY_BOX_COLOR_GDK,
	ARG_DAY_COLOR,
	ARG_DAY_COLOR_GDK
};


static void gnome_month_item_class_init (GnomeMonthItemClass *class);
static void gnome_month_item_init       (GnomeMonthItem      *mitem);
static void gnome_month_item_destroy    (GtkObject           *object);
static void gnome_month_item_set_arg    (GtkObject           *object,
					 GtkArg              *arg,
					 guint                arg_id);
static void gnome_month_item_get_arg    (GtkObject           *object,
					 GtkArg              *arg,
					 guint                arg_id);



static GnomeCanvasGroupClass *parent_class;


GtkType
gnome_month_item_get_type (void)
{
	static GtkType month_item_type = 0;

	if (!month_item_type) {
		GtkTypeInfo month_item_info = {
			"GnomeMonthItem",
			sizeof (GnomeMonthItem),
			sizeof (GnomeMonthItemClass),
			(GtkClassInitFunc) gnome_month_item_class_init,
			(GtkObjectInitFunc) gnome_month_item_init,
			NULL, /* reserved_1 */
			NULL, /* reserved_2 */
			(GtkClassInitFunc) NULL
		};

		month_item_type = gtk_type_unique (gnome_canvas_group_get_type (), &month_item_info);
	}

	return month_item_type;
}

static void
gnome_month_item_class_init (GnomeMonthItemClass *class)
{
	GtkObjectClass *object_class;
	GnomeCanvasItemClass *item_class;

	object_class = (GtkObjectClass *) class;
	item_class = (GnomeCanvasItemClass *) class;

	parent_class = gtk_type_class (gnome_canvas_group_get_type ());

	gtk_object_add_arg_type ("GnomeMonthItem::year", GTK_TYPE_UINT, GTK_ARG_READWRITE, ARG_YEAR);
	gtk_object_add_arg_type ("GnomeMonthItem::month", GTK_TYPE_UINT, GTK_ARG_READWRITE, ARG_MONTH);
	gtk_object_add_arg_type ("GnomeMonthItem::x", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_X);
	gtk_object_add_arg_type ("GnomeMonthItem::y", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_Y);
	gtk_object_add_arg_type ("GnomeMonthItem::width", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_WIDTH);
	gtk_object_add_arg_type ("GnomeMonthItem::height", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_HEIGHT);
	gtk_object_add_arg_type ("GnomeMonthItem::anchor", GTK_TYPE_ANCHOR_TYPE, GTK_ARG_READWRITE, ARG_ANCHOR);
	gtk_object_add_arg_type ("GnomeMonthItem::heading_padding", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_HEAD_PADDING);
	gtk_object_add_arg_type ("GnomeMonthItem::day_padding", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_DAY_PADDING);
	gtk_object_add_arg_type ("GnomeMonthItem::day_names", GTK_TYPE_POINTER, GTK_ARG_WRITABLE, ARG_DAY_NAMES);
	gtk_object_add_arg_type ("GnomeMonthItem::heading_height", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_HEADING_HEIGHT);
	gtk_object_add_arg_type ("GnomeMonthItem::heading_anchor", GTK_TYPE_ANCHOR_TYPE, GTK_ARG_READWRITE, ARG_HEADING_ANCHOR);
	gtk_object_add_arg_type ("GnomeMonthItem::day_anchor", GTK_TYPE_ANCHOR_TYPE, GTK_ARG_READWRITE, ARG_DAY_ANCHOR);
	gtk_object_add_arg_type ("GnomeMonthItem::start_on_monday", GTK_TYPE_BOOL, GTK_ARG_READWRITE, ARG_START_ON_MONDAY);
	gtk_object_add_arg_type ("GnomeMonthItem::heading_font", GTK_TYPE_STRING, GTK_ARG_WRITABLE, ARG_HEAD_FONT);
	gtk_object_add_arg_type ("GnomeMonthItem::heading_font_gdk", GTK_TYPE_GDK_FONT, GTK_ARG_READWRITE, ARG_HEAD_FONT_GDK);
	gtk_object_add_arg_type ("GnomeMonthItem::day_font", GTK_TYPE_STRING, GTK_ARG_WRITABLE, ARG_DAY_FONT);
	gtk_object_add_arg_type ("GnomeMonthItem::day_font_gdk", GTK_TYPE_GDK_FONT, GTK_ARG_READWRITE, ARG_DAY_FONT_GDK);
	gtk_object_add_arg_type ("GnomeMonthItem::heading_color", GTK_TYPE_STRING, GTK_ARG_WRITABLE, ARG_HEAD_COLOR);
	gtk_object_add_arg_type ("GnomeMonthItem::heading_color_gdk", GTK_TYPE_GDK_COLOR, GTK_ARG_READWRITE, ARG_HEAD_COLOR_GDK);
	gtk_object_add_arg_type ("GnomeMonthItem::outline_color", GTK_TYPE_STRING, GTK_ARG_WRITABLE, ARG_OUTLINE_COLOR);
	gtk_object_add_arg_type ("GnomeMonthItem::outline_color_gdk", GTK_TYPE_GDK_COLOR, GTK_ARG_READWRITE, ARG_OUTLINE_COLOR_GDK);
	gtk_object_add_arg_type ("GnomeMonthItem::day_box_color", GTK_TYPE_STRING, GTK_ARG_WRITABLE, ARG_DAY_BOX_COLOR);
	gtk_object_add_arg_type ("GnomeMonthItem::day_box_color_gdk", GTK_TYPE_GDK_COLOR, GTK_ARG_READWRITE, ARG_DAY_BOX_COLOR_GDK);
	gtk_object_add_arg_type ("GnomeMonthItem::day_color", GTK_TYPE_STRING, GTK_ARG_WRITABLE, ARG_DAY_COLOR);
	gtk_object_add_arg_type ("GnomeMonthItem::day_color_gdk", GTK_TYPE_GDK_COLOR, GTK_ARG_READWRITE, ARG_DAY_COLOR_GDK);

	object_class->destroy = gnome_month_item_destroy;
	object_class->set_arg = gnome_month_item_set_arg;
	object_class->get_arg = gnome_month_item_get_arg;
}

/* Calculates the minimum heading height based on the heading font size and padding.  It also
 * calculates the minimum width of the month item based on the width of the headings.
 */
static void
check_heading_sizes (GnomeMonthItem *mitem)
{
	double m_height;
	double m_width;
	int width;
	int max_width;
	int i;

	/* Calculate minimum height */

	m_height = mitem->head_font->ascent + mitem->head_font->descent + 2 * mitem->head_padding;

	if (mitem->head_height < m_height)
		mitem->head_height = m_height;
	
	/* Go through each heading and remember the widest one */

	max_width = 0;

	for (i = 0; i < 7; i++) {
		width = gdk_string_width (mitem->head_font, mitem->day_names[i]);
		if (max_width < width)
			max_width = width;
	}

	m_width = 7 * (max_width + 2 * mitem->head_padding);

	if (mitem->width < m_width)
		mitem->width = m_width;
}

/* Calculates the minimum width and height of the month item based on the day font size and padding.
 * Assumes that the minimum heading height has already been computed.
 */
static void
check_day_sizes (GnomeMonthItem *mitem)
{
	double m_height;
	double m_width;
	int width;
	int max_width;
	char buf[100];
	int i;

	/* Calculate minimum height */

	m_height = mitem->head_height + 6 * (mitem->day_font->ascent + mitem->day_font->descent + 2 * mitem->day_padding);

	if (mitem->height < m_height)
		mitem->height = m_height;

	/* Calculate minimum width */

	max_width = 0;

	for (i = 1; i < 32; i++) {
		sprintf (buf, "%d", i);
		width = gdk_string_width (mitem->day_font, buf);
		if (max_width < width)
			max_width = width;
	}

	m_width = 7 * (max_width + 2 * mitem->day_padding);

	if (mitem->width < m_width)
		mitem->width = m_width;
}

/* Calculates the minimum size of the month item based on the font sizes and paddings.  If the
 * current size of the month item is smaller than the required minimum size, this function will
 * change the size to the appropriate values.
 */
static void
check_sizes (GnomeMonthItem *mitem)
{
	check_heading_sizes (mitem);
	check_day_sizes (mitem);
}

/* Recalculates the position of the toplevel calendar group based on the logical position and anchor */
static void
reanchor (GnomeMonthItem *mitem)
{
	double x, y;

	x = mitem->x;
	y = mitem->y;

	switch (mitem->anchor) {
	case GTK_ANCHOR_NW:
	case GTK_ANCHOR_W:
	case GTK_ANCHOR_SW:
		break;

	case GTK_ANCHOR_N:
	case GTK_ANCHOR_CENTER:
	case GTK_ANCHOR_S:
		x -= mitem->width / 2;
		break;

	case GTK_ANCHOR_NE:
	case GTK_ANCHOR_E:
	case GTK_ANCHOR_SE:
		x -= mitem->width;
		break;
	}

	switch (mitem->anchor) {
	case GTK_ANCHOR_NW:
	case GTK_ANCHOR_N:
	case GTK_ANCHOR_NE:
		break;

	case GTK_ANCHOR_W:
	case GTK_ANCHOR_CENTER:
	case GTK_ANCHOR_E:
		y -= mitem->height / 2;
		break;

	case GTK_ANCHOR_SW:
	case GTK_ANCHOR_S:
	case GTK_ANCHOR_SE:
		y -= mitem->height;
		break;
	}

	/* Explicitly use the canvas group class prefix since the month item class has x and y
	 * arguments as well.
	 */

	gnome_canvas_item_set (GNOME_CANVAS_ITEM (mitem),
			       "GnomeCanvasGroup::x", x,
			       "GnomeCanvasGroup::y", y,
			       NULL);
}

/* Takes an anchor specification and the corners of a rectangle, and returns an anchored point with
 * respect to that rectangle.
 */
static void
get_label_anchor (GtkAnchorType anchor, double x1, double y1, double x2, double y2, double *x, double *y)
{
	switch (anchor) {
	case GTK_ANCHOR_NW:
	case GTK_ANCHOR_W:
	case GTK_ANCHOR_SW:
		*x = x1;
		break;

	case GTK_ANCHOR_N:
	case GTK_ANCHOR_CENTER:
	case GTK_ANCHOR_S:
		*x = (x1 + x2) / 2.0;
		break;

	case GTK_ANCHOR_NE:
	case GTK_ANCHOR_E:
	case GTK_ANCHOR_SE:
		*x = x2;
		break;
	}

	switch (anchor) {
	case GTK_ANCHOR_NW:
	case GTK_ANCHOR_N:
	case GTK_ANCHOR_NE:
		*y = y1;
		break;

	case GTK_ANCHOR_W:
	case GTK_ANCHOR_CENTER:
	case GTK_ANCHOR_E:
		*y = (y1 + y2) / 2.0;
		break;

	case GTK_ANCHOR_SW:
	case GTK_ANCHOR_S:
	case GTK_ANCHOR_SE:
		*y = y2;
		break;
	}
}

/* Resets the position of the day name headings in the calendar */
static void
reshape_headings (GnomeMonthItem *mitem)
{
	double width;
	int i;
	double x, y;

	width = mitem->width / 7;

	for (i = 0; i < 7; i++) {
		/* Group */
		gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_HEAD_GROUP + i],
				       "x", width * i,
				       "y", 0.0,
				       NULL);

		/* Box */
		gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_HEAD_BOX + i],
				       "x1", 0.0,
				       "y1", 0.0,
				       "x2", width,
				       "y2", mitem->head_height,
				       NULL);

		/* Label */
		get_label_anchor (mitem->head_anchor,
				  mitem->head_padding,
				  mitem->head_padding,
				  width - mitem->head_padding,
				  mitem->head_height - mitem->head_padding,
				  &x, &y);

		gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_HEAD_LABEL + i],
				       "x", x,
				       "y", y,
				       "anchor", mitem->head_anchor,
				       NULL);
	}
}

/* Resets the position of the days in the calendar */
static void
reshape_days (GnomeMonthItem *mitem)
{
	double width, height;
	double x, y;
	int row, col;
	int i;

	width = mitem->width / 7;
	height = (mitem->height - mitem->head_height) / 6;

	i = 0;

	for (row = 0; row < 6; row++)
		for (col = 0; col < 7; col++) {
			/* Group */
			gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_DAY_GROUP + i],
					       "x", width * col,
					       "y", mitem->head_height + height * row,
					       NULL);

			/* Box */
			gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_DAY_BOX + i],
					       "x1", 0.0,
					       "y1", 0.0,
					       "x2", width,
					       "y2", height,
					       NULL);

			/* Label */
			get_label_anchor (mitem->day_anchor,
					  mitem->day_padding,
					  mitem->day_padding,
					  width - mitem->day_padding,
					  height - mitem->day_padding,
					  &x, &y);

			gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_DAY_LABEL + i],
					       "x", x,
					       "y", y,
					       "anchor", mitem->day_anchor,
					       NULL);

			i++;
		}
}

/* Changes the positions and resizes the items in the calendar to match the new size of the
 * calendar.
 */
static void
reshape (GnomeMonthItem *mitem)
{
	check_sizes (mitem);
	reanchor (mitem);
	reshape_headings (mitem);
	reshape_days (mitem);
}

/* Sets the font for all the day headings */
static void
set_head_font (GnomeMonthItem *mitem)
{
	int i;

	for (i = 0; i < 7; i++)
		gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_HEAD_LABEL + i],
				       "font_gdk", mitem->head_font,
				       NULL);
}

/* Sets the color for all the headings */
static void
set_head_color (GnomeMonthItem *mitem)
{
	int i;
	GdkColor outline;
	GdkColor head;

	outline.pixel = mitem->outline_pixel;
	head.pixel = mitem->head_pixel;

	for (i = 0; i < 7; i++) {
		gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_HEAD_BOX + i],
				       "fill_color_gdk", &outline,
				       NULL);

		gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_HEAD_LABEL + i],
				       "fill_color_gdk", &head,
				       NULL);
	}
}

/* Creates the items for the day name headings */
static void
create_headings (GnomeMonthItem *mitem)
{
	int i;

	/* Just create the items; they will be positioned and configured by a call to reshape() */

	for (i = 0; i < 7; i++) {
		/* Group */
		mitem->items[GNOME_MONTH_ITEM_HEAD_GROUP + i] =
			gnome_canvas_item_new (GNOME_CANVAS_GROUP (mitem),
					       gnome_canvas_group_get_type (),
					       NULL);

		/* Box */
		mitem->items[GNOME_MONTH_ITEM_HEAD_BOX + i] =
			gnome_canvas_item_new (GNOME_CANVAS_GROUP (mitem->items[GNOME_MONTH_ITEM_HEAD_GROUP + i]),
					       gnome_canvas_rect_get_type (),
					       NULL);

		/* Label */
		mitem->items[GNOME_MONTH_ITEM_HEAD_LABEL + i] =
			gnome_canvas_item_new (GNOME_CANVAS_GROUP (mitem->items[GNOME_MONTH_ITEM_HEAD_GROUP + i]),
					       gnome_canvas_text_get_type (),
					       NULL);
	}

	set_head_font (mitem);
	set_head_color (mitem);
}

/* Returns the number of leap years since year 1 up to (but not including) the specified year */
static int
leap_years_up_to (int year)
{
	return (year / 4					/* trivial leapness */
		- ((year > 1700) ? (year / 100 - 17) : 0)	/* minus centuries since 1700 */
		+ ((year > 1600) ? ((year - 1600) / 400) : 0));	/* plus centuries since 1700 divisible by 400 */
}

/* Returns whether the specified year is a leap year */
static int
is_leap_year (int year)
{
	if (year <= 1752)
		return !(year % 4);
	else
		return (!(year % 4) && (year % 100)) || !(year % 400);
}

/* Returns the 1-based day number within the year of the specified date */
static int
day_in_year (int day, int month, int year)
{
	int is_leap, i;

	is_leap = is_leap_year (year);

	for (i = 0; i < month; i++)
		day += days_in_month [is_leap][i];

	return day;
}

/* Returns the day of the week (zero-based, zero is Sunday) for the specified date.  For the days
 * that were removed on the Gregorian reformation, it returns Thursday.
 */
static int
day_in_week (int day, int month, int year)
{
	int n;

	n = (year - 1) * 365 + leap_years_up_to (year - 1) + day_in_year (day, month, year);

	if (n < REFORMATION_DAY)
		return (n - 1 + SATURDAY) % 7;

	if (n >= (REFORMATION_DAY + MISSING_DAYS))
		return (n - 1 + SATURDAY - MISSING_DAYS) % 7;

	return THURSDAY;
}

/* Fills the 42-element days array with the day numbers for the specified month.  Slots outside the
 * bounds of the month are filled with zeros.  The starting and ending indexes of the days are
 * returned in the start and end arguments.
 */
static void
build_month (int month, int year, int start_on_monday, int *days, int *start, int *end)
{
	int i;
	int d_month, d_week;

	/* Note that months are zero-based, so September is month 8 */

	if ((year == 1752) && (month == 8)) {
		memcpy (days, sept_1752, 42 * sizeof (int));

		if (start)
			*start = SEPT_1752_START;

		if (end)
			*end = SEPT_1752_END;

		return;
	}

	for (i = 0; i < 42; i++)
		days[i] = 0;

	d_month = days_in_month[is_leap_year (year)][month];
	d_week = day_in_week (1, month, year);

	if (start_on_monday)
		d_week = (d_week + 6) % 7;

	for (i = 0; i < d_month; i++)
		days[d_week + i] = i + 1;

	if (start)
		*start = d_week;

	if (end)
		*end = d_week + d_month - 1;
}

/* Set the day numbers in the monthly calendar */
static void
set_days (GnomeMonthItem *mitem)
{
	int i;
	int start, end;
	char buf[100];

	build_month (mitem->month, mitem->year, mitem->start_on_monday, mitem->day_numbers, &start, &end);

	/* Clear days before start of month */

	for (i = 0; i < start; i++)
		gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_DAY_LABEL + i],
				       "text", NULL,
				       NULL);

	/* Set days of month */

	for (; start <= end; start++, i++) {
		sprintf (buf, "%d", mitem->day_numbers[start]);
		gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_DAY_LABEL + i],
				       "text", buf,
				       NULL);
	}

	/* Clear days after end of month */

	for (; i < 42; i++)
		gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_DAY_LABEL + i],
				       "text", NULL,
				       NULL);
}

/* Sets the font for all the day numbers */
static void
set_day_font (GnomeMonthItem *mitem)
{
	int i;

	for (i = 0; i < 42; i++)
		gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_DAY_LABEL + i],
				       "font_gdk", mitem->day_font,
				       NULL);
}

/* Sets the color for all the day items */
static void
set_day_color (GnomeMonthItem *mitem)
{
	int i;
	GdkColor outline;
	GdkColor day_box;
	GdkColor day;

	outline.pixel = mitem->outline_pixel;
	day_box.pixel = mitem->day_box_pixel;
	day.pixel = mitem->day_pixel;

	for (i = 0; i < 42; i++) {
		gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_DAY_BOX + i],
				       "outline_color_gdk", &outline,
				       "fill_color_gdk", &day_box,
				       NULL);

		gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_DAY_LABEL + i],
				       "fill_color_gdk", &day,
				       NULL);
	}
}

/* Creates the items for the days */
static void
create_days (GnomeMonthItem *mitem)
{
	int i;

	/* Just create the items; they will be positioned and configured by a call to reshape() */

	for (i = 0; i < 42; i++) {
		/* Group */
		mitem->items[GNOME_MONTH_ITEM_DAY_GROUP + i] =
			gnome_canvas_item_new (GNOME_CANVAS_GROUP (mitem),
					       gnome_canvas_group_get_type (),
					       NULL);

		/* Box */
		mitem->items[GNOME_MONTH_ITEM_DAY_BOX + i] =
			gnome_canvas_item_new (GNOME_CANVAS_GROUP (mitem->items[GNOME_MONTH_ITEM_DAY_GROUP + i]),
					       gnome_canvas_rect_get_type (),
					       NULL);

		/* Label */
		mitem->items[GNOME_MONTH_ITEM_DAY_LABEL + i] =
			gnome_canvas_item_new (GNOME_CANVAS_GROUP (mitem->items[GNOME_MONTH_ITEM_DAY_GROUP + i]),
					       gnome_canvas_text_get_type (),
					       NULL);
	}

	set_day_font (mitem);
	set_day_color (mitem);
	set_days (mitem);
}

/* Resets the text of the day name headings */
static void
set_day_names (GnomeMonthItem *mitem)
{
	int i;

	for (i = 0; i < 7; i++)
		gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_HEAD_LABEL + i],
				       "text", mitem->day_names[mitem->start_on_monday ? ((i + 1) % 7) : i],
				       NULL);
}

/* Creates all the canvas items that make up the calendar */
static void
create_items (GnomeMonthItem *mitem)
{
	mitem->items = g_new (GnomeCanvasItem *, GNOME_MONTH_ITEM_LAST);

	create_headings (mitem);
	create_days (mitem);

	/* Initialize by default to three-letter day names */

	mitem->day_names[0] = g_strdup (_("Sun"));
	mitem->day_names[1] = g_strdup (_("Mon"));
	mitem->day_names[2] = g_strdup (_("Tue"));
	mitem->day_names[3] = g_strdup (_("Wed"));
	mitem->day_names[4] = g_strdup (_("Thu"));
	mitem->day_names[5] = g_strdup (_("Fri"));
	mitem->day_names[6] = g_strdup (_("Sat"));
	
	set_day_names (mitem);
	reshape (mitem);
}

static void
gnome_month_item_init (GnomeMonthItem *mitem)
{
	time_t t;
	struct tm *tm;

	/* Initialize to the current month by default */

	t = time (NULL);
	tm = localtime (&t);

	mitem->year = tm->tm_year + 1900;
	mitem->month = tm->tm_mon;

	mitem->x = 0.0;
	mitem->y = 0.0;
	mitem->width = 150.0; /* not unreasonable defaults, I hope */
	mitem->height = 100.0;
	mitem->anchor = GTK_ANCHOR_NW;
	mitem->head_padding = 0.0;
	mitem->day_padding = 2.0;
	mitem->head_height = 14.0;
	mitem->head_anchor = GTK_ANCHOR_CENTER;
	mitem->day_anchor = GTK_ANCHOR_CENTER;

	/* Load the default fonts */

	mitem->head_font = gdk_font_load (DEFAULT_FONT);
	if (!mitem->head_font) {
		mitem->head_font = gdk_font_load ("fixed");
		g_assert (mitem->head_font != NULL);
	}

	mitem->day_font = gdk_font_load (DEFAULT_FONT);
	if (!mitem->day_font) {
		mitem->day_font = gdk_font_load ("fixed");
		g_assert (mitem->day_font != NULL);
	}
}

GnomeCanvasItem *
gnome_month_item_new (GnomeCanvasGroup *parent)
{
	GnomeMonthItem *mitem;

	g_return_val_if_fail (parent != NULL, NULL);
	g_return_val_if_fail (GNOME_IS_CANVAS_GROUP (parent), NULL);

	mitem = GNOME_MONTH_ITEM (gnome_canvas_item_new (parent,
							 gnome_month_item_get_type (),
							 NULL));

	gnome_month_item_construct (mitem);

	return GNOME_CANVAS_ITEM (mitem);
}

void
gnome_month_item_construct (GnomeMonthItem *mitem)
{
	GdkColor color;

	g_return_if_fail (mitem != NULL);
	g_return_if_fail (GNOME_IS_MONTH_ITEM (mitem));

	gnome_canvas_get_color (GNOME_CANVAS_ITEM (mitem)->canvas, "#d6d6d6d6d6d6", &color);
	mitem->head_pixel = color.pixel;
	mitem->day_box_pixel = color.pixel;

	gnome_canvas_get_color (GNOME_CANVAS_ITEM (mitem)->canvas, "black", &color);
	mitem->outline_pixel = color.pixel;
	mitem->day_pixel = color.pixel;

	create_items (mitem);
}

static void
free_day_names (GnomeMonthItem *mitem)
{
	int i;

	if (mitem->day_names[0])
		for (i = 0; i < 7; i++)
			g_free (mitem->day_names[i]);
}

static void
gnome_month_item_destroy (GtkObject *object)
{
	GnomeMonthItem *mitem;

	g_return_if_fail (object != NULL);
	g_return_if_fail (GNOME_IS_MONTH_ITEM (object));

	mitem = GNOME_MONTH_ITEM (object);

	free_day_names (mitem);

	if (GTK_OBJECT_CLASS (parent_class)->destroy)
		(* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}

/* Sets the color of the specified pixel value to that of the specified argument, which must be in
 * GdkColor format if format is TRUE, otherwise it must be in string format.
 */
static void
set_color_arg (GnomeMonthItem *mitem, gulong *pixel, GtkArg *arg, int gdk_format, int set_head, int set_day)
{
	GdkColor color;

	if (gdk_format)
		*pixel = ((GdkColor *) GTK_VALUE_BOXED (*arg))->pixel;
	else {
		if (gnome_canvas_get_color (GNOME_CANVAS_ITEM (mitem)->canvas, GTK_VALUE_STRING (*arg), &color))
			*pixel = color.pixel;
		else
			*pixel = 0;
	}

	if (set_head)
		set_head_color (mitem);

	if (set_day)
		set_day_color (mitem);
}

static void
gnome_month_item_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
	GnomeMonthItem *mitem;
	char **day_names;
	int i;

	mitem = GNOME_MONTH_ITEM (object);

	switch (arg_id) {
	case ARG_YEAR:
		mitem->year = GTK_VALUE_UINT (*arg);
		set_days (mitem);
		break;

	case ARG_MONTH:
		mitem->month = GTK_VALUE_UINT (*arg);
		set_days (mitem);
		break;

	case ARG_X:
		mitem->x = GTK_VALUE_DOUBLE (*arg);
		reanchor (mitem);
		break;

	case ARG_Y:
		mitem->y = GTK_VALUE_DOUBLE (*arg);
		reanchor (mitem);
		break;

	case ARG_WIDTH:
		mitem->width = fabs (GTK_VALUE_DOUBLE (*arg));
		reshape (mitem);
		break;

	case ARG_HEIGHT:
		mitem->height = fabs (GTK_VALUE_DOUBLE (*arg));
		reshape (mitem);
		break;

	case ARG_ANCHOR:
		mitem->anchor = GTK_VALUE_ENUM (*arg);
		reanchor (mitem);
		break;

	case ARG_HEAD_PADDING:
		mitem->head_padding = fabs (GTK_VALUE_DOUBLE (*arg));
		reshape (mitem);
		break;

	case ARG_DAY_PADDING:
		mitem->day_padding = fabs (GTK_VALUE_DOUBLE (*arg));
		reshape (mitem);
		break;

	case ARG_DAY_NAMES:
		day_names = GTK_VALUE_POINTER (*arg);

		/* First, check that none of the names is null */

		for (i = 0; i < 7; i++)
			if (!day_names[i]) {
				g_warning ("Day number %d was NULL; day names cannot be NULL!", i);
				return;
			}

		/* Set the new names */

		free_day_names (mitem);
		for (i = 0; i < 7; i++)
			mitem->day_names[i] = g_strdup (day_names[i]);

		set_day_names (mitem);
		reshape (mitem);
		break;

	case ARG_HEADING_HEIGHT:
		mitem->head_height = fabs (GTK_VALUE_DOUBLE (*arg));
		reshape (mitem);
		break;

	case ARG_HEADING_ANCHOR:
		mitem->head_anchor = GTK_VALUE_ENUM (*arg);
		reshape (mitem);
		break;

	case ARG_DAY_ANCHOR:
		mitem->day_anchor = GTK_VALUE_ENUM (*arg);
		reshape (mitem);
		break;

	case ARG_START_ON_MONDAY:
		mitem->start_on_monday = GTK_VALUE_BOOL (*arg);
		set_day_names (mitem);
		set_days (mitem);
		break;

	case ARG_HEAD_FONT:
		gdk_font_unref (mitem->head_font);

		mitem->head_font = gdk_font_load (GTK_VALUE_STRING (*arg));
		if (!mitem->head_font) {
			mitem->head_font = gdk_font_load ("fixed");
			g_assert (mitem->head_font != NULL);
		}

		set_head_font (mitem);
		reshape (mitem);
		break;

	case ARG_HEAD_FONT_GDK:
		gdk_font_unref (mitem->head_font);

		mitem->head_font = GTK_VALUE_BOXED (*arg);
		gdk_font_ref (mitem->head_font);
		set_head_font (mitem);
		reshape (mitem);
		break;

	case ARG_DAY_FONT:
		gdk_font_unref (mitem->day_font);

		mitem->day_font = gdk_font_load (GTK_VALUE_STRING (*arg));
		if (!mitem->day_font) {
			mitem->day_font = gdk_font_load ("fixed");
			g_assert (mitem->day_font != NULL);
		}

		set_day_font (mitem);
		reshape (mitem);
		break;

	case ARG_DAY_FONT_GDK:
		gdk_font_unref (mitem->day_font);

		mitem->day_font = GTK_VALUE_BOXED (*arg);
		gdk_font_ref (mitem->day_font);
		set_day_font (mitem);
		reshape (mitem);
		break;

	case ARG_HEAD_COLOR:
		set_color_arg (mitem, &mitem->head_pixel, arg, FALSE, TRUE, FALSE);
		break;

	case ARG_HEAD_COLOR_GDK:
		set_color_arg (mitem, &mitem->head_pixel, arg, TRUE, TRUE, FALSE);
		break;

	case ARG_OUTLINE_COLOR:
		set_color_arg (mitem, &mitem->outline_pixel, arg, FALSE, TRUE, TRUE);
		break;

	case ARG_OUTLINE_COLOR_GDK:
		set_color_arg (mitem, &mitem->outline_pixel, arg, TRUE, TRUE, TRUE);
		break;

	case ARG_DAY_BOX_COLOR:
		set_color_arg (mitem, &mitem->day_box_pixel, arg, FALSE, FALSE, TRUE);
		break;

	case ARG_DAY_BOX_COLOR_GDK:
		set_color_arg (mitem, &mitem->day_box_pixel, arg, TRUE, FALSE, TRUE);
		break;

	case ARG_DAY_COLOR:
		set_color_arg (mitem, &mitem->day_pixel, arg, FALSE, FALSE, TRUE);
		break;

	case ARG_DAY_COLOR_GDK:
		set_color_arg (mitem, &mitem->day_pixel, arg, TRUE, FALSE, TRUE);
		break;

	default:
		break;
	}
}

/* Allocates a GdkColor structure filled with the specified pixel, and puts it into the specified
 * arg for returning it in the get_arg method.
 */
static void
get_color_arg (GnomeMonthItem *mitem, gulong pixel, GtkArg *arg)
{
	GdkColor *color;

	color = g_new (GdkColor, 1);
	color->pixel = pixel;
	gdk_color_context_query_color (GNOME_CANVAS_ITEM (mitem)->canvas->cc, color);
	GTK_VALUE_BOXED (*arg) = color;
}

static void
gnome_month_item_get_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
	GnomeMonthItem *mitem;

	mitem = GNOME_MONTH_ITEM (object);

	switch (arg_id) {
	case ARG_YEAR:
		GTK_VALUE_UINT (*arg) = mitem->year;
		break;

	case ARG_MONTH:
		GTK_VALUE_UINT (*arg) = mitem->month;
		break;

	case ARG_X:
		GTK_VALUE_DOUBLE (*arg) = mitem->x;
		break;

	case ARG_Y:
		GTK_VALUE_DOUBLE (*arg) = mitem->y;
		break;

	case ARG_WIDTH:
		GTK_VALUE_DOUBLE (*arg) = mitem->width;
		break;

	case ARG_HEIGHT:
		GTK_VALUE_DOUBLE (*arg) = mitem->height;
		break;

	case ARG_ANCHOR:
		GTK_VALUE_ENUM (*arg) = mitem->anchor;
		break;

	case ARG_HEAD_PADDING:
		GTK_VALUE_DOUBLE (*arg) = mitem->head_padding;
		break;

	case ARG_DAY_PADDING:
		GTK_VALUE_DOUBLE (*arg) = mitem->day_padding;
		break;

	case ARG_HEADING_HEIGHT:
		GTK_VALUE_DOUBLE (*arg) = mitem->head_height;
		break;

	case ARG_HEADING_ANCHOR:
		GTK_VALUE_ENUM (*arg) = mitem->head_anchor;
		break;

	case ARG_DAY_ANCHOR:
		GTK_VALUE_ENUM (*arg) = mitem->day_anchor;
		break;

	case ARG_START_ON_MONDAY:
		GTK_VALUE_BOOL (*arg) = mitem->start_on_monday;
		break;

	case ARG_HEAD_FONT_GDK:
		GTK_VALUE_BOXED (*arg) = mitem->head_font;
		break;

	case ARG_DAY_FONT_GDK:
		GTK_VALUE_BOXED (*arg) = mitem->day_font;
		break;

	case ARG_HEAD_COLOR_GDK:
		get_color_arg (mitem, mitem->head_pixel, arg);
		break;

	case ARG_OUTLINE_COLOR_GDK:
		get_color_arg (mitem, mitem->outline_pixel, arg);
		break;

	case ARG_DAY_BOX_COLOR_GDK:
		get_color_arg (mitem, mitem->day_box_pixel, arg);
		break;

	case ARG_DAY_COLOR_GDK:
		get_color_arg (mitem, mitem->day_pixel, arg);
		break;

	default:
		arg->type = GTK_TYPE_INVALID;
		break;
	}
}

GnomeCanvasItem *
gnome_month_item_num2child (GnomeMonthItem *mitem, int child_num)
{
	g_return_val_if_fail (mitem != NULL, NULL);
	g_return_val_if_fail (GNOME_IS_MONTH_ITEM (mitem), NULL);

	return mitem->items[child_num];
}

int
gnome_month_item_child2num (GnomeMonthItem *mitem, GnomeCanvasItem *child)
{
	int i;

	g_return_val_if_fail (mitem != NULL, -1);
	g_return_val_if_fail (GNOME_IS_MONTH_ITEM (mitem), -1);
	g_return_val_if_fail (child != NULL, -1);
	g_return_val_if_fail (GNOME_IS_CANVAS_ITEM (child), -1);

	for (i = 0; i < GNOME_MONTH_ITEM_LAST; i++)
		if (mitem->items[i] == child)
			return i;

	return -1;
}

int
gnome_month_item_num2day (GnomeMonthItem *mitem, int child_num)
{
	g_return_val_if_fail (mitem != NULL, 0);
	g_return_val_if_fail (GNOME_IS_MONTH_ITEM (mitem), 0);

	if ((child_num >= GNOME_MONTH_ITEM_DAY_GROUP) && (child_num < GNOME_MONTH_ITEM_LAST)) {
		child_num = (child_num - GNOME_MONTH_ITEM_DAY_GROUP) % 42;
		return mitem->day_numbers[child_num];
	} else
		return 0;
}

int
gnome_month_item_day2index (GnomeMonthItem *mitem, int day_num)
{
	int i;

	g_return_val_if_fail (mitem != NULL, -1);
	g_return_val_if_fail (GNOME_IS_MONTH_ITEM (mitem), -1);
	g_return_val_if_fail (day_num >= 1, -1);

	/* Find first day of month */

	for (i = 0; mitem->day_numbers[i] == 0; i++)
		;

	/* Find the specified day */

	for (; (mitem->day_numbers[i] != 0) && (i < 42); i++)
		if (mitem->day_numbers[i] == day_num)
			return i;

	/* Bail out */

	return -1;
}