/**
 * ms-ole-summary.c: MS Office OLE support
 *
 * Authors:
 *    Michael Meeks (mmeeks@gnu.org)
 *    Frank Chiulli (fc-linux@home.com)
 * From work by:
 *    Caolan McNamara (Caolan.McNamara@ul.ie)
 * Built on work by:
 *    Somar Software's CPPSUM (http://www.somar.com)
 **/

#include <stdio.h>
#include <string.h>
#include <glib.h>
#include "ms-ole.h"
#include "ms-ole-summary.h"

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#define SUMMARY_ID(x) ((x) & 0xff)

typedef struct {
	guint32             offset;
	guint32             id;
	MsOlePropertySetID  ps_id;
} item_t;

const guint32	sum_fmtid[4] =  {
				 0xF29F85E0,
		           	 0x10684FF9,
		           	 0x000891AB,
		           	 0xD9B3272B
				};

const guint32	doc_fmtid[4] =  {	
				 0xD5CDD502,
		           	 0x101B2E9C,
		           	 0x00089793,
		           	 0xAEF92C2B
			        };


const guint32	user_fmtid[4] = {			
				 0XD5CDD505,
				 0X101B2E9C,
		           	 0X00089793,
		           	 0XAEF92C2B
				};


static gboolean
read_items (MsOleSummary *si, MsOlePropertySetID ps_id)
{
	gint sect;
	
	for (sect = 0; sect < (gint) si->sections->len; sect++) {
		MsOleSummarySection st;
		guint8 data[8];
		gint   i;
		
		st = g_array_index (si->sections, MsOleSummarySection, sect);

		if (st.ps_id != ps_id)
			continue;

		si->s->lseek (si->s, st.offset, MsOleSeekSet);
		if (!si->s->read_copy (si->s, data, 8))
			return FALSE;
		
		st.bytes = MS_OLE_GET_GUINT32 (data);
		st.props = MS_OLE_GET_GUINT32 (data + 4);

		if (st.props == 0)
			continue;
		
		for (i = 0; i < (gint) st.props; i++) {
			item_t item;
			if (!si->s->read_copy (si->s, data, 8))
				return FALSE;

			item.id     = MS_OLE_GET_GUINT32 (data);
			item.offset = MS_OLE_GET_GUINT32 (data + 4);
			item.offset = item.offset + st.offset;
			item.ps_id  = ps_id;
			g_array_append_val (si->items, item);
		}
	}
	return TRUE;
}

typedef struct {
	MsOleSummaryPID  id;
	guint32          len;
	guint8          *data;
} write_item_t;


#define PROPERTY_HDR_LEN	8
#define PROPERTY_DESC_LEN	8
static void
write_items (MsOleSummary *si)
{
	MsOlePos cur_pos;
	MsOlePos pos = 48; /* magic offset see: _create_stream */
	guint8   data[PROPERTY_DESC_LEN];
	guint8   fill_data[] = {0, 0, 0, 0};
	guint32  i, num;
	guint32  offset = 0;
	GList   *l;

	/*
	 *  Write out the property descriptors.
	 *  Keep track of the number of properties and number of bytes for the properties.
	 */
	si->s->lseek (si->s, pos + PROPERTY_HDR_LEN, MsOleSeekSet);

	l = si->write_items;
	num = g_list_length (l);
	i = 0;
	offset = PROPERTY_HDR_LEN + num * PROPERTY_DESC_LEN;
	while (l) {
		write_item_t *w = l->data;
		g_return_if_fail (w != NULL);

		/*
		 *  The offset is calculated from the start of the 
		 *  properties header.  The offset must be on a 
		 *  4-byte boundary.  Therefore all data written must be
		 *  in multiples of 4-bytes.
		 */
		MS_OLE_SET_GUINT32 (data + 0, w->id & 0xff);
		MS_OLE_SET_GUINT32 (data + 4, offset);
		si->s->write (si->s, data, PROPERTY_DESC_LEN);

		offset += w->len;
		if ((w->len & 0x3) > 0)
			offset += (4 - (w->len & 0x3));
			
		i++;

		l = g_list_next (l);
	}

	g_return_if_fail (i == num);
	
	/*
	 *  Write out the section header.
	 */
	si->s->lseek (si->s, pos, MsOleSeekSet);
	MS_OLE_SET_GUINT32 (data + 0, offset);
	MS_OLE_SET_GUINT32 (data + 4, i);
	si->s->write (si->s, data, PROPERTY_HDR_LEN);

	/*
	 *  Write out the property values.
	 *  Keep track of the last position written to.
	 */
	cur_pos = pos + PROPERTY_HDR_LEN + num*PROPERTY_DESC_LEN;
	si->s->lseek (si->s, cur_pos, MsOleSeekSet);
	l = si->write_items;
	while (l) {
		write_item_t *w = l->data;
		si->s->write (si->s, w->data, w->len);
		cur_pos += w->len;
		l = g_list_next (l);

		/*
		 * Write out any fill.
		 */
		if ((w->len & 0x3) > 0) {
		        cur_pos += (4 - (w->len & 0x3));
			si->s->write (si->s, fill_data, 4 - (w->len & 0x3));
		}

	}

	/*
	 * Pad it out to a BB file.
	 */
	{
		int     i;
		for (i = cur_pos; i < 0x1000; i+=4)
			si->s->write (si->s, fill_data, 4);
	}

}

/**
 * ms_ole_summary_open_stream:
 * @stream: stream object
 * @psid: Property Set ID, indicates which property set to open
 * 
 * Opens @s as a summary stream, returns NULL on failure.
 * 
 * Return value: %NULL if unable to open summary stream or a pointer to the 
 * Summary Stream.
 **/
MsOleSummary *
ms_ole_summary_open_stream (MsOleStream *stream,
			    const MsOlePropertySetID psid)
{
	guint8              data[64];
	guint16             byte_order;
	gboolean            panic = FALSE;
	guint32             os_version;
	MsOleSummary       *si;
	gint                i, sections;

	g_return_val_if_fail (stream != NULL, NULL);

	if (!stream->read_copy (stream, data, 28))
		return NULL;

	si                = g_new (MsOleSummary, 1);

	si->s             = stream;
	si->write_items   = NULL;
	si->sections      = NULL;
	si->items         = NULL;
	si->read_mode     = TRUE;

	byte_order        = MS_OLE_GET_GUINT16(data);
	if (byte_order != 0xfffe)
		panic     = TRUE;

	if (MS_OLE_GET_GUINT16 (data + 2) != 0) /* Format */
		panic     = TRUE;

	os_version        = MS_OLE_GET_GUINT32 (data + 4);

	for (i = 0; i < 16; i++)
		si->class_id[i] = data[8 + i];

	sections          = MS_OLE_GET_GUINT32 (data + 24);

	if (panic) {
		ms_ole_summary_close (si);
		return NULL;
	}

	si->sections = g_array_new (FALSE, FALSE, sizeof (MsOleSummarySection));

	for (i = 0; i < sections; i++) {
		MsOleSummarySection sect;
		if (!stream->read_copy (stream, data, 16 + 4)) {
			ms_ole_summary_close (si);
			return NULL;
		}
		
		if (psid == MS_OLE_PS_SUMMARY_INFO) {
			if (MS_OLE_GET_GUINT32 (data +  0) == sum_fmtid[0] &&
			    MS_OLE_GET_GUINT32 (data +  4) == sum_fmtid[1] &&
			    MS_OLE_GET_GUINT32 (data +  8) == sum_fmtid[2] &&
			    MS_OLE_GET_GUINT32 (data + 12) == sum_fmtid[3]    ) {
				si->ps_id  = MS_OLE_PS_SUMMARY_INFO;
				sect.ps_id = MS_OLE_PS_SUMMARY_INFO;
			
			} else {
				ms_ole_summary_close (si);
				return NULL;
			}
			
		} else if (psid == MS_OLE_PS_DOCUMENT_SUMMARY_INFO) {
			if (MS_OLE_GET_GUINT32 (data +  0) == doc_fmtid[0] &&
		            MS_OLE_GET_GUINT32 (data +  4) == doc_fmtid[1] &&
		            MS_OLE_GET_GUINT32 (data +  8) == doc_fmtid[2] &&
		            MS_OLE_GET_GUINT32 (data + 12) == doc_fmtid[3]    ) {
				si->ps_id  = MS_OLE_PS_DOCUMENT_SUMMARY_INFO;
				sect.ps_id = MS_OLE_PS_DOCUMENT_SUMMARY_INFO;
			
			} else if (MS_OLE_GET_GUINT32 (data +  0) == user_fmtid[0] &&
		            	   MS_OLE_GET_GUINT32 (data +  4) == user_fmtid[1] &&
				   MS_OLE_GET_GUINT32 (data +  8) == user_fmtid[2] &&
				   MS_OLE_GET_GUINT32 (data + 12) == user_fmtid[3]    ) {
				si->ps_id  = MS_OLE_PS_DOCUMENT_SUMMARY_INFO;
				sect.ps_id = MS_OLE_PS_USER_DEFINED_SUMMARY_INFO;
			
			} else {
				ms_ole_summary_close (si);
				return NULL;
			}
		}

		sect.offset = MS_OLE_GET_GUINT32 (data + 16);
		g_array_append_val (si->sections, sect);
		/* We want to read the offsets of the items here into si->items */
	}

	si->items = g_array_new (FALSE, FALSE, sizeof (item_t));

	for (i = 0; i < sections; i++) {
		MsOleSummarySection st;

		st = g_array_index (si->sections, MsOleSummarySection, i);
		if (!read_items (si, st.ps_id)) {
			g_warning ("Serious error reading items");
			ms_ole_summary_close (si);
			return NULL;
		}
	}

	return si;
}

/**
 * ms_ole_summary_open:
 * @f: filesystem object.
 * 
 * Opens the SummaryInformation stream, returns NULL on failure.
 * 
 * Return value: %NULL if unable to open summary stream or a pointer to the 
 * SummaryInformation Stream.
 **/
MsOleSummary *
ms_ole_summary_open (MsOle *f)
{
	MsOleStream *s;
	MsOleErr     result;
	g_return_val_if_fail (f != NULL, NULL);

	result = ms_ole_stream_open (&s, f, "/",
				     "\05SummaryInformation", 'r');
	if (result != MS_OLE_ERR_OK || !s)
		return NULL;

	return ms_ole_summary_open_stream (s, MS_OLE_PS_SUMMARY_INFO);
}


/**
 * ms_ole_docsummary_open:
 * @f: filesystem object.
 * 
 * Opens the DocumentSummaryInformation stream, returns NULL on failure.
 * 
 * Return value: %NULL if unable to open summary stream or a pointer to the 
 * DocumentSummaryInformation Stream.
 **/
MsOleSummary *
ms_ole_docsummary_open (MsOle *f)
{
	MsOleStream *s;
	MsOleErr     result;
	g_return_val_if_fail (f != NULL, NULL);

	result = ms_ole_stream_open (&s, f, "/",
	                             "\05DocumentSummaryInformation", 'r');
	if (result != MS_OLE_ERR_OK || !s)
		return NULL;

	return ms_ole_summary_open_stream (s, MS_OLE_PS_DOCUMENT_SUMMARY_INFO);
}


/*
 * Cheat by hard coding magic numbers and chaining on.
 */
/**
 * ms_ole_summary_create_stream:
 * @s: stream object
 * @psid: Property Set ID, indicates which property set to open
 * 
 * Creates @s as a summary stream (@psid determines which one), returns NULL on
 * failure.
 * 
 * Return value: %NULL if unable to create stream, otherwise a pointer to a new
 * summary stream.
 **/
MsOleSummary *
ms_ole_summary_create_stream (MsOleStream *s, const MsOlePropertySetID psid)
{
	guint8        data[78];
	MsOleSummary *si;
	g_return_val_if_fail (s != NULL, NULL);

	MS_OLE_SET_GUINT16 (data +  0, 0xfffe); /* byte order */
	MS_OLE_SET_GUINT16 (data +  2, 0x0000); /* format */
	MS_OLE_SET_GUINT16 (data +  4, 0x0001); /* OS version A */
	MS_OLE_SET_GUINT16 (data +  6, 0x0000); /* OS version B */

	MS_OLE_SET_GUINT32 (data +  8, 0x0000); /* class id */
	MS_OLE_SET_GUINT32 (data + 12, 0x0000);
	MS_OLE_SET_GUINT32 (data + 16, 0x0000);
	MS_OLE_SET_GUINT32 (data + 20, 0x0000);

	if (psid == MS_OLE_PS_SUMMARY_INFO) {
		MS_OLE_SET_GUINT32 (data + 24, 0x0001); /* Sections */

		MS_OLE_SET_GUINT32 (data + 28, sum_fmtid[0]); /* ID */
		MS_OLE_SET_GUINT32 (data + 32, sum_fmtid[1]);
		MS_OLE_SET_GUINT32 (data + 36, sum_fmtid[2]);
		MS_OLE_SET_GUINT32 (data + 40, sum_fmtid[3]);

		MS_OLE_SET_GUINT32 (data + 44, 0x30); /* Section offset = 48 */

		MS_OLE_SET_GUINT32 (data + 48,  0); /* bytes */
		MS_OLE_SET_GUINT32 (data + 52,  0); /* properties */

		s->write (s, data, 56);

	} else if (psid == MS_OLE_PS_DOCUMENT_SUMMARY_INFO) {
		MS_OLE_SET_GUINT32 (data + 24, 0x0001); /* Sections */

		MS_OLE_SET_GUINT32 (data + 28, doc_fmtid[0]); /* ID */
		MS_OLE_SET_GUINT32 (data + 32, doc_fmtid[1]);
		MS_OLE_SET_GUINT32 (data + 36, doc_fmtid[2]);
		MS_OLE_SET_GUINT32 (data + 40, doc_fmtid[3]);

		MS_OLE_SET_GUINT32 (data + 44, 0x30); /* Section offset = 48 */

		MS_OLE_SET_GUINT32 (data + 48,  0); /* bytes */
		MS_OLE_SET_GUINT32 (data + 52,  0); /* properties */

		s->write (s, data, 56);

	}

	s->lseek (s, 0, MsOleSeekSet);

	si = ms_ole_summary_open_stream (s, psid);
	si->read_mode = FALSE;

	return si;
}


/**
 * ms_ole_summary_create:
 * @f: filesystem object.
 * 
 * Create a SummaryInformation stream, returns NULL on failure.
 * 
 * Return value: %NULL if unable to create the stream, otherwise a pointer to a
 * new SummaryInformation stream.
 **/
MsOleSummary *
ms_ole_summary_create (MsOle *f)
{
	MsOleStream *s;
	MsOleErr     result;

	g_return_val_if_fail (f != NULL, NULL);

	result = ms_ole_stream_open (&s, f, "/",
				     "\05SummaryInformation", 'w');
	if (result != MS_OLE_ERR_OK || !s) {
		printf ("ms_ole_summary_create: Can't open stream for writing\n");
		return NULL;
	}

	return ms_ole_summary_create_stream (s, MS_OLE_PS_SUMMARY_INFO);
}


/**
 * ms_ole_docsummary_create:
 * @f: filesystem object.
 * 
 * Create a DocumentSummaryInformation stream, returns NULL on failure.
 * 
 * Return value: %NULL if unable to create the stream, otherwise a pointer to a
 * new DocumentSummaryInformation stream.
 **/
MsOleSummary *
ms_ole_docsummary_create (MsOle *f)
{
	MsOleStream *s;
	MsOleErr     result;

	g_return_val_if_fail (f != NULL, NULL);

	result = ms_ole_stream_open (&s, f, "/",
				     "\05DocumentSummaryInformation", 'w');
	if (result != MS_OLE_ERR_OK || !s) {
		printf ("ms_ole_docsummary_create: Can't open stream for writing\n");
		return NULL;
	}

	return ms_ole_summary_create_stream (s, MS_OLE_PS_DOCUMENT_SUMMARY_INFO);
}


/* FIXME: without the helpful type */
/**
 * ms_ole_summary_get_properties:
 * @si: summary stream
 * 
 * Returns an array of MsOleSummaryPID.
 * 
 * Return value: an array of property ids in the current summary stream or 
 * %NULL if either the summary stream is non-existent or the summary stream
 * contains no properties.
 **/
GArray *
ms_ole_summary_get_properties (MsOleSummary *si)
{
	GArray *ans;
	gint i;

	g_return_val_if_fail (si != NULL, NULL);
	g_return_val_if_fail (si->items != NULL, NULL);

	ans = g_array_new (FALSE, FALSE, sizeof (MsOleSummaryPID));
	g_array_set_size  (ans, si->items->len);
	for (i = 0; i < (gint) si->items->len; i++)
		g_array_index (ans, MsOleSummaryPID, i) = 
			g_array_index (si->items, item_t, i).id;

	return ans;
}

/**
 * ms_ole_summary_close:
 * @si: FIXME
 * 
 * FIXME
 **/
void
ms_ole_summary_close (MsOleSummary *si)
{
	g_return_if_fail (si != NULL);
	g_return_if_fail (si->s != NULL);

	if (!si->read_mode)
		write_items (si);
	
	if (si->sections)
		g_array_free (si->sections, TRUE);
	si->sections = NULL;

	if (si->items)
		g_array_free (si->items, TRUE);
	si->items = NULL;

	if (si->s)
		ms_ole_stream_close (&si->s);
	si->s = NULL;

	g_free (si);
}


/*
 *                        Record handling code
 */
#define TYPE_SHORT	0x02		/*   2, VT_I2,		2-byte signed integer  */
#define TYPE_LONG       0x03		/*   3, VT_I4,		4-byte signed integer  */
#define TYPE_BOOLEAN	0x0b		/*  11, VT_BOOL,	Boolean value  */
#define TYPE_STRING     0x1e		/*  30, VT_LPSTR,	Pointer to null terminated ANSI string */
#define TYPE_TIME       0x40		/*  64, VT_FILETIME,	64-bit FILETIME structure  */
#define TYPE_PREVIEW    0x47		/*  71, VT_CF,		Pointer to a CLIPDATA structure  */

/* Seeks to the correct place, and returns a handle or NULL on failure */
static item_t *
seek_to_record (MsOleSummary *si, MsOleSummaryPID id)
{
	gint i;
	g_return_val_if_fail (si->items, FALSE);

	/* These should / could be sorted for speed */
	for (i = 0; i < (gint) si->items->len; i++) {
		item_t item = g_array_index (si->items, item_t, i);
		if (item.id == (guint32) SUMMARY_ID(id)) {
			gboolean is_summary, is_doc_summary;

			is_summary     = ((si->ps_id == MS_OLE_PS_SUMMARY_INFO) && 
					  (item.ps_id == MS_OLE_PS_SUMMARY_INFO));
			is_doc_summary = ((si->ps_id == MS_OLE_PS_DOCUMENT_SUMMARY_INFO) && 
					  (item.ps_id == MS_OLE_PS_DOCUMENT_SUMMARY_INFO));
			if (is_summary || is_doc_summary) {
				si->s->lseek (si->s, item.offset, MsOleSeekSet);
				return &g_array_index (si->items, item_t, i);
			}
		}
	}
	return NULL;
}

/**
 * ms_ole_summary_get_string:
 * @si: FIXME
 * @id: FIXME
 * @available: FIXME
 * 
 * FIXME
 * Note: Ensure that you free returned value after use.
 * 
 * Return value: FIXME
 **/
char *
ms_ole_summary_get_string (MsOleSummary *si, MsOleSummaryPID id,
			   gboolean *available)
{
	guint8   data[8];
	guint32  type, len;
	gchar   *ans;
	item_t *item;

	g_return_val_if_fail (available != NULL, 0);
	*available = FALSE;
	g_return_val_if_fail (si != NULL, NULL);
	g_return_val_if_fail (si->read_mode, NULL);
	g_return_val_if_fail (MS_OLE_SUMMARY_TYPE (id) ==
			      MS_OLE_SUMMARY_TYPE_STRING, NULL);

	if (!(item = seek_to_record (si, id)))
		return NULL;

	if (!si->s->read_copy (si->s, data, 8))
		return NULL;

	type = MS_OLE_GET_GUINT32 (data);
	len  = MS_OLE_GET_GUINT32 (data + 4);

	if (type != TYPE_STRING) { /* Very odd */
		g_warning ("Summary string type mismatch");
		return NULL;
	}

	ans = g_new (gchar, len + 1);
	
	if (!si->s->read_copy (si->s, (guint8 *)ans, len)) {
		g_free (ans);
		return NULL;
	}

	ans[len] = '\0';

	*available = TRUE;
	return ans;
}

/**
 * ms_ole_summary_get_short:
 * @si: FIXME
 * @id: FIXME
 * @available: FIXME
 * 
 * FIXME
 * 
 * Return value: FIXME
 **/
guint16
ms_ole_summary_get_short (MsOleSummary *si, MsOleSummaryPID id,
			 gboolean *available)
{
	guint8   data[8];
	guint32  type;
	guint32  value;
	item_t  *item;

	g_return_val_if_fail (available != NULL, 0);
	*available = FALSE;
	g_return_val_if_fail (si != NULL, 0);
	g_return_val_if_fail (si->read_mode, 0);
	g_return_val_if_fail (MS_OLE_SUMMARY_TYPE (id) ==
			      MS_OLE_SUMMARY_TYPE_SHORT, 0);

	if (!(item = seek_to_record (si, id)))
		return 0;

	if (!si->s->read_copy (si->s, data, 8))
		return 0;

	type  = MS_OLE_GET_GUINT32 (data);
	value = MS_OLE_GET_GUINT16 (data + 4);

	if (type != TYPE_SHORT) { /* Very odd */
		g_warning ("Summary short type mismatch");
		return 0;
	}

	*available = TRUE;
	return value;
}

/**
 * ms_ole_summary_get_boolean:
 * @si: FIXME
 * @id: FIXME
 * @available: FIXME
 * 
 * FIXME
 * 
 * Return value: FIXME
 **/
gboolean
ms_ole_summary_get_boolean (MsOleSummary *si, MsOleSummaryPID id,
			    gboolean *available)
{
	guint8    data[8];
	guint32   type;
	gboolean  value;
	item_t   *item;

	g_return_val_if_fail (available != NULL, 0);
	*available = FALSE;
	g_return_val_if_fail (si != NULL, 0);
	g_return_val_if_fail (si->read_mode, 0);
	g_return_val_if_fail (MS_OLE_SUMMARY_TYPE (id) ==
			      MS_OLE_SUMMARY_TYPE_BOOLEAN, 0);

	if (!(item = seek_to_record (si, id)))
		return 0;

	if (!si->s->read_copy (si->s, data, 8))
		return 0;

	type  = MS_OLE_GET_GUINT32  (data);
	value = MS_OLE_GET_GUINT16 (data + 4);

	if (type != TYPE_BOOLEAN) { /* Very odd */
		g_warning ("Summary boolean type mismatch");
		return 0;
	}

	*available = TRUE;
	return value;
}

/**
 * ms_ole_summary_get_long:
 * @si: FIXME
 * @id: FIXME
 * @available: FIXME
 * 
 * FIXME
 * 
 * Return value: FIXME
 **/
guint32
ms_ole_summary_get_long (MsOleSummary *si, MsOleSummaryPID id,
			 gboolean *available)
{
	guint8  data[8];
	guint32 type, value;
	item_t *item;

	g_return_val_if_fail (available != NULL, 0);
	*available = FALSE;
	g_return_val_if_fail (si != NULL, 0);
	g_return_val_if_fail (si->read_mode, 0);
	g_return_val_if_fail (MS_OLE_SUMMARY_TYPE (id) ==
			      MS_OLE_SUMMARY_TYPE_LONG, 0);

	if (!(item = seek_to_record (si, id)))
		return 0;

	if (!si->s->read_copy (si->s, data, 8))
		return 0;

	type  = MS_OLE_GET_GUINT32 (data);
	value = MS_OLE_GET_GUINT32 (data + 4);

	if (type != TYPE_LONG) { /* Very odd */
		g_warning ("Summary long type mismatch");
		return 0;
	}

	*available = TRUE;
	return value;
}


/*
 *  filetime_to_unixtime
 *
 *  Convert a FILETIME format to unixtime
 *  FILETIME is the number of 100ns units since January 1, 1601.
 *  unixtime is the number of seconds since January 1, 1970.
 *
 *  The difference in 100ns units between the two dates is:
 *	116,444,736,000,000,000  (TIMEDIF)
 *  (I'll let you do the math)
 *  If we divide this into pieces,
 *    high 32-bits = 27111902 or  TIMEDIF / 16^8
 *    mid  16-bits =    54590 or (TIMEDIF - (high 32-bits * 16^8)) / 16^4
 *    low  16-bits =    32768 or (TIMEDIF - (high 32-bits * 16^8) - (mid 16-bits * 16^4)
 *
 *  where all math is integer.
 *
 *  Adapted from work in 'wv' by:
 *    Caolan McNamara (Caolan.McNamara@ul.ie)
 */
#define HIGH32_DELTA	27111902
#define MID16_DELTA	   54590
#define LOW16_DELTA        32768

/**
 * filetime_to_unixtime:
 * @low_time: FIXME
 * @high_time: FIXME
 * 
 * Converts a FILETIME format to unixtime. FILETIME is the number of 100ns units
 * since January 1, 1601. unixtime is the number of seconds since January 1,
 * 1970.
 * 
 * Return value: FIXME
 **/
glong filetime_to_unixtime (guint32 low_time, guint32 high_time);
glong
filetime_to_unixtime (guint32 low_time, guint32 high_time)
{
	guint32		 low16;		/* 16 bit, low    bits */
	guint32		 mid16;		/* 16 bit, medium bits */
	guint32		 hi32;		/* 32 bit, high   bits */
	unsigned int	 carry;		/* carry bit for subtraction */
	int		 negative;	/* whether a represents a negative value */

	/* Copy the time values to hi32/mid16/low16 */
	hi32  =  high_time;
	mid16 = low_time >> 16;
	low16 = low_time &  0xffff;

	/* Subtract the time difference */
	if (low16 >= LOW16_DELTA           )
		low16 -=             LOW16_DELTA        , carry = 0;
	else
		low16 += (1 << 16) - LOW16_DELTA        , carry = 1;

	if (mid16 >= MID16_DELTA    + carry)
		mid16 -=             MID16_DELTA + carry, carry = 0;
	else
		mid16 += (1 << 16) - MID16_DELTA - carry, carry = 1;

	hi32 -= HIGH32_DELTA + carry;

	/* If a is negative, replace a by (-1-a) */
	negative = (hi32 >= ((guint32)1) << 31);
	if (negative) {
		/* Set a to -a - 1 (a is hi32/mid16/low16) */
		low16 = 0xffff - low16;
		mid16 = 0xffff - mid16;
		hi32 = ~hi32;
	}

	/*
	 *  Divide a by 10000000 (a = hi32/mid16/low16), put the rest into r.
         * Split the divisor into 10000 * 1000 which are both less than 0xffff.
	 */
	mid16 += (hi32 % 10000) << 16;
	hi32  /=       10000;
	low16 += (mid16 % 10000) << 16;
	mid16 /=       10000;
	low16 /=       10000;

	mid16 += (hi32 % 1000) << 16;
	hi32  /=       1000;
	low16 += (mid16 % 1000) << 16;
	mid16 /=       1000;
	low16 /=       1000;

	/* If a was negative, replace a by (-1-a) and r by (9999999 - r) */
	if (negative) {
		/* Set a to -a - 1 (a is hi32/mid16/low16) */
		low16 = 0xffff - low16;
		mid16 = 0xffff - mid16;
		hi32 = ~hi32;
	}

	/*  Do not replace this by << 32, it gives a compiler warning and 
	 *  it does not work
	 */
	return ((((glong)hi32) << 16) << 16) + (mid16 << 16) + low16;

}


/**
 * unixtime_to_filetime:
 * @unix_time: FIXME
 * @time_high: FIXME
 * @time_low: FIXME
 * 
 * Converts a unixtime format to FILETIME. FILETIME is the number of 100ns units
 * since January 1, 1601. unixtime is the number of seconds since January 1,
 * 1970.
 **/
void unixtime_to_filetime (time_t unix_time, unsigned int *time_high,
			   unsigned int *time_low);
void
unixtime_to_filetime (time_t unix_time, unsigned int *time_high, unsigned int *time_low)
{
	unsigned int	 low_16;
	unsigned int	 mid_16;
	unsigned int	 high32;
	unsigned int	 carry;
	
	/*
	 *  First split unix_time up.
	 */
	high32 = (unix_time >> 16) >> 16;
	mid_16 =  unix_time >> 16;
	low_16 =  unix_time  & 0xffff;
	
	/*
	 *  Convert seconds to 100 ns units by multipling by 10,000,000.
	 *  Do this in two steps, 10,000 and 1,000.
	 */
	low_16 *= 10000;
	carry   = (low_16) >> 16;
	low_16  = low_16 & 0xffff;
	
	mid_16 *= 10000;
	mid_16 += carry;
	carry   = (mid_16 >> 16);
	mid_16  = mid_16 & 0xffff;
	
	high32 *= 10000;
	high32 += carry;
	
	
	low_16 *= 1000;
	carry   = (low_16) >> 16;
	low_16  = low_16 & 0xffff;
	
	mid_16 *= 1000;
	mid_16 += carry;
	carry   = (mid_16 >> 16);
	mid_16  = mid_16 & 0xffff;
	
	high32 *= 1000;
	high32 += carry;
	
	/*
	 *  Now add in the time difference.
	 */
	low_16 += LOW16_DELTA;
	mid_16 += (low_16 >> 16);
	low_16  =  low_16  & 0xffff;
	
	mid_16 += MID16_DELTA;
	high32 += (mid_16 >> 16);
	mid_16  =  mid_16  & 0xffff;
	
	high32 += HIGH32_DELTA;
	
	*time_high = high32;
	*time_low  = (mid_16 << 16) + low_16;
	
	return;
	
}


/**
 * ms_ole_summary_get_time:
 * @si: FIXME
 * @id: FIXME
 * @available: FIXME
 * 
 * FIXME
 * 
 * Return value: FIXME
 **/
#if 0
GTimeVal
ms_ole_summary_get_time (MsOleSummary *si, MsOleSummaryPID id,
			 gboolean *available)
{
	guint8   data[12];
	guint32  type;
	guint32  low_time;
	guint32  high_time;
	item_t  *item;
	GTimeVal time;

	time.tv_sec  = 0;   /* Magic numbers */
	time.tv_usec = 0;
/*	g_date_set_dmy (&time.date, 18, 6, 1977); */

	g_return_val_if_fail (available != NULL, time);
	*available = FALSE;
	g_return_val_if_fail (si != NULL, time);
	g_return_val_if_fail (si->read_mode, time);
	g_return_val_if_fail (MS_OLE_SUMMARY_TYPE (id) ==
			      MS_OLE_SUMMARY_TYPE_TIME, time);

	if (!(item = seek_to_record (si, id)))
		return time;

	if (!si->s->read_copy (si->s, data, 12))
		return time;

	type      = MS_OLE_GET_GUINT32 (data);
	low_time  = MS_OLE_GET_GUINT32 (data + 4);
	high_time = MS_OLE_GET_GUINT32 (data + 8);

	if (type != TYPE_TIME) { /* Very odd */
		g_warning ("Summary time type mismatch");
		return time;
	}

	time.tv_sec = filetime_to_unixtime (low_time, high_time);
	
	*available = TRUE;
	return time;
}
#endif

/**
 * ms_ole_summary_preview_destroy:
 * @d: FIXME
 * 
 * FIXME
 **/
void
ms_ole_summary_preview_destroy (MsOleSummaryPreview d)
{
	if (d.data)
		g_free (d.data);
	d.data = NULL;
}

/**
 * ms_ole_summary_get_preview:
 * @si: FIXME
 * @id: FIXME
 * @available: FIXME
 * 
 * FIXME
 * 
 * Return value: FIXME
 **/
MsOleSummaryPreview
ms_ole_summary_get_preview (MsOleSummary *si, MsOleSummaryPID id,
			    gboolean *available)
{
	guint8  data[8];
	guint32 type;
	MsOleSummaryPreview ans;
	item_t *item;

	ans.len  = 0;
	ans.data = NULL;

	g_return_val_if_fail (available != NULL, ans);
	*available = FALSE;
	g_return_val_if_fail (si != NULL, ans);
	g_return_val_if_fail (si->read_mode, ans);
	g_return_val_if_fail (MS_OLE_SUMMARY_TYPE (id) ==
			      MS_OLE_SUMMARY_TYPE_OTHER, ans);

	if (!(item = seek_to_record (si, id)))
		return ans;

	if (!si->s->read_copy (si->s, data, 8))
		return ans;

	type     = MS_OLE_GET_GUINT32 (data);
	ans.len  = MS_OLE_GET_GUINT32 (data + 4);

	if (type != TYPE_PREVIEW) { /* Very odd */
		g_warning ("Summary wmf type mismatch");
		return ans;
	}

	ans.data = g_new (guint8, ans.len + 1);
	
	if (!si->s->read_copy (si->s, ans.data, ans.len)) {
		g_free (ans.data);
		return ans;
	}

	*available = TRUE;
	return ans;
}

static write_item_t *
write_item_t_new (MsOleSummary *si, MsOleSummaryPID id)
{
	write_item_t *w = g_new (write_item_t, 1);

	g_return_val_if_fail (si != NULL, NULL);
	g_return_val_if_fail (!si->read_mode, NULL);

	w->id           = id;
	w->len          = 0;
	w->data         = NULL;
	si->write_items = g_list_append (si->write_items, w);

	return w;
}

/**
 * ms_ole_summary_set_preview:
 * @si: FIXME
 * @id: FIXME
 * @preview: FIXME
 * 
 * FIXME
 **/
void
ms_ole_summary_set_preview (MsOleSummary *si, MsOleSummaryPID id,
			    const MsOleSummaryPreview *preview)
{
	write_item_t *w;

	g_return_if_fail (si != NULL);
	g_return_if_fail (!si->read_mode);
	g_return_if_fail (preview != NULL);

	w = write_item_t_new (si, id);

	w->data = g_new (guint8, preview->len + 8);

	MS_OLE_SET_GUINT32 (w->data + 0, TYPE_PREVIEW);
	MS_OLE_SET_GUINT32 (w->data + 4, preview->len);

	memcpy (w->data + 8, preview->data, preview->len);
	
	w->len = preview->len + 8;
}

/**
 * ms_ole_summary_set_time:
 * @si: FIXME
 * @id: FIXME
 * @time: FIXME
 * 
 * FIXME
 **/
#if 0
void
ms_ole_summary_set_time (MsOleSummary *si, MsOleSummaryPID id,
			 GTimeVal time)
{
	unsigned int	 time_high;
	unsigned int	 time_low;
	write_item_t	*w;

	g_return_if_fail (si != NULL);
	g_return_if_fail (!si->read_mode);

	w = write_item_t_new (si, id);

	w->data = g_new (guint8, 12);
	w->len  = 12;

	MS_OLE_SET_GUINT32 (w->data + 0, TYPE_TIME);
	
        unixtime_to_filetime ((time_t)time.tv_sec, &time_high, &time_low);
	
	MS_OLE_SET_GUINT32 (w->data + 4, time_low);
	MS_OLE_SET_GUINT32 (w->data + 8, time_high);
}
#endif

/**
 * ms_ole_summary_set_boolean:
 * @si: FIXME
 * @id: FIXME
 * @bool: FIXME
 * 
 * FIXME
 **/
void
ms_ole_summary_set_boolean (MsOleSummary *si, MsOleSummaryPID id,
			    gboolean value)
{
	write_item_t *w;

	g_return_if_fail (si != NULL);
	g_return_if_fail (!si->read_mode);

	w = write_item_t_new (si, id);

	w->data = g_new (guint8, 8);
	w->len  = 6;

	MS_OLE_SET_GUINT32 (w->data + 0, TYPE_BOOLEAN);
	MS_OLE_SET_GUINT16 (w->data + 4, value);
}



/**
 * ms_ole_summary_set_short:
 * @si: FIXME
 * @id: FIXME
 * @i: FIXME
 * 
 * FIXME
 **/
void
ms_ole_summary_set_short (MsOleSummary *si, MsOleSummaryPID id,
			  guint16 i)
{
	write_item_t *w;

	g_return_if_fail (si != NULL);
	g_return_if_fail (!si->read_mode);

	w = write_item_t_new (si, id);

	w->data = g_new (guint8, 8);
	w->len  = 6;

	MS_OLE_SET_GUINT32 (w->data + 0, TYPE_SHORT);
	MS_OLE_SET_GUINT16 (w->data + 4, i);
}

/**
 * ms_ole_summary_set_long:
 * @si: FIXME
 * @id: FIXME
 * @i: FIXME
 * 
 * FIXME
 **/
void
ms_ole_summary_set_long (MsOleSummary *si, MsOleSummaryPID id,
			 guint32 i)
{
	write_item_t *w;

	g_return_if_fail (si != NULL);
	g_return_if_fail (!si->read_mode);

	w = write_item_t_new (si, id);

	w->data = g_new (guint8, 8);
	w->len  = 8;

	MS_OLE_SET_GUINT32 (w->data + 0, TYPE_LONG);
	MS_OLE_SET_GUINT32 (w->data + 4, i);
}

/**
 * ms_ole_summary_set_string:
 * @si: FIXME
 * @id: FIXME
 * @str: FIXME
 * 
 * FIXME
 **/
void
ms_ole_summary_set_string (MsOleSummary *si, MsOleSummaryPID id,
			   const gchar *str)
{
	write_item_t *w;
	guint32 len;

	g_return_if_fail (si != NULL);
	g_return_if_fail (str != NULL);
	g_return_if_fail (!si->read_mode);

	w       = write_item_t_new (si, id);
	len     = strlen (str) + 1;
	w->len  = len + 8;
	w->data = g_new (guint8, len + 8);
	
	MS_OLE_SET_GUINT32 (w->data + 0, TYPE_STRING);
	MS_OLE_SET_GUINT32 (w->data + 4, len);

	memcpy (w->data + 8, str, len);
}