/*
 * vcalical - An vcal/ical converter 
 * Copyright (C) 2006  Daniel Gollub <dgollub@suse.de>
 * Copyright (C) 2006  Christopher Stender <cstender@suse.de>
 * 
 * This library 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 library 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 library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 * 
 */
 
#include "xml-support.h"
#include "vformat.h"
#include "xml-vcal.h"
#include <glib.h>


/* ical 2 vcal */
#define ATTR_COUNT (sizeof(rrule_attr)/sizeof(rrule_attr[0]))
#define PARAM_COUNT (sizeof(rrule_param)/sizeof(rrule_param[0]))

enum {
	FIELD_FREQ,
	FIELD_INTERVAL,
	FIELD_FREQMOD,
	FIELD_FREQMOD2,
	FIELD_COUNTUNTIL,
	NUM_OF_FIELDS
};

enum {
	TYPE_ATTR,
	TYPE_PARAM
};

struct _rrule_attr {
	const char *ical;
	const char *vcal;
	int field;
} rrule_attr[] = {
	{ "BYDAY",  " ", FIELD_FREQMOD },
	{ "BYMONTH", " ", FIELD_FREQMOD },
	{ "BYMONTHDAY", " ", FIELD_FREQMOD },
	{ "BYYEARDAY", " ", FIELD_FREQMOD },
	{ "COUNT", " #", FIELD_COUNTUNTIL },
	{ "FREQ", "", FIELD_FREQ },
	{ "INTERVAL", "", FIELD_INTERVAL },
	{ "UNTIL", " ", FIELD_COUNTUNTIL }
};

struct _rrule_param {
	const char *ical;
	const char *vcal;
} rrule_param[] = {
	{ "DAILY", "D" },
	{ "MONTHLY", "M" },
	{ "WEEKLY", "W" },
	{ "YEARLY", "YM" }
};

static int comp_attr(const void *m1, const void *m2) {
	struct _rrule_attr *mi1 = (struct _rrule_attr *) m1;
	struct _rrule_attr *mi2 = (struct _rrule_attr *) m2;
	return strcmp(mi1->ical, mi2->ical);
}
static int comp_param(const void *m1, const void *m2) {
	struct _rrule_param *mi1 = (struct _rrule_param *) m1;
	struct _rrule_param *mi2 = (struct _rrule_param *) m2;
	return strcmp(mi1->ical, mi2->ical);
}	

struct _rrule_attr *_parse_rrule_attr(const char *ical) {

	struct _rrule_attr key, *res;
	key.ical = ical;

	res = bsearch(&key, rrule_attr, ATTR_COUNT, sizeof(struct _rrule_attr), comp_attr);

	if (!res)
		return NULL;

	return res;
}

const char *_parse_rrule_param(const char *ical) {

	struct _rrule_param key, *res;
	const char *ret = NULL;

	key.ical = ical;
	
	res = bsearch(&key, rrule_param, PARAM_COUNT, sizeof(struct _rrule_param), comp_param); 

	if (!res)
		ret = ical;
	else
		ret = res->vcal;

	return ret;
}

char *_blank_field(char *field) {
	if (field)
		g_free(field);

	return g_strdup("");
}



char *_adapt_param(const char *param) {

	int i, len;
	GString *ret = g_string_new("");

	len = strlen(param);

	for (i=0; i < len; i++) {
		switch(param[i]) {
			// evil sperators like ','
			case ',':
				ret = g_string_append_c(ret, ' ');
				break;
			default:	
				ret = g_string_append_c(ret,param[i]);
		}
	}

	return g_string_free(ret, FALSE);
}

void _vcal_hook(char **icalattrs, char **vcalattrs, char **icalparams, char **vcalparams) {
	
	if (!strcmp(icalparams[FIELD_FREQ], "MONTHLY")) {
		// Workround for RRULE:MP1 1+ SU 20071003T193000
		 if(!strcmp(icalattrs[FIELD_FREQMOD], "BYDAY")) {
			char sign = '+';
			char wday[3];
			int nthday;

			g_free(vcalparams[FIELD_FREQ]);
			vcalparams[FIELD_FREQ] = g_strdup("MP");

			g_free(vcalparams[FIELD_FREQMOD]);
			
			if (strlen(icalparams[FIELD_FREQMOD]) > 3)
				sscanf(icalparams[FIELD_FREQMOD], "%c%d%c%c", &sign, &nthday, &wday[0], &wday[1]);
			else	
				sscanf(icalparams[FIELD_FREQMOD], "%d%c%c", &nthday, &wday[0], &wday[1]);

			wday[2] = '\0';

			vcalparams[FIELD_FREQMOD] = g_strdup_printf("%d%c %s", nthday, sign, wday);

		// Workaround for RRULE:MD1 .......
//		} else if (!strcmp(icalattrs[FIELD_FREQMOD], "BYMONTHDAY")) {
		} else {	
			g_free(vcalparams[FIELD_FREQ]);
			vcalparams[FIELD_FREQ] = g_strdup("MD");
		}
	}	
	
	if (!strcmp(icalparams[FIELD_FREQ], "YEARLY") && icalparams[FIELD_FREQMOD]) {
		if (!strcmp(icalattrs[FIELD_FREQMOD], "BYYEARDAY")) {
			g_free(vcalparams[FIELD_FREQ]);
			vcalparams[FIELD_FREQ] = g_strdup("YD");
		} else if ((!strcmp(icalattrs[FIELD_FREQMOD], "BYMONTH") && !strcmp(icalattrs[FIELD_FREQMOD2], "BYMONTHDAY"))
			|| (!strcmp(icalattrs[FIELD_FREQMOD2], "BYMONTH") && !strcmp(icalattrs[FIELD_FREQMOD], "BYMONTHDAY"))) {

			g_free(vcalparams[FIELD_FREQ]);
			vcalparams[FIELD_FREQ] = g_strdup("YM");

			vcalattrs[FIELD_FREQMOD] = _blank_field(vcalattrs[FIELD_FREQMOD]);
			vcalattrs[FIELD_FREQMOD2] = _blank_field(vcalattrs[FIELD_FREQMOD2]);
			vcalparams[FIELD_FREQMOD] = _blank_field(vcalparams[FIELD_FREQMOD]);
			vcalparams[FIELD_FREQMOD2] = _blank_field(vcalparams[FIELD_FREQMOD2]);
		}
	}

	// Set INTERVAL to 1 if nothing is set and BYMONTHDAY is not used
	if (icalparams[FIELD_INTERVAL] ==  NULL) {
		vcalparams[FIELD_INTERVAL] = g_strdup("1");
	}
}

char *conv_ical2vcal_rrule(const char *ical) {

	osync_trace(TRACE_ENTRY, "%s(%s)", __func__, ical);

        int i;
        const char *pos, *prev;
        size_t len;
	char *icalattrs[NUM_OF_FIELDS] = { NULL };
	char *vcalattrs[NUM_OF_FIELDS] = { NULL };
	char *icalparams[NUM_OF_FIELDS] = { NULL };
	char *vcalparams[NUM_OF_FIELDS] = { NULL };
	struct _rrule_attr *field_attr;
	const char *tmp = NULL;

	GString *vcal10 = g_string_new("");

        pos = prev = ical;

        // FREQ=WEEKLY;INTERVAL=1;BYDAY=TU,FR;UNTIL=20060901T182200Z
        // W1 TU FR 20060901T182200Z
	// *FREQ**INTERVAL* *FREQ-MOD* *COUNT/UNTIL*

        while ((pos = strstr(pos, "="))) {

		GString *attr = g_string_new("");
		GString *param = g_string_new("");

                len = pos - prev;

		// not equal is required ... ignoring = 
                for (i=0; i < len; i++)
			attr = g_string_append_c(attr, prev[i]);

		pos++;
		prev = pos;

                pos = strstr(pos, ";");
		if (pos == NULL)
			pos = ical + strlen(ical);

                len = pos - prev;
                for (i=0; i < len; i++)
			param = g_string_append_c(param, prev[i]);

		field_attr = _parse_rrule_attr(attr->str);
		if (field_attr == NULL)
			goto next;

		if (icalattrs[field_attr->field] && field_attr->field == FIELD_FREQMOD)
			field_attr->field += 1;

		vcalattrs[field_attr->field] = g_strdup(field_attr->vcal);
		icalattrs[field_attr->field] = g_strdup(attr->str);

		tmp = _parse_rrule_param(param->str);
		if (tmp)
			vcalparams[field_attr->field] = _adapt_param(tmp);
		else
			vcalparams[field_attr->field] = g_strdup(""); 
		icalparams[field_attr->field] = g_strdup(param->str);

		g_string_free(attr, TRUE);
		g_string_free(param, TRUE);
next:

		prev = pos + 1;

        }

	for (i=0; i < NUM_OF_FIELDS; i++) {
		if (!vcalparams[i])
			vcalparams[i] = g_strdup("");
		if (!vcalattrs[i])
			vcalattrs[i] = g_strdup("");
		if (!vcalparams[i])
			vcalparams[i] = g_strdup("");
		if (!icalattrs[i])
			icalattrs[i] = g_strdup("");
	}

	_vcal_hook(icalattrs, vcalattrs, icalparams, vcalparams);

	for (i=0; i < NUM_OF_FIELDS; i++) {
		// If no end is set append #0 - recurrence for ever
		if (i == FIELD_COUNTUNTIL && strlen(vcalparams[i]) == 0)
			vcalparams[i] = g_strdup(" #0"); 

		if (vcalattrs[i]) {
			vcal10 = g_string_append(vcal10, vcalattrs[i]);
//			printf("(%i) \"%s\"\n", i, vcalattrs[i]);
			g_free(vcalattrs[i]);
		}
		
		if (vcalparams[i]) {
			vcal10 = g_string_append(vcal10, vcalparams[i]);
//			printf("(#%i) \"%s\"\n", i, vcalparams[i]);
			g_free(vcalparams[i]);
		}

		if (icalattrs[i])
			g_free(icalattrs[i]);

		if (icalparams[i])
			g_free(icalparams[i]);

	}

	osync_trace(TRACE_EXIT, "%s: %s", __func__, vcal10->str);
	return g_string_free(vcal10, FALSE);
}

/* vcal 2 ical */
GList *conv_vcal2ical_rrule(const char *vcal) {

	osync_trace(TRACE_ENTRY, "%s(%s)", __func__, vcal);

	gchar** blocks = g_strsplit(vcal, " ", 256);
	int offset = 0;

	int frequency_state = 0;
	char *frequency = NULL;
	char *frequency_block = NULL;

	int interval;

	int duration_number = -1;
	char *duration_block;

	char* freq_mod = NULL;	


	/* count blocks */
	int counter; 
	for(counter=0; blocks[counter]; counter++);

	frequency_block = blocks[0];
	duration_block = blocks[counter-1];

		
	/* get frequency: only D(1), W(2), MP(3), MD(4), YD(5) and YM(6) is allowed */
	switch (*frequency_block++) {
		case 'D': frequency_state = 1; frequency = "DAILY"; break;
		case 'W': frequency_state = 2; frequency = "WEEKLY"; break;
		case 'M': frequency_state = 0;
			switch (*frequency_block++) {
				case 'P': frequency_state = 3; frequency = "MONTHLY"; break;
				case 'D': frequency_state = 4; frequency = "MONTHLY"; break;
				default:
					osync_trace(TRACE_INTERNAL, "invalid frequency M<X>");
			}
		break;	  
		case 'Y': frequency_state = 0;
			switch (*frequency_block++) {
				case 'D': frequency_state = 5; frequency = "YEARLY"; break; 
				case 'M': frequency_state = 6; frequency = "YEARLY"; break;
				default:
					osync_trace(TRACE_INTERNAL, "invalid frequency Y<X>");
			}
		break;
		default:
			osync_trace(TRACE_INTERNAL, "invalid or missing frequency");
	}


	/* get interval (integer) */
	char* e;
	interval = strtol(frequency_block, &e, 10);
	if (e == frequency_block) {
		osync_trace(TRACE_INTERNAL, "interval is missing.");
	}
	if (*e != 0) {
		osync_trace(TRACE_INTERNAL, "interval is to long.");
	}


	/* get frequency modifier if there are more than two blocks */
	if (counter > 2) {

		GString *fm_buffer = g_string_new("");
		int i;
	
		/* for each modifier do... */
		for(i=1; i < counter-1; i++) {

			int count;
			char sign;

			if(fm_buffer->len > 0)
				g_string_append(fm_buffer, ",");

			/* check frequency modifier */
			if (sscanf(blocks[i], "%d%c" , &count, &sign) == 2) {
				
				/* we need to convert $COUNT- to -$COUNT -> RFC2445 */
				if (sign == '-')
					count = -count;

				g_string_append_printf(fm_buffer, "%d", count);

				if (i < counter-2 && !sscanf(blocks[i+1], "%d", &count)) {
					
					g_string_append_printf(fm_buffer, " %s", blocks[i+1]);
					i++;
					
				}
				
			} else {
				
				/* e.g. Day or 'LD' (Last day) */
				g_string_append(fm_buffer, blocks[i]);
				
			}
		}

		freq_mod = fm_buffer->str;
		g_string_free(fm_buffer, FALSE);
	}

	char *until = NULL;

	/* get duration (number OR timestamp, but nothing is required) */
	if (sscanf(duration_block, "#%d", &duration_number) < 1) {
		if (!osync_time_isdate(duration_block)) {

			/* Check if this duration_block is a localtime timestamp.
			 * If it is not UTC change the offset from 0 to the system UTC offset. 
			 * vcal doesn't store any TZ information. This means the device have to be
			 * in the same Timezone as the host.
			 */
			if (!osync_time_isutc(duration_block)) {
				struct tm *ttm = osync_time_vtime2tm(duration_block);
				offset = osync_time_timezone_diff(ttm); 
				g_free(ttm);
			}

			until = osync_time_vtime2utc(duration_block, offset);
		} else {
			until = g_strdup(duration_block);
		}
	}		

	g_strfreev(blocks);


	/* generate new RRULE: D(1), W(2), MP(3), MD(4), YD(5) and YM(6) */
	GList *new_rrule = NULL; 

	new_rrule = g_list_append(new_rrule, g_strdup_printf("FREQ=%s", frequency));
	new_rrule = g_list_append(new_rrule, g_strdup_printf("INTERVAL=%d", interval));

	if (duration_number > 0)
		new_rrule = g_list_append(new_rrule, g_strdup_printf("COUNT=%d", duration_number));

	if(freq_mod != NULL) {
		switch(frequency_state) {
			case 2:	new_rrule = g_list_append(new_rrule, g_strdup_printf("BYDAY=%s", freq_mod)); break;
			case 3:	new_rrule = g_list_append(new_rrule, g_strdup_printf("BYDAY=%s", freq_mod)); break;
			case 4:	new_rrule = g_list_append(new_rrule, g_strdup_printf("BYMONTHDAY=%s", freq_mod)); break;
			case 5:	new_rrule = g_list_append(new_rrule, g_strdup_printf("BYYEARDAY=%s", freq_mod)); break;
			case 6:	new_rrule = g_list_append(new_rrule, g_strdup_printf("BYMONTH=%s", freq_mod)); break;
			default:
				break;	
		}
	}

	if (until != NULL) {
		new_rrule = g_list_append(new_rrule, g_strdup_printf("UNTIL=%s", until));	
		g_free(until);
	}

	osync_trace(TRACE_EXIT, "%s", __func__);

	return new_rrule;
}





syntax highlighted by Code2HTML, v. 0.9.1