icalcomponent* icalmime_parse(char* (*get_string)(char *s, size_t size, 
						       void *d),
				void *data)
{
    struct sspm_part *parts;
    int i, last_level=0;
    icalcomponent *root=0, *parent=0, *comp=0, *last = 0;

    if ( (parts = (struct sspm_part *)
	  malloc(NUM_PARTS*sizeof(struct sspm_part)))==0) {
	icalerror_set_errno(ICAL_NEWFAILED_ERROR);
	return 0;
    }

    memset(parts,0,sizeof(parts));

    sspm_parse_mime(parts, 
		    NUM_PARTS, /* Max parts */
		    icalmime_local_action_map, /* Actions */ 
		    get_string,
		    data, /* data for get_string*/
		    0 /* First header */);



    for(i = 0; i <NUM_PARTS && parts[i].header.major != SSPM_NO_MAJOR_TYPE ; i++){

#define TMPSZ 1024
	char mimetype[TMPSZ];			       
	const char* major = sspm_major_type_string(parts[i].header.major);
	const char* minor = sspm_minor_type_string(parts[i].header.minor);

	if(parts[i].header.minor == SSPM_UNKNOWN_MINOR_TYPE ){
	    assert(parts[i].header.minor_text !=0);
	    minor = parts[i].header.minor_text;
	}
	
	snprintf(mimetype,sizeof(mimetype),"%s/%s",major,minor);

	comp = icalcomponent_new(ICAL_XLICMIMEPART_COMPONENT);

	if(comp == 0){
	    /* HACK Handle Error */
	    assert(0);
	}

	if(parts[i].header.error!=SSPM_NO_ERROR){
	    const char *str="Unknown error";
	    char temp[256];
	    if(parts[i].header.error==SSPM_MALFORMED_HEADER_ERROR){
		str = "Malformed header, possibly due to input not in MIME format";
	    }

	    if(parts[i].header.error==SSPM_UNEXPECTED_BOUNDARY_ERROR){
		str = "Got an unexpected boundary, possibly due to a MIME header for a MULTIPART part that is missing the Content-Type line";
	    }

	    if(parts[i].header.error==SSPM_WRONG_BOUNDARY_ERROR){
		str = "Got the wrong boundary for the opening of a MULTIPART part.";
	    }

	    if(parts[i].header.error==SSPM_NO_BOUNDARY_ERROR){
		str = "Got a multipart header that did not specify a boundary";
	    }

	    if(parts[i].header.error==SSPM_NO_HEADER_ERROR){
		str = "Did not get a header for the part. Is there a blank\
line between the header and the previous boundary\?";

	    }

	    if(parts[i].header.error_text != 0){
		snprintf(temp,256,
			 "%s: %s",str,parts[i].header.error_text);
	    } else {
		strcpy(temp,str);
	    }

	    icalcomponent_add_property
		(comp,
		 icalproperty_vanew_xlicerror(
		     temp,
		     icalparameter_new_xlicerrortype(
			 ICAL_XLICERRORTYPE_MIMEPARSEERROR),
		     0));  
	}

	if(parts[i].header.major != SSPM_NO_MAJOR_TYPE &&
	   parts[i].header.major != SSPM_UNKNOWN_MAJOR_TYPE){

	    icalcomponent_add_property(comp,
		icalproperty_new_xlicmimecontenttype((char*)
				icalmemory_strdup(mimetype)));

	}

	if (parts[i].header.encoding != SSPM_NO_ENCODING){

	    icalcomponent_add_property(comp,
	       icalproperty_new_xlicmimeencoding(
		   sspm_encoding_string(parts[i].header.encoding)));
	}

	if (parts[i].header.filename != 0){
	    icalcomponent_add_property(comp,
	       icalproperty_new_xlicmimefilename(parts[i].header.filename));
	}

	if (parts[i].header.content_id != 0){
	    icalcomponent_add_property(comp,
	       icalproperty_new_xlicmimecid(parts[i].header.content_id));
	}

	if (parts[i].header.charset != 0){
	    icalcomponent_add_property(comp,
	       icalproperty_new_xlicmimecharset(parts[i].header.charset));
	}

	/* Add iCal components as children of the component */
	if(parts[i].header.major == SSPM_TEXT_MAJOR_TYPE &&
	   parts[i].header.minor == SSPM_CALENDAR_MINOR_TYPE &&
	   parts[i].data != 0){

	    icalcomponent_add_component(comp,
					(icalcomponent*)parts[i].data);
	    parts[i].data = 0;

	} else 	if(parts[i].header.major == SSPM_TEXT_MAJOR_TYPE &&
	   parts[i].header.minor != SSPM_CALENDAR_MINOR_TYPE &&
	   parts[i].data != 0){

	    /* Add other text components as "DESCRIPTION" properties */

	    icalcomponent_add_property(comp,
               icalproperty_new_description(
		   (char*)icalmemory_strdup((char*)parts[i].data)));

	    parts[i].data = 0;
	}
	

	if(root!= 0 && parts[i].level == 0){
	    /* We've already assigned the root, but there is another
               part at the root level. This is probably a parse
               error*/
	    icalcomponent_free(comp);
	    continue;
	}

	if(parts[i].level == last_level && last_level != 0){
	    icalerror_assert(parent!=0,"No parent for adding component");

	    icalcomponent_add_component(parent,comp);

	} else if (parts[i].level == last_level && last_level == 0 &&
	    root == 0) {

	    root = comp;
	    parent = comp;

	} else if (parts[i].level > last_level){	    

	    parent = last;
	    icalcomponent_add_component(parent,comp);

	    last_level = parts[i].level;

	} else if (parts[i].level < last_level){

	    if (parent) 
	        parent = icalcomponent_get_parent(parent);
	    icalcomponent_add_component(parent,comp);

	    last_level = parts[i].level;
	} else { 
	    assert(0);
	}

	last = comp;
	last_level = parts[i].level;
	assert(parts[i].data == 0);
    }

    sspm_free_parts(parts,NUM_PARTS);
    free(parts);

    return root;
}
/* Calculates the UTC offset of a given local time in the given
   timezone.  It is the number of seconds to add to UTC to get local
   time.  The is_daylight flag is set to 1 if the time is in
   daylight-savings time. */
int
icaltimezone_get_utc_offset		(icaltimezone	*zone,
					 struct icaltimetype	*tt,
					 int		*is_daylight)
{
    icaltimezonechange *zone_change, *prev_zone_change, tt_change, tmp_change;
    int change_num, step, utc_offset_change, cmp;
    int change_num_to_use;
    int want_daylight;

    if (tt == NULL)
	return 0;

    if (is_daylight)
	*is_daylight = 0;

    /* For local times and UTC return 0. */
    if (zone == NULL || zone == &utc_timezone)
	return 0;

    /* Use the builtin icaltimezone if possible. */
    if (zone->builtin_timezone)
	zone = zone->builtin_timezone;

    /* Make sure the changes array is expanded up to the given time. */
    icaltimezone_ensure_coverage (zone, tt->year);

    if (!zone->changes || zone->changes->num_elements == 0)
	return 0;

    /* Copy the time parts of the icaltimetype to an icaltimezonechange so we
       can use our comparison function on it. */
    tt_change.year   = tt->year;
    tt_change.month  = tt->month;
    tt_change.day    = tt->day;
    tt_change.hour   = tt->hour;
    tt_change.minute = tt->minute;
    tt_change.second = tt->second;

    /* This should find a change close to the time, either the change before
       it or the change after it. */
    change_num = icaltimezone_find_nearby_change (zone, &tt_change);

    /* Sanity check. */
    icalerror_assert (change_num >= 0,
		      "Negative timezone change index");
    icalerror_assert (change_num < zone->changes->num_elements,
		      "Timezone change index out of bounds");

    /* Now move backwards or forwards to find the timezone change that applies
       to tt. It should only have to do 1 or 2 steps. */
    zone_change = icalarray_element_at (zone->changes, change_num);
    step = 1;
    change_num_to_use = -1;
    for (;;) {
	/* Copy the change, so we can adjust it. */
	tmp_change = *zone_change;

	/* If the clock is going backward, check if it is in the region of time
	   that is used twice. If it is, use the change with the daylight
	   setting which matches tt, or use standard if we don't know. */
	if (tmp_change.utc_offset < tmp_change.prev_utc_offset) {
	    /* If the time change is at 2:00AM local time and the clock is
	       going back to 1:00AM we adjust the change to 1:00AM. We may
	       have the wrong change but we'll figure that out later. */
	    icaltimezone_adjust_change (&tmp_change, 0, 0, 0,
					tmp_change.utc_offset);
	} else {
	    icaltimezone_adjust_change (&tmp_change, 0, 0, 0,
					tmp_change.prev_utc_offset);
	}

	cmp = icaltimezone_compare_change_fn (&tt_change, &tmp_change);

	/* If the given time is on or after this change, then this change may
	   apply, but we continue as a later change may be the right one.
	   If the given time is before this change, then if we have already
	   found a change which applies we can use that, else we need to step
	   backwards. */
	if (cmp >= 0)
	    change_num_to_use = change_num;
	else
	    step = -1;

	/* If we are stepping backwards through the changes and we have found
	   a change that applies, then we know this is the change to use so
	   we exit the loop. */
	if (step == -1 && change_num_to_use != -1)
	    break;

	change_num += step;

	/* If we go past the start of the changes array, then we have no data
	   for this time so we return a UTC offset of 0. */
	if (change_num < 0)
	    return 0;

	if (change_num >= zone->changes->num_elements)
	    break;

	zone_change = icalarray_element_at (zone->changes, change_num);
    }

    /* If we didn't find a change to use, then we have a bug! */
    icalerror_assert (change_num_to_use != -1,
		      "No applicable timezone change found");

    /* Now we just need to check if the time is in the overlapped region of
       time when clocks go back. */
    zone_change = icalarray_element_at (zone->changes, change_num_to_use);

    utc_offset_change = zone_change->utc_offset - zone_change->prev_utc_offset;
    if (utc_offset_change < 0 && change_num_to_use > 0) {
	tmp_change = *zone_change;
	icaltimezone_adjust_change (&tmp_change, 0, 0, 0,
				    tmp_change.prev_utc_offset);

	if (icaltimezone_compare_change_fn (&tt_change, &tmp_change) < 0) {
	    /* The time is in the overlapped region, so we may need to use
	       either the current zone_change or the previous one. If the
	       time has the is_daylight field set we use the matching change,
	       else we use the change with standard time. */
	    prev_zone_change = icalarray_element_at (zone->changes,
						     change_num_to_use - 1);

	    /* I was going to add an is_daylight flag to struct icaltimetype,
	       but iCalendar doesn't let us distinguish between standard and
	       daylight time anyway, so there's no point. So we just use the
	       standard time instead. */
	    want_daylight = (tt->is_daylight == 1) ? 1 : 0;

#if 0
	    if (zone_change->is_daylight == prev_zone_change->is_daylight)
		printf (" **** Same is_daylight setting\n");
#endif

	    if (zone_change->is_daylight != want_daylight
		&& prev_zone_change->is_daylight == want_daylight)
		zone_change = prev_zone_change;
	}
    }

    /* Now we know exactly which timezone change applies to the time, so
       we can return the UTC offset and whether it is a daylight time. */
    if (is_daylight)
	*is_daylight = zone_change->is_daylight;
    return zone_change->utc_offset;
}
/** Calculates the UTC offset of a given UTC time in the given
   timezone.  It is the number of seconds to add to UTC to get local
   time.  The is_daylight flag is set to 1 if the time is in
   daylight-savings time. */
int
icaltimezone_get_utc_offset_of_utc_time	(icaltimezone	*zone,
					 struct icaltimetype	*tt,
					 int		*is_daylight)
{
    icaltimezonechange *zone_change, tt_change, tmp_change;
    int change_num, step, change_num_to_use;

    if (is_daylight)
	*is_daylight = 0;

    /* For local times and UTC return 0. */
    if (zone == NULL || zone == &utc_timezone)
	return 0;

    /* Use the builtin icaltimezone if possible. */
    if (zone->builtin_timezone)
	zone = zone->builtin_timezone;

    /* Make sure the changes array is expanded up to the given time. */
    icaltimezone_ensure_coverage (zone, tt->year);

    if (!zone->changes || zone->changes->num_elements == 0)
	return 0;

    /* Copy the time parts of the icaltimetype to an icaltimezonechange so we
       can use our comparison function on it. */
    tt_change.year   = tt->year;
    tt_change.month  = tt->month;
    tt_change.day    = tt->day;
    tt_change.hour   = tt->hour;
    tt_change.minute = tt->minute;
    tt_change.second = tt->second;

    /* This should find a change close to the time, either the change before
       it or the change after it. */
    change_num = icaltimezone_find_nearby_change (zone, &tt_change);

    /* Sanity check. */
    icalerror_assert (change_num >= 0,
		      "Negative timezone change index");
    icalerror_assert (change_num < zone->changes->num_elements,
		      "Timezone change index out of bounds");

    /* Now move backwards or forwards to find the timezone change that applies
       to tt. It should only have to do 1 or 2 steps. */
    zone_change = icalarray_element_at (zone->changes, change_num);
    step = 1;
    change_num_to_use = -1;
    for (;;) {
	/* Copy the change and adjust it to UTC. */
	tmp_change = *zone_change;

	/* If the given time is on or after this change, then this change may
	   apply, but we continue as a later change may be the right one.
	   If the given time is before this change, then if we have already
	   found a change which applies we can use that, else we need to step
	   backwards. */
	if (icaltimezone_compare_change_fn (&tt_change, &tmp_change) >= 0)
	    change_num_to_use = change_num;
	else
	    step = -1;

	/* If we are stepping backwards through the changes and we have found
	   a change that applies, then we know this is the change to use so
	   we exit the loop. */
	if (step == -1 && change_num_to_use != -1)
	    break;

	change_num += step;

	/* If we go past the start of the changes array, then we have no data
	   for this time so we return a UTC offset of 0. */
	if (change_num < 0)
	    return 0;

	if (change_num >= zone->changes->num_elements)
	    break;

	zone_change = icalarray_element_at (zone->changes, change_num);
    }

    /* If we didn't find a change to use, then we have a bug! */
    icalerror_assert (change_num_to_use != -1,
		      "No applicable timezone change found");

    /* Now we know exactly which timezone change applies to the time, so
       we can return the UTC offset and whether it is a daylight time. */
    zone_change = icalarray_element_at (zone->changes, change_num_to_use);
    if (is_daylight)
	*is_daylight = zone_change->is_daylight;

    return zone_change->utc_offset;
}
/** This parses the zones.tab file containing the names and locations
   of the builtin timezones. It creates the builtin_timezones array
   which is an icalarray of icaltimezone structs. It only fills in the
   location, latitude and longtude fields; the rest are left
   blank. The VTIMEZONE component is loaded later if it is needed. The
   timezones in the zones.tab file are sorted by their name, which is
   useful for binary searches. */
static void
icaltimezone_parse_zone_tab		(void)
{
    char *filename;
    FILE *fp;
    char buf[1024];  /* Used to store each line of zones.tab as it is read. */
    char location[1024]; /* Stores the city name when parsing buf. */
    unsigned int filename_len;
    int latitude_degrees, latitude_minutes, latitude_seconds;
    int longitude_degrees, longitude_minutes, longitude_seconds;
    icaltimezone zone;

    icalerror_assert (builtin_timezones == NULL,
		      "Parsing zones.tab file multiple times");

    builtin_timezones = icalarray_new (sizeof (icaltimezone), 32);

    filename_len = strlen (get_zone_directory()) + strlen (ZONES_TAB_FILENAME)
	+ 2;

    filename = (char*) malloc (filename_len);
    if (!filename) {
	icalerror_set_errno(ICAL_NEWFAILED_ERROR);
	return;
    }

    snprintf (filename, filename_len, "%s/%s", get_zone_directory(),
	      ZONES_TAB_FILENAME);

    fp = fopen (filename, "r");
    free (filename);
    if (!fp) {
	icalerror_set_errno(ICAL_FILE_ERROR);
	return;
    }

    while (fgets (buf, sizeof(buf), fp)) {
	if (*buf == '#') continue;

	/* The format of each line is: "latitude longitude location". */
	if (sscanf (buf, "%4d%2d%2d %4d%2d%2d %s",
		    &latitude_degrees, &latitude_minutes,
		    &latitude_seconds,
		    &longitude_degrees, &longitude_minutes,
		    &longitude_seconds,
		    location) != 7) {
	    fprintf (stderr, "Invalid timezone description line: %s\n", buf);
	    continue;
	}

	icaltimezone_init (&zone);
	zone.location = strdup (location);

	if (latitude_degrees >= 0)
	    zone.latitude = (double) latitude_degrees
		+ (double) latitude_minutes / 60
		+ (double) latitude_seconds / 3600;
	else
	    zone.latitude = (double) latitude_degrees
		- (double) latitude_minutes / 60
		- (double) latitude_seconds / 3600;

	if (longitude_degrees >= 0)
	    zone.longitude = (double) longitude_degrees
		+ (double) longitude_minutes / 60
		+ (double) longitude_seconds / 3600;
	else
	    zone.longitude = (double) longitude_degrees
		- (double) longitude_minutes / 60
		- (double) longitude_seconds / 3600;

	icalarray_append (builtin_timezones, &zone);

#if 0
	printf ("Found zone: %s %f %f\n",
		location, zone.latitude, zone.longitude);
#endif
	free (zone.location);
    }

    fclose (fp);
}