#import "TimeUtil.h"
#import "Util.h"
#import "AnonymousTerminator.h"
#import "Constraints.h"
#import "FutureSource.h"

@implementation TimeUtil

+(NSTimeInterval) time {
    return [[NSProcessInfo processInfo] systemUptime];
}

+(Future*) scheduleEvaluate:(Function)function
                 afterDelay:(NSTimeInterval)delay
                  onRunLoop:(NSRunLoop*)runLoop
            unlessCancelled:(id<CancelToken>)unlessCancelledToken {
    
    require(function != NULL);
    require(runLoop != nil);
    require(delay >= 0);
    
    FutureSource* result = [FutureSource new];
    Action evaler = ^{
        [result trySetResult:function()];
    };
    [self scheduleHelper:evaler
              withPeriod:delay
               onRunLoop:runLoop
               repeating:false
          untilCancelled:unlessCancelledToken
       andRunImmediately:NO];
    
    [unlessCancelledToken whenCancelledTryCancel:result];
    return result;
}

+(Future*) scheduleEvaluate:(Function)function
                         at:(NSDate*)date
                  onRunLoop:(NSRunLoop*)runLoop
            unlessCancelled:(id<CancelToken>)unlessCancelledToken {
    
    require(function != NULL);
    require(runLoop != nil);
    require(date != nil);
    
    NSTimeInterval delay = [date timeIntervalSinceNow];
    return [self scheduleEvaluate:function
                       afterDelay:MAX(0, delay)
                        onRunLoop:runLoop
                  unlessCancelled:unlessCancelledToken];
}

+(void) scheduleRun:(Action)action
         afterDelay:(NSTimeInterval)delay
          onRunLoop:(NSRunLoop*)runLoop
    unlessCancelled:(id<CancelToken>)unlessCancelledToken {
    
    require(action != NULL);
    require(runLoop != nil);
    require(delay >= 0);
    if (delay == INFINITY) return;
    
    [self scheduleHelper:action
              withPeriod:delay
               onRunLoop:runLoop
               repeating:false
          untilCancelled:unlessCancelledToken
       andRunImmediately:NO];
}

+(void) scheduleRun:(Action)action
                 at:(NSDate*)date
          onRunLoop:(NSRunLoop*)runLoop
    unlessCancelled:(id<CancelToken>)unlessCancelledToken {
    
    require(action != NULL);
    require(runLoop != nil);
    require(date != nil);
    
    NSTimeInterval delay = [date timeIntervalSinceNow];
    [self scheduleRun:action
           afterDelay:MAX(0, delay)
            onRunLoop:runLoop
      unlessCancelled:unlessCancelledToken];
}

+(void) scheduleRun:(Action)action
       periodically:(NSTimeInterval)interval
          onRunLoop:(NSRunLoop*)runLoop
     untilCancelled:(id<CancelToken>)untilCancelledToken
  andRunImmediately:(BOOL)shouldRunImmediately{
    
    require(action != NULL);
    require(runLoop != nil);
    require(interval > 0);
    
    [self scheduleHelper:action
              withPeriod:interval
               onRunLoop:runLoop
               repeating:true
          untilCancelled:untilCancelledToken
       andRunImmediately:shouldRunImmediately];
}

+(void) scheduleHelper:(Action)callback
            withPeriod:(NSTimeInterval)interval
             onRunLoop:(NSRunLoop*)runLoop
             repeating:(bool)repeats
        untilCancelled:(id<CancelToken>)untilCancelledToken
     andRunImmediately:(BOOL)shouldRunImmediately{
    
    require(callback != NULL);
    require(runLoop != nil);
    require(interval >= 0);
    require(!repeats || interval > 0);
    
    if ([untilCancelledToken isAlreadyCancelled]) {
        return;
    }
    if (!repeats && interval == 0) {
        callback();
        return;
    }
    if (shouldRunImmediately){
        callback();
    }
    
    callback = [callback copy];
    __block bool hasBeenCancelled = false;
    __block NSObject* cancelLock = [NSObject new];
    
    Operation* callbackUnlessCancelled = [Operation operation:^{
        @synchronized(cancelLock) {
            if (hasBeenCancelled) return;
            callback();
        }
    }];
    
    NSTimer* timer = [NSTimer timerWithTimeInterval:interval
                                             target:callbackUnlessCancelled
                                           selector:[callbackUnlessCancelled selectorToRun]
                                           userInfo:nil
                                            repeats:repeats];
    [runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
    
    [untilCancelledToken whenCancelled:^{
        @synchronized(cancelLock) {
            hasBeenCancelled = true;
            [timer invalidate];
        }
    }];
}

@end
