import { EventSchema } from '../../schemas/Event' ; import { isRecurrent } from './utils' ; import { clone } from '../../utils' ; import { EventId } from 'teambition-types' ; const { rrulestr } = require ( 'rrule' ) ; export type Timeframe = { startDate : Date ; endDate : Date ; } ; export interface DateInfo { startDate : string ; endDate : string ; recurrence ? : string [ ] ; } export type InstanceCreator < T > = ( source : T , timeframe ? : Timeframe ) => T ; type CmpOption = 'byStartDate' | 'byEndDate' ; type TimeframeFrom = { startDateFrom : Date ; include : boolean ; } ; export class RecurrenceFactory < T extends DateInfo > implements IterableIterator < T | undefined > { readonly type : string ; protected readonly source : T ; private startDateCursor : Date | undefined ; private done : boolean ; private readonly makeInst : InstanceCreator < T > ; private duration : number ; private isRecurrence : boolean ; private rruleSet : any ; [ Symbol . iterator ] = ( ) => this ; protected constructor ( source , type , makeInst ) { this . source = source ; this . type = type ; this . done = false ; this . duration = new Date ( this . source . endDate ) . valueOf ( ) - new Date ( this . source . startDate ) . valueOf ( ) ; this . isRecurrence = isRecurrent ( this . source ) ; if ( this . isRecurrence ) { this . rruleSet = rrulestr ( this . source . recurrence ! . join ( '\n' ) , { forceset : true } ) ; this . startDateCursor = this . rruleSet . all ( ( _ , i ) => i < 1 ) [ 0 ] ; } this . makeInst = makeInst ; } protected makeInstance ( timeframe ? ) { const target = clone ( this . source ) ; return this . isRecurrence && timeframe ? this . makeInst ( target , timeframe ) : this . makeInst ( target ) ; } isRecurrent ( ) { return this . isRecurrence ; } next ( ) { const doneRet = { value : undefined , done : true } ; if ( this . done ) { return doneRet ; } if ( ! this . isRecurrence ) { this . done = true ; return { value : this . makeInstance ( ) , done : false } ; } if ( ! this . startDateCursor ) { this . done = true ; return doneRet ; } const eventSpan = this . getOneTimeframeAfter ( this . startDateCursor , true ) ; if ( ! eventSpan ) { this . done = true ; return doneRet ; } const result = { value : this . makeInstance ( eventSpan ) , done : false } ; this . startDateCursor = this . rruleSet . after ( eventSpan . startDate ) ; return result ; } private getOneTimeframeAfter ( unadjustedStartDate , include ? ) { const startDate = this . rruleSet . after ( unadjustedStartDate , include ) ; return startDate ? { startDate , endDate : new Date ( startDate . valueOf ( ) + this . duration ) } : null ; } private takePred = ( from , fromCmpOption ) => ( eSpan ? ) : boolean | TimeframeFrom => { switch ( fromCmpOption ) { case 'byStartDate' : return eSpan ? eSpan . startDate >= from : { startDateFrom : from , include : true } ; case 'byEndDate' : return eSpan ? eSpan . endDate > from : { startDateFrom : new Date ( from . valueOf ( ) - this . duration ) , include : false } ; } } ; private slice ( from , fromCmpOption , to , toCmpOption ) { const takePred = this . takePred ( from , fromCmpOption ) ; const stopPred = ( eSpan ) : boolean => { return toCmpOption === 'byStartDate' && eSpan . startDate >= to || toCmpOption === 'byEndDate' && eSpan . endDate > to ; } ; const result = [ ] ; let initialEventSpan ; if ( ! this . isRecurrence ) { initialEventSpan = { startDate : new Date ( this . source . startDate ) , endDate : new Date ( this . source . endDate ) } ; if ( takePred ( initialEventSpan ) as boolean && ! stopPred ( initialEventSpan ) ) { result . push ( initialEventSpan ) ; } return result ; } const initial = takePred ( ) as TimeframeFrom ; initialEventSpan = this . getOneTimeframeAfter ( initial . startDateFrom , initial . include ) ; if ( ! initialEventSpan ) { return [ ] ; } let curr ; for ( curr = initialEventSpan ; curr !== null ; curr = this . getOneTimeframeAfter ( curr . startDate ) ) { if ( stopPred ( curr ) ) { break ; } result . push ( curr ) ; } return result ; } takeUntil ( startDateUntil , endDateUntil ? ) { return this . takeFrom ( new Date ( this . source . startDate ) , startDateUntil , endDateUntil ) ; } takeFrom ( fromDate , startDateTo , endDateTo ? ) { let toDate = startDateTo ; let toCmpOption ; if ( ! endDateTo || ( endDateTo . valueOf ( ) >= startDateTo . valueOf ( ) + this . duration ) ) { toCmpOption = 'byStartDate' ; } else { toDate = endDateTo ; toCmpOption = 'byEndDate' ; } return this . slice ( fromDate , 'byEndDate' , toDate , toCmpOption ) . map ( ( eventSpan ) => this . makeInstance ( eventSpan ) ) ; } after ( date ) { if ( ! this . isRecurrence ) { if ( new Date ( this . source . startDate ) < date ) { return null ; } else { return this . makeInstance ( ) ; } } const targetEventSpan = this . getOneTimeframeAfter ( date , true ) ; if ( ! targetEventSpan ) { return null ; } else { return this . makeInstance ( targetEventSpan ) ; } } findByTimestamp ( timestamp ) { const expectedDate = new Date ( timestamp ) ; if ( isNaN ( timestamp ) || isNaN ( expectedDate . valueOf ( ) ) ) { return null ; } const targetEventSpan = this . getOneTimeframeAfter ( expectedDate , true ) ; if ( ! targetEventSpan || targetEventSpan . startDate . valueOf ( ) !== expectedDate . valueOf ( ) ) { return null ; } return this . makeInstance ( targetEventSpan ) ; } } export class EventGenerator extends RecurrenceFactory < EventSchema > { private static makeInst : InstanceCreator < EventSchema > = ( event , timeframe ? ) => ! timeframe ? event : Object . assign ( event , { _id : `${ event . _id }_${ timeframe . startDate . valueOf ( ) }` , startDate : timeframe . startDate . toISOString ( ) , endDate : timeframe . endDate . toISOString ( ) } ) ; constructor ( event ) { super ( event , 'event' , EventGenerator . makeInst ) ; } findByEventId ( eventId ) { const originEventId = this . source . _id ; if ( ! this . isRecurrent ( ) ) { return eventId === originEventId ? this . makeInstance ( ) : null ; } const [ id , timestampStr ] = eventId . split ( '_' , 2 ) ; if ( id !== originEventId ) { return null ; } return this . findByTimestamp ( Number ( timestampStr ) ) ; } } export class Recurrence extends RecurrenceFactory < DateInfo > { private static makeInst : InstanceCreator < DateInfo > = ( dateInfo , timeframe ? ) => ! timeframe ? dateInfo : Object . assign ( dateInfo , { startDate : timeframe . startDate . toISOString ( ) , endDate : timeframe . endDate . toISOString ( ) } ) ; constructor ( dateInfo ) { super ( dateInfo , 'dateInfo' , Recurrence . makeInst ) ; } }