/**
 * Copyright (c) 2006, Opera Software ASA
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Opera Software ASA nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY OPERA SOFTWARE ASA AND CONTRIBUTORS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL OPERA SOFTWARE ASA AND CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 *	Class for calendarEvents
 *
 *	This class handles calendar events that can have several occurences
 *	calendarEvent are stored in a public list.
 *
 *	@constructor
 *	@author Mathieu HENRI, Opera Software ASA
 *	@version	1.0.1
*/
function CalendarEvents()
{
	calendarEventsSelf	= this;


	/** dictionnary with the Id of the calendarEvent **/
	this.calendarEventIdList			= {};
	/** array of the calendarEvent **/
	this.calendarEventArray				= [];


	/********* Interface methods ***********/


	/**
	 *	convert the array of calendarEvent into a JSON string
	 *	@return a JSONString
	 */
	this.toString = function()
	{
		var	eventsList = []

		for( var i=0,e; e=this.calendarEventArray[i]; i++ )
			eventsList.push(
			{
				id						:e.id,
				start					:e.start.valueOf(),
				end						:e.end.valueOf(),
				description				:e.description,
				type					:e.type,
				url						:e.url,
				reminderCount			:e.reminderCount,
				reminderInterval		:e.reminderInterval,
				remindersMaxLate		:e.remindersMaxLate,
				occurenceCount			:e.occurenceCount,
				occurenceInterval		:e.occurenceInterval,
				occurenceIntervalUnit	:e.occurenceIntervalUnit
			})


		return eventsList.toJSONString()
	}


	/**
	 *	parse a JSON string to populate the array of calendarEvent instance
	 *	@return	a boolean indicating the success of the method
	 */
	this.parse = function( str )
	{
		// wipe all the events first
		for( var i=0,e; e=this.calendarEventArray[i]; i++ )
			this.deleteEvent( e.id );

		var eventsList = str.parseJSON()
		for( var i=0,e; e=eventsList[i]; i++ )
			if( !this.setEvent(
				e.id,
				new Date( e.start ),
				new Date( e.end ),
				e.description,
				e.type,
				e.url,
				e.reminderCount,
				e.reminderInterval,
				e.remindersMaxLate,
				e.occurenceCount,
				e.occurenceInterval,
				e.occurenceIntervalUnit )
			)
				return false;

		return true
	}


	/**
	 *	set the ReminderCallback function
	 *	see the reminderFallback method for the list of arguments
	 *	the ReminderCallback function should accept
	 *	@param	fct	reminder function
	 *	@return	a boolean indicating the success of the method
	 */
	this.setReminderCallback	= function( fct )
	{
		if( typeof(fct)!='function' )
			return false

		this.reminderCallback = fct;
		return true;
	}


	/**
	 *	reset the ReminderCallback function
	 */
	this.resetReminderCallback	= function()
	{
		delete this.reminderCallback;
	}


	/**
	 *	call the reminderCallback function for one occurence of a calendarEvent
	 *	@param	escapedId	escapedId of the calendarEvent
	 *	@param	occurence
	 *	@param	reminderIndex	the index of the reminder in the range [ -reminderCount ; remindersMaxLate ]
	 */
	this.callReminder = function( escapedId, occurence, reminderIndex )
	{
		var	e	= this.getEvent( unescape( escapedId ) )
		if( !e )
			return false;

		if( e.nextOccurenceStart<=new Date() )
			e.nextOccurence( true );

		return (this.reminderCallback?this.reminderCallback:this.reminderFallback)( e, occurence, reminderIndex );
	}

	/**
	 *	the reminder fallback method
	 *	@param	evt  a calendarEvent
	 *	@param	occurence  the occurence related to this reminder
	 *	@param	reminderIndex  the index of the reminder in the range [ -reminderCount ; remindersMaxLate ]
	 */
	this.reminderFallback	= function( evt, occurence, reminderIndex )
	{
		if( !confirm( '____calendarEvents.reminderFallback____\n\nREMINDER #'+ reminderIndex +' for the occurence #'+ occurence +' of\n\n'+ evt.id +'\n\ndo you want to keep the reminders for this occurence ?' ) )
			calendarEventsSelf.stopEventReminder( evt.id, occurence );

		return true;
	}


	/**
	 *	stops the reminder for one occurence of a calendarEvent
	 *	@param	id	id of the calendarEvent
	 *	@param	occurence	the index of the occurence of the calendarEvent to stop the reminders of
	 *	@return	a boolean indicating if reminder have been stopped correctly
	 */
	this.stopEventReminder = function( id, occurence )
	{
		var	e	= this.getEvent( id )
		if( !e )
			return false;

		return e.clearReminderTimeout( occurence );
	}


	/**
	 *	dismiss the next occurence of a calendarEvent and move on
	 *	to the following occurence
	 *	@param	id	id of the calendarEvent
	 *	@return	a
	 */
	this.dismissEvent = function( id )
	{
		var	e = this.getEvent( id );

		if( !e )
			return false;

		e.dismiss();
		this.sortEvents();

		return true;
	}


	/**
	 *	get all the instances of calendarEvent
	 *	@return an array with all the instances of calendarEvent
	 */
	this.getAllEvents = function()
	{
		return this.calendarEventArray;
	}


	/**
	 *	get a calendarEvent
	 *	@param	id	id of the desired calendarEvent
	 *	@return	the calendarevent or null if it does not exist
	 */
	this.getEvent = function( id )
	{
		if( !this.calendarEventIdList[ id ] )
			return null;

		for( var i=0,e; e=this.calendarEventArray[i]; i++ )
			if( e.id==id )
				return e;
	}


	/**
	 *	get all the instances of calendarEvent of a given type
	 *	@param	type	the desired type of calendarEvent
	 *	@return	an array of calendarEvent
	 */
	this.getEventsByType = function( type )
	{
		var	events = [];

		for( var i=0,e; e=this.calendarEventArray[i]; i++ )
			if( e.type==type )
				events.push( e );

		return events;
	}


	/**
	 *	get the index of a calendarEvent in the array of calendarEvent
	 *	@param	id	id of the desired calendarEvent
	 *	@return	the index of the desried calendarevent in the array or -1 if it does not exist
	 */
	this.getEventIndex = function( id )
	{
		if( !this.calendarEventIdList[ id ] )
			return -1;

		for( var i=0,e; e=this.calendarEventArray[i]; i++ )
			if( e.id==id )
				return i;
	}


	/**
	 *	delete a calendarEvent
	 *	@param	id	id of the desired calendarEvent
	 *	@return	a boolean indicating the success of the method
	 */
	this.deleteEvent = function( id )
	{
		if( !this.calendarEventIdList[ id ] )
			return false;

		var	eventIndex = this.getEventIndex( id );

		this.calendarEventArray[ eventIndex ].clearAllReminderTimeout();

		delete this.calendarEventIdList[ id ];
		delete this.calendarEventArray[ eventIndex ];
		this.sortEvents()
		return true;
	}


	/**
	 *	sort the array of calendarEvent by increasing date of next occurence
	 */
	this.sortEvents = function()
	{
		this.calendarEventArray.sort( function( a,b )
		{
			return a.nextOccurenceStart-b.nextOccurenceStart;
		} )
	}


	/**
	 *	create a calendarEvent
	 *	@param	id	id of the desired calendarEvent
	 *	@param	start	the start date of the first occurence
	 *	@param	end		the end date of the first occurence, or null if the calendarEvent has no duration
	 *	@param	description		the description of the calendarEvent
	 *	@param	type		the type of the calendarEvent
	 *	@param	url		a URL related to the calendarEvent
	 *	@param	reminderCount	the number of reminder before the calendarEvent
	 *	@param	reminderInterval	the interval between the reminders (in ms)
	 *	@param	remindersMaxLate	the number of reminder after the beginning of the calendarEvent
	 *	@param	occurenceCount		the number of occurence or null if there is only occurence
	 *	@param	occurenceInterval	the amount of occurenceIntervalUnit between the occurences of the calendarEvent
	 *	@param	occurenceIntervalUnit	the time unit of occurenceInterval ( see the isValidUnit method )
	 *	@see 	#isValidUnit
	 *	@return	the calendarEvent or null if its creation failled
	 */
	this.setEvent = function( id, start, end, description, type, url, reminderCount, reminderInterval, remindersMaxLate, occurenceCount, occurenceInterval, occurenceIntervalUnit )
	{
		if( this.calendarEventIdList[ id ] )
			return null;

		var	occurenceCount			= occurenceCount||1,
			occurenceInterval		= occurenceInterval||0,
			occurenceIntervalUnit	= occurenceIntervalUnit||'milliseconds'

		if( occurenceInterval<0 )	//	events do not occurs back in time ... do they ?
			return null;

		if( !isValidUnit( occurenceIntervalUnit ) )
			return null;

		var	now = new Date();

		this.calendarEventIdList[ id ] = true;
		this.calendarEventArray.push( new CalendarEvent( id, start, end, description, type, url, reminderCount, reminderInterval, remindersMaxLate, occurenceCount, occurenceInterval, occurenceIntervalUnit ) );
		this.sortEvents();

		return this.getEvent( id );
	}


	/**
	 *	increments a date by a given quantiy of time units
	 *	@param	date	the base date
	 *	@param	quantity	the quantiy of time units
	 *	@param	unit	the unit of time to add	( see the isValidUnit method )
	 *	@see 	#isValidUnit
	 *	@return	return the incremented date or quantity if quantity is +/-Infinity
	 */
	this.incrementDate = function( date, quantity, unit )
	{
		var unit = (unit||'milliseconds').toLowerCase();


		if( Math.abs(quantity)==Infinity )
			return quantity;

		if( unit=='milliseconds' )
			return new Date( date.valueOf()+quantity );

		if( unit=='seconds' )
			return new Date( date.valueOf()+quantity*1000 );
		if( unit=='minutes' )
			return new Date( date.valueOf()+quantity*1000*60 );
		if( unit=='hours' )
			return new Date( date.valueOf()+quantity*1000*60*60 );
		if( unit=='days' )
			return new Date( date.valueOf()+quantity*1000*60*60*24 );
		if( unit=='weeks' )
			return new Date( date.valueOf()+quantity*1000*60*60*24*7 );

		var	retDate = new Date( date );

		if( unit=='years' )
			quantity *= 12;

		retDate.setMonth( retDate.getMonth()+quantity )

		return retDate;
	}


	/**
	 *	get the calendarEvent between two dates
	 *	@param	rangeBegin	the lower limit date
	 *	@param	rangeEnd	the upper limit date
	 *	@return	an array with all the calendarEvent that might occur between the two dates
	 */
	this.getEventsInPeriod = function( rangeBegin, rangeEnd )
	{
		var	eventsInPeriodArray = [],
			rangeBegin	= rangeBegin.valueOf(),
			rangeEnd	= rangeEnd.valueOf();

		if( rangeBegin>rangeEnd )	//	invalid date range
			return eventsInPeriodArray;

		for( var i=0,e; e=this.calendarEventArray[i]; i++ )
			if( e.getOccurencesStartingBefore( rangeEnd ).length > e.getOccurencesEndingBefore( rangeBegin ).length )
				eventsInPeriodArray.push( e );

		return eventsInPeriodArray;
	}


	/**
	 *	get the number of ms from a given date to the closest calendarEvent
	 *	@param	dateStart	the reference date
	 *	@param	inFuture	a boolean ( true by default ) indicating if the interval must be looked in the future or in the past
	 *	@return the number of ms from a given date to the closest calendarEvent
	 */
	this.getFreeRange = function( dateStart, inFuture )
	{
		var	inFuture					= inFuture==undefined?true:(inFuture?true:false),
			timestampClosestOccurence	= inFuture?Infinity:-Infinity,
			dateStart					= dateStart.valueOf()

		//	no calendarEvent in the array -> +/-Infinity
		if( !this.calendarEventArray.length )
			return timestampClosestOccurence;

		for( var i=0,e; e=this.calendarEventArray[i]; i++ )
		{
			var previousOccurences		= inFuture?e.getOccurencesEndingBefore( dateStart ):e.getOccurencesStartingBefore( dateStart ),
				usefulOccurenceDates	= { start:e.start,	end:e.end }

			if( previousOccurences.length )
			{
				usefulOccurenceDates = inFuture?e.getOccurenceDates( previousOccurences.length+1 ):previousOccurences.pop();
				if( !usefulOccurenceDates )
					continue;
			}

			//	right during the occurence of a calendarEvent -> 0
			if( usefulOccurenceDates.start<=dateStart && usefulOccurenceDates.end>=dateStart )
				return 0;

			if( inFuture && usefulOccurenceDates.start>dateStart )
				timestampClosestOccurence = Math.min( timestampClosestOccurence, usefulOccurenceDates.start );
			else if( !inFuture && usefulOccurenceDates.end<dateStart )
				timestampClosestOccurence = Math.max( timestampClosestOccurence, usefulOccurenceDates.end );
		}

		return timestampClosestOccurence-dateStart;
	}


	/**
	 *	shift a calendarEvent by a given quantiy of time units
	 *	@param	id	id of the calendarEvent
	 *	@param	time	the quantiy of time units
	 *	@param	timeUnit	the unit of time to add	( see the isValidUnit method )
	 *	@see 	#isValidUnit
	 *	@return	a boolean indicating the success of the method
	 */
	this.shiftEvent = function( id, time, timeUnit, overlap )
	{
		var	e			= this.getEvent( id );
			overlap		= overlap==undefined?true:(overlap?true:false)

		if( !e || !isValidUnit( timeUnit ) || typeof( time )!='number' )
			return false;

		var	candidateStart	= this.incrementDate( e.nextOccurenceStart,	time, timeUnit );
			candidateEnd	= this.incrementDate( e.nextOccurenceEnd,	time, timeUnit );

		if( !overlap )
		{
			var	eventsInCandidatePeriod = this.getEventsInPeriod( candidateStart, candidateEnd );
			if( eventsInCandidatePeriod.length>1 || (eventsInCandidatePeriod.length==1 && eventsInCandidatePeriod[0].id!=id) )
				return false;
		}

		this.getEvent( id ).shiftDates( time, timeUnit );
		this.sortEvents();

		return true;
	}


	/**
	 *	get the closest date to a given date when a desired amount ms of  will not overlap some calendarEvent
	 *	@param	dateStart	the reference date
	 *	@param	desiredLength	the amount of ms desired
	 *	@param	inFuture	a boolean ( true by default ) indicating if the date must be looked in the future or in the past
	 *	@return the closest date the desired amount of ms is available or null
	 */
	this.findFreeRange = function( dateStart, desiredLength, inFuture )
	{
		var	inFuture					= inFuture==undefined?true:(inFuture?true:false),
			timestampClosestOccurence	= inFuture?Infinity:-Infinity,
			timestampStart				= dateStart.valueOf()


		if( !this.calendarEventArray.length )
			return timestampStart;

		//	check if the intervals could allow desiredLength
		var	maxInterval = 0,
			maixmizedIntervalPerUnit = {
				'milliseconds':	1,
				'seconds':		1000,
				'minutes':		1000*60,
				'hours':		1000*60*60,
				'days':			1000*60*60*24,
				'weeks':		1000*60*60*24*7,
				'months':		1000*60*60*24*31,
				'years':		1000*60*60*24*366
			};

		for( var i=0,e; e=this.calendarEventArray[i]; i++ )
			if( e.occurenceCount>1 )
				maxInterval = Math.max( maxInterval, maixmizedIntervalPerUnit[ e.occurenceIntervalUnit ]*e.occurenceInterval );

		if( maxInterval<desiredLength )
			return null;

		//	search the closest date available
		var	attempts	= 0;
		while( attempts<99 )
		{
			var	timestampStartOld	= timestampStart,
				candidateDateStart	= new Date( timestampStart ),
				candidateDateEnd	= new Date( timestampStart+desiredLength ),
				eventsInPeriod		= this.getEventsInPeriod( candidateDateStart, candidateDateEnd );

			if( !eventsInPeriod.length )
				return timestampStart;

			for( var i=0,e; e=eventsInPeriod[i]; i++ )
			{
				if( inFuture )
				{
					var usefulOccurences = e.getOccurencesStartingBefore( candidateDateEnd )
					if( usefulOccurences.length )
						timestampStart = Math.max( timestampStart, usefulOccurences.pop().end.valueOf() )
				}
				else
				{
					var usefulOccurences = e.getOccurencesEndingBefore( candidateDateEnd )
					if( usefulOccurences.length )
						timestampStart = Math.min( timestampStart, usefulOccurences.pop().start.valueOf()-desiredLength )
				}

			}

			if( timestampStartOld == timestampStart )
				return timestampStart;					//	no change == looping through the last occurences

			attempts++;
		}
		return null;
	}




	/**
	 *	check the validity of a time unit
	 *	@member	CalendarEvents
	 *	@private
	 *	@param	unit	a time unit provided by the user or a calendarEvent
	 *	@return	true if the time unit is valid, false otherwise
	 */
	function isValidUnit( unit )
	{
		switch( (unit||'').toLowerCase() )
		{
			case 'milliseconds':
			case 'seconds':
			case 'minutes':
			case 'hours':
			case 'days':
			case 'weeks':
			case 'months':
			case 'years':
				return true;
		}
		return false;
	}
}







/**
 *	Class representing one calendarEvent
 *	@constructor
 *	@author Mathieu HENRI, Opera Software ASA
 *	@version	1.0
 */
function CalendarEvent( _id, _start, _end, _description, _type, _url, _reminderCount, _reminderInterval, _remindersMaxLate, _occurenceCount, _occurenceInterval, _occurenceIntervalUnit )
{
	calendarEventSelf = this;

	this.id						= _id;
	this.start					= _start;
	this.end					= _end||_start;
	this.description			= _description;
	this.type					= _type;
	this.url					= _url;

	//	reminder(s)
	this.reminderCount			= _reminderCount||0;
	this.reminderInterval		= _reminderInterval||0;
	this.remindersMaxLate		= _remindersMaxLate||0;

	//	occurence(s)
	this.occurenceCount			= _occurenceCount||1;
	this.occurenceInterval		= _occurenceInterval||0;
	this.occurenceIntervalUnit	= _occurenceIntervalUnit||0;


	this.occurenceDates			= [];


	var	currentOccurenceIndex	= 1,
		dismissedOccurences		= [],
		reminderTimeoutArray	= []


	/********* Interface methods ***********/

	/**
	 *	dismiss the current occurence and move on to the next one
	 */
	this.dismiss = function()
	{
		dismissedOccurences[ currentOccurenceIndex ] = true;
		this.nextOccurence();
	}


	/**
	 *	set the timeout for the reminders of a given occurence
	 *	@param	occurence	default to the currenceOccurence
	 *	@return	a boolean indicating of the desired occurence exist or false if it has been dismissed
	 */
	this.setReminderTimeout = function( occurence )
	{
		var	occurence		= occurence!=undefined?occurence:currentOccurenceIndex,
			occurenceDates	= this.getOccurenceDates( occurence )

		if( !occurenceDates )
			return false;

		var	timestampDelta	= occurenceDates.start-(new Date().valueOf());

		if( !reminderTimeoutArray[occurence] )
			reminderTimeoutArray[occurence] = []

		this.clearReminderTimeout( occurence );

		if( dismissedOccurences[ occurence ] )
			return false;

		for( var i=-this.reminderCount; i<=this.remindersMaxLate; i++ )
		{
			var delay = timestampDelta+i*this.reminderInterval
			if( delay>0 )
				reminderTimeoutArray[occurence].push( setTimeout( 'calendarEventsSelf.callReminder( "'+ escape( this.id ) +'", '+ occurence +', '+ i +' )', delay ) )
		}
		return true;
	}


	/**
	 *	clear the timeout for the reminders of a given occurence
	 *	@param	occurence	default to the currenceOccurence
	 */
	this.clearReminderTimeout = function( occurence )
	{
		var	occurence= occurence!=undefined?occurence:currentOccurenceIndex

		if( reminderTimeoutArray[occurence] )
			while( reminderTimeoutArray[occurence].length )
				clearTimeout( reminderTimeoutArray[occurence].pop() );
	}


	/**
	 *	clear ALL the timeout for the reminders
	 */
	this.clearAllReminderTimeout = function()
	{
		for( var occurence in reminderTimeoutArray )
			if(typeof(occurence)=='number')
				this.clearReminderTimeout( occurence );
	}

	/**
	 *	shift the dates of the calendar event by a a given quantity of time units and reset the reminder timeout and reset the reminder timeout
	 *	@param	time	the quantiy of time units
	 *	@param	timeUnit	the unit of time to add	( see the isValidUnit method )
	 *	@see 	calendarEvents#isValidUnit
	 */
	this.shiftDates = function( time, timeUnit )
	{

		this.start	= calendarEventsSelf.incrementDate( this.start,	time, timeUnit );
		this.end	= calendarEventsSelf.incrementDate( this.end,	time, timeUnit );

		//	wipe the occurenceDates
		this.occurenceDates = [];

		// reset nextOccurenceDates
		var nextOccurenceDates = this.getOccurenceDates( currentOccurenceIndex );
		this.nextOccurenceStart	= nextOccurenceDates.start;
		this.nextOccurenceEnd	= nextOccurenceDates.end;

		this.setReminderTimeout();
	}


	/**
	 *	get the start and end dates of a given occurence
	 *	@param	occurenceIndex	the index of the desired occurence
	 *	@return	an object with a start and end dates or null if the desired occurenceIndex is invalid
	 */
	this.getOccurenceDates = function( occurenceIndex )
	{
		if( occurenceIndex<1 || occurenceIndex>this.occurenceCount || dismissedOccurences[ occurenceIndex ] )
			return null;

		if( this.occurenceDates[ occurenceIndex ] )
			return this.occurenceDates[ occurenceIndex ];


		occurenceDates = {
			start:	calendarEventsSelf.incrementDate( this.start,	(occurenceIndex-1)*this.occurenceInterval, this.occurenceIntervalUnit ),
			end:	calendarEventsSelf.incrementDate( this.end,		(occurenceIndex-1)*this.occurenceInterval, this.occurenceIntervalUnit )
		}
		this.occurenceDates[ occurenceIndex ] = occurenceDates;

		return occurenceDates;
	}

	/**
	 *	move on to the next occurence
	 *	@param	maintainReminderTimeout	an optionnal boolean ( false by default ) indicating if the reminder timeouts should be maintain for the current occurence
	 *	@return	a boolean indicating if the next occurence exist
	 */
	this.nextOccurence = function( maintainReminderTimeout )
	{
		var	maintainReminderTimeout	= maintainReminderTimeout!=undefined?maintainReminderTimeout:false
		if( !maintainReminderTimeout )
			this.clearReminderTimeout();

		if( currentOccurenceIndex<this.occurenceCount )
		{

			var nextOccurenceDates = this.getOccurenceDates( currentOccurenceIndex );

			this.nextOccurenceStart	= nextOccurenceDates.start;
			this.nextOccurenceEnd	= nextOccurenceDates.end;

			currentOccurenceIndex++;

			this.setReminderTimeout();
			return true;
		}

		this.nextOccurenceStart	=
		this.nextOccurenceEnd	= Infinity;

		return false;
	}


	/**
	 *	get the dates of all the occurences ending before a given date
	 *	@param	dateLimit	the reference date
	 *	@return	an array of objects with the start and end dates of the corresponding occurences
	 */
	this.getOccurencesEndingBefore = function( dateLimit )
	{
		var occurenceDates = [];

		var occurenceCount	= this.occurenceCount,
			j 				= 1,
			datesOccurenceJ	= {start:this.start,end:this.end,occurenceIndex:1};

		while( datesOccurenceJ.end<dateLimit && j<=occurenceCount )
		{
			occurenceDates.push( datesOccurenceJ )
			datesOccurenceJ					= this.getOccurenceDates( ++j );
			if( !datesOccurenceJ )
				return occurenceDates;
			datesOccurenceJ.occurenceIndex	= j;
		}

		return occurenceDates;
	}


	/**
	 *	get the dates of all the occurences starting before a given date
	 *	@param	dateLimit	the reference date
	 *	@return	an array of objects in the form of {@link String#utility} with the start and end dates of the corresponding occurences
	 */
	this.getOccurencesStartingBefore = function( dateLimit )
	{
		var occurenceDates = [];

		var occurenceCount	= this.occurenceCount,
			j 				= 1,
			datesOccurenceJ	= {start:this.start,end:this.end,occurenceIndex:1};

		while( datesOccurenceJ.start<dateLimit && j<=occurenceCount )
		{
			occurenceDates.push( datesOccurenceJ )
			datesOccurenceJ					= this.getOccurenceDates( ++j );
			if( !datesOccurenceJ )
				return occurenceDates;
			datesOccurenceJ.occurenceIndex	= j;
		}

		return occurenceDates;
	}



	//	make sure the next occurence in not in the past
	var now	= new Date();
	this.nextOccurenceStart	= this.start;
	this.nextOccurenceEnd	= this.end;
	this.setReminderTimeout();

	if( this.occurenceCount )
	while( now>this.nextOccurenceEnd && currentOccurenceIndex<this.occurenceCount )
		this.nextOccurence();
}


