


#import "SDAnimatedImagePlayer.h"
#import "NSImage+Compatibility.h"
#import "SDDisplayLink.h"
#import "SDDeviceHelper.h"
#import "SDImageFramePool.h"
#import "SDInternalMacros.h"

@interface SDAnimatedImagePlayer () {
    NSRunLoopMode _runLoopMode;
}

@property (nonatomic, strong) SDImageFramePool *framePool;

@property (nonatomic, strong, readwrite) UIImage *currentFrame;
@property (nonatomic, assign, readwrite) NSUInteger currentFrameIndex;
@property (nonatomic, assign, readwrite) NSUInteger currentLoopCount;
@property (nonatomic, strong) id<SDAnimatedImageProvider> animatedProvider;
@property (nonatomic, assign) NSUInteger currentFrameBytes;
@property (nonatomic, assign) NSTimeInterval currentTime;
@property (nonatomic, assign) BOOL bufferMiss;
@property (nonatomic, assign) BOOL needsDisplayWhenImageBecomesAvailable;
@property (nonatomic, assign) BOOL shouldReverse;
@property (nonatomic, strong) SDDisplayLink *displayLink;

@end

@implementation SDAnimatedImagePlayer

- (instancetype)initWithProvider:(id<SDAnimatedImageProvider>)provider {
    self = [super init];
    if (self) {
        NSUInteger animatedImageFrameCount = provider.animatedImageFrameCount;
        
        if (animatedImageFrameCount <= 1) {
            return nil;
        }
        self.totalFrameCount = animatedImageFrameCount;
        
        self.totalLoopCount = provider.animatedImageLoopCount;
        self.animatedProvider = provider;
        self.playbackRate = 1.0;
        self.framePool = [SDImageFramePool registerProvider:provider];
    }
    return self;
}

+ (instancetype)playerWithProvider:(id<SDAnimatedImageProvider>)provider {
    SDAnimatedImagePlayer *player = [[SDAnimatedImagePlayer alloc] initWithProvider:provider];
    return player;
}

- (void)dealloc {
    
    [SDImageFramePool unregisterProvider:self.animatedProvider];
}



- (SDDisplayLink *)displayLink {
    if (!_displayLink) {
        _displayLink = [SDDisplayLink displayLinkWithTarget:self selector:@selector(displayDidRefresh:)];
        [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:self.runLoopMode];
        [_displayLink stop];
    }
    return _displayLink;
}

- (void)setRunLoopMode:(NSRunLoopMode)runLoopMode {
    if ([_runLoopMode isEqual:runLoopMode]) {
        return;
    }
    if (_displayLink) {
        if (_runLoopMode) {
            [_displayLink removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:_runLoopMode];
        }
        if (runLoopMode.length > 0) {
            [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:runLoopMode];
        }
    }
    _runLoopMode = [runLoopMode copy];
}

- (NSRunLoopMode)runLoopMode {
    if (!_runLoopMode) {
        _runLoopMode = [[self class] defaultRunLoopMode];
    }
    return _runLoopMode;
}



- (void)setupCurrentFrame {
    if (self.currentFrameIndex != 0) {
        return;
    }
    if (self.playbackMode == SDAnimatedImagePlaybackModeReverse ||
               self.playbackMode == SDAnimatedImagePlaybackModeReversedBounce) {
        self.currentFrameIndex = self.totalFrameCount - 1;
    }
    
    if (!self.currentFrame && [self.animatedProvider isKindOfClass:[UIImage class]]) {
        UIImage *image = (UIImage *)self.animatedProvider;
        
        #if SD_MAC
        UIImage *posterFrame = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp];
        #else
        UIImage *posterFrame = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation];
        #endif
        if (posterFrame) {
            
            [self calculateMaxBufferCountWithFrame:posterFrame];
            
            self.needsDisplayWhenImageBecomesAvailable = YES;
            [self.framePool setFrame:posterFrame atIndex:self.currentFrameIndex];
        }
    }
    
}

- (void)resetCurrentFrameStatus {
    
    _currentFrame = nil;
    _currentFrameIndex = 0;
    _currentLoopCount = 0;
    _currentTime = 0;
    _bufferMiss = NO;
    _needsDisplayWhenImageBecomesAvailable = NO;
}

- (void)clearFrameBuffer {
    [self.framePool removeAllFrames];
}


- (void)startPlaying {
    [self.displayLink start];
    
    [self setupCurrentFrame];
}

- (void)stopPlaying {
    
    [_displayLink stop];
    
    [self resetCurrentFrameStatus];
}

- (void)pausePlaying {
    [_displayLink stop];
}

- (BOOL)isPlaying {
    return _displayLink.isRunning;
}

- (void)seekToFrameAtIndex:(NSUInteger)index loopCount:(NSUInteger)loopCount {
    if (index >= self.totalFrameCount) {
        return;
    }
    self.currentFrameIndex = index;
    self.currentLoopCount = loopCount;
    self.currentFrame = [self.animatedProvider animatedImageFrameAtIndex:index];
    [self handleFrameChange];
}


- (void)displayDidRefresh:(SDDisplayLink *)displayLink {
    
    
    if (!self.isPlaying) {
        return;
    }
    
    NSUInteger totalFrameCount = self.totalFrameCount;
    if (totalFrameCount <= 1) {
        
        [self stopPlaying];
        return;
    }
    
    NSTimeInterval playbackRate = self.playbackRate;
    if (playbackRate <= 0) {
        
        [self stopPlaying];
        return;
    }
    
    
    NSTimeInterval duration = self.displayLink.duration;
    
    NSUInteger currentFrameIndex = self.currentFrameIndex;
    NSUInteger nextFrameIndex = (currentFrameIndex + 1) % totalFrameCount;
    
    if (self.playbackMode == SDAnimatedImagePlaybackModeReverse) {
        nextFrameIndex = currentFrameIndex == 0 ? (totalFrameCount - 1) : (currentFrameIndex - 1) % totalFrameCount;
        
    } else if (self.playbackMode == SDAnimatedImagePlaybackModeBounce ||
               self.playbackMode == SDAnimatedImagePlaybackModeReversedBounce) {
        if (currentFrameIndex == 0) {
            self.shouldReverse = NO;
        } else if (currentFrameIndex == totalFrameCount - 1) {
            self.shouldReverse = YES;
        }
        nextFrameIndex = self.shouldReverse ? (currentFrameIndex - 1) : (currentFrameIndex + 1);
        nextFrameIndex %= totalFrameCount;
    }
    
    
    
    if (self.needsDisplayWhenImageBecomesAvailable) {
        UIImage *currentFrame = [self.framePool frameAtIndex:currentFrameIndex];
        
        
        if (currentFrame) {
            
            self.currentFrame = currentFrame;
            [self handleFrameChange];
            
            self.bufferMiss = NO;
            self.needsDisplayWhenImageBecomesAvailable = NO;
        }
        else {
            self.bufferMiss = YES;
        }
    }
    
    
    if (!self.bufferMiss) {
        
        self.currentTime += duration;
        NSTimeInterval currentDuration = [self.animatedProvider animatedImageDurationAtIndex:currentFrameIndex];
        currentDuration = currentDuration / playbackRate;
        if (self.currentTime < currentDuration) {
            
            [self prefetchFrameAtIndex:currentFrameIndex
                             nextIndex:nextFrameIndex];
            return;
        }
        
        
        self.needsDisplayWhenImageBecomesAvailable = YES;
        self.currentFrameIndex = nextFrameIndex;
        self.currentTime -= currentDuration;
        NSTimeInterval nextDuration = [self.animatedProvider animatedImageDurationAtIndex:nextFrameIndex];
        nextDuration = nextDuration / playbackRate;
        if (self.currentTime > nextDuration) {
            
            self.currentTime = nextDuration;
        }
        
        
        if (nextFrameIndex == 0) {
            
            self.currentLoopCount++;
            [self handleLoopChange];
            
            
            NSUInteger maxLoopCount = self.totalLoopCount;
            if (maxLoopCount != 0 && (self.currentLoopCount >= maxLoopCount)) {
                [self stopPlaying];
                return;
            }
        }
    }
    
    
    if (!self.isPlaying) {
        return;
    }
    
    [self prefetchFrameAtIndex:currentFrameIndex
                     nextIndex:nextFrameIndex];
}




- (void)prefetchFrameAtIndex:(NSUInteger)currentIndex
                   nextIndex:(NSUInteger)nextIndex {
    NSUInteger fetchFrameIndex = currentIndex;
    UIImage *fetchFrame = nil;
    if (!self.bufferMiss) {
        fetchFrameIndex = nextIndex;
        fetchFrame = [self.framePool frameAtIndex:nextIndex];
    }
    BOOL bufferFull = NO;
    if (self.framePool.currentFrameCount == self.totalFrameCount) {
        bufferFull = YES;
    }
    if (!fetchFrame && !bufferFull) {
        
        [self calculateMaxBufferCountWithFrame:self.currentFrame];
        
        [self.framePool prefetchFrameAtIndex:fetchFrameIndex];
    }
}

- (void)handleFrameChange {
    if (self.animationFrameHandler) {
        self.animationFrameHandler(self.currentFrameIndex, self.currentFrame);
    }
}

- (void)handleLoopChange {
    if (self.animationLoopHandler) {
        self.animationLoopHandler(self.currentLoopCount);
    }
}


- (void)calculateMaxBufferCountWithFrame:(nonnull UIImage *)frame {
    NSUInteger bytes = self.currentFrameBytes;
    if (bytes == 0) {
        bytes = CGImageGetBytesPerRow(frame.CGImage) * CGImageGetHeight(frame.CGImage);
        if (bytes == 0) {
            bytes = 1024;
        } else {
            
            self.currentFrameBytes = bytes;
        }
    }
    
    NSUInteger max = 0;
    if (self.maxBufferSize > 0) {
        max = self.maxBufferSize;
    } else {
        
        NSUInteger total = [SDDeviceHelper totalMemory];
        NSUInteger free = [SDDeviceHelper freeMemory];
        max = MIN(total * 0.2, free * 0.6);
    }
    
    NSUInteger maxBufferCount = (double)max / (double)bytes;
    if (!maxBufferCount) {
        
        maxBufferCount = 1;
    }
    
    self.framePool.maxBufferCount = maxBufferCount;
}

+ (NSString *)defaultRunLoopMode {
    
    return [NSProcessInfo processInfo].activeProcessorCount > 1 ? NSRunLoopCommonModes : NSDefaultRunLoopMode;
}

@end
