/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
 * kolab-util.c
 *
 *  Created on: 29.09.2010
 *      Author: Hendrik Helwich <h.helwich@tarent.de>,
 *      		Andreas Grau <a.grau@tarent.de>
 */

/*
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 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 Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor Boston, MA 02110-1301, USA
 */

#define _XOPEN_SOURCE 600 /* glibc2 needs this */

#include <config.h>

#include <glib/gi18n-lib.h>

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>
#include "kolab-util.h"
#include "../logging.h"
#include "../util.h"


/**
 * Find the mail part in the mail part array which contains the kolab xml by
 * checking the mime type of each mail part. The XML will be parsed in memory
 * and the XML tree will be returned.
 *
 * @param  kmail
 *         a kolab multipart mail structure
 * @param  error
 *         a glib error structure
 * @return An XML tree which holds the kolab XML data of this kolab multipart
 *         mail or NULL if an error occurs (*error is not NULL).
 *         If no error is thrown this document mus be freed by the caller later.
 *
 * @throws KOLABCONV_ERROR_READ_KOLAB_INPUT_IS_NULL
 *         if the parameter kmail is NULL
 * @throws KOLABCONV_ERROR_READ_KOLAB_MISSING_XML_PART
 *         if the kolab XML mail part could not be found
 * @throws KOLABCONV_ERROR_READ_KOLAB_MALFORMED_XML
 *         if the kolab XML part holds no wellformed XML data
 */

/**
 * convert ISO date to GDate
 */
GDate*
string_to_g_date (gchar *date_string)
{
	gchar **date_tokens = g_strsplit (date_string, "-", -1);

	gint year = strtol(date_tokens[0], NULL, 10);
	gint month = strtol(date_tokens[1], NULL, 10);
	gint day = strtol(date_tokens[2], NULL, 10);

	g_strfreev(date_tokens);

	return g_date_new_dmy ((GDateDay)day, (GDateMonth)month, (GDateYear)year);
}

/*
 * Converts GDate to a String.
 *
 * Important:
 * Returned char has to be freed with g_free(), because memory is allocated with "strdup"!
 */
gchar*
g_date_to_string (GDate *date)
{
	return g_strdup_printf("%04d-%02d-%02d", date->year, date->month, date->day);
}

/**
 * A conversion function, which converts a Date_or_datetime object to a string
 * representation of date or datetime.
 *
 * @param Date_or_datetime
 * 	  A pointer to the Date_or_datetime struct which needs to be converted.
 *
 * @return A pointer to newly created string representation of Date_or_datetime.
 *
 * Returned Pointer has to be freed with g_free()!
 */
gchar*
date_or_datetime_to_string (const Date_or_datetime *dodt)
{
	gchar *date_string = g_new0(gchar, 50);

	if (dodt) {
		if (dodt->date) {
			gchar *gdate_str = g_date_to_string (dodt->date);
			strcpy (date_string, gdate_str);
			g_free(gdate_str);
		} else if (dodt->date_time)
			datetime_to_string(dodt->date_time, date_string);
		else
			return strcpy (date_string, "NULL");
	}

	return date_string;
}


void
datetime_to_string (const time_t *time, gchar *date_string)
{
	struct tm * timeinfo;
	timeinfo = gmtime (time);

	sprintf (date_string, "%04d-%02d-%02dT%02d:%02d:%02dZ",
	         timeinfo->tm_year + 1900,
	         timeinfo->tm_mon + 1,
	         timeinfo->tm_mday,
	         timeinfo->tm_hour,
	         timeinfo->tm_min,
	         timeinfo->tm_sec);

	g_debug ("**** date_or_datetime_to_string() dateString: %s", date_string);
}

xmlDocPtr
util_get_kolab_xml_part(const Kolab_conv_mail *klb_mail, GList **other_parts, GError **error)
{
	xmlDocPtr doc = NULL;
	guint i;

	/* check input paramters */
	if (klb_mail == NULL) {
		g_set_error (error, KOLABCONV_ERROR_READ_KOLAB,
		             KOLABCONV_ERROR_READ_KOLAB_INPUT_IS_NULL,
		             _("Kolab mail struct must not be null"));
		return NULL;
	}
	/* iterate over mail parts, search kolab xml part, parse and return it */
	for (i = 0; i < klb_mail->length; i++) {
		Kolab_conv_mail_part *mail_part = klb_mail->mail_parts + i;
		if (g_str_has_prefix(mail_part->mime_type, KOLAB_MESSAGE_MIMETYPE_PREFIX)) {
			/* found kolab xml part => parse it in memory */
			doc = xmlReadMemory (mail_part->data, (int) mail_part->length, NULL, NULL, 0);
			if (doc == NULL) {
				g_set_error (error, KOLABCONV_ERROR_READ_KOLAB,
				             KOLABCONV_ERROR_READ_KOLAB_MALFORMED_XML,
				             _("XML parser returned null for data part %d"), i);
				return NULL;
			}
		} else /* part must be cloned here because it will be freed when converting from internal */
			*other_parts = g_list_append(*other_parts, clone_kolab_conv_mail_part(mail_part));
	}
	if (doc == NULL) /* no kolab xml part could be found */
		g_set_error (error, KOLABCONV_ERROR_READ_KOLAB,
		             KOLABCONV_ERROR_READ_KOLAB_MISSING_XML_PART,
		             _("Kolab mail struct has no Kolab XML data part"));

	return doc;
}

/**
 * Gets the text content of a given XML element node. If the node has no
 * content, an empty string is returned.
 *
 * @param  node
 * @return
 */
gchar*
xml_get_node_text(xmlNodePtr node)
{
	/* get text content form node */
	gchar* value = NULL;
	xmlNodePtr n;
	for (n = node->children; n != NULL; n = n->next) {
		if (n->type == XML_TEXT_NODE && value == NULL)
			value = (gchar *) n->content;
		else
			log_warn("tag %s is not a simple text element as expected", node->name);
	}
	/* if node has no text content set value to empty string to be able to
	 * differentiate between an existing and a not existing element
	 */
	return value == NULL ? "" : value;
}

gboolean
xml_get_boolean(xmlNodePtr node)
{
	gchar *value = xml_get_node_text(node);
	value = trim(value);
	if (g_strcmp0 (value, "true") == 0)
		return TRUE;
	if (g_strcmp0 (value, "false") != 0)
		log_warn("invalid boolean value %s", value);
	return FALSE;
}

/*
 * Handles a GList of Strings. Reads XML and writes GList.
 */
gboolean
handle_element_GList_String (gchar* list_name, gchar* element_name, GList **store_string_list, xmlNodePtr node)
{
	xmlNodePtr element_node = NULL; /* used in for loop */

	if (g_strcmp0 ((gchar *) node->name, list_name) != 0)
		return FALSE;

	for (element_node = node->children; element_node != NULL; element_node = element_node->next) {
		if (element_node->type == XML_ELEMENT_NODE) {
			if (g_strcmp0 ((gchar *) element_node->name, element_name) == 0) {
				GString *value = g_string_new(xml_get_node_text(element_node));
				*store_string_list = g_list_append(*store_string_list, value);
				g_debug ("**** handle_element_GList_String() node: %s - value: '%s'", element_node->name, value->str);
			}
		}
	}
	return TRUE;
}

gboolean
handle_element_boolean (gchar* name, gboolean *store, xmlNodePtr node)
{
	if (g_strcmp0 ((gchar *) node->name, name) != 0)
		return FALSE;

	*store = xml_get_boolean(node);
	return TRUE;
}

gboolean
handle_element_chars (gchar* name, gchar **store, xmlNodePtr node)
{
	if (g_strcmp0 ((gchar *) node->name, name) != 0)
		return FALSE;

	/* GString store is expected NULL. If not, maybe the element
	 * unexpectedly occurs multiple times => warn and reassign
	 */
	if (*store != NULL) {
		log_warn("multiple assignment of string variable");
		g_free(*store);
	}

	/* create gchar with the text content of the node */
	*store = g_strdup_printf("%s", xml_get_node_text(node));
	return TRUE;
}


/**
 * Checks if the given XML element node does match the given name and creates a
 * GString with the text content of a given XML node if the name is matching.
 *
 * @param  name
 * @param  store
 * @param  node
 * @return TRUE if the name of the XML node does match the given name.
 */
gboolean
handle_element_string (gchar* name, GString **store, xmlNodePtr node)
{
	gchar *value = NULL;
	if (!handle_element_chars (name, &value, node))
		return FALSE;

	/* GString store is expected NULL. If not, maybe the element
	 * unexpectedly occurs multiple times => warn and reassign
	 */
	if (*store != NULL) {
		log_warn("multiple assignment of string variable");
		g_string_free(*store, TRUE);
	}

	if (strlen(value) == 0)
		*store = NULL;
	else {
		*store = g_string_new(value);
		g_debug ("**** handle_element_string() node: %s - value: '%s'", node->name, value);
	}

	g_free(value);

	return TRUE;
}

/**
 * Checks if the given XML element node does match the given name and creates an
 * integer by parsing the text content of a given XML node if the name is
 * matching.
 *
 * @param  name
 * @param  store
 * @param  node
 * @return TRUE if the name of the XML node does match the given name.
 */
gboolean
handle_element_integer (gchar* name, gint *store, xmlNodePtr node)
{
	gchar *value = NULL;
	gchar *p = NULL;

	if (g_strcmp0 ((gchar *) node->name, name) != 0)
		return FALSE;

	value = xml_get_node_text(node);
	g_debug ("**** handle_element_integer() node: %s - value: %s", node->name, value);
	errno = 0;
	*store = strtol(value, &p, 10);
	if (errno != 0 || *p != 0 || p == value)
		log_warn("illegal integer value in element %s", name); /* TODO: error handling */
	return TRUE;
}

gboolean
handle_element_double (gchar* name, gdouble *store, xmlNodePtr node)
{
	gchar *value = NULL;
	gchar *p = NULL;

	if (g_strcmp0 ((gchar *) node->name, name) != 0)
		return FALSE;

	value = xml_get_node_text(node);
	g_debug ("**** handle_element_double() node: %s - value: %s", node->name, value);
	errno = 0;
	*store = strtod(value, &p);
	if (errno != 0 || *p != 0 || p == value)
		log_warn("illegal double value in element %s", name); /* TODO error handling */
	return TRUE;
}

gboolean
handle_element_date (gchar* name, GDate** store, xmlNodePtr node)
{
	GDate* date = NULL;
	gchar* value = NULL;
	gchar** date_tokens = NULL;
	gint day = 0, month = 0, year = 0;

	if (g_strcmp0 ((gchar *) node->name, name) != 0)
		return FALSE;

	value = xml_get_node_text(node);
	date_tokens = g_strsplit (value, "-", -1);

	year = strtol(date_tokens[0], NULL, 10);
	month = strtol(date_tokens[1], NULL, 10);
	day = strtol(date_tokens[2], NULL, 10);

	g_strfreev(date_tokens);

	date = g_date_new_dmy ((GDateDay)day, (GDateMonth)month, (GDateYear)year);

	*store = date;

	return TRUE;
}

gboolean
handle_element_date_or_datetime (gchar* name, Date_or_datetime** store, xmlNodePtr node)
{
	gchar *value = NULL;

	if (g_strcmp0 ((gchar *) node->name, name) != 0)
		return FALSE;

	/* store is expected NULL. If not, maybe the element
	 * unexpectedly occurs multiple times => warn and reassign
	 */
	if (*store != NULL)
		log_warn("multiple assignment of string variable");
	else
		*store = g_new0(Date_or_datetime, 1);

	value = xml_get_node_text(node);

	g_debug ("**** handle_element_date_or_datetime() node: %s - value: '%s'", node->name, value);

	if (strlen (value) < 12) {
		gint day = 0, month = 0, year = 0;

		/* Populate only date */
		gchar** date_tokens = g_strsplit (value, "-", -1);
		if (*date_tokens == NULL) {
			*store = NULL;
			return TRUE;
		}

		year = strtol(date_tokens[0], NULL, 10);
		month = strtol(date_tokens[1], NULL, 10);
		day = strtol(date_tokens[2], NULL, 10);

		(*store)->date = g_date_new_dmy ((GDateDay)day, (GDateMonth)month, (GDateYear)year);
		(*store)->date_time = NULL;

		g_strfreev(date_tokens);

	} else {
		struct tm t;
		time_t tt;

		/* convert string to 'struct tm' */
		if (value[10] == 'T') /* remove 'T' in iso timestamp if neccessary */
			value[10] = ' ';

		strptime(value, "%Y-%m-%d %H:%M:%S", &t); /* string to tm */

		tt = time_gm(&t); /* tm to seconds */

		g_debug ("**** handle_element_date_or_datetime() tt: '%lu'", tt);

		(*store)->date_time = g_new0(time_t, 1);
		*((*store)->date_time) = tt;
		(*store)->date = NULL;
	}

	return TRUE;
}



gboolean
handle_element_datetime (gchar* name, time_t** store, xmlNodePtr node)
{
	gchar *value = NULL;
	struct tm t;

	if (g_strcmp0 ((gchar *) node->name, name) != 0)
		return FALSE;

	/* store is expected NULL. If not, maybe the element
	 * unexpectedly occurs multiple times => warn and reassign
	 */
	if (*store != NULL)
		log_warn("multiple assignment of string variable");
	else
		*store = g_new0(time_t, 1);

	value = xml_get_node_text(node);

	/* convert string to 'struct tm' */
	if (value[10] == 'T') /* remove 'T' in iso timestamp if neccessary */
		value[10] = ' ';

	strptime(value, "%Y-%m-%d %H:%M:%S", &t); /* string to tm */

	**store = time_gm(&t); /* tm to seconds */

	return TRUE;
}

/* XML-TREE manipulation functions */

/*
 * converts a GList of strings to xml nodes with the same name below one node
 */
void convert_GList_String_to_xmlNodes (GList *string_list, char* node_name, xmlNodePtr node)
{
	GList *iterator;
	iterator = string_list;

	while(iterator != NULL){
		gchar *element = ((gchar*)string_list->data);

		add_child_node(node, node_name, element);

		iterator = iterator->next;
	}
	g_list_free(iterator);
}

gchar*
convert_xmlNode_to_String (const xmlNodePtr node)
{
	xmlDocPtr doc = NULL;
	xmlChar *xmlbuff = NULL;
	gint buffersize;
	gchar *xmlStr = NULL;

	doc = xmlNewDoc(BAD_CAST "1.0");

	xmlDocSetRootElement (doc, xmlCopyNode (node, 1));

	xmlDocDumpFormatMemoryEnc (doc, &xmlbuff, &buffersize, "UTF-8", 1);
	xmlStr = g_strdup((gchar*) xmlbuff);

	xmlFree (xmlbuff);
	xmlFreeDoc (doc);

	return xmlStr;

}




/**
 * Logs the xml part of a kolab mail struct for debugging
 *
 * @param  kolab_mail
 *         A kolab mail struct which xml attachment will be logged
 */
void
log_xml_part (Kolab_conv_mail *kolab_mail)
{
	gchar* xml = NULL;
	if (kolab_mail) {
		guint i;
		for (i = 0; i < kolab_mail->length; i++)
			if (g_str_has_prefix(kolab_mail->mail_parts[i].mime_type, KOLAB_MESSAGE_MIMETYPE_PREFIX))
				xml =  kolab_mail->mail_parts[i].data;
		g_debug("Kolab XML:\n%s", xml);
	}
}

void
printKolabXmlMailPartArray(const GList* kolabconvMailArray)
{

	const GList *iterator = kolabconvMailArray;
	gint i=1;

	log_debug("\n*********************Printing kolabXmlmailArray:***************************");

	while(iterator != NULL){
		log_debug("\n\nFile no. %d", i);
		/* log_debug("\n\tOrder: %d", ((Kolab_conv_mail_part*)iterator->data)->order  ); */
		log_debug("\n\tMimeType: %s", ((Kolab_conv_mail_part*)iterator->data)->mime_type  );
		log_debug("\n\tName: %s", ((Kolab_conv_mail_part*)iterator->data)->name  );
		log_debug("\n\tLength: %d", ((Kolab_conv_mail_part*)iterator->data)->length  );
		if( strcmp(((Kolab_conv_mail_part*)iterator->data)->mime_type, "application/x-vnd.kolab.contact") == 0)
			log_debug("\n\tXML: %s", ((Kolab_conv_mail_part*)iterator->data)->data  );


		iterator = iterator->next;
		i++;
	}
	log_debug("\n***************************************************************************\n");

}

void
printXMLTree (xmlNodePtr node, int depth)
{
	xmlNodePtr children;
	gint i = 0;

	log_debug ("\n");
	for (i = 0; i < depth; i++) {
		log_debug ("\t");
	}

	log_debug ("Node-name: %s, type=%d", node->name, node->type);
	if (node->content != NULL)
		log_debug (" & Node-content: %s", node->content);

	for (children = node->children; children != NULL; children = children->next) {
		printXMLTree (children, depth + 1);
	}
}




xmlNodePtr
add_child_node (xmlNodePtr root_node, gchar *child_name, gchar *child_contents)
{
	/* xmlNewTextChild() implicitly converts reserved XML characters (e.g. '&') to entities */
	return xmlNewTextChild (root_node, NULL, BAD_CAST child_name, BAD_CAST child_contents);
}

void
add_property (xmlNodePtr node, gchar *propertyName, gchar *propertyContents)
{
	xmlNewProp (node, BAD_CAST propertyName, BAD_CAST propertyContents);
}


/**
 * This formats a UTC offset as "+HHMM" or "+HHMMSS".
 * buffer should have space for 8 characters.
 * (copied form libical/src/libical/icaltimezone.c)
 */
void
format_utc_offset (gint utc_offset, gchar *buffer)
{
	const gchar *sign = "+";
	gint hours, minutes, seconds;

	if (utc_offset < 0) {
		utc_offset = -utc_offset;
		sign = "-";
	}

	hours = utc_offset / 3600;
	minutes = (utc_offset % 3600) / 60;
	seconds = utc_offset % 60;

	/* Sanity check. Standard timezone offsets shouldn't be much more than 12
	   hours, and daylight saving shouldn't change it by more than a few hours.
	   (The maximum offset is 15 hours 56 minutes at present.) */
	if (hours < 0 || hours >= 24 || minutes < 0 || minutes >= 60 || seconds < 0 || seconds
	    >= 60) {
		log_warn ("Warning: Strange timezone offset: H:%i M:%i S:%i\n", hours,
		          minutes, seconds);
	}
	if (seconds == 0)
		snprintf (buffer, 8, "%s%02i%02i", sign, hours, minutes);
	else
		snprintf (buffer, 8, "%s%02i%02i%02i", sign, hours, minutes, seconds);
}


/**
 * @param  out
 *         size must be 21 if is_utc is true or 20 otherwise
 */
void
k_write_datetime(struct tm *in, gchar *out, gboolean is_utc)
{
	sprintf(out, is_utc ?
	        "%02d-%02d-%04dT%02d:%02d:%02dZ" :
	        "%02d-%02d-%04dT%02d:%02d:%02d",
	        in->tm_mday, in->tm_mon, in->tm_year,
	        in->tm_hour, in->tm_min, in->tm_sec);
}

/**
 * Maps normal kolab priority to advanced Kontact/Evolution priority with 10 instead of 6 specified values.
 */
gint priority_k_to_xkcal(gint k_prio)
{
	switch (k_prio) {
	case 1:  return 1;
	case 2:  return 3;
	case 3:  return 5;
	case 4:  return 7;
	case 5:  return 9;
	default: return 0;
	}
}

/**
 * Convert detailed priority to simple kolab priority, like kontact does
 */
gint priority_xkcal_to_k(gint xkcal_prio) {
	switch (xkcal_prio) {
	case 0:  return 3;
	case 1:  return 1;
	case 2:  return 1;
	case 3:  return 2;
	case 4:  return 2;
	case 5:  return 3;
	case 6:  return 3;
	case 7:  return 4;
	case 8:  return 4;
	case 9:  return 5;
	default: return 3;
	}
}
