#import "DesiredBufferDepthController.h"
#import "Constraints.h"
#import "PreferencesUtil.h"
#import "Util.h"
#import "AudioPacker.h"

#define MAX_DESIRED_FRAME_DELAY 12
#define MIN_DESIRED_FRAME_DELAY 0.5
#define DROPOUT_THRESHOLD 10

#define DESIRED_BUFFER_DEPTH_DECAY_RATE 0.01

@implementation DesiredBufferDepthController

+(DesiredBufferDepthController*) desiredBufferDepthControllerForJitterQueue:(JitterQueue*)jitterQueue {
    require(jitterQueue != nil);
    
    NSTimeInterval audioDurationPerPacket = (NSTimeInterval)(AUDIO_FRAMES_PER_PACKET*[SpeexCodec frameSizeInSamples])
                                          / SAMPLE_RATE;
    double initialDesiredBufferDepth = [[Environment preferences] getCachedOrDefaultDesiredBufferDepth];
    
    DropoutTracker* dropoutTracker = [DropoutTracker dropoutTrackerWithAudioDurationPerPacket:audioDurationPerPacket];

    DecayingSampleEstimator* decayingDesiredBufferDepth =
        [DecayingSampleEstimator decayingSampleEstimatorWithInitialEstimate:initialDesiredBufferDepth
                                                      andDecayPerUnitSample:DESIRED_BUFFER_DEPTH_DECAY_RATE];
    
    DesiredBufferDepthController* result = [DesiredBufferDepthController new];
    result->dropoutTracker = dropoutTracker;
    result->decayingDesiredBufferDepth = decayingDesiredBufferDepth;
    result->desiredDelayLogger = [[Environment logging] getValueLoggerForValue:@"desired buffer depth" from:self];
    
    [jitterQueue registerWatcher:result];
    return result;
}

-(double) getAndUpdateDesiredBufferDepth {
    double r = [decayingDesiredBufferDepth currentEstimate];
    [decayingDesiredBufferDepth updateWithNextSample:[dropoutTracker getDepthForThreshold:DROPOUT_THRESHOLD]];
    [decayingDesiredBufferDepth forceEstimateTo:[NumberUtil clamp:[decayingDesiredBufferDepth currentEstimate]
                                                            toMin:MIN_DESIRED_FRAME_DELAY
                                                           andMax:MAX_DESIRED_FRAME_DELAY]];
    [desiredDelayLogger logValue:r];
    return r;
}

-(void) notifyArrival:(uint16_t)sequenceNumber {
    [dropoutTracker observeSequenceNumber:sequenceNumber];
}
-(void) notifyBadArrival:(uint16_t)sequenceNumber ofType:(enum JitterBadArrivalType)arrivalType {
}
-(void) notifyBadDequeueOfType:(enum JitterBadDequeueType)type {
}
-(void) notifyDequeue:(uint16_t)sequenceNumber withRemainingEnqueuedItemCount:(NSUInteger)remainingCount {
}
-(void) notifyResyncFrom:(uint16_t)oldReadHeadSequenceNumber to:(uint16_t)newReadHeadSequenceNumber {
}
-(void) notifyDiscardOverflow:(uint16_t)sequenceNumber resyncingFrom:(uint16_t)oldReadHeadSequenceNumber to:(uint16_t)newReadHeadSequenceNumber {
}

-(void) terminate {
    [[Environment preferences] setCachedDesiredBufferDepth:[decayingDesiredBufferDepth currentEstimate]];
}

@end
