//
//  GIFDecoder.m
//  AnimatedGifExample
//
//  Created by Dong Li on 12-5-3.
//  Copyright (c) 2013年 ITX. All rights reserved.
//

#import "GIFDecoder.h"

typedef struct __DATA_PACKET {
const UInt8 *bytes;
int length;
} DATA_PACKET;


@interface AnimatedGifFrame : NSObject {
	NSData *_header;
	NSData *_data;
	double _duration;
	int _disposalMethod;
	CGRect _area;
}

@property (nonatomic, copy) NSData *header;
@property (nonatomic, copy) NSData *data;
@property (nonatomic) double duration;
@property (nonatomic) int disposalMethod;
@property (nonatomic) CGRect area;

@end

@implementation AnimatedGifFrame

@synthesize header = _header;
@synthesize data = _data;
@synthesize duration = _duration;
@synthesize disposalMethod = _disposalMethod;
@synthesize area = _area;

- (void)dealloc {
}


@end

#pragma mark -

@interface GIFDecoder() {
    DATA_PACKET _sourcePacket;
	DATA_PACKET _bufferPacket;
	DATA_PACKET _globalPacket;
    NSMutableData *_screenData;
    NSMutableData *_frameData;
	NSMutableArray *_frames;
    CGSize _screenSize;
    int _currentCursor;
    int _colorF;
    int _colorC;
    int _colorS;
    int _sorted;
}

- (BOOL)getBytes:(int)length;
- (BOOL)skipBytes:(int)length;
- (BOOL)readExtensions;
- (BOOL)readDescriptor;
- (NSArray *)generateImagesAndDuration:(NSTimeInterval *)duration;

@end

@implementation GIFDecoder

+ (NSArray *)decodeFile:(NSString *)path duration:(NSTimeInterval *)duration {
    NSData *data = [[NSData alloc] initWithContentsOfFile:path];
    GIFDecoder *decoder = [[GIFDecoder alloc] init];
    NSArray *images = [decoder decode:data duration:duration];
    
    return images;
}

- (id)init {
    if (self = [super init]) {
        _frames = [[NSMutableArray alloc] initWithCapacity:64];
        _screenData = [[NSMutableData alloc] initWithCapacity:SIZE_1_MB];
        _frameData = [[NSMutableData alloc] initWithCapacity:SIZE_1_MB];
    }
    return self;
}

- (NSArray *)decode:(NSData *)data duration:(NSTimeInterval *)duration {
    _sourcePacket.bytes = [data bytes];
    _sourcePacket.length = [data length];
	_currentCursor = 0;
	
    // Identifier + Version: 6 bytes, GIF89a, throw away
	if (![self skipBytes:6]) {
        return nil;
    }
    // Logical Screen Descriptor: 7 Bytes
	if (![self getBytes:7]) {
        return nil;
    }
	
    // Deep copy
    [_screenData setLength:0];
    [_screenData appendBytes:_bufferPacket.bytes length:_bufferPacket.length];
	
    _screenSize.width = *(UInt16 *)_bufferPacket.bytes;
    _screenSize.height = *(UInt16 *)(_bufferPacket.bytes + 2);
	
    _colorF = (_bufferPacket.bytes[4] & 0x80) ? 1 : 0;
    _sorted = (_bufferPacket.bytes[4] & 0x08) ? 1 : 0;
	
	_colorC = (_bufferPacket.bytes[4] & 0x07);
	_colorS = 2 << _colorC;
	
	if (1 == _colorF) {
		if (![self getBytes:(3 * _colorS)]) {
            return nil;
        }
        
        // Deep copy
        _globalPacket = _bufferPacket;
	}
	
    BOOL succeeded = YES;
    
	while (succeeded && [self getBytes:1]) {
        if (0x3B == _bufferPacket.bytes[0]) {
            // This is the end
            break;
        } else if (0x21 == _bufferPacket.bytes[0]) {
            // Graphic Control Extension (#n of n)
            succeeded = [self readExtensions];
        } else if (0x2C == _bufferPacket.bytes[0]) {
            // Image Descriptor (#n of n)
            succeeded = [self readDescriptor];
        }
	}
    
    NSArray *images = [self generateImagesAndDuration:duration];
    
    if (nil == images) {
        UIImage *image = [[UIImage alloc] initWithData:data];
        
        images = [NSArray arrayWithObjects:image, nil];
        }
    
	[_frames removeAllObjects];
    
    return images;
}

//
// This method converts the arrays of GIF data to an animation, counting
// up all the seperate frame delays, and setting that to the total duration
// since the iPhone Cocoa framework does not allow you to set per frame
// delays.
//
// Returns nil when there are no frames present in the GIF, or
// an autorelease UIImageView* with the animation.
- (NSArray *)generateImagesAndDuration:(NSTimeInterval *)duration {
    const NSInteger numberOfFrames = [_frames count];
    NSMutableArray *images = nil;
    
    if (numberOfFrames > 1) {
        images = [NSMutableArray arrayWithCapacity:numberOfFrames];
        
		CGRect screenBounds = { 0, 0, _screenSize.width, _screenSize.height };
		
		CGContextRef context = NULL;
		NSTimeInterval totalDuration = 0;
        
        UIGraphicsBeginImageContext(_screenSize);
        context = UIGraphicsGetCurrentContext();
        
		for (NSInteger i = 0; i < numberOfFrames; ++ i) {
            // Get Frame
			AnimatedGifFrame *frame = [_frames objectAtIndex:i];
            UIImage *image = [[UIImage alloc] initWithData:[frame data]];
            
            if (nil == image) {
                continue;
            }
            
            // Initialize Flag
            UIImage *previousCanvas = nil;
            
            // Save Context
			CGContextSaveGState(context);
            // Change CTM
			CGContextScaleCTM(context, 1.0, -1.0);
			CGContextTranslateCTM(context, 0.0, -_screenSize.height);
            
            // Check if lastFrame exists
            CGRect clipRect = CGRectZero;
            
            const int disposalMethod = [frame disposalMethod];
            const CGRect frameArea = [frame area];
            
            // Disposal Method (Operations before draw frame)
            switch (disposalMethod)
            {
                case 1: // Do not dispose (draw over context)
                {
                    // Create Rect (y inverted) to clipping
                    clipRect = RECT(frameArea.origin.x, _screenSize.height - frameArea.size.height - frameArea.origin.y, frameArea.size.width, frameArea.size.height);
                    // Clip Context
                    CGContextClipToRect(context, clipRect);
                    break;
                }
                case 2: // Restore to background the rect when the actual frame will go to be drawed
                {
                    // Create Rect (y inverted) to clipping
                    clipRect = RECT(frameArea.origin.x, _screenSize.height - frameArea.size.height - frameArea.origin.y, frameArea.size.width, frameArea.size.height);
                    // Clip Context
                    CGContextClipToRect(context, clipRect);
                    break;
                }
                case 3: // Restore to Previous
                {
                    // Get Canvas
                    previousCanvas = UIGraphicsGetImageFromCurrentImageContext();
                    
                    // Create Rect (y inverted) to clipping
                    clipRect = RECT(frameArea.origin.x, _screenSize.height - frameArea.size.height - frameArea.origin.y, frameArea.size.width, frameArea.size.height);
                    // Clip Context
                    CGContextClipToRect(context, clipRect);
                    break;
                }
            }
            
            // Draw Actual Frame
			CGContextDrawImage(context, screenBounds, image.CGImage);
            // Restore State
			CGContextRestoreGState(context);
            // Add Image created (only if the duration > 0)
            
            const NSTimeInterval durationOfFrame = [frame duration];
            
            if (durationOfFrame > 0) {
                UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
                
                if (newImage) {
                    [images addObject:newImage];
                    totalDuration += durationOfFrame;
                }
            }
            
            // Disposal Method (Operations afte draw frame)
            switch (disposalMethod)
            {
                case 2: // Restore to background color the zone of the actual frame
                {
                    // Save Context
                    CGContextSaveGState(context);
                    // Change CTM
                    CGContextScaleCTM(context, 1.0, -1.0);
                    CGContextTranslateCTM(context, 0.0, -_screenSize.height);
                    // Clear Context
                    CGContextClearRect(context, clipRect);
                    // Restore Context
                    CGContextRestoreGState(context);
                    break;
                }
                case 3: // Restore to Previous Canvas
                {
                    // Save Context
                    CGContextSaveGState(context);
                    // Change CTM
                    CGContextScaleCTM(context, 1.0, -1.0);
                    CGContextTranslateCTM(context, 0.0, -_screenSize.height);
                    // Clear Context
                    CGContextClearRect(context, frameArea);
                    // Draw previous frame
                    CGContextDrawImage(context, screenBounds, previousCanvas.CGImage);
                    // Restore State
                    CGContextRestoreGState(context);
                    break;
                }
            }
            }
        
        UIGraphicsEndImageContext();
        
		// GIFs store the delays as 1/100th of a second,
        *duration = totalDuration / 100.f;
        
	} else {
        *duration = 0.f;
    }
    return images;
}

- (BOOL)readExtensions {
	// 21! But we still could have an Application Extension,
	// so we want to check for the full signature.
    if (![self getBytes:1]) {
        return NO;
    }
    
	UInt8 cur = _bufferPacket.bytes[0];
    UInt8 prev = 0x00;
    
	while (0x00 != cur)
    {
		// TODO: Known bug, the sequence F9 04 could occur in the Application Extension, we
		//       should check whether this combo follows directly after the 21.
		if (0x04 == cur && 0xF9 == prev)
		{
			if (![self getBytes:5]) {
                return NO;
            }
            
			AnimatedGifFrame *frame = [[AnimatedGifFrame alloc] init];
			
			const UInt8 *buffer = _bufferPacket.bytes;
			frame.disposalMethod = (buffer[0] & 0x1c) >> 2;
			
			// We save the delays for easy access.
			frame.duration = (buffer[1] | buffer[2] << 8);
			
			u_char board[8];
			board[0] = 0x21;
			board[1] = 0xF9;
			board[2] = 0x04;
			
			for(int i = 3, j = 0; j < 5; i++, j++) {
				board[i] = buffer[j];
			}
			
			frame.header = [NSData dataWithBytes:board length:8];
            
			[_frames addObject:frame];
			break;
		}
		
		prev = cur;
        
        if (![self getBytes:1]) {
            return NO;
        }
        cur = _bufferPacket.bytes[0];
	}
    
    return YES;
}

- (BOOL)readDescriptor {
	if (![self getBytes:9]) {
        return NO;
    }
    
	DATA_PACKET tempScreen = _bufferPacket;
	
	const UInt8 *aBuffer = _bufferPacket.bytes;
	
	CGRect rect;
	rect.origin.x = ((int)aBuffer[1] << 8) | aBuffer[0];
	rect.origin.y = ((int)aBuffer[3] << 8) | aBuffer[2];
	rect.size.width = ((int)aBuffer[5] << 8) | aBuffer[4];
	rect.size.height = ((int)aBuffer[7] << 8) | aBuffer[6];
    
	AnimatedGifFrame *frame = [_frames lastObject];
	frame.area = rect;
	
	_colorF = (aBuffer[8] & 0x80) ? 1 : 0;
	
	u_char GIF_code = _colorC, GIF_sort = _sorted;
	
	if (1 == _colorF) {
		GIF_code = (aBuffer[8] & 0x07);
        GIF_sort = (aBuffer[8] & 0x20) ? 1 : 0;
	}
	
	int GIF_size = (2 << GIF_code);
	
	size_t bLength = _screenData.length;
	u_char bBuffer[bLength];
    memcpy(bBuffer, [_screenData bytes], bLength);
	
	bBuffer[4] = (bBuffer[4] & 0x70);
	bBuffer[4] = (bBuffer[4] | 0x80);
	bBuffer[4] = (bBuffer[4] | GIF_code);
	
	if (GIF_sort) {
		bBuffer[4] |= 0x08;
	}
	
    const char cHeader[] = "GIF89a";
    
    [_frameData setLength:0];
    [_frameData appendBytes:cHeader length:(sizeof(cHeader) - 1)];
    [_frameData appendData:_screenData];
    
    [_screenData setLength:0];
    [_screenData appendBytes:bBuffer length:bLength];
    
	if (1 == _colorF) {
		if (![self getBytes:(3 * GIF_size)]) {
            return NO;
        }
		[_frameData appendBytes:_bufferPacket.bytes length:_bufferPacket.length];
	} else {
		[_frameData appendBytes:_globalPacket.bytes length:_globalPacket.length];
	}
	
	// Add Graphic Control Extension Frame (for transparancy)
	[_frameData appendData:frame.header];
	
	char endC = 0x2c;
	[_frameData appendBytes:&endC length:sizeof(endC)];
	
	size_t cLength = tempScreen.length;
	u_char cBuffer[cLength];
    memcpy(cBuffer, tempScreen.bytes, tempScreen.length);
	
	cBuffer[8] &= 0x40;
	
	[_frameData appendBytes:tempScreen.bytes length:tempScreen.length];
	if (![self getBytes:1]) {
        return NO;
    }
    [_frameData appendBytes:_bufferPacket.bytes length:_bufferPacket.length];
	
	while (YES)
    {
        if (![self getBytes:1]) {
            return NO;
        }
        [_frameData appendBytes:_bufferPacket.bytes length:_bufferPacket.length];
		
		int len = (int)_bufferPacket.bytes[0];
        
		if (len) {
            if (![self getBytes:len]) {
                return NO;
            }
			[_frameData appendBytes:_bufferPacket.bytes length:_bufferPacket.length];
        } else {
            break;
        }
	}
	
	endC = 0x3b;
	[_frameData appendBytes:&endC length:sizeof(endC)];
	
	// save the frame into the array of frames
	frame.data = _frameData;
    
    return YES;
}

/* Puts (int) length into the _bufferPacket from file, returns whether read was succesfull */
- (BOOL)getBytes:(int)length {
    const int newCursor = _currentCursor + length;
    
	if (_sourcePacket.length >= newCursor) // Don't read across the edge of the file..
    {
		_bufferPacket.bytes = _sourcePacket.bytes + _currentCursor;
        _bufferPacket.length = length;
        _currentCursor = newCursor;
		return YES;
	}
    return NO;
}

/* Skips (int) length bytes in the GIF, faster than reading them and throwing them away.. */
- (BOOL)skipBytes:(int)length {
    const int newCursor = _currentCursor + length;
    
    if (_sourcePacket.length >= newCursor) {
        _currentCursor = newCursor;
        return YES;
    }
    return NO;
}

- (void)dealloc {
}

@end
