//
//  MLDelayableTimer.m
//  monalxmpp
//
//  Created by Thilo Molitor on 24.06.24.
//  Copyright © 2024 monal-im.org. All rights reserved.
//

#import "MLConstants.h"
#import "HelperTools.h"
#import "MLDelayableTimer.h"

@interface MLDelayableTimer()
{
    NSTimer* _wrappedTimer;
    monal_timer_block_t _Nullable _cancelHandler;
    NSString* _Nullable _description;
    NSTimeInterval _timeout;
    NSTimeInterval _remainingTime;
    NSUUID* _uuid;
}
@end

@implementation MLDelayableTimer

-(instancetype) initWithHandler:(monal_timer_block_t) handler andCancelHandler:(monal_timer_block_t _Nullable) cancelHandler timeout:(NSTimeInterval) timeout tolerance:(NSTimeInterval) tolerance andDescription:(NSString* _Nullable) description
{
    self = [super init];
    _wrappedTimer = [NSTimer timerWithTimeInterval:timeout repeats:NO block:^(NSTimer* _) {
        handler(self);
    }];
    _cancelHandler = cancelHandler;
    _timeout = timeout;
    _wrappedTimer.tolerance = tolerance;
    _description = description;
    _remainingTime = 0;
    _uuid = [NSUUID UUID];
    return self;
}

-(NSString*) description
{
    return [NSString stringWithFormat:@"%@(%G|%G) %@", [_uuid UUIDString], _timeout, _wrappedTimer.fireDate.timeIntervalSinceNow, _description];
}

-(void) start
{
    @synchronized(self) {
        if(!_wrappedTimer.valid)
        {
            showErrorOnAlpha(nil, @"Could not start already fired timer: %@", self);
            return;
        }
        DDLogDebug(@"Starting timer: %@", self);
        //scheduling and unscheduling of a timer must be done from the same thread --> use our runloop
        [self scheduleBlockInRunLoop:^{
            [[HelperTools getExtraRunloopWithIdentifier:MLRunLoopIdentifierTimer] addTimer:self->_wrappedTimer forMode:NSRunLoopCommonModes];
        }];
    }
}

-(void) trigger
{
    @synchronized(self) {
        if(!_wrappedTimer.valid)
        {
            showErrorOnAlpha(nil, @"Could not trigger already fired timer: %@", self);
            return;
        }
        DDLogDebug(@"Triggering timer: %@", self);
        [_wrappedTimer fire];
    }
}

-(void) pause
{
    @synchronized(self) {
        if(!_wrappedTimer.valid)
        {
            DDLogWarn(@"Tried to pause already fired timer: %@", self);
            return;
        }
        NSTimeInterval remaining = _wrappedTimer.fireDate.timeIntervalSinceNow;
        if(remaining == 0)
        {
            DDLogWarn(@"Tried to pause timer the exact second its firing: %@", self);
            return;
        }
        DDLogDebug(@"Pausing timer: %@", self);
        _wrappedTimer.fireDate = NSDate.distantFuture;      //postpone timer virtually indefinitely
        _remainingTime = remaining;
    }
}

-(void) resume
{
    @synchronized(self) {
        if(!_wrappedTimer.valid)
        {
            DDLogWarn(@"Tried to resume already fired timer: %@", self);
            return;
        }
        if(_remainingTime == 0)
        {
            DDLogWarn(@"Tried to resume non-paused timer: %@", self);
            return;
        }
        DDLogDebug(@"Resuming timer: %@", self);
        _wrappedTimer.fireDate = [NSDate dateWithTimeIntervalSinceNow:_remainingTime];
        _remainingTime = 0;
    }
}

-(void) cancel
{
    @synchronized(self) {
        if(!_wrappedTimer.valid)
        {
            DDLogWarn(@"Tried to cancel already fired timer: %@", self);
            return;
        }
        DDLogDebug(@"Canceling timer: %@", self);
        [self invalidate];
    }
    _cancelHandler(self);
}

-(void) invalidate
{
    @synchronized(self) {
        if(!_wrappedTimer.valid)
        {
            DDLogWarn(@"Could not invalidate already invalid timer: %@", self);
            return;
        }
        //DDLogVerbose(@"Invalidating timer: %@", self);
        //scheduling and unscheduling of a timer must be done from the same thread --> use our runloop
        [self scheduleBlockInRunLoop:^{
            [self->_wrappedTimer invalidate];
        }];
    }
}

-(void) scheduleBlockInRunLoop:(monal_void_block_t) block
{
    NSRunLoop* runLoop = [HelperTools getExtraRunloopWithIdentifier:MLRunLoopIdentifierTimer];
//     NSCondition* condition = [NSCondition new];
//     [condition lock];
    CFRunLoopPerformBlock([runLoop getCFRunLoop], (__bridge CFStringRef)NSDefaultRunLoopMode, ^{
        block();
//         [condition lock];
//         [condition signal];
//         [condition unlock];
    });
    CFRunLoopWakeUp([runLoop getCFRunLoop]);    //trigger wakeup of runloop to execute the block as soon as possible
//     //wait for our block to finish executing
//     [condition wait];
//     [condition unlock];
}

@end