/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  Copyright (C) 2008 OMC Denmark ApS.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 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 Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * DEFINITIONS:
 *
 * Base date - The date in minutes since 1601 excluding the time of day.
 *
 * A Week - All weeks starts on a Sunday which is numbered 0 (zero), Monday
 * is 1 and so forth.
 *
 */

#include "recur_utils.h"
#include "conv_utils.h"

// The initial amount of memory allocated to the exception blob
#define BASE_SIZE_OF_EXCEPTION_BLOB (300)

// Recurrence types
#define RECUR_DAILY   (0x0A + 0x00)
#define RECUR_WEEKLY  (0x0A + 0x01)
#define RECUR_MONTHLY (0x0A + 0x02)
#define RECUR_YEARLY  (0x0A + 0x03)

// Exception types
#define DESCRIPTION_DEVIATION      (0x0001) // Description has changed
#define ATTENDEE_DEVIATION         (0x0002) // Attendee list has changed
#define REMINDER_TIME_DEVIATION    (0x0004) // Reminder time has changed
#define REMINDER_SET_DEVIATION     (0x0008) // Reminder added
#define LOCATION_DEVIATION         (0x0010) // Locatetion has changed
#define SHOW_TIME_AS_DEVIATION     (0x0020) // Transparency has changed
#define RESERVED_DEVIATION         (0x0040) // Unknown, reserved for MAPI
#define IS_ALL_DAY_DEVIATION       (0x0080) // Is-All-Day has changed
#define LABEL_COLOR_DEVIATION      (0x0100) // Label/Color has changed
#define NOTES_ATTACHMENT_DEVIATION (0x0200) // A note or an attachment has changed/been added

// Calendar utility defines
#define BASE_YEAR (1601)
#define MINUTES_IN_DAY (24 * 60)
#define MINUTES_IN_WEEK (24 * 60 * 7)
#define MINUTES_IN_YEAR (24 * 60 * 365) // remember to adjust for leap year when using this define
#define cFileTimeUnitsPerSecond (10000000i64)

// MAPI day bitmask values
#define MAPI_DAY_SUNDAY     (0x01)
#define MAPI_DAY_MONDAY     (0x02)
#define MAPI_DAY_TUESDAY    (0x04)
#define MAPI_DAY_WEDNESSDAY (0x08)
#define MAPI_DAY_THURSDAY   (0x10)
#define MAPI_DAY_FRIDAY     (0x20)
#define MAPI_DAY_SATURDAY   (0x40)

// returns the lesser of __A__ and __B__
#define MIN(__A__, __B__) ((__A__ < __B__) ? __A__ : __B__)

// returns the absolute value of __A__
#define ABS(__A__) ((__A__ < 0) ? ((-1) *__A__) : __A__)

static const unsigned __int32 DAYS_IN_MONTH[14] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0};

static const ::BRUTUS::Month MONTH[12] = {::BRUTUS::January,
					  ::BRUTUS::February,
					  ::BRUTUS::March,
					  ::BRUTUS::April,
					  ::BRUTUS::May,
					  ::BRUTUS::June,
					  ::BRUTUS::July,
					  ::BRUTUS::August,
					  ::BRUTUS::September,
					  ::BRUTUS::October,
					  ::BRUTUS::November,
					  ::BRUTUS::December};

// standard header preamble for recurrence BLOB
static const unsigned __int8 RECUR_HEADER[4] = { 0x04, 0x30, 0x04, 0x30 };

// misc interplaced constants
static const unsigned __int8 OUTLOOK_2003      = 0x08;
static const unsigned __int8 CONSTANT_ONE[3]   = { 0x20, 0x00, 0x00 };
static const unsigned __int8 CONSTANT_TWO[4]   = { 0x06, 0x30, 0x00, 0x00 };
static const unsigned __int8 CONSTANT_THREE[3] = { 0x30, 0x00, 0x00 };
static const unsigned __int8 CONSTANT_FOUR[8]  = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
static const unsigned __int8 TERMINATOR_ONE[4] = { 0x00, 0x00, 0x00, 0x00 };
static const unsigned __int8 TERMINATOR_TWO[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };


/*********************************************************************
 ***                                                               ***
 ***                   Inline helper functions                     ***
 ***                                                               ***
 *********************************************************************/

#pragma warning( disable : 4715 )
static inline unsigned __int32
brutus_month_to_mapi(const ::BRUTUS::Month m)
{
	unsigned __int32 retv;

	for (retv = 0; retv < 12; retv++) {
		if (m == MONTH[retv])
			return (retv + 1);
	}
#pragma warning( default : 4715 )
}

/*
 * Will return the base size of the recurrence type pattern
 * defining data structure in bytes. The exception data
 * therefore begins at offset get_base_size() in the binary
 * representation.
 */
static inline size_t
get_base_size(::BRUTUS::RecurrenceType type)
{
	switch (type) {
	case ::BRUTUS::recur_daily_every_N_days :
		return 22;
	case ::BRUTUS::recur_weekly_every_N_weeks :
		return 26;
	case ::BRUTUS::recur_weekly_every_N_weeks_for_regenerating_tasks :
		return 22;
	case ::BRUTUS::recur_monthly_every_N_months_on_day_D :
		return 26;
	case ::BRUTUS::recur_monthly_every_N_months_on_Xth_Y :
		return 30;
	case ::BRUTUS::recur_yearly_on_day_D_of_month_M :
		return 26;
	case ::BRUTUS::recur_yearly_on_the_Xth_Y_of_month_M :
		return 30;
	case ::BRUTUS::recur_none :
	default :
		return 0;
	}
}

/*
 * Converts minutes since 1601 into FILETIME
 *
 * Formerly known as "ConvertRecurrenceMinutesToDate"
 */
static inline FILETIME
minutes_to_filetime(unsigned __int32 minutes)
{
	unsigned __int64 fileTimeUnitsBeforeStart = (unsigned __int64)minutes * 60i64 * cFileTimeUnitsPerSecond;
	FILETIME ft = {
		(unsigned __int32)(fileTimeUnitsBeforeStart & 0xFFFFFFFF),
		(unsigned __int32)(fileTimeUnitsBeforeStart >> 32)
	};

	return ft;
}

/*
 * Converts FILETIME into minutes since 1601
 *
 * Fomerly known as "ConvertRecurrenceDateToMinutes"
 */
static inline unsigned __int32
filetime_to_minutes(FILETIME * const date)
{
	unsigned __int64 minutes = (((unsigned __int64)date->dwHighDateTime)<< 32) + date->dwLowDateTime;

	minutes = minutes/60i64;
	minutes = minutes/cFileTimeUnitsPerSecond;

	return (unsigned __int32)minutes;
}

/*
 * time represented as minutes since some start time (like BASE_YEAR)
 * is converted into minutes to the beginning of the day by subtracting
 * the time of date (in minutes) since the same start time
 */
static inline unsigned __int32
minutes_to_base_date(unsigned __int32 minutes)
{
	return (minutes - (minutes % MINUTES_IN_DAY)); // minutes in a day => 1444
}

static inline unsigned __int32
day_of_week_brutus_to_mapi(::BRUTUS::DayOfWeek day)
{
	if (::BRUTUS::Sunday == day)
		return 0;
	else if (::BRUTUS::Monday == day)
		return 1;
	else if (::BRUTUS::Tuesday == day)
		return 2;
	else if (::BRUTUS::Wednessday == day)
		return 3;
	else if (::BRUTUS::Thursday == day)
		return 4;
	else if (::BRUTUS::Friday == day)
		return 5;
	else if (::BRUTUS::Saturday == day)
		return 6;
	else
		return 0; // Sunday...
}

static inline unsigned __int32
mapi_day_to_bitvalue(const size_t mapi_day)
{
	switch (mapi_day) {
	case 0 :
		return MAPI_DAY_SUNDAY;
	case 1 :
		return MAPI_DAY_MONDAY;
	case 2 :
		return MAPI_DAY_TUESDAY;
	case 3 :
		return MAPI_DAY_WEDNESSDAY;
	case 4 :
		return MAPI_DAY_THURSDAY;
	case 5 :
		return MAPI_DAY_FRIDAY;
	case 6 :
		return MAPI_DAY_SATURDAY;
	default :
		return MAPI_DAY_SUNDAY;
	}
}

static inline ::BRUTUS::DayOfWeek
mapi_days_to_brutus_days_bitmask(const unsigned __int32 mapi_days)
{
	::BRUTUS::DayOfWeek brutus_days = 0x00;
	if (mapi_days & MAPI_DAY_SUNDAY)
		brutus_days |= ::BRUTUS::Sunday;
	if (mapi_days & MAPI_DAY_MONDAY)
		brutus_days |= ::BRUTUS::Monday;
	if (mapi_days & MAPI_DAY_TUESDAY)
		brutus_days |= ::BRUTUS::Tuesday;
	if (mapi_days & MAPI_DAY_WEDNESSDAY)
		brutus_days |= ::BRUTUS::Wednessday;
	if (mapi_days & MAPI_DAY_THURSDAY)
		brutus_days |= ::BRUTUS::Thursday;
	if (mapi_days & MAPI_DAY_FRIDAY)
		brutus_days |= ::BRUTUS::Friday;
	if (mapi_days & MAPI_DAY_SATURDAY)
		brutus_days |= ::BRUTUS::Saturday;

	return brutus_days;
}

static inline unsigned __int32
brutus_days_to_mapi_days_bitmask(const ::BRUTUS::DayOfWeek which_days) // bitmask to determine which days are active
{
	unsigned __int32 mapi_days = 0;

	if (which_days & ::BRUTUS::Sunday)
		mapi_days |= MAPI_DAY_SUNDAY;

	if (which_days & ::BRUTUS::Monday)
		mapi_days |= MAPI_DAY_MONDAY;

	if (which_days & ::BRUTUS::Tuesday)
		mapi_days |= MAPI_DAY_TUESDAY;

	if (which_days & ::BRUTUS::Wednessday)
		mapi_days |= MAPI_DAY_WEDNESSDAY;

	if (which_days & ::BRUTUS::Thursday)
		mapi_days |= MAPI_DAY_THURSDAY;

	if (which_days & ::BRUTUS::Friday)
		mapi_days |= MAPI_DAY_FRIDAY;

	if (which_days & ::BRUTUS::Saturday)
		mapi_days |= MAPI_DAY_SATURDAY;

	return mapi_days;
}

/*
 * Iterate to the n'th next month:
 *
 * (month == 11) ==> (month + 3 = 2), (month - 1 = 10)
 */
static inline size_t
iterate_month(size_t n,
	      size_t month) // January = 1, February = 2 ....
{
	if ((month < 1) || (month > 12))
		throw invalid_operation();

	n = n % 12;
	if (!n)
		return month;

	while (n) {
		switch (month) {
		case 1 :
			if (n < 0) {
				n++;
				month = 12;
			} else {
				n--;
				month = 2;
			}
			break;
		case 2 :
			if (n < 0) {
				n++;
				month = 1;
			} else {
				n--;
				month = 3;
			}
			break;
		case 3 :
			if (n < 0) {
				n++;
				month = 2;
			} else {
				n--;
				month = 4;
			}
			break;
		case 4 :
			if (n < 0) {
				n++;
				month = 3;
			} else {
				n--;
				month = 5;
			}
			break;
		case 5 :
			if (n < 0) {
				n++;
				month = 4;
			} else {
				n--;
				month = 6;
			}
			break;
		case 6 :
			if (n < 0) {
				n++;
				month = 5;
			} else {
				n--;
				month = 7;
			}
			break;
		case 7 :
			if (n < 0) {
				n++;
				month = 6;
			} else {
				n--;
				month = 8;
			}
			break;
		case 8 :
			if (n < 0) {
				n++;
				month = 7;
			} else {
				n--;
				month = 9;
			}
			break;
		case 9 :
			if (n < 0) {
				n++;
				month = 8;
			} else {
				n--;
				month = 10;
			}
			break;
		case 10 :
			if (n < 0) {
				n++;
				month = 9;
			} else {
				n--;
				month = 11;
			}
			break;
		case 11 :
			if (n < 0) {
				n++;
				month = 10;
			} else {
				n--;
				month = 12;
			}
			break;
		case 12 :
			if (n < 0) {
				n++;
				month = 11;
			} else {
				n--;
				month = 1;
			}
			break;
		}
	}

	return month;
}

/*
 * Iterate to the n'th next weekday:
 *
 * (weekday == 6) ==> (weekday + 3 = 2), (weekday - 1 = 5)
 */
static inline size_t
iterate_weekday(size_t n,
		size_t weekday) // Sunday = 0, Monday = 1, ....
{
	if ((weekday < 0) || (weekday > 6))
		throw invalid_operation();

	n = n % 7;
	if (!n)
		return weekday;

	while (n) {
		switch (weekday) {
		case 0 :
			if (n < 0) {
				n++;
				weekday = 6;
			} else {
				n--;
				weekday = 1;
			}
			break;
		case 1 :
			if (n < 0) {
				n++;
				weekday = 0;
			} else {
				n--;
				weekday = 2;
			}
			break;
		case 2 :
			if (n < 0) {
				n++;
				weekday = 1;
			} else {
				n--;
				weekday = 3;
			}
			break;
		case 3 :
			if (n < 0) {
				n++;
				weekday = 2;
			} else {
				n--;
				weekday = 4;
			}
			break;
		case 4 :
			if (n < 0) {
				n++;
				weekday = 3;
			} else {
				n--;
				weekday = 5;
			}
			break;
		case 5 :
			if (n < 0) {
				n++;
				weekday = 4;
			} else {
				n--;
				weekday = 6;
			}
			break;
		case 6 :
			if (n < 0) {
				n++;
				weekday = 5;
			} else {
				n--;
				weekday = 0;
			}
			break;
		}
	}

	return weekday;
}

/*
 * Count the number of bits set in a 32 bit bitmask
 */
static inline size_t
bitcount(unsigned __int32 bm)
{
	size_t count = 0;

	while (bm) {
		if (bm & 0x1)
			count++;
		bm >>= 1;
	}

	return count;
}

/*
 * Assume that a pattern says that a series occurs on Tuesdays, Fridays
 * and Saturdays on every week. Now assume that only the first two occurrences
 * are active for a given week.
 *
 * This function then calculates the number of minutes from the start of the
 * week, Sunday, until the start of the day on which the last active occurence
 * happens, in this case Friday.
 *
 * The result would then be: MINUTES_IN_DAY * 5 (because there are 5 complete
 * days before the beginning of Friday)
 *
 */
static inline size_t
minutes_in_week_before_day_of_last_occurrence(const ::BRUTUS::DayOfWeek which_days, // bitmask to determine which days are active
					      const size_t occurrence_count) // the number of occurrences in the pattern that are counted
{
	size_t last_day;
	size_t rem = occurrence_count;

	if (bitcount(which_days) < rem)
		throw invalid_operation();

	do { // get the last active day in the week
		if (which_days & ::BRUTUS::Sunday) {
			rem--;
			if (!rem) {
				last_day = 0;
				break;
			}
		}

		if (which_days & ::BRUTUS::Monday) {
			rem--;
			if (!rem) {
				last_day = 1;
				break;
			}
		}

		if (which_days & ::BRUTUS::Tuesday) {
			rem--;
			if (!rem) {
				last_day = 2;
				break;
			}
		}

		if (which_days & ::BRUTUS::Wednessday) {
			rem--;
			if (!rem) {
				last_day = 3;
				break;
			}
		}

		if (which_days & ::BRUTUS::Thursday) {
			rem--;
			if (!rem) {
				last_day = 4;
				break;
			}
		}

		if (which_days & ::BRUTUS::Friday) {
			rem--;
			if (!rem) {
				last_day = 5;
				break;
			}
		}

		if (which_days & ::BRUTUS::Saturday) {
			rem--;
			if (!rem) {
				last_day = 6;
				break;
			}
		}
	} while (0);

	return (last_day * MINUTES_IN_DAY);
}

static inline bool
leapyear(const int year)
{
	//
	// the algorithm below is far simpler to read than the equivalent:
	//
	// return (!(year % 4) && ((year % 100) || (!(year % 100) && !(year % 400))));
	//
	if (!(year % 4)) { // divisible by 4
		if (year % 100) // not divisible by 100
			return true;

		if (!(year % 400)) // divisible by 100 and 400
			return true;
	}
	return false;
}

/*
 * Will return the number of days in a given month while taking
 * leap years into account
 */
static inline unsigned __int32
days_in_month(const size_t year,
	      const size_t month) // January = 1, February = 2 ....
{
	if ((month < 1) || (month > 12))
		throw invalid_operation();

	return ((2 == month) ? (DAYS_IN_MONTH[month] + (leapyear(year) ? 1 : 0)) : DAYS_IN_MONTH[month]);
}

// See: http://www.tondering.dk/claus/cal/node3.html
static inline unsigned __int32
gregorian_to_julian(unsigned int day,         // 1...DAYS_IN_MONTH[month]
		    const unsigned int month, // January = 1, February = 2 ....
		    const int year)           // year >= -4799 (which is later than 4800BC)
{
	if (year <= -4800)
		throw invalid_operation();

	if (day > days_in_month(year, month))
		day = days_in_month(year, month);

	const unsigned __int32 a = (14 - month)/12;
	const unsigned __int32 y = year + 4800 - a;
	const unsigned __int32 m = month + (12 * a) - 3;

	return (day + (((153*m)+2)/5) + (365*y) + (y/4) - (y/100) + (y/400) - 32045);
}

/*
 * Will return the number of days until the hits'th next valid
 * occurrence of specific weekdays
 */
static inline size_t
days_until_next_Nth_monthly_occurrence(const size_t year,  // start year
				       const size_t month, // start month
				       const size_t day,   // start day
				       const size_t N,     // every N'th month
				       const size_t hits,  // the number of hits to advance (hits >= 1), (hits == 1) will most likely hit on the start date...
				       const size_t ValidMonth,           // the month of year where events are valid (ValidMonth == 0 => all months)
				       const size_t which,                // 1 for first time a specific weekday occurs, 2 for second, etc. 5 for last occurrence.
				       const ::BRUTUS::DayOfWeek DayMask) // which days of week to look for
{
	FILETIME ft;
	SYSTEMTIME st;
	size_t this_year = year;
	size_t this_month = month;
	size_t days_in_this_month = days_in_month(this_year, this_month);
	const size_t mapi_weekdays_to_look_for = brutus_days_to_mapi_days_bitmask(DayMask);

	if ((year < 1)
	    || (hits < 1)
	    || (month < 1)
	    || (month > 12)
	    || (ValidMonth < 0)
	    || (ValidMonth > 12)
	    || (day > days_in_this_month)
	    || (which < 1)
	    || (which > 5))
		throw invalid_operation();

	// deduce the day of week for the this month
	st.wYear = this_year;
	st.wMonth = this_month;
	st.wDay = day;
	st.wHour = 0;
	st.wMinute = 0;
	st.wSecond = 0;
	st.wMilliseconds = 0;
	SystemTimeToFileTime(&st, &ft);
	FileTimeToSystemTime(&ft, &st);

	// count all days up to the sought-after occurrence of specific weekdays
	size_t d;
	size_t accumulated_hits = 0;
	size_t count[7] = { 0 }; // number of time DayMask dyas has been seen in the current month
	size_t last[7] = { 0 };  // day in month of the last occurrenc of the week days
	size_t mapi_weekday = (size_t)st.wDayOfWeek;
	size_t day_in_month = day;

	size_t prev_month;
	unsigned __int32 prev_julian_date;
	unsigned __int32 julian_date;

	size_t retv = 0;
	do { // step one day at a time and count every match
		last[mapi_weekday] = day_in_month;
		if ((!ValidMonth || (ValidMonth == this_month)) && (mapi_weekdays_to_look_for & mapi_day_to_bitvalue(mapi_weekday))) {
			count[mapi_weekday] += 1;
			for (d = 0; d < 7; d++) {
				if ((5 != which ) && (which == count[d])) {
					accumulated_hits++;
					if (hits == accumulated_hits)
						goto out;
				}
			}
		}
		retv++;

		day_in_month++;
		if (day_in_month > days_in_this_month) { // month-wrap
			if ((!ValidMonth || (ValidMonth == this_month)) && (5 == which)) { // looking for the last occurring day match
				mapi_weekday = iterate_weekday(-6, mapi_weekday);
				for (d = 0; d < 7; d++) {
					if (!(mapi_weekdays_to_look_for & mapi_day_to_bitvalue(mapi_weekday))) {
						mapi_weekday = iterate_weekday(1, mapi_weekday);
						continue;
					}

					accumulated_hits++;
					if (hits == accumulated_hits) {
						retv -= days_in_this_month - last[mapi_weekday]; // retv might has been taken to far towards the end of the month
						goto out;
					}
					mapi_weekday = iterate_weekday(1, mapi_weekday);
				}
				mapi_weekday = iterate_weekday(-1, mapi_weekday);
			}

			prev_month = this_month;
			this_month = iterate_month(N, this_month);
			prev_julian_date = gregorian_to_julian(day_in_month - 1, prev_month, this_year);
			if (12 < (N + prev_month)) // year-wrap
				this_year += (N + prev_month) / 12;
			if (1 < N) {
				julian_date = gregorian_to_julian(1, this_month, this_year);
				retv += prev_julian_date - julian_date;
			}

			for (d = 0; d < 7; d++) {
				count[d] = 0;
				last[d] = 0 ;
			}

			day_in_month = 1;
			days_in_this_month = days_in_month(this_year, this_month);
		}

		mapi_weekday = iterate_weekday(1, mapi_weekday);
	} while (true);

out:
	return retv;
}

/*
 * Will return the number of hits until the specifies base date
 * is reached
 */
static inline size_t
Nth_next_monthly_occurrence_hits_until_date(const size_t year,  // start year
					    const size_t month, // start month
					    const size_t day,   // start day
					    const size_t N,     // every N'th month
					    const unsigned __int32 base_date,  // the final base date
					    const size_t ValidMonth,           // the month of year where events are valid (ValidMonth == 0 => all months)
					    const size_t which,                // 1 for first time a specific weekday occurs, 2 for second, etc. 5 for last occurrence.
					    const ::BRUTUS::DayOfWeek DayMask) // which days of week to look for
{
	FILETIME ft;
	SYSTEMTIME st;
	size_t this_year = year;
	size_t this_month = month;
	size_t days_in_this_month = days_in_month(this_year, this_month);
	const size_t mapi_weekdays_to_look_for = brutus_days_to_mapi_days_bitmask(DayMask);

	if ((year < 1)
	    || (month < 1)
	    || (month > 12)
	    || (ValidMonth < 0)
	    || (ValidMonth > 12)
	    || (day > days_in_this_month)
	    || (which < 1)
	    || (which > 5))
		throw invalid_operation();

	// deduce the day of week for the this month
	st.wYear = this_year;
	st.wMonth = this_month;
	st.wDay = day;
	st.wHour = 0;
	st.wMinute = 0;
	st.wSecond = 0;
	st.wMilliseconds = 0;
	SystemTimeToFileTime(&st, &ft);
	FileTimeToSystemTime(&ft, &st);

	unsigned __int32 bdate;
	bdate = filetime_to_minutes(&ft);
	bdate = minutes_to_base_date(bdate);

	// count all days up to the sought-after occurrence of specific weekdays
	size_t d;
	size_t accumulated_hits = 0;
	size_t count[7] = { 0 }; // number of time DayMask dyas has been seen in the current month
	size_t last[7] = { 0 };  // day in month of the last occurrenc of the week days
	size_t mapi_weekday = (size_t)st.wDayOfWeek;
	size_t day_in_month = day;

	size_t prev_month;
	unsigned __int32 prev_julian_date;
	unsigned __int32 julian_date;

	size_t retv = 0;
	do { // step one day at a time and count every match
		last[mapi_weekday] = day_in_month;
		if ((!ValidMonth || (ValidMonth == this_month)) && (mapi_weekdays_to_look_for & mapi_day_to_bitvalue(mapi_weekday))) {
			count[mapi_weekday] += 1;
			for (d = 0; d < 7; d++) {
				if ((5 != which ) && (which == count[d])) {
					accumulated_hits++;
					if (base_date <= bdate)
						return accumulated_hits;
				}
			}
		}
		retv++;
		bdate += MINUTES_IN_DAY;

		day_in_month++;
		if (day_in_month > days_in_this_month) { // month-wrap
			if ((!ValidMonth || (ValidMonth == this_month)) && (5 == which)) { // looking for the last occurring day match
				mapi_weekday = iterate_weekday(-6, mapi_weekday);
				retv -= 6;
				bdate -= 6 * MINUTES_IN_DAY;

				for (d = 0; d < 7; d++) {
					if (!(mapi_weekdays_to_look_for & mapi_day_to_bitvalue(mapi_weekday))) {
						mapi_weekday = iterate_weekday(1, mapi_weekday);
						continue;
					}
					accumulated_hits++;
					retv += d;
					bdate += d * MINUTES_IN_DAY;

					if (base_date <= bdate)
						return accumulated_hits;

					mapi_weekday = iterate_weekday(1, mapi_weekday);
				}
				mapi_weekday = iterate_weekday(-1, mapi_weekday);
			}

			prev_month = this_month;
			this_month = iterate_month(N, this_month);
			prev_julian_date = gregorian_to_julian(day_in_month - 1, prev_month, this_year);
			if (12 < (N + prev_month)) // year-wrap
				this_year += (N + prev_month) / 12;
			if (1 < N) {
				julian_date = gregorian_to_julian(1, this_month, this_year);
				retv += prev_julian_date - julian_date;
				bdate += (prev_julian_date - julian_date) * MINUTES_IN_DAY;
			}

			for (d = 0; d < 7; d++) {
				count[d] = 0;
				last[d] = 0 ;
			}

			day_in_month = 1;
			days_in_this_month = days_in_month(this_year, this_month);
		}

		mapi_weekday = iterate_weekday(1, mapi_weekday);
	} while (true);
}

/*
 * Will return the number of minutes in a given year while taking
 * leap years into account
 */
static inline unsigned __int32
minutes_in_year(const int year)
{
	return (MINUTES_IN_YEAR + (leapyear(year) ? MINUTES_IN_DAY : 0));
}

/*
 * Will return the number of minutes since 1601 to the beginning of the
 * year while taking leap years into account
 */
static inline unsigned __int32
year_to_minutes(const int year)
{
	int n;
	unsigned __int32 retv = 0;

	for (n = BASE_YEAR; n < year; n ++)
		retv += minutes_in_year(year);

	return retv;
}

static inline unsigned __int16
recurrence_exception_brutus_to_mapi(const ::BRUTUS::RecurrenceException kind)
{
	switch (kind) {
	case ::BRUTUS::ExceptionDescription :
		return DESCRIPTION_DEVIATION;
	case ::BRUTUS::ExceptionAttendee :
		return ATTENDEE_DEVIATION;
	case ::BRUTUS::ExceptionReminderTime :
		return REMINDER_TIME_DEVIATION;
	case ::BRUTUS::ExceptionReminderSet :
		return REMINDER_SET_DEVIATION;
	case ::BRUTUS::ExceptionLocation :
		return LOCATION_DEVIATION;
	case ::BRUTUS::ExceptionShowTimeAs :
		return SHOW_TIME_AS_DEVIATION;
	case ::BRUTUS::ExceptionIsAllDay :
		return IS_ALL_DAY_DEVIATION;
	case ::BRUTUS::ExceptionLabelColor :
		return LABEL_COLOR_DEVIATION;
	case ::BRUTUS::ExceptionNotesAttachment :
		return NOTES_ATTACHMENT_DEVIATION;
	}
}

// best fit...
static inline ::BRUTUS::Month
minutes_to_month(const unsigned __int32 minutes)
{
	int n;
	unsigned __int32 cutof = 0;

	for (n = 0; n < 13; n++) {
		cutof += MINUTES_IN_DAY * ((unsigned __int32)((float)DAYS_IN_MONTH[n]/2.0) + (unsigned __int32)((float)DAYS_IN_MONTH[n+1]/2.0));
		if (minutes <= cutof)
			return MONTH[n];
	}
	return ::BRUTUS::December;
}

#pragma warning( disable : 4715 )
static inline unsigned __int8
how_to_end_brutus_to_mapi(::BRUTUS::RecurrenceTerminationType how)
{
	switch (how) {
	case ::BRUTUS::term_date :
		return 0x21;
	case ::BRUTUS::term_number :
		return 0x22;
	case ::BRUTUS::term_never :
		return 0x23;
	}
#pragma warning( default : 4715 )
}

/*********************************************************************
 ***                                                               ***
 ***                     Misc helper classes                       ***
 ***                                                               ***
 *********************************************************************/


/*********************************************************************
 ***                         ByteArray                             ***
 ***                                                               ***
 *** Array of unsigned __int8. The data is not ordered or          ***
 *** interpreted in any way. Instances of this class will hold     ***
 *** unordered binary data.                                        ***
 ***                                                               ***
 *********************************************************************/

class ByteArray {
public:
	ByteArray(void)
		{
			bytes_ = NULL;
			allocated_ = 0;
			len_ = 0;
		};

	ByteArray(size_t prealloc)
		{
			bytes_ = (unsigned __int8*)malloc(prealloc);
			allocated_ = bytes_ ? prealloc : 0;
			len_ = 0;
		};

	~ByteArray(void)
		{
			free(bytes_);
		};

	void reset(void)
		{
			if (bytes_)
				free(bytes_);

			bytes_ = NULL;
			allocated_ = 0;
			len_ = 0;
		};

	void reset(size_t prealloc)
		{
			if (bytes_)
				free(bytes_);

			bytes_ = (unsigned __int8*)malloc(prealloc);
			allocated_ = bytes_ ? prealloc : 0;
			len_ = 0;
		};

	/*
	 * Append length bytes from data to byte array
	 */
	bool append(const size_t length,
		    const void *data)
		{
			if (!length || !data)
				return true;

			void *tmp = (void*)bytes_;

			if ((len_ + length) > allocated_) {
				tmp = realloc((void*)bytes_, len_ + length);
				if (!tmp)
					return false;
				allocated_ = len_ + length;
			}

			bytes_ = (unsigned __int8*)tmp;
			if (memcpy_s(&(bytes_[len_]), length, data, length))
				return false;

			len_ += length;

			return true;
		};

	bool append(const SBinary *bin)
		{
			return append((const size_t)bin->cb, (const void*)bin->lpb);
		};

	/*
	 * Insert length bytes from data at index idx into byte array
	 */
	bool insert(const size_t idx,
		    const size_t length,
		    const void *data)
		{
			if (!length || !data)
				return true;

			if (idx == len_)
				return append(length, data);

			if (idx > len_)
				return false;

			const size_t new_length = len_ + length;
			unsigned __int8 *buf = (unsigned __int8 *)malloc(new_length);
			if (!buf)
				return false;

			if (memcpy_s(buf, new_length, bytes_, idx)) {
				free(buf);
				return false;
			}

			if (memcpy_s(&(buf[idx]), new_length-idx, data, length)) {
				free(buf);
				return false;
			}

			if (memcpy_s(&(buf[idx+length]), new_length-idx-length, &(bytes_[idx]), len_-idx)) {
				free(buf);
				return false;
			}

			free(bytes_);

			bytes_ = buf;
			allocated_ = new_length;
			len_ = new_length;

			return true;
		};

	template<class T> T get_as(const size_t idx) // throws (invalid_index)
		{
			return *get_pointer_to<T>(idx);
		};

	template<class T> void set_as(const size_t idx,
				      const T val) // throws (invalid_index)
		{
			T *p = get_pointer_to<T>(idx);

			*p = val;
		};

	bool get_sbinary(void *Parent,
			 SBinary * const bin)
		{
			bin->cb = len_;

			if (!len_) {
				bin->lpb = NULL;
				return false;
			}

			if (Parent) {
				if (S_OK != MAPIAllocateMore(len_, Parent, (void**)&bin->lpb))
					return false;
			} else {
				if (S_OK != MAPIAllocateBuffer(len_, (void**)&bin->lpb))
					return false;
			}

			memcpy_s((void *)bin->lpb, len_, (void *)bytes_, len_);
			return true;
		};

	size_t length(void)
		{
			return len_;
		}

	void length(size_t length)
		{
			// realloc() is not required to return NULL so this is needed
			if (!length) {
				free(bytes_);
				bytes_ = NULL;
				allocated_ = 0;
				len_ = 0;

				return;
			}

			void *tmp;
			tmp = realloc((void*)bytes_, length);
			if (!tmp)
				throw std::bad_alloc();

			free(bytes_);
			bytes_ = (unsigned __int8*)tmp;
			allocated_ = length;
			len_ = (length > len_) ? len_ : length;
		}

private:
	template<class T> T* get_pointer_to(const size_t idx) // throws (invalid_index)
		{
			if (idx >= len_)
				throw invalid_index();

			return (T*)(&bytes_[idx]);
		};

	unsigned __int8 *bytes_; // byte array - allocated memory chunk
	size_t allocated_;       // size of allocated memory chunk
	size_t len_;             // used size of allocated memory chunk
};


/*********************************************************************
 ***                          Int32Array                           ***
 ***                                                               ***
 *** Instances of this class will hold an array of ordered and     ***
 *** unique __int32                                                ***
 ***                                                               ***
 *********************************************************************/

class Int32Array {
public:
	Int32Array(void)
		{
			array_ = NULL;
			allocated_ = 0;
			len_ = 0;
		};

	~Int32Array(void)
		{
			free(array_);
		};

	void clear(void)
		{
			if (!array_)
				return;

			free(array_);
			array_ = NULL;
			allocated_ = 0;
			len_ = 0;
		};

	/*
	 * Insert one __int32 - the index will be decided so that the array remains ordered
	 */
	void insert(const unsigned __int32 val)
		{
			if (present(val))
				return;

			const size_t new_alloc = allocated_ + sizeof(unsigned __int32);
			unsigned __int32 *buf = (unsigned __int32 *)malloc(new_alloc);
			if (!buf)
				throw std::bad_alloc();

			size_t idx;
			if (!len_)
				idx = 0;
			else {
				for (idx = 0; idx < len_; idx++)
					if (val <= array_[idx])
						break;
			}

			size_t n;
			for (n = 0; n < idx; n++)
				buf[n] = array_[n];
			buf[idx] = val;
			len_++;
			for (n = idx + 1; n < len_; n++)
				buf[n] = array_[n-1];

			free(array_);
			array_ = buf;
			allocated_ = new_alloc;

			return;
		};

	/*
	 * Remove one __int32 from internal array
	 */
	void remove(const unsigned __int32 val)
		{
			if (!len_)
				return;

			size_t n;
			size_t k;
			for (n = 0; n < len_; n++) {
				if (val == array_[n]) {
					len_--;
					if (!len_) {
						free(array_);
						array_ = NULL;
						allocated_ = 0;
						return;
					}

					for (k = n; k < len_; k++)
						array_[k] = array_[k+1];

					allocated_ -= sizeof(unsigned __int32);
					void *tmp = realloc((void*)array_, allocated_ );
					if (!tmp)
						throw std::bad_alloc();
					else
						array_ = (unsigned __int32*)tmp;
					break;
				}
			}

		};

	unsigned __int32 at(const size_t idx) // throws (invalid_index)
		{
			return *get_pointer_to(idx);
		};

	void assign(const size_t idx,
		    const unsigned __int32 val) // throws (invalid_index)
		{
			unsigned __int32 *p = get_pointer_to(idx);

			*p = val;
		};

	size_t length(void)
		{
			return len_;
		};

	unsigned __int32& operator[](const size_t idx) // throws (invalid_index)
		{
			if (idx >= len_)
				throw invalid_index();

			return array_[idx];
		};

private:
	unsigned __int32 *get_pointer_to(const size_t idx) // throws (invalid_index)
		{
			if (idx >= len_)
				throw invalid_index();

			return (&array_[idx]);
		};

	// checks for duplicates
	bool present(const unsigned __int32 val)
		{
			size_t n;
			for (n = 0; n < len_; n++) {
				if (val > array_[n])
					continue;
				if (val == array_[n])
					return true;
				if (val < array_[n])
					return false;
			}
			return false;
		};

	unsigned __int32 *array_; // ordered __int32 array - allocated memory chunk of ascending __int32's
	size_t allocated_;        // size of allocated memory chunk
	size_t len_;              // length (in number of __int32's) of allocated memory chunk
};


/**************************************************************
 ***                     RecurDate                          ***
 ***                                                        ***
 *** Instances of this class are used to convert in both     ***
 *** directions between oID and base date                   ***
 ***                                                        ***
 **************************************************************/

class RecurDate {
public:
	RecurDate(void)
		{
			pattern_set_ = false;
			start_.dwLowDateTime = 0;
			start_.dwHighDateTime = 0;
		};

	~RecurDate(void)
		{
		};

	void set_start(const unsigned __int32 start) // minutes since 1601
		{
			start_ = minutes_to_filetime(start);
		};

	void set_start(SYSTEMTIME * const start)
		{
			SystemTimeToFileTime(start, &start_);
		};

	void set_start(FILETIME * const start)
		{
			start_.dwLowDateTime = start->dwLowDateTime;
			start_.dwHighDateTime = start->dwHighDateTime;
		};

	void set_end(const unsigned __int32 end) // minutes since 1601
		{
			end_ = minutes_to_filetime(end);
		};

	void set_end(SYSTEMTIME * const end)
		{
			SystemTimeToFileTime(end, &end_);
		};

	void set_end(FILETIME * const end)
		{
			end_.dwLowDateTime = end->dwLowDateTime;
			end_.dwHighDateTime = end->dwHighDateTime;
		};

	void set_pattern(const ::BRUTUS::RecurrencePattern & pattern)
		{
			pattern_ = pattern;
			pattern_set_ = true;
		};

	/*
	 * Base date of ocurrence ID "oID" (the occurence number)
	 *
	 * (oID == 0) ==> the series
	 * (oID > 0)  ==> occurence number/ID
	 */
	unsigned __int32 get_base_date_of_occurence(const size_t oID) // throws (invalid_operation)
		{
			if (!pattern_set_)
				throw invalid_operation();

			switch (pattern_->_d()) {
			case ::BRUTUS::recur_daily_every_N_days :
			{
				return get_base_date(oID, pattern_->daily_every_N_days());
			}
			break;
			case ::BRUTUS::recur_weekly_every_N_weeks :
			{
				return get_base_date(oID, pattern_->weekly_every_N_weeks());
			}
			break;
			case ::BRUTUS::recur_weekly_every_N_weeks_for_regenerating_tasks :
			{
				return get_base_date(oID, pattern_->weekly_every_N_weeks_for_regenerating_tasks());
			}
			break;
			case ::BRUTUS::recur_monthly_every_N_months_on_day_D :
			{
				return get_base_date(oID, pattern_->monthly_every_N_months_on_day_D());
			}
			break;
			case ::BRUTUS::recur_monthly_every_N_months_on_Xth_Y :
			{
				return get_base_date(oID, pattern_->monthly_every_N_months_on_Xth_Y());
			}
			break;
			case ::BRUTUS::recur_yearly_on_day_D_of_month_M :
			{
				return get_base_date(oID, pattern_->yearly_on_day_D_of_month_M());
			}
			break;
			case ::BRUTUS::recur_yearly_on_the_Xth_Y_of_month_M :
			{
				return get_base_date(oID, pattern_->yearly_on_the_Xth_Y_of_month_M());
			}
			break;
			case ::BRUTUS::recur_none :
			default :
				throw invalid_operation();
			}
		};

	/*
	 * Will return the oID as a function of a future valid BaseDate
	 */
	size_t get_oID_of_occurence(const unsigned __int32 BaseDate) // throws (invalid_operation)
		{
			if (!pattern_set_)
				throw invalid_operation();

			unsigned __int32 bdate;
			bdate = filetime_to_minutes(&start_);
			bdate = minutes_to_base_date(bdate);
			if (BaseDate < bdate)
				throw invalid_operation();

			switch (pattern_->_d()) {
			case ::BRUTUS::recur_daily_every_N_days :
			{
				return get_oID(BaseDate, pattern_->daily_every_N_days());
			}
			break;
			case ::BRUTUS::recur_weekly_every_N_weeks :
			{
				return get_oID(BaseDate, pattern_->weekly_every_N_weeks());
			}
			break;
			case ::BRUTUS::recur_weekly_every_N_weeks_for_regenerating_tasks :
			{
				return get_oID(BaseDate, pattern_->weekly_every_N_weeks_for_regenerating_tasks());
			}
			break;
			case ::BRUTUS::recur_monthly_every_N_months_on_day_D :
			{
				return get_oID(BaseDate, pattern_->monthly_every_N_months_on_day_D());
			}
			break;
			case ::BRUTUS::recur_monthly_every_N_months_on_Xth_Y :
			{
				return get_oID(BaseDate, pattern_->monthly_every_N_months_on_Xth_Y());
			}
			break;
			case ::BRUTUS::recur_yearly_on_day_D_of_month_M :
			{
				return get_oID(BaseDate, pattern_->yearly_on_day_D_of_month_M());
			}
			break;
			case ::BRUTUS::recur_yearly_on_the_Xth_Y_of_month_M :
			{
				return get_oID(BaseDate, pattern_->yearly_on_the_Xth_Y_of_month_M());
			}
			break;
			case ::BRUTUS::recur_none :
			default :
				throw invalid_operation();
			}
		};

	/*
	 * Start date (with time) of ocurrence id "oID" (the occurence number)
	 *
	 * (oID == 0) ==> the series
	 * (oID > 0)  ==> occurence number/ID
	 */
	unsigned __int32 get_start_datetime_of_occurence(const size_t oID) // throws (invalid_operation)
		{
			if (!pattern_set_)
				throw invalid_operation();


			switch (pattern_->_d()) {
			case ::BRUTUS::recur_daily_every_N_days :
			{
				return get_start_datetime(oID, pattern_->daily_every_N_days());
			}
			break;
			case ::BRUTUS::recur_weekly_every_N_weeks :
			{
				return get_start_datetime(oID, pattern_->weekly_every_N_weeks());
			}
			break;
			case ::BRUTUS::recur_weekly_every_N_weeks_for_regenerating_tasks :
			{
				return get_start_datetime(oID, pattern_->weekly_every_N_weeks_for_regenerating_tasks());
			}
			break;
			case ::BRUTUS::recur_monthly_every_N_months_on_day_D :
			{
				return get_start_datetime(oID, pattern_->monthly_every_N_months_on_day_D());
			}
			break;
			case ::BRUTUS::recur_monthly_every_N_months_on_Xth_Y :
			{
				return get_start_datetime(oID, pattern_->monthly_every_N_months_on_Xth_Y());
			}
			break;
			case ::BRUTUS::recur_yearly_on_day_D_of_month_M :
			{
				return get_start_datetime(oID, pattern_->yearly_on_day_D_of_month_M());
			}
			break;
			case ::BRUTUS::recur_yearly_on_the_Xth_Y_of_month_M :
			{
				return get_start_datetime(oID, pattern_->yearly_on_the_Xth_Y_of_month_M());
			}
			break;
			case ::BRUTUS::recur_none :
			default :
				throw invalid_operation();
			}
		};

#pragma warning( disable : 4715 )
	unsigned __int32 occurrence_time_to_oid(const ::BRUTUS::OccurrenceTime & oc_time)
		{

			switch (oc_time._d()) {
			case ::BRUTUS::tt_oID :
				return oc_time.oID();
			case ::BRUTUS::tt_BaseDate :
				return get_oID_of_occurence(oc_time.bdate());
			case ::BRUTUS::tt_FILETIME :
			{
				FILETIME ft;
				::BRUTUS::FILETIME bft = oc_time.ft();
				unsigned __int32 bdate;

				filetime_brutus_to_mapi_no_alloc(&bft, ft);
				bdate = filetime_to_minutes(&ft);
				bdate = minutes_to_base_date(bdate);
				return get_oID_of_occurence(bdate);
			}
			}
#pragma warning( default : 4715 )
		};

#pragma warning( disable : 4715 )
	unsigned __int32 occurrence_time_to_base_date(const ::BRUTUS::OccurrenceTime & oc_time)
		{

			switch (oc_time._d()) {
			case ::BRUTUS::tt_oID :
				return get_base_date_of_occurence(oc_time.oID());
			case ::BRUTUS::tt_BaseDate :
				return oc_time.bdate();
			case ::BRUTUS::tt_FILETIME :
			{
				FILETIME ft;
				::BRUTUS::FILETIME bft = oc_time.ft();
				unsigned __int32 bdate;

				filetime_brutus_to_mapi_no_alloc(&bft, ft);
				bdate = filetime_to_minutes(&ft);
				return minutes_to_base_date(bdate);
			}
			}
#pragma warning( default : 4715 )
		};

#pragma warning( disable : 4715 )
	FILETIME occurrence_time_to_filetime(const ::BRUTUS::OccurrenceTime & oc_time)
		{
			switch (oc_time._d()) {
			case ::BRUTUS::tt_oID :
			{
				unsigned __int32 tmp = filetime_to_minutes(&start_);
				unsigned __int32 bdate = minutes_to_base_date(tmp);
				tmp -= bdate; // time to start with date component removed
				bdate = get_base_date_of_occurence(oc_time.oID());
				tmp += bdate;

				return minutes_to_filetime(tmp);
			}
			case ::BRUTUS::tt_BaseDate :
			{
				unsigned __int32 tmp = filetime_to_minutes(&start_);
				unsigned __int32 bdate = minutes_to_base_date(tmp);
				tmp -= bdate; // time to start with date component removed
				bdate = oc_time.bdate();
				tmp += bdate;

				return minutes_to_filetime(tmp);
			}
			case ::BRUTUS::tt_FILETIME :
			{
				FILETIME ft;
				::BRUTUS::FILETIME bft = oc_time.ft();

				filetime_brutus_to_mapi_no_alloc(&bft, ft);

				return ft;
			}
			}
#pragma warning( default : 4715 )
		};

	unsigned __int32 occurrence_times_equal(const ::BRUTUS::OccurrenceTime & ot_a,
						const ::BRUTUS::OccurrenceTime & ot_b)
		{
			const unsigned __int32 oid_a = occurrence_time_to_oid(ot_a);
			const unsigned __int32 oid_b = occurrence_time_to_oid(ot_b);

			return (oid_a == oid_b);
		};

private:
	FILETIME start_; // start time of first instance (zero if infinite sequence)
	FILETIME end_;   // start time of last instance (zero if infinite sequence)

	bool pattern_set_;
	::BRUTUS::RecurrencePattern_var pattern_;

	unsigned __int32 get_base_date(const size_t oID,
				       const ::BRUTUS::data_recur_daily_every_N_days & pattern)
		{
			if (oID < 0)
				throw invalid_operation();

			unsigned __int32 retv;

			retv = filetime_to_minutes(&start_);
			retv = minutes_to_base_date(retv);

			if (!oID || (1 == oID)) // the first occurrence
				return retv;

			retv += pattern.N * (MINUTES_IN_DAY * (oID - 1));

			return retv;
		};

	size_t get_oID(const unsigned __int32 base_date,
		       const ::BRUTUS::data_recur_daily_every_N_days & pattern)
		{
			size_t retv;
			unsigned __int32 bdate;

			bdate = filetime_to_minutes(&start_);
			bdate = minutes_to_base_date(bdate);

			if (base_date == bdate) // the first occurrence
				return 1;

			if ((base_date - bdate) % (pattern.N * MINUTES_IN_DAY)) // invalid base date
				throw invalid_operation();

			retv = ((base_date - bdate)/(pattern.N * MINUTES_IN_DAY)) + 1;

			return retv;
		};

	unsigned __int32 get_base_date(const size_t oID,
				       const ::BRUTUS::data_recur_weekly_every_N_weeks & pattern)
		{
			if (oID < 0)
				throw invalid_operation();

			if (!bitcount(pattern.which_days))
				throw invalid_operation();

			unsigned __int32 bdate;

			// calculate the number of mintes since BASE_YEAR to the
			// day of the very first occurrence in the unmodified series
			bdate = filetime_to_minutes(&start_);
			bdate = minutes_to_base_date(bdate);

			if (!oID || (1 == oID)) // the first occurrence
				return bdate;

			SYSTEMTIME st;
			FileTimeToSystemTime(&start_, &st);
			size_t mapi_weekday = (size_t)st.wDayOfWeek;
			const size_t mapi_weekdays_to_look_for = brutus_days_to_mapi_days_bitmask(pattern.which_days);

			size_t n;
			size_t count = 1;
			for (n = st.wDayOfWeek + 1; n < 7; n++) { // iterate over the first week starting the day following bdate (== start_)
				bdate += MINUTES_IN_DAY;
				mapi_weekday = iterate_weekday(1, mapi_weekday);
				if (!mapi_weekday) // week wrap
					break;

				if (mapi_weekdays_to_look_for & mapi_day_to_bitvalue(mapi_weekday)) {
					count++;

					if (oID == count) // hit!
						return bdate;
				}
			}

			//
			// HINT - decrement offset in days to skip fast forward in time
			//

			do { // find the correct hit
				bdate += (pattern.N - 1) * MINUTES_IN_WEEK; // skip the empty weeks

				for (mapi_weekday = 0; mapi_weekday < 7; mapi_weekday++) { // iterate over the this week
					if (mapi_weekdays_to_look_for & mapi_day_to_bitvalue(mapi_weekday)) {
						count++;

						if (oID == count) // hit!
							return bdate;
					}

					// one more day...
					bdate += MINUTES_IN_DAY;
				}
			} while (true);
		};

	size_t get_oID(const unsigned __int32 base_date,
		       const ::BRUTUS::data_recur_weekly_every_N_weeks & pattern)
		{
			unsigned __int32 bdate;

			// calculate the number of mintes since BASE_YEAR to the
			// day of the very first occurrence in the unmodified series
			bdate = filetime_to_minutes(&start_);
			bdate = minutes_to_base_date(bdate);

			if (base_date == bdate) // the first occurrence
				return 1;

			SYSTEMTIME st;
			FileTimeToSystemTime(&start_, &st);
			size_t mapi_weekday = (size_t)st.wDayOfWeek;
			const size_t mapi_weekdays_to_look_for = brutus_days_to_mapi_days_bitmask(pattern.which_days);

			size_t n;
			size_t oid = 1;
			size_t last_hit = oid;
			for (n = st.wDayOfWeek + 1; n < 7; n++) { // iterate over the first week starting the day following bdate (== start_)
				bdate += MINUTES_IN_DAY;
				if (base_date < bdate) // on invalid input
					return last_hit;

				mapi_weekday = iterate_weekday(1, mapi_weekday);
				if (!mapi_weekday) // week wrap
					break;

				if (mapi_weekdays_to_look_for & mapi_day_to_bitvalue(mapi_weekday)) {
					oid++;
					last_hit = oid;

					if (base_date == bdate) // hit!
						return oid;
				}
			}

			//
			// HINT - decrement offset in days to skip fast forward in time
			//

			do { // find the correct hit
				bdate += (pattern.N - 1) * MINUTES_IN_WEEK; // skip the empty weeks

				for (mapi_weekday = 0; mapi_weekday < 7; mapi_weekday++) { // iterate over the this week
					if (mapi_weekdays_to_look_for & mapi_day_to_bitvalue(mapi_weekday)) {
						oid++;
						last_hit = oid;

						if (base_date == bdate) // hit!
							return oid;
					}

					// one more day...
					bdate += MINUTES_IN_DAY;
					if (base_date < bdate) // to avoid endless loops due to invalid input
						return last_hit;
				}
			} while (true);
		};

	unsigned __int32 get_base_date(const size_t oID,
				       const ::BRUTUS::data_recur_weekly_every_N_weeks_for_regenerating_tasks & pattern)
		{
			if (oID < 0)
				throw invalid_operation();

			unsigned __int32 retv;

			retv = filetime_to_minutes(&start_);
			retv = minutes_to_base_date(retv);

			if (!oID || (1 == oID)) // the first occurrence
				return retv;

			retv += pattern.N * (MINUTES_IN_WEEK * (oID - 1));

			return retv;
		};

	size_t get_oID(const unsigned __int32 base_date,
		       const ::BRUTUS::data_recur_weekly_every_N_weeks_for_regenerating_tasks & pattern)
		{
			size_t retv;
			unsigned __int32 bdate;

			bdate = filetime_to_minutes(&start_);
			bdate = minutes_to_base_date(bdate);

			if (base_date == bdate) // the first occurrence
				return 1;

			if ((base_date - bdate) % (pattern.N * MINUTES_IN_WEEK)) // invalid base date
				throw invalid_operation();

			retv = ((base_date - bdate)/(pattern.N * MINUTES_IN_WEEK)) + 1;

			return retv;

		};

	unsigned __int32 get_base_date(const size_t oID,
				       const ::BRUTUS::data_recur_monthly_every_N_months_on_day_D & pattern)
		{
			if (oID < 0)
				throw invalid_operation();

			unsigned __int32 retv = 0;

			retv = filetime_to_minutes(&start_);
			retv = minutes_to_base_date(retv);

			if (!oID || (1 == oID)) // the first occurrence
				return retv;

			SYSTEMTIME st;
			FileTimeToSystemTime(&start_, &st);

			unsigned __int32 julian_date_start = gregorian_to_julian(pattern.D, st.wMonth, st.wYear);
			unsigned __int32 delta_months = (oID - 1) * pattern.N;
			unsigned __int32 delta_years = delta_months / 12;

			unsigned __int32 end_month = iterate_month(delta_months % 12, st.wMonth);
			unsigned __int32 end_year = st.wYear + delta_years;
			unsigned __int32 julian_date_end = gregorian_to_julian(pattern.D, end_month, end_year);

			unsigned __int32 days = julian_date_end - julian_date_start;
			retv += days * MINUTES_IN_DAY;

			return retv;
		};

	unsigned __int32 get_oID(const unsigned __int32 base_date,
				 const ::BRUTUS::data_recur_monthly_every_N_months_on_day_D & pattern)
		{
			unsigned __int32 bdate;

			bdate = filetime_to_minutes(&start_);
			bdate = minutes_to_base_date(bdate);

			if (base_date == bdate) // the first occurrence
				return 1;

			if ((base_date - bdate) % MINUTES_IN_DAY)
				throw invalid_operation();

			SYSTEMTIME st;
			FileTimeToSystemTime(&start_, &st);
			const unsigned __int32 julian_days_delta = (base_date - bdate) / MINUTES_IN_DAY;
			const unsigned __int32 julian_date_start = gregorian_to_julian(pattern.D, st.wMonth, st.wYear);
			const unsigned __int32 julian_date_end = julian_date_start + julian_days_delta;

			unsigned __int32 month = st.wMonth;
			unsigned __int32 month_prev = month;
			unsigned __int32 year = st.wYear;
			unsigned __int32 oid = 1;
			do {
				oid++;
				month = iterate_month(pattern.N, month);
				if (12 < (pattern.N + month_prev))
					year += (pattern.N + month_prev) / 12;
				month_prev = month;
				if (julian_date_end <= gregorian_to_julian(pattern.D, month, year)) // to prevent endless loops
					break;
			} while (true);

			return oid;
		};

	unsigned __int32 get_base_date(const size_t oID,
				       const ::BRUTUS::data_recur_monthly_every_N_months_on_Xth_Y & pattern)
		{
			if (oID < 0)
				throw invalid_operation();

			unsigned __int32 retv = 0;

			retv = filetime_to_minutes(&start_);
			retv = minutes_to_base_date(retv);

			if (!oID || (1 == oID)) // the first occurrence
				return retv;

			SYSTEMTIME st;
			FileTimeToSystemTime(&start_, &st);
			size_t days = days_until_next_Nth_monthly_occurrence(st.wYear,
									     st.wMonth,
									     st.wDay,
									     pattern.N,
									     oID,
									     0,
									     pattern.X,
									     pattern.Y);
			retv += days * MINUTES_IN_DAY;

			return retv;
		};

	unsigned __int32 get_oID(const unsigned __int32 base_date,
				 const ::BRUTUS::data_recur_monthly_every_N_months_on_Xth_Y & pattern)
		{
			unsigned __int32 bdate;

			bdate = filetime_to_minutes(&start_);
			bdate = minutes_to_base_date(bdate);

			if (base_date == bdate) // the first occurrence
				return 1;

			SYSTEMTIME st;
			FileTimeToSystemTime(&start_, &st);
			return Nth_next_monthly_occurrence_hits_until_date(st.wYear,
									   st.wMonth,
									   st.wDay,
									   pattern.N,
									   base_date,
									   0,
									   pattern.X,
									   pattern.Y);
		};

	unsigned __int32 get_base_date(const size_t oID,
				       const ::BRUTUS::data_recur_yearly_on_day_D_of_month_M & pattern)
		{
			if (oID < 0)
				throw invalid_operation();

			unsigned __int32 retv;

			retv = filetime_to_minutes(&start_);
			retv = minutes_to_base_date(retv);

			if (!oID || (1 == oID)) // the first occurrence
				return retv;

			SYSTEMTIME st;
			FileTimeToSystemTime(&start_, &st);
			size_t year = st.wYear;

			size_t n;
			size_t days = 0;
			for (n = 1; n <= oID; n++) {
				if (pattern.M <= 2)
					days += 365 + (leapyear(year) ? 1 : 0);
				else
					days += 365 + (leapyear(year+1) ? 1 : 0);
			}
			retv += days * MINUTES_IN_DAY;

			return retv;
		};

	size_t get_oID(const unsigned __int32 base_date,
		       const ::BRUTUS::data_recur_yearly_on_day_D_of_month_M & pattern)
		{
			unsigned __int32 bdate;

			bdate = filetime_to_minutes(&start_);
			bdate = minutes_to_base_date(bdate);

			if (base_date == bdate) // the first occurrence
				return 1;

			SYSTEMTIME st;
			FileTimeToSystemTime(&start_, &st);
			size_t year = st.wYear;

			size_t oid = 1;
			do {
				if (pattern.M <= 2)
					bdate += (365 + (leapyear(year) ? 1 : 0)) * MINUTES_IN_DAY;
				else
					bdate += (365 + (leapyear(year+1) ? 1 : 0)) * MINUTES_IN_DAY;
				oid++;

				if (base_date <= bdate)
					return oid;
			} while (true);
		};

	unsigned __int32 get_base_date(const size_t oID,
				       const ::BRUTUS::data_recur_yearly_on_the_Xth_Y_of_month_M & pattern)
		{
			if (oID < 0)
				throw invalid_operation();

			unsigned __int32 retv = 0;

			retv = filetime_to_minutes(&start_);
			retv = minutes_to_base_date(retv);

			if (!oID || (1 == oID)) // the first occurrence
				return retv;

			SYSTEMTIME st;
			FileTimeToSystemTime(&start_, &st);
			size_t days = days_until_next_Nth_monthly_occurrence(st.wYear,
									     st.wMonth,
									     st.wDay,
									     12,
									     oID,
									     pattern.M,
									     pattern.X,
									     pattern.Y);
			retv += days * MINUTES_IN_DAY;

			return retv;
		};

	size_t get_oID(const unsigned __int32 base_date,
		       const ::BRUTUS::data_recur_yearly_on_the_Xth_Y_of_month_M & pattern)
		{
			unsigned __int32 bdate;

			bdate = filetime_to_minutes(&start_);
			bdate = minutes_to_base_date(bdate);

			if (base_date == bdate) // the first occurrence
				return 1;

			SYSTEMTIME st;
			FileTimeToSystemTime(&start_, &st);
			return Nth_next_monthly_occurrence_hits_until_date(st.wYear,
									   st.wMonth,
									   st.wDay,
									   12,
									   base_date,
									   0,
									   pattern.X,
									   pattern.Y);
		};

	unsigned __int32 get_start_datetime(const size_t oID,
					    const ::BRUTUS::data_recur_daily_every_N_days & pattern)
		{
			unsigned __int32 retv = 0;

			retv = filetime_to_minutes(&start_); // start in minutes from 1601
			retv -= minutes_to_base_date(retv);  // time of day in minutes from midnight
			retv += get_base_date(oID, pattern); // add to base date of occurrene

			return retv;
		};

	unsigned __int32 get_start_datetime(const size_t oID,
					    const ::BRUTUS::data_recur_weekly_every_N_weeks & pattern)
		{
			unsigned __int32 retv = 0;

			retv = filetime_to_minutes(&start_); // start in minutes from 1601
			retv -= minutes_to_base_date(retv);  // time of day in minutes from midnight
			retv += get_base_date(oID, pattern); // add to base date of occurrene

			return retv;
		};

	unsigned __int32 get_start_datetime(const size_t oID,
					    const ::BRUTUS::data_recur_weekly_every_N_weeks_for_regenerating_tasks & pattern)
		{
			unsigned __int32 retv = 0;

			retv = filetime_to_minutes(&start_); // start in minutes from 1601
			retv -= minutes_to_base_date(retv);  // time of day in minutes from midnight
			retv += get_base_date(oID, pattern); // add to base date of occurrene

			return retv;
		};

	unsigned __int32 get_start_datetime(const size_t oID,
					    const ::BRUTUS::data_recur_monthly_every_N_months_on_day_D & pattern)
		{
			unsigned __int32 retv = 0;

			retv = filetime_to_minutes(&start_); // start in minutes from 1601
			retv -= minutes_to_base_date(retv);  // time of day in minutes from midnight
			retv += get_base_date(oID, pattern); // add to base date of occurrene

			return retv;
		};

	unsigned __int32 get_start_datetime(const size_t oID,
					    const ::BRUTUS::data_recur_monthly_every_N_months_on_Xth_Y & pattern)
		{
			unsigned __int32 retv = 0;

			retv = filetime_to_minutes(&start_); // start in minutes from 1601
			retv -= minutes_to_base_date(retv);  // time of day in minutes from midnight
			retv += get_base_date(oID, pattern); // add to base date of occurrene

			return retv;
		};

	unsigned __int32 get_start_datetime(const size_t oID,
					    const ::BRUTUS::data_recur_yearly_on_day_D_of_month_M & pattern)
		{
			unsigned __int32 retv = 0;

			retv = filetime_to_minutes(&start_); // start in minutes from 1601
			retv -= minutes_to_base_date(retv);  // time of day in minutes from midnight
			retv += get_base_date(oID, pattern); // add to base date of occurrene

			return retv;
		};

	unsigned __int32 get_start_datetime(const size_t oID,
					    const ::BRUTUS::data_recur_yearly_on_the_Xth_Y_of_month_M & pattern)
		{
			unsigned __int32 retv = 0;

			retv = filetime_to_minutes(&start_); // start in minutes from 1601
			retv -= minutes_to_base_date(retv);  // time of day in minutes from midnight
			retv += get_base_date(oID, pattern); // add to base date of occurrene

			return retv;
		};
};


/**************************************************************
 ***                   OccurrenceDiff                       ***
 ***                                                        ***
 ***      Helper class - static array of changes.           ***
 ***                                                        ***
 ***      This class describes the changes to a single      ***
 ***      occurrence in the series.                         ***
 ***                                                        ***
 **************************************************************/

class OccurrenceDiff {
public:
	OccurrenceDiff(const size_t oID,             // occurrence ID (number in series)
		       const unsigned __int32 start, // start time for the occurrence
		       const unsigned __int32 end)   // end time for the occurrence
		{
			size_t n;

			oID_ = oID;
			start_ = start;
			end_ = end;
			for (n = 0; n < len_; n++)
				changes_[n].kind = ::BRUTUS::ExceptionNONE;
		};

	~OccurrenceDiff(void)
		{
			size_t n;

			for (n = 0; n < len_; n++) {
				switch (changes_[n].kind) {
				case ::BRUTUS::ExceptionDescription :
				case ::BRUTUS::ExceptionLocation :
					free(changes_[n].str);
					break;
				default :
					break;
				}
			}
		};

	/*
	 * Will return the occurrence ID, oID
	 */
	size_t oID(void)
		{
			return oID_;
		};

	// Will add a content change to an existing OccurrenceDiff.
	// You can not change the start or end times using add_change(). You
	// need to merge those changes with the += operator.
	void add_change(const ::BRUTUS::RecurrenceException kind,
			const void *data)
		{
			if ((::BRUTUS::ExceptionStartTime == kind)
			    || (::BRUTUS::ExceptionEndTime == kind)
			    || (::BRUTUS::ExceptionNONE == kind)
			    || (::BRUTUS::ExceptionDeletion == kind))
				return;

			size_t idx = kind_to_index(kind);

			// overwite previous string
			if (kind == changes_[idx].kind) {
				switch (kind) {
				case ::BRUTUS::ExceptionDescription :
				case ::BRUTUS::ExceptionLocation :
					free(changes_[idx].str);
					break;
				default :
					break;
				}
			}

			changes_[idx].kind = kind;
			switch (kind) {
			case ::BRUTUS::ExceptionDescription :
			case ::BRUTUS::ExceptionLocation :
				changes_[idx].str = _strdup((const char*)data);
				break;
			case ::BRUTUS::ExceptionAttendee :
			case ::BRUTUS::ExceptionNotesAttachment :
				break;
			default :
				changes_[idx].int32 = *((unsigned __int32*)data);
			}
		}

	// merging changes - rval will merge with lval
	OccurrenceDiff& operator+=(const OccurrenceDiff rval)
		{
			size_t idx;

			// merge the start and end times
			start_ = rval.start_;
			end_ = rval.end_;

			// now merge content changes
			for (idx = 0; idx < len_; idx++) {
				if (::BRUTUS::ExceptionNONE == rval.changes_[idx].kind)
					continue;

				// free previous string if it exists
				switch (changes_[idx].kind) {
				case ::BRUTUS::ExceptionDescription :
				case ::BRUTUS::ExceptionLocation :
					free(changes_[idx].str);
					break;
				default :
					break;
				}
				changes_[idx].kind = rval.changes_[idx].kind;

				// overwrite previous change data
				switch (changes_[idx].kind) {
				case ::BRUTUS::ExceptionDescription :
				case ::BRUTUS::ExceptionLocation :
					changes_[idx].str = _strdup(rval.changes_[idx].str);
					break;
				case ::BRUTUS::ExceptionAttendee :
				case ::BRUTUS::ExceptionNotesAttachment :
					break;
				default :
					changes_[idx].int32 = rval.changes_[idx].int32;
				}

			}
			return *this;
		}

	/*
	 * Will write onto BinaryRepresentation the binary representation
	 * of all modifications to this occurrence in a recurrent serie as
	 * recorded by this class instance
	 */
	void write(ByteArray & BinaryRepresentation)
		{
			BinaryRepresentation.append(sizeof(unsigned __int32), &start_);
			BinaryRepresentation.append(sizeof(unsigned __int32), &end_);
			BinaryRepresentation.append(sizeof(unsigned __int32), &start_);

			// append change bitmask
			size_t idx;
			unsigned __int16 change_flags = 0;
			for (idx = 0; idx < len_; idx++)
				change_flags |= kind_to_bitflag(changes_[idx].kind);
			BinaryRepresentation.append(sizeof(change_flags), &change_flags);

			// no content changes, only the start and/or the end time might have changed
			if (!change_flags)
				return;

			// append all int32 change data
			for (idx = 0; idx < len_; idx++) {
				switch (changes_[idx].kind) {
				case ::BRUTUS::ExceptionReminderTime :
				case ::BRUTUS::ExceptionReminderSet :
				case ::BRUTUS::ExceptionShowTimeAs :
				case ::BRUTUS::ExceptionIsAllDay :
				case ::BRUTUS::ExceptionLabelColor :
					BinaryRepresentation.append(sizeof(unsigned __int32), &changes_[idx].int32);
					break;
				default :
					break;
				}
			}

			// append all string change data
			size_t n;
			bool string_changes = false;
			unsigned __int16 strlen_with_terminator;
			unsigned __int16 strlen_without_terminator;
			for (idx = 0; idx < len_; idx++) {
				switch (changes_[idx].kind) {
				case ::BRUTUS::ExceptionDescription :
				case ::BRUTUS::ExceptionLocation :
					string_changes = true;
					strlen_without_terminator = strlen(changes_[idx].str);
					strlen_with_terminator = strlen_without_terminator + sizeof(char);

					BinaryRepresentation.append(sizeof(strlen_with_terminator), &strlen_with_terminator);    // length with '\0'
					BinaryRepresentation.append(sizeof(strlen_without_terminator), &strlen_without_terminator); // length without '\0'

					for (n = 0; n < strlen_without_terminator; n++)
						BinaryRepresentation.append(sizeof(unsigned __int8), &(changes_[idx].str[n])); // exclude terminating '\0'
					break;
				default :
					break;
				}
			}
			if (string_changes) {
				BinaryRepresentation.append(sizeof(TERMINATOR_TWO), TERMINATOR_TWO);
				BinaryRepresentation.append(sizeof(unsigned __int32), &start_);
				BinaryRepresentation.append(sizeof(unsigned __int32), &end_);
				BinaryRepresentation.append(sizeof(unsigned __int32), &start_);

				// append all string change data in unicode
				LPWSTR wstr = NULL;
				for (idx = 0; idx < len_; idx++) {
					switch (changes_[idx].kind) {
					case ::BRUTUS::ExceptionDescription :
					case ::BRUTUS::ExceptionLocation :
						wstr = ascii_to_unicode(NULL, changes_[idx].str);

						strlen_without_terminator = strlen(changes_[idx].str);
						BinaryRepresentation.append(sizeof(strlen_with_terminator), &strlen_with_terminator); // length with '\0'
						for (n = 0; n < strlen_without_terminator; n++)
							BinaryRepresentation.append(sizeof(WCHAR), &(wstr[n])); // exclude terminating '\0'

						break;
					default :
						break;
					}
					if (wstr)
						MAPIFreeBuffer(wstr);
					wstr = NULL;
				}
			}

		};

private:
	size_t kind_to_index(const ::BRUTUS::RecurrenceException kind)
		{
			switch (kind) {
			case ::BRUTUS::ExceptionDescription :
				return 0;
			case ::BRUTUS::ExceptionAttendee :
				return 1;
			case ::BRUTUS::ExceptionReminderTime :
				return 2;
			case ::BRUTUS::ExceptionReminderSet :
				return 3;
			case ::BRUTUS::ExceptionLocation :
				return 4;
			case ::BRUTUS::ExceptionShowTimeAs :
				return 5;
			case ::BRUTUS::ExceptionIsAllDay :
				return 6;
			case ::BRUTUS::ExceptionLabelColor :
				return 7;
			case ::BRUTUS::ExceptionNotesAttachment :
				return 8;
			default :
				throw invalid_operation();
			}
		};

	size_t kind_to_bitflag(const ::BRUTUS::RecurrenceException kind)
		{
			switch (kind) {
			case ::BRUTUS::ExceptionDescription :
				return DESCRIPTION_DEVIATION;
			case ::BRUTUS::ExceptionAttendee :
				return ATTENDEE_DEVIATION; // creates attachment
			case ::BRUTUS::ExceptionReminderTime :
				return REMINDER_TIME_DEVIATION;
			case ::BRUTUS::ExceptionReminderSet :
				return REMINDER_SET_DEVIATION;
			case ::BRUTUS::ExceptionLocation :
				return LOCATION_DEVIATION;
			case ::BRUTUS::ExceptionShowTimeAs :
				return SHOW_TIME_AS_DEVIATION;
			case ::BRUTUS::ExceptionIsAllDay :
				return IS_ALL_DAY_DEVIATION;
			case ::BRUTUS::ExceptionLabelColor :
				return LABEL_COLOR_DEVIATION;
			case ::BRUTUS::ExceptionNotesAttachment :
				return NOTES_ATTACHMENT_DEVIATION; // creates attachment
			default :
				throw invalid_operation();
			}
		};

	static const size_t len_ = 9;

	struct Change {
		::BRUTUS::RecurrenceException kind;
		union {
			char *str;
			unsigned __int32 int32;
		};
	};
	struct Change changes_[len_];

	size_t oID_;
	unsigned __int32 start_; // start datetime of changed occurence in minutes since 1601
	unsigned __int32 end_;   // end datetime of changed occurence in minutes since 1601
};


/**************************************************************
 ***                  OccurrenceDiffArray                   ***
 ***                                                        ***
 ***      Helper class - ordered ascending array of changes ***
 ***      to individual occurrences in a recurrence serie.  ***
 ***                                                        ***
 ***      This class describes all changes to a recurrence  ***
 ***      serie.                                            ***
 ***                                                        ***
 **************************************************************/

class OccurrenceDiffArray {
public:
	OccurrenceDiffArray(void)
		{
			len_ = 0;
			allocated_ = 0;
			change_list_ = NULL;
		};

	~OccurrenceDiffArray(void)
		{
			size_t n;
			for (n = 0; n < len_; n++) {
				if (change_list_[n])
					delete change_list_[n];
			};
			free(change_list_);
		};

	void insert(OccurrenceDiff *diff)
		{
			size_t n;

			// check if we need to allocate another chunk. There must
			// always be room for at least len_+1 entries
			if ((len_ + 1) >= (allocated_ / sizeof(OccurrenceDiff*))) {
				allocated_ += sizeof(OccurrenceDiff*) * chunk_size_;
				void *tmp = realloc((void*)change_list_, allocated_);
				if (!tmp) {
					allocated_ -= sizeof(OccurrenceDiff*) * chunk_size_;
					throw std::bad_alloc();
				}
				change_list_ = (OccurrenceDiff**)tmp;

				for (n = len_; n < len_ + chunk_size_; n++)
					change_list_[n] = NULL;
			}

			// merge with duplicate
			n = find(diff->oID());
			if (-1 != n) { // found - will merge
				*change_list_[n] += *diff;
				delete diff;
				return;
			}

			// get index (n)
			for (n = 0; n < len_; n++) {
				if (diff->oID() < change_list_[n]->oID())
					break;
			}

			// push bigger entries one step up
			size_t k;
			for (k = len_;  k > n; k--)
				change_list_[k] = change_list_[k-1];

			// put the new diff into the right index
			change_list_[n] = diff;
		};

	void remove(const size_t oID)
		{
			size_t n = find(oID);

			if (-1 == n)
				return;;

			delete change_list_[n];

			// pull bigger entries one step down
			len_--;
			size_t k;
			for (k = n;  k < len_; k++)
				change_list_[k] = change_list_[k+1];

			// fix the last entry
			change_list_[len_] = NULL;
		};

	size_t find(const size_t oID)
		{
			size_t n;

			for (n = 0; n < len_; n++) {
				if (oID == change_list_[n]->oID())
					return n;
			}
			return -1;
		};

	/*
	 * Will write onto BinaryRepresentation the binary representation
	 * of all exceptions to all occurrences of a recurrent serie as
	 * recorded by this class instance for each individual occurrence
	 */
	void write(ByteArray & BinaryRepresentation)
		{
			size_t n;

			for (n = 0; n < len_; n++)
				change_list_[n]->write(BinaryRepresentation);
		};
private:
	size_t len_;
	size_t allocated_;
	OccurrenceDiff **change_list_;
	static const size_t chunk_size_ = 10;
};

/*******************************************************
 ***           Common recurrence data class          ***
 *******************************************************/

class RecurBase {
public:
	RecurBase(void)
		{
			initialized_ = false;
		};

	~RecurBase(void)
		{
		};

	/*
	 * Will clear the internal binary representation
	 */
	void reset(void)
		{
			binary_repr.length(0);
			exception_dates_.clear();
			modification_dates_.clear();
		};

	bool same_time(const ::BRUTUS::RecurrenceExceptionData & exception_data,
		       const ::BRUTUS::OccurrenceTime & oc_time)
		{
			return dater_.occurrence_times_equal(exception_data.oc_time, oc_time);
		}

	/*
	 * Will delete a single occurence in the series
	 */
	RecurBase & operator-=(const ::BRUTUS::OccurrenceTime oc_time) // occurence ID (or number in the series) - throws (invalid_operation)
		{
			if (!initialized_)
				throw invalid_operation();

			unsigned __int32 date_of_deletion = dater_.occurrence_time_to_base_date(oc_time);

			// maybe it was previously modified but is now beeing deleted
			modification_dates_.remove(date_of_deletion);
			diff_list_.remove(dater_.occurrence_time_to_oid(oc_time));

			// insert as deletion in series
			deletion_dates_.insert(date_of_deletion);
			exception_dates_.insert(date_of_deletion);

			return *this;
		};

	/*
	 * Will append a single modification to a occurence in the series
	 */
	RecurBase & operator+=(const ::BRUTUS::RecurrenceExceptionData & data) // throws (invalid_operation)
		{
			if (!initialized_)
				throw invalid_operation();

			if (::BRUTUS::ExceptionDeletion == data.data._d())
				throw invalid_operation();

			unsigned __int32 oid = dater_.occurrence_time_to_oid(data.oc_time);
			unsigned __int32 base_date_of_change = dater_.occurrence_time_to_base_date(data.oc_time);

			deletion_dates_.remove(base_date_of_change);
			modification_dates_.insert(base_date_of_change);
			exception_dates_.insert(base_date_of_change);

			FILETIME ft;
			filetime_brutus_to_mapi_no_alloc(&data.start, ft);
			unsigned __int32 start_of_occurrence = filetime_to_minutes(&ft);
			filetime_brutus_to_mapi_no_alloc(&data.end, ft);
			unsigned __int32 end_of_occurrence = filetime_to_minutes(&ft);

			OccurrenceDiff *diff = new OccurrenceDiff(oid, start_of_occurrence, end_of_occurrence);
			if ((::BRUTUS::ExceptionStartTime != data.data._d()) && (::BRUTUS::ExceptionEndTime != data.data._d())) {
				void *content_change;
				unsigned __int32 int32;
				char *str = NULL;

				switch (data.data._d()) {
				case ::BRUTUS::ExceptionDescription :
				case ::BRUTUS::ExceptionLocation :
					str = _strdup((const char*)data.data.str());
					content_change = str;
					break;
				case ::BRUTUS::ExceptionAttendee :
				case ::BRUTUS::ExceptionNotesAttachment :
					break;
				default :
					int32 = (unsigned __int32)data.data.int32();
					content_change = &int32;
				}
				diff->add_change(data.data._d(), content_change); // add content change
				if (str)
					free(str);
			}
			diff_list_.insert(diff);

			return *this;
		};

	/*
	 * Will return the binary representation of a recurring event or task
	 * as well as all accumulated modifications to individual occurrences.
	 *
	 * You must call init() on the derived recurrence class and then add
	 * individual exceptions to occurrences before calling this function.
	 */
	bool get_sbinary(void *Parent,
			 SBinary * const bin)
		{
			reset();
			prefill();
			finalize();

			return binary_repr.get_sbinary(Parent, bin);
		};

protected:
	virtual void prefill(void) = 0; // throws (invalid_operation)

	void finalize(void); // throws (invalid_operation)

	/*
	 * The following members must be initialized
	 */
	bool initialized_; // has the class been initialized?

	bool is_event_; // true (for events) or false (for tasks)

	unsigned __int32 start_;    // start time of occurence serie in minutes since 1601
	unsigned __int32 end_;      // end time of occurence serie in minutes since 1601
	unsigned __int32 duration_; // duration of the event/task in minutes

	unsigned __int32 occurrence_count_;   // The total number of occurrences to happen if how_to_end is term_date or term_number. 10 (the default number) for an infinite sequence.
	unsigned __int32 exception_count_;    // Number of exceptions to the pattern (i.e. deleted or modified occurrences)
	unsigned __int32 modification_count_; // Number of modified occurrences

	::BRUTUS::RecurrenceTerminationType how_to_end_; // never, after a certain number of occurences or on a specific date
	::BRUTUS::DayOfWeek first_day_of_week_;          // First day of week

	/*
	 * The following members must be build iteratively
	 */
	Int32Array exception_dates_;    // base date for all exceptions
	Int32Array modification_dates_; // base date for all modified occurences

	ByteArray binary_repr; // The Outlook 2003 compatible binary representation of the recurrence data

	// calendar utility class
	RecurDate dater_;

private:
	void append_modification_data(ByteArray & BinaryRepresentation); // throws (invalid_operation)

	OccurrenceDiffArray diff_list_; // changed occurrences and their modified data

	Int32Array deletion_dates_; // base date for all deleted occurences
};

void RecurBase::append_modification_data(ByteArray & BinaryRepresentation) // throws (invalid_operation)
{
	if (!BinaryRepresentation.length())
		throw invalid_operation();

	diff_list_.write(BinaryRepresentation);
}

void RecurBase::finalize(void) // throws (invalid_operation)
{
	if (!binary_repr.length())
		throw invalid_operation();

	size_t n;
	unsigned __int8 tmp8;
	unsigned __int16 tmp16;
	unsigned __int32 tmp32;

	exception_count_ = exception_dates_.length();
	modification_count_ = modification_dates_.length();

	//
	// Additional occurence data - common for all pattern types
	//
	tmp8 = how_to_end_brutus_to_mapi(how_to_end_);
	binary_repr.append(sizeof(__int8), &tmp8);
	binary_repr.append(sizeof(CONSTANT_ONE), CONSTANT_ONE);
	binary_repr.append(sizeof(occurrence_count_), &occurrence_count_);
	tmp32 = day_of_week_brutus_to_mapi(first_day_of_week_);
	binary_repr.append(sizeof(tmp32), &tmp32);
	binary_repr.append(sizeof(exception_count_), &exception_count_);

	// base date of each exception - TODO: calculate base date
	for (n = 0; n < exception_dates_.length(); n++) {
		tmp32 = exception_dates_[n];
		binary_repr.append(sizeof(tmp32), &tmp32);
	}

	// number of changed exceptions
	binary_repr.append(sizeof(modification_count_), &modification_count_);

	// base date of each changed exception - TODO: calculate base date
	for (n = 0; n < modification_dates_.length(); n++) {
		tmp32 = modification_dates_[n];
		binary_repr.append(sizeof(tmp32), &tmp32);
	}

	// start and end times of series
	binary_repr.append(sizeof(start_), &start_);
	binary_repr.append(sizeof(end_), &end_);

	if (is_event_) {
		binary_repr.append(sizeof(CONSTANT_TWO), CONSTANT_TWO);
		binary_repr.append(sizeof(OUTLOOK_2003), &OUTLOOK_2003);
		binary_repr.append(sizeof(CONSTANT_THREE), CONSTANT_THREE);
		tmp32 = start_ % MINUTES_IN_DAY;
		binary_repr.append(sizeof(tmp32), &tmp32);
		tmp32 += duration_;
		binary_repr.append(sizeof(tmp32), &tmp32);
	}

	tmp16 = (unsigned __int16)modification_count_;
	binary_repr.append(sizeof(tmp16), &tmp16);

	// write exception data
	append_modification_data(binary_repr);

	// terminator
	for (n = 0; n < modification_count_; n++)
		binary_repr.append(sizeof(TERMINATOR_ONE), TERMINATOR_ONE);
	binary_repr.append(sizeof(TERMINATOR_TWO), TERMINATOR_TWO);
}


/*******************************************************
 ***               Daily every N days                ***
 *******************************************************/

class mapi_daily_every_N_days : public RecurBase {
public:
	mapi_daily_every_N_days(void)
		{
		};

	~mapi_daily_every_N_days(void)
		{
		};

	// throws (invalid_operation)
	void init(const unsigned __int32 N,                             // interval in days
		  FILETIME * const start,                               // start time of first instance
		  FILETIME * const end,                                 // start time of last instance (zero if infinite sequence)
		  const unsigned __int32 duration,                      // duration of the event/task in minutes
		  const unsigned __int32 occurrence_count,              // total number of occurences
		  const ::BRUTUS::RecurrenceTerminationType how_to_end, // by date, by number or never
		  const ::BRUTUS::DayOfWeek first_day_of_week,          // Sunday, Monday ... or Saturday
		  const bool is_event,                                  // is_event (true) or task (false)
		  const bool regenerating_task)
		{
			dater_.set_start(start);
			dater_.set_end(end);

			is_event_ = is_event;

			start_ = filetime_to_minutes(start);
			end_ = (end->dwLowDateTime || end->dwHighDateTime) ? filetime_to_minutes(end) : 0;
			interval_ = N * 60 * 24;
			duration_ = duration;
			occurrence_count_ = (::BRUTUS::term_never == how_to_end) ? 10 : occurrence_count;

			regenerating_ = regenerating_task ? 1 : 0;

			how_to_end_ = how_to_end;
			first_day_of_week_ = first_day_of_week;

			initialized_ = true;
		};

	// throws (invalid_operation)
	void prefill(void)
		{
			if (!initialized_)
				throw invalid_operation();

			unsigned __int32 tmp32;

			binary_repr.length(BASE_SIZE_OF_EXCEPTION_BLOB);

			//
			// Base header - common for all pattern types
			//
			binary_repr.append(sizeof(RECUR_HEADER), RECUR_HEADER);
			binary_repr.append(sizeof(preamble_), preamble_);
			binary_repr.append(sizeof(sub_type_), &sub_type_);

			//
			// Base pattern struct - unique for this type of pattern
			//

			// calculate and write the value at index 10
			tmp32 = start_ % interval_;
			binary_repr.append(sizeof(tmp32), &tmp32);

			binary_repr.append(sizeof(regenerating_), &regenerating_);
		}
private:
	/*
	 * Must be initialized
	 */
	unsigned __int32 interval_;     // number of minutes between occurences
	unsigned __int32 regenerating_; // boolean (0/1) - regenerating (1) or not (0)

	/*
	 * The following members are static constants
	 */
	static const unsigned __int8 preamble_[2];
	static const unsigned __int32 sub_type_ = 0;
};
const unsigned __int8 mapi_daily_every_N_days::preamble_[2] = { RECUR_DAILY, 0x20 };


/*******************************************************
 ***              Weekly every N days                ***
 *******************************************************/

class mapi_weekly_every_N_weeks : public RecurBase {
public:
	mapi_weekly_every_N_weeks(void)
		{
		};

	~mapi_weekly_every_N_weeks(void)
		{
		};

	// throws (invalid_operation)
	void init(const unsigned __int32 N,                             // interval in weeks
		  FILETIME * const start,                               // start time of first instance
		  FILETIME * const end,                                 // start time of last instance (zero if infinite sequence)
		  const unsigned __int32 duration,                      // duration of the event/task in minutes
		  const unsigned __int32 occurrence_count,              // total number of occurences
		  const ::BRUTUS::RecurrenceTerminationType how_to_end, // by date, by number or never
		  const ::BRUTUS::DayOfWeek first_day_of_week,          // Sunday, Monday ... or Saturday
		  const bool is_event,                                  // is event (true) or task (false)
		  const bool regenerating_task,
		  const ::BRUTUS::DayOfWeek DayMask)                    // bitmask of days upon which the event is occurring
		{
			dater_.set_start(start);
			dater_.set_end(end);

			is_event_ = is_event;

			start_ = filetime_to_minutes(start);
			end_ = (end->dwLowDateTime || end->dwHighDateTime) ? filetime_to_minutes(end) : 0;
			interval_ = N * 60 * 24 * 7;
			duration_ = duration;
			occurrence_count_ = (::BRUTUS::term_never == how_to_end) ? 10 : occurrence_count;

			regenerating_ = regenerating_task ? 1 : 0;

			how_to_end_ = how_to_end;
			first_day_of_week_ = first_day_of_week;

			day_mask_ = brutus_days_to_mapi_days_bitmask(DayMask);

			initialized_ = true;
		};

	// throws (invalid_operation)
	void prefill(void)
		{
			if (!initialized_)
				throw invalid_operation();

			binary_repr.length(BASE_SIZE_OF_EXCEPTION_BLOB);

			//
			// Base header - common for all pattern types
			//
			binary_repr.append(sizeof(RECUR_HEADER), RECUR_HEADER);
			binary_repr.append(sizeof(preamble_), preamble_);
			binary_repr.append(sizeof(sub_type_), &sub_type_);

			//
			// Base pattern struct - unique for this type of pattern
			//
			unsigned __int32 tmp32;

			// get day of week for the start of the series
			SYSTEMTIME st;
			FILETIME ft = minutes_to_filetime(start_);
			FileTimeToSystemTime(&ft, &st);

			// calculate and write the value at index 10
			tmp32 = start_ % interval_;
			if ((6 * 24 * 60) > tmp32)
				tmp32 += interval_;
			tmp32 -= (st.wDayOfWeek - day_of_week_brutus_to_mapi(first_day_of_week_)) * 24 * 60;
			binary_repr.append(sizeof(tmp32), &tmp32);

			tmp32 = interval_ / (60 * 24 * 7);
			binary_repr.append(sizeof(tmp32), &tmp32);
			binary_repr.append(sizeof(regenerating_), &regenerating_);
			binary_repr.append(sizeof(day_mask_), &day_mask_);
		}
private:
	/*
	 * Must be initialized
	 */
	unsigned __int32 interval_;     // number of minutes between occurrences
	unsigned __int32 regenerating_; // boolean (0/1) - regenerating (1) or not (0)
	unsigned __int32 day_mask_;     // bitmask to determine which days of the week to occur on

	/*
	 * The following members are static constants
	 */
	static const unsigned __int8 preamble_[2];
	static const unsigned __int32 sub_type_ = 1;
};
const unsigned __int8 mapi_weekly_every_N_weeks::preamble_[2] = { RECUR_WEEKLY, 0x20 };


/*******************************************************
 ***   Weekly every N days for regenerating tasks    ***
 *******************************************************/

class mapi_weekly_every_N_weeks_for_regenerating_tasks : public RecurBase {
public:
	mapi_weekly_every_N_weeks_for_regenerating_tasks(void)
		{
		};

	~mapi_weekly_every_N_weeks_for_regenerating_tasks(void)
		{
		};

	// throws (invalid_operation)
	void init(const unsigned __int32 N,                             // interval in weeks
		  FILETIME * const start,                               // start time of first instance
		  FILETIME * const end,                                 // start time of last instance (zero if infinite sequence)
		  const unsigned __int32 duration,                      // duration of the event/task in minutes
		  const unsigned __int32 occurrence_count,              // total number of occurences
		  const ::BRUTUS::RecurrenceTerminationType how_to_end, // by date, by number or never
		  const ::BRUTUS::DayOfWeek first_day_of_week)          // Sunday, Monday ... or Saturday
		{
			dater_.set_start(start);
			dater_.set_end(end);

			is_event_ = false;

			start_ = filetime_to_minutes(start);
			end_ = (end->dwLowDateTime || end->dwHighDateTime) ? filetime_to_minutes(end) : 0;
			interval_ = N * 60 * 24 * 7;
			duration_ = duration;
			occurrence_count_ = (::BRUTUS::term_never == how_to_end) ? 10 : occurrence_count;

			how_to_end_ = how_to_end;
			first_day_of_week_ = first_day_of_week;

			initialized_ = true;
		};

	// throws (invalid_operation)
	void prefill(void)
		{
			if (!initialized_)
				throw invalid_operation();

			binary_repr.length(BASE_SIZE_OF_EXCEPTION_BLOB);

			//
			// Base header - common for all pattern types
			//
			binary_repr.append(sizeof(RECUR_HEADER), RECUR_HEADER);
			binary_repr.append(sizeof(preamble_), preamble_);
			binary_repr.append(sizeof(sub_type_), &sub_type_);

			//
			// Base pattern struct - unique for this type of pattern
			//
			unsigned __int32 tmp32;

			// calculate and write the value at index 10
			tmp32 = (start_ % (60 * 24 * 7)) + (interval_/(60 * 24 * 7));
			binary_repr.append(sizeof(tmp32), &tmp32);

			binary_repr.append(sizeof(interval_), &interval_);
			tmp32 = 1;
			binary_repr.append(sizeof(tmp32), &tmp32);
		}
private:
	/*
	 * Must be initialized
	 */
	unsigned __int32 interval_;     // number of minutes between occurrences

	/*
	 * The following members are static constants
	 */
	static const unsigned __int8 preamble_[2];
	static const unsigned __int32 sub_type_ = 0;
	static const unsigned __int32 regenerating_ = 1;
};
const unsigned __int8 mapi_weekly_every_N_weeks_for_regenerating_tasks::preamble_[2] = { RECUR_WEEKLY, 0x20 };


/*******************************************************
 ***          Monthly every N months on day D        ***
 *******************************************************/

class mapi_monthly_every_N_months_on_day_D : public RecurBase {
public:
	mapi_monthly_every_N_months_on_day_D(void)
		{
		};

	~mapi_monthly_every_N_months_on_day_D(void)
		{
		};

	// throws (invalid_operation)
	void init(const unsigned __int32 N,                             // interval in months
		  const unsigned __int32 D,                             // day to occur on
		  FILETIME * const start,                               // start time of first instance
		  FILETIME * const end,                                 // start time of last instance (zero if infinite sequence)
		  const unsigned __int32 duration,                      // duration of the event/task in minutes
		  const unsigned __int32 occurrence_count,              // total number of occurences
		  const ::BRUTUS::RecurrenceTerminationType how_to_end, // by date, by number or never
		  const bool regenerating_task,
		  const ::BRUTUS::DayOfWeek first_day_of_week)          // Sunday, Monday ... or Saturday
		{
			dater_.set_start(start);
			dater_.set_end(end);

			is_event_ = false;

			start_ = filetime_to_minutes(start);
			end_ = (end->dwLowDateTime || end->dwHighDateTime) ? filetime_to_minutes(end) : 0;
			n_ = N;
			d_ = D;
			duration_ = duration;
			occurrence_count_ = (::BRUTUS::term_never == how_to_end) ? 10 : occurrence_count;

			how_to_end_ = how_to_end;
			regenerating_ = regenerating_task ? 1 : 0;
			first_day_of_week_ = first_day_of_week;

			initialized_ = true;
		};

	// throws (invalid_operation)
	void prefill(void)
		{
			if (!initialized_)
				throw invalid_operation();

			binary_repr.length(BASE_SIZE_OF_EXCEPTION_BLOB);

			//
			// Base header - common for all pattern types
			//
			binary_repr.append(sizeof(RECUR_HEADER), RECUR_HEADER);
			binary_repr.append(sizeof(preamble_), preamble_);
			binary_repr.append(sizeof(sub_type_), &sub_type_);

			//
			// Base pattern struct - unique for this type of pattern
			//
			unsigned __int32 tmp32 = 0;


			// get calendar start values for the start of the series
			SYSTEMTIME st;
			FILETIME ft = minutes_to_filetime(start_);
			FileTimeToSystemTime(&ft, &st);

			// calculate and write the value at index 10
			unsigned __int32 monthIndex = ((((12 % n_) * ((st.wYear - 1601) % n_)) % n_) + (st.wMonth - 1)) % n_;
			for(unsigned __int32 i = 0; i < monthIndex; i++)
				tmp32 += DAYS_IN_MONTH[(i % 12) + 1] * 24 * 60; // do not take leap years into account
			binary_repr.append(sizeof(tmp32), &tmp32);

			binary_repr.append(sizeof(n_), &n_);
			binary_repr.append(sizeof(regenerating_), &regenerating_);
			binary_repr.append(sizeof(d_), &d_);
		}
private:
	/*
	 * Must be initialized
	 */
	unsigned __int32 n_;            // number of months between occurrences
	unsigned __int32 d_;            // day in month to occur on - 1..31
	unsigned __int32 regenerating_; // boolean (0/1) - regenerating (1) or not (0)

	/*
	 * The following members are static constants
	 */
	static const unsigned __int8 preamble_[2];
	static const unsigned __int32 sub_type_ = 2;
};
const unsigned __int8 mapi_monthly_every_N_months_on_day_D::preamble_[2] = { RECUR_MONTHLY, 0x20 };


/*******************************************************
 ***          Monthly every N months on Xth Y        ***
 *******************************************************/

class mapi_monthly_every_N_months_on_Xth_Y : public RecurBase {
public:
	mapi_monthly_every_N_months_on_Xth_Y(void)
		{
		};

	~mapi_monthly_every_N_months_on_Xth_Y(void)
		{
		};

	// throws (invalid_operation)
	void init(const unsigned __int32 N,                             // interval in months
		  const unsigned __int32 X,                             // 1 => first occurrence, 2 => second occurrence ... 5 => last occurrence
		  const ::BRUTUS::DayOfWeek DayMask,                    // bitmask for weekdays to occur on
		  FILETIME * const start,                               // start time of first instance
		  FILETIME * const end,                                 // start time of last instance (zero if infinite sequence)
		  const unsigned __int32 duration,                      // duration of the event/task in minutes
		  const unsigned __int32 occurrence_count,              // total number of occurences
		  const ::BRUTUS::RecurrenceTerminationType how_to_end, // by date, by number or never
		  const bool regenerating_task,
		  const ::BRUTUS::DayOfWeek first_day_of_week)          // Sunday, Monday ... or Saturday
		{
			dater_.set_start(start);
			dater_.set_end(end);

			is_event_ = false;

			start_ = filetime_to_minutes(start);
			end_ = (end->dwLowDateTime || end->dwHighDateTime) ? filetime_to_minutes(end) : 0;
			n_ = N;
			x_ = X;
			y_ = brutus_days_to_mapi_days_bitmask(DayMask);
			duration_ = duration;
			occurrence_count_ = (::BRUTUS::term_never == how_to_end) ? 10 : occurrence_count;

			how_to_end_ = how_to_end;
			regenerating_ = regenerating_task ? 1 : 0;
			first_day_of_week_ = first_day_of_week;

			initialized_ = true;
		};

	// throws (invalid_operation)
	void prefill(void)
		{
			if (!initialized_)
				throw invalid_operation();

			binary_repr.length(BASE_SIZE_OF_EXCEPTION_BLOB);

			//
			// Base header - common for all pattern types
			//
			binary_repr.append(sizeof(RECUR_HEADER), RECUR_HEADER);
			binary_repr.append(sizeof(preamble_), preamble_);
			binary_repr.append(sizeof(sub_type_), &sub_type_);

			//
			// Base pattern struct - unique for this type of pattern
			//
			unsigned __int32 tmp32 = 0;

			// get calendar start values for the start of the series
			SYSTEMTIME st;
			FILETIME ft = minutes_to_filetime(start_);
			FileTimeToSystemTime(&ft, &st);

			// calculate and write the value at index 10
			unsigned __int32 monthIndex = ((((12 % n_) * ((st.wYear - 1601) % n_)) % n_) + (st.wMonth - 1)) % n_;
			for(unsigned __int32 i = 0; i < monthIndex; i++)
				tmp32 += DAYS_IN_MONTH[(i % 12) + 1] * 24 * 60; // do not take leap years into account
			binary_repr.append(sizeof(tmp32), &tmp32);

			binary_repr.append(sizeof(n_), &n_);
			binary_repr.append(sizeof(regenerating_), &regenerating_);
			binary_repr.append(sizeof(y_), &y_);
			binary_repr.append(sizeof(x_), &x_);
		}
private:
	/*
	 * Must be initialized
	 */
	unsigned __int32 n_;            // number of months between occurrences
	unsigned __int32 y_;            // bitmask for weekdays
	unsigned __int32 x_;            // occurrence number - x == 2 => every second day as specified by y_
	unsigned __int32 regenerating_; // boolean (0/1) - regenerating (1) or not (0)

	/*
	 * The following members are static constants
	 */
	static const unsigned __int8 preamble_[2];
	static const unsigned __int32 sub_type_ = 3;
};
const unsigned __int8 mapi_monthly_every_N_months_on_Xth_Y::preamble_[2] = { RECUR_MONTHLY, 0x20 };


/*******************************************************
 ***            Yearly on day D of months M          ***
 *******************************************************/

class mapi_yearly_on_day_D_of_month_M : public RecurBase {
public:
	mapi_yearly_on_day_D_of_month_M(void)
		{
		};

	~mapi_yearly_on_day_D_of_month_M(void)
		{
		};

	// throws (invalid_operation)
	void init(const unsigned __int32 D,                             // day to occure on - 1..31
		  const unsigned __int32 M,                             // month to occur in - 1..12
		  FILETIME * const start,                               // start time of first instance
		  FILETIME * const end,                                 // start time of last instance (zero if infinite sequence)
		  const unsigned __int32 duration,                      // duration of the event/task in minutes
		  const unsigned __int32 occurrence_count,              // total number of occurences
		  const ::BRUTUS::RecurrenceTerminationType how_to_end, // by date, by number or never
		  const bool regenerating_task,
		  const ::BRUTUS::DayOfWeek first_day_of_week)          // Sunday, Monday ... or Saturday
		{
			dater_.set_start(start);
			dater_.set_end(end);

			is_event_ = false;

			start_ = filetime_to_minutes(start);
			end_ = (end->dwLowDateTime || end->dwHighDateTime) ? filetime_to_minutes(end) : 0;
			d_ = D;
			m_ = M;
			duration_ = duration;
			occurrence_count_ = (::BRUTUS::term_never == how_to_end) ? 10 : occurrence_count;

			how_to_end_ = how_to_end;
			regenerating_ = regenerating_task ? 1 : 0;
			first_day_of_week_ = first_day_of_week;

			initialized_ = true;
		};

	// throws (invalid_operation)
	void prefill(void)
		{
			if (!initialized_)
				throw invalid_operation();

			binary_repr.length(BASE_SIZE_OF_EXCEPTION_BLOB);

			//
			// Base header - common for all pattern types
			//
			binary_repr.append(sizeof(RECUR_HEADER), RECUR_HEADER);
			binary_repr.append(sizeof(preamble_), preamble_);
			binary_repr.append(sizeof(sub_type_), &sub_type_);

			//
			// Base pattern struct - unique for this type of pattern
			//
			unsigned __int32 tmp32 = 0;

			// calculate and write the value at index 10
			for(unsigned __int32 i = 0; i < m_; i++)
				tmp32 += DAYS_IN_MONTH[i] * 24 * 60; // do not take leap years into account
			binary_repr.append(sizeof(tmp32), &tmp32);

			tmp32 = 12;
			binary_repr.append(sizeof(tmp32), &tmp32);
			binary_repr.append(sizeof(regenerating_), &regenerating_);
			binary_repr.append(sizeof(d_), &d_);
		}
private:
	/*
	 * Must be initialized
	 */
	unsigned __int32 d_;            // day in month to occur on - 1..31
	unsigned __int32 m_;            // month to occur in
	unsigned __int32 regenerating_; // boolean (0/1) - regenerating (1) or not (0)

	/*
	 * The following members are static constants
	 */
	static const unsigned __int8 preamble_[2];
	static const unsigned __int32 sub_type_ = 2;
};
const unsigned __int8 mapi_yearly_on_day_D_of_month_M::preamble_[2] = { RECUR_YEARLY, 0x20 };


/*******************************************************
 ***          Yearly on the Xth Y of months M        ***
 *******************************************************/

class mapi_yearly_on_the_Xth_Y_of_month_M : public RecurBase {
public:
	mapi_yearly_on_the_Xth_Y_of_month_M(void)
		{
		};

	~mapi_yearly_on_the_Xth_Y_of_month_M(void)
		{
		};

	// throws (invalid_operation)
	void init(const unsigned __int32 M,                             // month to occur in
		  const unsigned __int32 X,                             // 1 => first occurrence, 2 => second occurrence ... 5 => last occurrence
		  const ::BRUTUS::DayOfWeek DayMask,                    // bitmask for weekdays to occur on
		  FILETIME * const start,                               // start time of first instance
		  FILETIME * const end,                                 // start time of last instance (zero if infinite sequence)
		  const unsigned __int32 duration,                      // duration of the event/task in minutes
		  const unsigned __int32 occurrence_count,              // total number of occurences
		  const ::BRUTUS::RecurrenceTerminationType how_to_end, // by date, by number or never
		  const ::BRUTUS::DayOfWeek first_day_of_week)          // Sunday, Monday ... or Saturday
		{
			dater_.set_start(start);
			dater_.set_end(end);

			is_event_ = false;

			start_ = filetime_to_minutes(start);
			end_ = (end->dwLowDateTime || end->dwHighDateTime) ? filetime_to_minutes(end) : 0;
			m_ = M;
			x_ = X;
			y_ = brutus_days_to_mapi_days_bitmask(DayMask);
			duration_ = duration;
			occurrence_count_ = (::BRUTUS::term_never == how_to_end) ? 10 : occurrence_count;

			how_to_end_ = how_to_end;
			first_day_of_week_ = first_day_of_week;

			initialized_ = true;
		};

	// throws (invalid_operation)
	void prefill(void)
		{
			if (!initialized_)
				throw invalid_operation();

			binary_repr.length(BASE_SIZE_OF_EXCEPTION_BLOB);

			//
			// Base header - common for all pattern types
			//
			binary_repr.append(sizeof(RECUR_HEADER), RECUR_HEADER);
			binary_repr.append(sizeof(preamble_), preamble_);
			binary_repr.append(sizeof(sub_type_), &sub_type_);

			//
			// Base pattern struct - unique for this type of pattern
			//
			unsigned __int32 tmp32 = 0;

			// calculate and write the value at index 10
			for(unsigned __int32 i = 0; i < m_; i++)
				tmp32 += DAYS_IN_MONTH[i] * 24 * 60; // do not take leap years into account
			binary_repr.append(sizeof(tmp32), &tmp32);

			tmp32 = 12;
			binary_repr.append(sizeof(tmp32), &tmp32);

			tmp32 = 0;
			binary_repr.append(sizeof(tmp32), &tmp32);

			binary_repr.append(sizeof(y_), &y_);
			binary_repr.append(sizeof(x_), &x_);
		}
private:
	/*
	 * Must be initialized
	 */
	unsigned __int32 y_;            // bitmask for weekdays
	unsigned __int32 x_;            // occurrence number - x == 2 => every second day as specified by y_
	unsigned __int32 m_;            // month to occur in

	/*
	 * The following members are static constants
	 */
	static const unsigned __int8 preamble_[2];
	static const unsigned __int32 sub_type_ = 3;
};
const unsigned __int8 mapi_yearly_on_the_Xth_Y_of_month_M::preamble_[2] = { RECUR_YEARLY, 0x20 };


/*******************************************************
 ***                  MAPIRecurrence                 ***
 *******************************************************/

MAPIRecurrence::MAPIRecurrence(void)
{
	mapi_pattern_ = NULL;
	data_ = new ByteArray();
	type_ = ::BRUTUS::recur_none;
	spec_size_ = 0;
};

MAPIRecurrence::~MAPIRecurrence(void)
{
	if (data_)
		delete data_;

	if (mapi_pattern_)
		delete mapi_pattern_;
};

template<class T> T
MAPIRecurrence::get_as(const size_t idx)
{
	return data_->get_as<T>(idx);
};


void
MAPIRecurrence::reset(void)
{
	if (mapi_pattern_)
		delete mapi_pattern_;
	mapi_pattern_ = NULL;

	if (data_)
		delete data_;
	data_ = new ByteArray();

	type_ = ::BRUTUS::recur_none;
	spec_size_ = 0;
};

bool
MAPIRecurrence::read(const SBinary *RecurrenceState)
{
	return data_->append(RecurrenceState);
}

bool
MAPIRecurrence::write(void *Parent,
		      SBinary * const bin)
{
	return data_->get_sbinary(Parent, bin);
}


/*
 * Will return the pattern type and set spec_size_.
 * spec_size_ is the offset in bytes to where the real
 * exception data starts in the binary representation.
 */
::BRUTUS::RecurrenceType
MAPIRecurrence::get_type(void)
{
	if (::BRUTUS::recur_none != type_)
		return type_;

	const unsigned __int8 type = get_as<unsigned __int8>(4);
	const unsigned __int32 sub_type = get_as<unsigned __int32>(6);

	spec_size_ = sizeof(unsigned __int32) + 2*sizeof(unsigned __int8);
	switch (type) {
	case RECUR_DAILY :
		switch (sub_type) {
		case 0 :
			type_ = ::BRUTUS::recur_daily_every_N_days;
			spec_size_ += 4*sizeof(unsigned __int32);
			break;
		case 1 :
			type_ = ::BRUTUS::recur_weekly_every_N_weeks; // not a bug!!
			spec_size_ += 5*sizeof(unsigned __int32);
			break;
		default :
			break;
		}
		break;
	case RECUR_WEEKLY :
		switch (sub_type) {
		case 0 :
			type_ = ::BRUTUS::recur_weekly_every_N_weeks_for_regenerating_tasks;
			spec_size_ += 4*sizeof(unsigned __int32);
			break;
		case 1 :
			type_ = ::BRUTUS::recur_weekly_every_N_weeks;
			spec_size_ += 5*sizeof(unsigned __int32);
			break;
		default :
			break;
		}
		break;
	case RECUR_MONTHLY :
		switch (sub_type) {
		case 2 :
			type_ = ::BRUTUS::recur_monthly_every_N_months_on_day_D;
			spec_size_ += 5*sizeof(unsigned __int32);
			break;
		case 3 :
			type_ = ::BRUTUS::recur_monthly_every_N_months_on_Xth_Y;
			spec_size_ += 6*sizeof(unsigned __int32);
			break;
		default :
			break;
		}
		break;
	case RECUR_YEARLY :
		switch (sub_type) {
		case 2 :
			type_ = ::BRUTUS::recur_yearly_on_day_D_of_month_M;
			spec_size_ += 5*sizeof(unsigned __int32);
			break;
		case 3 :
			type_ = ::BRUTUS::recur_yearly_on_the_Xth_Y_of_month_M;
			spec_size_ += 6*sizeof(unsigned __int32);
			break;
		default :
			break;
		}
		break;
	default :
		spec_size_ = 0; // unknown data in BLOB
		break;
	}

	return type_;
}

::CORBA::ULong
MAPIRecurrence::get_occurrence_count(void)
{
	get_type();

	const size_t offset = 4*sizeof(unsigned __int8) + spec_size_;

	return (::CORBA::ULong)get_as<unsigned __int32>(offset);
}

::CORBA::ULong
MAPIRecurrence::get_exception_count(void)
{
	get_type();

	const size_t offset = 4*sizeof(unsigned __int8) + 2*sizeof(unsigned __int32) + spec_size_;

	return (::CORBA::ULong)get_as<unsigned __int32>(offset);
}

::CORBA::ULong
MAPIRecurrence::get_modification_count(void)
{
	get_type();

	const unsigned __int32 exception_count = get_exception_count();

	const size_t offset = 4*sizeof(unsigned __int8) + 3*sizeof(unsigned __int32) + exception_count*sizeof(unsigned __int32) + spec_size_;

	return (::CORBA::ULong)get_as<unsigned __int32>(offset);
}

::BRUTUS::RecurrenceTerminationType
MAPIRecurrence::get_termination_type(void)
{
	get_type();

	const unsigned __int8 term = get_as<unsigned __int8>(spec_size_);

	switch (term) {
	case 0x21 :
		return ::BRUTUS::term_date;
	case 0x22 :
		return ::BRUTUS::term_number;
	case 0x23 :
		return ::BRUTUS::term_never;
	}

	return ::BRUTUS::term_never;
}

::BRUTUS::DayOfWeek
MAPIRecurrence::get_first_day_of_week(void)
{
	get_type();

	const size_t offset = 4*sizeof(unsigned __int8) + sizeof(unsigned __int32) + spec_size_;

	const unsigned __int32 day = get_as<unsigned __int32>(offset);

	switch (day) {
	case 0 :
		return ::BRUTUS::Sunday;
	case 1 :
		return ::BRUTUS::Monday;
	case 2 :
		return ::BRUTUS::Tuesday;
	case 3 :
		return ::BRUTUS::Wednessday;
	case 4 :
		return ::BRUTUS::Thursday;
	case 5 :
		return ::BRUTUS::Friday;
	case 6 :
		return ::BRUTUS::Saturday;
	}

	return ::BRUTUS::Monday;
}

void
MAPIRecurrence::get_pattern(::BRUTUS::RecurrencePattern_out pattern)
{
	get_type();

	::BRUTUS::RecurrencePattern_var retv;
	try {
		retv = new ::BRUTUS::RecurrencePattern;
	}
	catch (std::bad_alloc &) {
		throw CORBA::NO_MEMORY();
	}

	switch (type_) {
	case ::BRUTUS::recur_daily_every_N_days :
	{
		::BRUTUS::data_recur_daily_every_N_days p;

			const unsigned __int32 N = get_as<unsigned __int32>(14);
			const bool r = get_as<unsigned __int32>(18) ? true : false;

			p.N = N/MINUTES_IN_DAY;
			p.regenerating_task = r;

			retv->daily_every_N_days(p);
	}
	break;
	case ::BRUTUS::recur_weekly_every_N_weeks :
	{
		::BRUTUS::data_recur_weekly_every_N_weeks p;

		const unsigned __int32 N = get_as<unsigned __int32>(14);
		const bool r = get_as<unsigned __int32>(18) ? true : false;
		const unsigned __int32 days = get_as<unsigned __int32>(22);

		p.N = N;
		p.regenerating_task = r;
		p.which_days = mapi_days_to_brutus_days_bitmask(days);

		retv->weekly_every_N_weeks(p);
	}
	break;
	case ::BRUTUS::recur_weekly_every_N_weeks_for_regenerating_tasks :
	{
		::BRUTUS::data_recur_weekly_every_N_weeks_for_regenerating_tasks p;

		const unsigned __int32 N = get_as<unsigned __int32>(14);

		p.N = N/(7*MINUTES_IN_DAY);

		retv->weekly_every_N_weeks_for_regenerating_tasks(p);
	}
	break;
	case ::BRUTUS::recur_monthly_every_N_months_on_day_D :
	{
		::BRUTUS::data_recur_monthly_every_N_months_on_day_D p;

		const unsigned __int32 N = get_as<unsigned __int32>(14);
		const bool r = get_as<unsigned __int32>(18) ? true : false;
		const unsigned __int32 D = get_as<unsigned __int32>(22);

		p.N = N;
		p.D = (::CORBA::Octet)((31 < D) ? 31 : D);
		p.regenerating_task = r;

		retv->monthly_every_N_months_on_day_D(p);
	}
	break;
	case ::BRUTUS::recur_monthly_every_N_months_on_Xth_Y :
	{
		::BRUTUS::data_recur_monthly_every_N_months_on_Xth_Y p;

		const unsigned __int32 N = get_as<unsigned __int32>(14);
		const bool r = get_as<unsigned __int32>(18) ? true : false;
		const unsigned __int32 Y = get_as<unsigned __int32>(22);
		const unsigned __int32 X = get_as<unsigned __int32>(26);

		p.N = N;
		p.X = (::CORBA::Octet)((5 < X) ? 5 : X);
		p.Y = mapi_days_to_brutus_days_bitmask(Y);
		p.regenerating_task = r;

		retv->monthly_every_N_months_on_Xth_Y(p);
	}
	break;
	case ::BRUTUS::recur_yearly_on_day_D_of_month_M :
	{
		::BRUTUS::data_recur_yearly_on_day_D_of_month_M p;

		const unsigned __int32 M = get_as<unsigned __int32>(10);
		const bool r = get_as<unsigned __int32>(18) ? true : false;
		const unsigned __int32 D = get_as<unsigned __int32>(22);

		p.M = minutes_to_month(M);
		p.regenerating_task = r;
		p.D = (::CORBA::Octet)((31 < D) ? 31 : D);

		retv->yearly_on_day_D_of_month_M(p);
	}
	break;
	case ::BRUTUS::recur_yearly_on_the_Xth_Y_of_month_M :
	{
		::BRUTUS::data_recur_yearly_on_the_Xth_Y_of_month_M p;

		const unsigned __int32 M = get_as<unsigned __int32>(10);
		const unsigned __int32 Y = get_as<unsigned __int32>(22);
		const unsigned __int32 X = get_as<unsigned __int32>(26);

		p.M = minutes_to_month(M);
		p.X = (::CORBA::Octet)((5 < X) ? 5 : X);
		p.Y = mapi_days_to_brutus_days_bitmask(Y);

		retv->yearly_on_the_Xth_Y_of_month_M(p);
	}
	break;
	case ::BRUTUS::recur_none :
	default :
		retv->_d(::BRUTUS::recur_none);
		break;
	}

	pattern = retv._retn();
}

void
MAPIRecurrence::get_exception_set(const bool is_event,
				  const ::BRUTUS::OccurrenceTimeType time_format,
				  FILETIME * const start,
				  ::BRUTUS::seq_RecurrenceExceptionData_out data_set)
{
	::BRUTUS::RecurrencePattern_var pattern;
	get_pattern(pattern.out());

	RecurDate dater;
	dater.set_pattern(pattern);
	dater.set_start(start);

	::BRUTUS::seq_RecurrenceExceptionData_var retv;
	try {
		retv = new ::BRUTUS::seq_RecurrenceExceptionData;
	}
	catch (std::bad_alloc &) {
		throw ::CORBA::NO_MEMORY();
	}
	retv->length(0);

	// check offset to where the real exception data starts
	if (!spec_size_) {
		data_set = retv._retn();
		return;
	}

	// the total number of exceptions (deletions and modifications)
	::CORBA::ULong count = get_exception_count();
	if (!count) // no exceptions at all
		goto out;
	retv->length(count);

	// offset to list base dates for all exceptions
	size_t offset = spec_size_ + 4*sizeof(unsigned __int8) + 3*sizeof(unsigned __int32);

	unsigned __int32 base_date;
	::CORBA::ULong n;

	// get all the base dates and calculate the oID
	for (n = 0; n < count; n++) {
		base_date = get_as<unsigned __int32>(offset + (n * sizeof(unsigned __int32)));
		retv[n].oc_time.bdate(base_date);
		retv[n].data._d(::BRUTUS::ExceptionDeletion);
		retv[n].data.moot(true);
	}

	// offset to base dates of all modified occurrences
	offset = spec_size_ + 4*sizeof(unsigned __int8) + 3*sizeof(unsigned __int32) + count*sizeof(unsigned __int32) + sizeof(unsigned __int32);
	count = get_modification_count();
	if (!count) // no modifications only deletions
		goto out;
	offset += count*sizeof(unsigned __int32) + 2*sizeof(unsigned __int32);
	if (is_event)
		offset += sizeof(unsigned __int32) + 4*sizeof(unsigned __int8) + 2*sizeof(unsigned __int32);
	offset += sizeof(unsigned __int16);

	FILETIME ft;
	unsigned __int16 str_idx;
	unsigned __int16 diff_mask;
	unsigned __int32 start_time;
	unsigned __int32 end_time;
	unsigned __int32 int32_val;
	char *str_val = NULL;
	::CORBA::ULong idx;
	for (n = 0; n < count; n++) {

		// start time, base date of start time and end time
		start_time = get_as<unsigned __int32>(offset);
		base_date = minutes_to_base_date(start_time);
		offset += sizeof(unsigned __int32);
		end_time = get_as<unsigned __int32>(offset);

		// diff mask
		offset += sizeof(unsigned __int32);
		offset += sizeof(unsigned __int32); // to get the offset past the undocumented ULONG
		diff_mask = get_as<unsigned __int16>(offset);

		// locate the modification within the superset of deletions and modifications
		for (idx = 0; idx < retv->length(); idx++) {
			if (retv[idx].oc_time.bdate() == base_date)
				break;
		}

		// set the ever-present start and end times
		ft = minutes_to_filetime(start_time);
		filetime_mapi_to_brutus(&ft, retv[idx].start);
		ft = minutes_to_filetime(end_time);
		filetime_mapi_to_brutus(&ft, retv[idx].end);

		// no content modifications
		if (!diff_mask) {
			retv[idx].data._d(::BRUTUS::ExceptionNONE);
			retv[idx].data.moot(false);
			continue;
		}

		// point to the first content modification
		offset += sizeof(unsigned __int16);

		// get integer modifications
		if (REMINDER_TIME_DEVIATION & diff_mask) {
			retv[idx].data._d(::BRUTUS::ExceptionReminderTime);
			int32_val = get_as<unsigned __int32>(offset);
			retv[idx].data.int32(int32_val);
			offset += sizeof(unsigned __int32); // step forward 4 bytes
		}
		if (REMINDER_SET_DEVIATION & diff_mask) {
			retv[idx].data._d(::BRUTUS::ExceptionReminderSet);
			int32_val = get_as<unsigned __int32>(offset);
			retv[idx].data.int32(int32_val);
			offset += sizeof(unsigned __int32);
		}
		if (SHOW_TIME_AS_DEVIATION & diff_mask) {
			retv[idx].data._d(::BRUTUS::ExceptionShowTimeAs);
			int32_val = get_as<unsigned __int32>(offset);
			retv[idx].data.int32(int32_val);
			offset += sizeof(unsigned __int32);
		}
		if (IS_ALL_DAY_DEVIATION & diff_mask) {
			retv[idx].data._d(::BRUTUS::ExceptionIsAllDay);
			int32_val = get_as<unsigned __int32>(offset);
			retv[idx].data.int32(int32_val);
			offset += sizeof(unsigned __int32);
		}
		if (LABEL_COLOR_DEVIATION & diff_mask) {
			retv[idx].data._d(::BRUTUS::ExceptionLabelColor);
			int32_val = get_as<unsigned __int32>(offset);
			retv[idx].data.int32(int32_val);
			offset += sizeof(unsigned __int32);
		}

		// get string modifications
		if (DESCRIPTION_DEVIATION & diff_mask) {
			retv[idx].data._d(::BRUTUS::ExceptionDescription);
			int32_val = get_as<unsigned __int16>(offset);
			str_val = (char*)malloc(int32_val);
			str_val[int32_val] = '\0';             // termination character
			offset += 2*sizeof(unsigned __int16);  // point to the first character in the string
			for (str_idx = 0; str_idx < (int32_val - 1); str_idx++) {
				str_val[str_idx] = get_as<char>(offset);
				offset += sizeof(char);
			}
			retv[idx].data.str((const char*)str_val);
			free(str_val);
		}
		if (LOCATION_DEVIATION & diff_mask) {
			retv[idx].data._d(::BRUTUS::ExceptionLocation);
			int32_val = get_as<unsigned __int16>(offset);
			str_val = (char*)malloc(int32_val);
			str_val[int32_val] = '\0';
			offset += 2*sizeof(unsigned __int16);
			for (str_idx = 0; str_idx < (int32_val - 1); str_idx++) {
				str_val[str_idx] = get_as<char>(offset);
				offset += sizeof(char);
			}
			retv[idx].data.str((const char*)str_val);
			free(str_val);
		}

		if (ATTENDEE_DEVIATION & diff_mask) {
			// not yet implemented
		}
		if (NOTES_ATTACHMENT_DEVIATION & diff_mask) {
			// not yet implemented
		}
	}

	// convert occurrence time to requested format
	for (n = 0; n < count; n++) {
		unsigned __int32 oid;
		unsigned __int32 bdate;
		::BRUTUS::FILETIME bft;

		switch (time_format) {
		case ::BRUTUS::tt_oID :
			oid = dater.occurrence_time_to_oid(retv[n].oc_time);
			retv[n].oc_time.oID(oid);
			break;
		case ::BRUTUS::tt_BaseDate :
			bdate = dater.occurrence_time_to_base_date(retv[n].oc_time);
			retv[n].oc_time.bdate(bdate);
			break;
		case ::BRUTUS::tt_FILETIME :
			ft = dater.occurrence_time_to_filetime(retv[n].oc_time);
			filetime_mapi_to_brutus(&ft, bft);
			retv[n].oc_time.ft(bft);
			break;
		default :
			break;
		}
	}
out:
	data_set = retv._retn();
}

bool
MAPIRecurrence::set_pattern(::CORBA::Boolean is_event,
			    const ::BRUTUS::FILETIME & start,
			    const ::BRUTUS::OccurrenceTime & end,
			    ::CORBA::ULong duration,
			    ::BRUTUS::DayOfWeek first_day_of_week,
			    ::BRUTUS::RecurrenceTerminationType how_to_end,
			    const ::BRUTUS::RecurrencePattern & pattern)
{
	// must overwrite
	reset();

	FILETIME ft_start;
	filetime_brutus_to_mapi_no_alloc(&start, ft_start);

	RecurDate dater;
	dater.set_start(&ft_start);
	dater.set_pattern(pattern);

	FILETIME ft_end = {0, 0};
	unsigned __int32 occurrence_count;

	// calculate end FILETIME and the total occurrence count of the series
	if (::BRUTUS::term_never == how_to_end)
		occurrence_count = 10; // Outlook 2003 default
	else
		occurrence_count = dater.occurrence_time_to_oid(end);

	// convert to SBinary
	SBinary bin;
	switch (pattern._d()) {
	case ::BRUTUS::recur_daily_every_N_days :
	{
		mapi_daily_every_N_days *mapi_pattern = new mapi_daily_every_N_days;
		mapi_pattern_ = mapi_pattern;

		mapi_pattern->init(pattern.daily_every_N_days().N,
				   &ft_start,
				   &ft_end,
				   duration,
				   occurrence_count,
				   how_to_end,
				   first_day_of_week,
				   (is_event ? true : false),
				   (pattern.daily_every_N_days().regenerating_task ? true : false));

		mapi_pattern->get_sbinary(NULL, &bin);
	}
	break;
	case ::BRUTUS::recur_weekly_every_N_weeks :
	{
		mapi_weekly_every_N_weeks *mapi_pattern = new mapi_weekly_every_N_weeks;
		mapi_pattern_ = mapi_pattern;

		mapi_pattern->init(pattern.weekly_every_N_weeks().N,
				   &ft_start,
				   &ft_end,
				   duration,
				   occurrence_count,
				   how_to_end,
				   first_day_of_week,
				   (is_event ? true : false),
				   (pattern.weekly_every_N_weeks().regenerating_task ? true : false),
				   pattern.weekly_every_N_weeks().which_days);

		mapi_pattern->get_sbinary(NULL, &bin);
	}
	break;
	case ::BRUTUS::recur_weekly_every_N_weeks_for_regenerating_tasks :
	{
		mapi_weekly_every_N_weeks_for_regenerating_tasks *mapi_pattern = new mapi_weekly_every_N_weeks_for_regenerating_tasks;
		mapi_pattern_ = mapi_pattern;

		mapi_pattern->init(pattern.weekly_every_N_weeks_for_regenerating_tasks().N,
				   &ft_start,
				   &ft_end,
				   duration,
				   occurrence_count,
				   how_to_end,
				   first_day_of_week);

		mapi_pattern->get_sbinary(NULL, &bin);
	}
	break;
	case ::BRUTUS::recur_monthly_every_N_months_on_day_D :
	{
		mapi_monthly_every_N_months_on_day_D *mapi_pattern = new mapi_monthly_every_N_months_on_day_D;
		mapi_pattern_ = mapi_pattern;

		mapi_pattern->init(pattern.monthly_every_N_months_on_day_D().N,
				   pattern.monthly_every_N_months_on_day_D().D,
				   &ft_start,
				   &ft_end,
				   duration,
				   occurrence_count,
				   how_to_end,
				   (pattern.monthly_every_N_months_on_day_D().regenerating_task ? true : false),
				   first_day_of_week);

		mapi_pattern->get_sbinary(NULL, &bin);
	}
	break;
	case ::BRUTUS::recur_monthly_every_N_months_on_Xth_Y :
	{
		mapi_monthly_every_N_months_on_Xth_Y *mapi_pattern = new mapi_monthly_every_N_months_on_Xth_Y;
		mapi_pattern_ = mapi_pattern;

		mapi_pattern->init(pattern.monthly_every_N_months_on_Xth_Y().N,
				   pattern.monthly_every_N_months_on_Xth_Y().X,
				   pattern.monthly_every_N_months_on_Xth_Y().Y,
				   &ft_start,
				   &ft_end,
				   duration,
				   occurrence_count,
				   how_to_end,
				   (pattern.monthly_every_N_months_on_Xth_Y().regenerating_task ? true : false),
				   first_day_of_week);

		mapi_pattern->get_sbinary(NULL, &bin);
	}
	break;
	case ::BRUTUS::recur_yearly_on_day_D_of_month_M :
	{
		mapi_yearly_on_day_D_of_month_M *mapi_pattern = new mapi_yearly_on_day_D_of_month_M;
		mapi_pattern_ = mapi_pattern;

		mapi_pattern->init(pattern.yearly_on_day_D_of_month_M().D,
				   brutus_month_to_mapi(pattern.yearly_on_day_D_of_month_M().M),
				   &ft_start,
				   &ft_end,
				   duration,
				   occurrence_count,
				   how_to_end,
				   (pattern.yearly_on_day_D_of_month_M().regenerating_task ? true : false),
				   first_day_of_week);

		mapi_pattern->get_sbinary(NULL, &bin);
	}
	break;
	case ::BRUTUS::recur_yearly_on_the_Xth_Y_of_month_M :
	{
		mapi_yearly_on_the_Xth_Y_of_month_M *mapi_pattern = new mapi_yearly_on_the_Xth_Y_of_month_M;
		mapi_pattern_ = mapi_pattern;

		mapi_pattern->init(brutus_month_to_mapi(pattern.yearly_on_the_Xth_Y_of_month_M().M),
				   pattern.yearly_on_the_Xth_Y_of_month_M().X,
				   pattern.yearly_on_the_Xth_Y_of_month_M().Y,
				   &ft_start,
				   &ft_end,
				   duration,
				   occurrence_count,
				   how_to_end,
				   first_day_of_week);

		mapi_pattern->get_sbinary(NULL, &bin);
	}
	break;
	case ::BRUTUS::recur_none :
	default :
		return false;
	}

	// swallow recurrence data
	bool retv = read(&bin);
	MAPIFreeBuffer(bin.lpb);

	return retv;
}

bool
MAPIRecurrence::set_exception(const ::BRUTUS::RecurrenceExceptionData & exception_data)
{
	if (!mapi_pattern_)
		throw invalid_operation();

	if (::BRUTUS::ExceptionNONE == exception_data.data._d())
		return true;
	else if (::BRUTUS::ExceptionDeletion == exception_data.data._d())
		*mapi_pattern_ -= exception_data.oc_time;
	else
		*mapi_pattern_ += exception_data;

	SBinary bin;
	mapi_pattern_->get_sbinary(NULL, &bin);

	// swallow recurrence data
	data_->reset(BASE_SIZE_OF_EXCEPTION_BLOB);
	bool retv = read(&bin);
	MAPIFreeBuffer(bin.lpb);

	return retv;
}

bool
MAPIRecurrence::set_exception_cond(const ::BRUTUS::RecurrenceExceptionData & exception_data,
				   const ::BRUTUS::OccurrenceTime & not_this)
{
	if (!mapi_pattern_->same_time(exception_data, not_this))
		return set_exception(exception_data);

	return true;
}
