//
//  ZipArchive.mm
//
//
//  Created by aish on 08-9-11.
//  acsolu@gmail.com
//  Copyright 2008  Inc. All rights reserved.
//

#import "ZipCoder.h"
#import <zlib.h>
#import <zconf.h>

@interface ZipCoder (Private)
- (NSDate *)date1980;
@end

@implementation ZipCoder

- (id)init {
	if (self = [super init]) {
	}
	return self;
}

- (void)dealloc {
	[self zipClose];
    [self unzipClose];
}

- (BOOL)zipCreateFilAtPath:(NSString *)path {
    return [self zipCreateFilAtPath:path password:nil];
}

- (BOOL)zipCreateFilAtPath:(NSString *)path password:(NSString *)password {
    if (NULL == _zipFile && NULL == _unzFile && (_zipFile = zipOpen([path fileSystemRepresentation], 0))) {
        _zipFilePath = path;
        _password = password;
        return YES;
    }
	return NO;
}

- (BOOL)zipAddSourceFileAtPath:(NSString *)path newName:(NSString *)newName inOperation:(NSOperation *)operation {
	if (NULL == _zipFile) {
		return NO;
    }
	
    @autoreleasepool {
        int errorCode = Z_ERRNO;
        zip_fileinfo zipInfo = { 0 };
        NSDictionary* attributes = [theFileManager attributesOfItemAtPath:path error:NULL];
        
        if (attributes) {
            NSDate* fileDate = (NSDate*)[attributes objectForKey:NSFileModificationDate];
            
            if (fileDate) {
                // some application does use dosDate, but tmz_date instead
                //	zipInfo.dosDate = [fileDate timeIntervalSinceDate:[self date1980] ];
                NSCalendar* currCalendar = [NSCalendar currentCalendar];
                const UInt32 flags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
                NSDateComponents* dateComponents = [currCalendar components:flags fromDate:fileDate];
                
                zipInfo.tmz_date.tm_sec = [dateComponents second];
                zipInfo.tmz_date.tm_min = [dateComponents minute];
                zipInfo.tmz_date.tm_hour = [dateComponents hour];
                zipInfo.tmz_date.tm_mday = [dateComponents day];
                zipInfo.tmz_date.tm_mon = [dateComponents month] - 1;
                zipInfo.tmz_date.tm_year = [dateComponents year];
            }
        }
        
        if (![operation isCancelled]) {
            if ([_password length] == 0) {
                errorCode = zipOpenNewFileInZip(_zipFile,
                                             [newName UTF8String],
                                             &zipInfo,
                                             NULL,
                                             0,
                                             NULL,
                                             0,
                                             NULL,//comment
                                             Z_DEFLATED,
                                             Z_DEFAULT_COMPRESSION);
            } else {
                if (![operation isCancelled]) {
                    int fd = open([path fileSystemRepresentation], O_RDONLY);
                    ssize_t readBytes = 0;
                    uLong crcValue = 0L;
                    Bytef *tempBuffer = NULL;
                    
                    do {
                        crcValue = crc32(crcValue, tempBuffer, readBytes);
                        readBytes = read(fd, _buffer, kZipBufferSize);
                        tempBuffer = (Bytef *)_buffer;
                    } while (readBytes > 0 && ![operation isCancelled]);
                    
                    close(fd);
                    
                    if (![operation isCancelled]) {
                        errorCode = zipOpenNewFileInZip3(_zipFile,
                                                      [newName UTF8String],
                                                      &zipInfo,
                                                      NULL,
                                                      0,
                                                      NULL,
                                                      0,
                                                      NULL,//comment
                                                      Z_DEFLATED,
                                                      Z_DEFAULT_COMPRESSION,
                                                      0,
                                                      15,
                                                      8,
                                                      Z_DEFAULT_STRATEGY,
                                                      [_password cStringUsingEncoding:NSASCIIStringEncoding],
                                                      crcValue);
                    }
                }
            }
        }
        
        if (Z_OK == errorCode && ![operation isCancelled]) {
            int fd = open([path fileSystemRepresentation], O_RDONLY);
            
            do {
                ssize_t readBytes = read(fd, _buffer, kZipBufferSize);
                if (readBytes > 0) {
                    errorCode = zipWriteInFileInZip(_zipFile, _buffer, readBytes);
                } else {
                    errorCode = Z_DATA_ERROR;
                }
            } while (Z_OK == errorCode && ![operation isCancelled]);
            
            close(fd);
            errorCode = zipCloseFileInZip(_zipFile);
        }
        
        return (Z_OK == errorCode);
    }
}

- (BOOL)zipClose {
    _zipFilePath = nil;
	_password = nil;
    
    BOOL succeeded = NO;
    
	if (_zipFile) {
        succeeded = (Z_OK == zipClose(_zipFile, NULL));
        _zipFile = NULL;
    }
	return succeeded;
}

- (BOOL)unzipOpenFileAtPath:(NSString *)path {
    return [self unzipOpenFileAtPath:path password:nil];
}

- (BOOL)unzipOpenFileAtPath:(NSString *)path password:(NSString *)password {
	if (NULL == _zipFile && NULL == _unzFile && (_unzFile = unzOpen([path fileSystemRepresentation]))) {
		unz_global_info globalInfo = { 0 };
        
		if (UNZ_OK == unzGetGlobalInfo(_unzFile, &globalInfo)) {
			DLog(@"%lu entries in the zip path", globalInfo.number_entry);
            _zipFilePath = path;
            _password = password;
            return YES;
		}
	}
	return NO;
}

- (BOOL)unzipFileToPath:(NSString *)path allowOverwrite:(BOOL)allowOverwrite inOperation:(NSOperation *)operation {
    NSCalendar *gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
    NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
    int errorCode = unzGoToFirstFile(_unzFile);
    BOOL succeeded = YES;
    
    while (UNZ_OK == errorCode && ![operation isCancelled]) {
        @autoreleasepool {
            if (1 > [_password length]) {
                errorCode = unzOpenCurrentFile(_unzFile);
            } else {
                errorCode = unzOpenCurrentFilePassword(_unzFile, [_password cStringUsingEncoding:NSASCIIStringEncoding]);
            }
            if (UNZ_OK != errorCode) {
                succeeded = NO;
                break;
            }
            unz_file_info fileInfo = { 0 };
            
            errorCode = unzGetCurrentFileInfo(_unzFile, &fileInfo, NULL, 0, NULL, 0, NULL, 0);
            if (UNZ_OK != errorCode) {
                succeeded = NO;
                unzCloseCurrentFile(_unzFile);
                break;
            }
            
            char fileName[PATH_MAX + 1];
            const uLong pathLength = (fileInfo.size_filename <= PATH_MAX) ? fileInfo.size_filename : PATH_MAX;
            
            unzGetCurrentFileInfo(_unzFile, &fileInfo, fileName, pathLength + 1, NULL, 0, NULL, 0);
            fileName[pathLength] = '\0';
            
            for (uLong j = 0; j < pathLength; ++ j) {
                if ('\\' == fileName[j]) {
                    fileName[j] = '/';
                }
            }
            
            // check if it contains directory
            NSString *relativePath = [NSString stringWithUTF8String:fileName];
            NSString* absolutePath = [path stringByAppendingPathComponent:relativePath];
            
            if ('/' == fileName[pathLength - 1]) {
                [theFileManager createDirectoryAtPath:absolutePath withIntermediateDirectories:YES attributes:nil error:nil];
            } else {
                [theFileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
                [theFileManager createFileAtPath:absolutePath contents:nil attributes:nil];
            }
            
            if (![operation isCancelled]) {
                FILE* fp = fopen([absolutePath fileSystemRepresentation], "wb");
                
                if (fp) {
                    // reading data and write to path
                    int readBytes = 0;
                    
                    while (![operation isCancelled] && (readBytes = unzReadCurrentFile(_unzFile, _buffer, kZipBufferSize)) > 0) {
                        fwrite(_buffer, readBytes, 1, fp);
                    }   // end while
                    
                    fclose(fp);
                    
                    if (![operation isCancelled]) {
                        DLog(@"path: %@\nsize: %@", relativePath, [[theFileManager attributesOfItemAtPath:absolutePath error:NULL] valueForKey:NSFileSize]);
                        //{{ thanks to brad.eaton for the solution
                        dateComponents.second = fileInfo.tmu_date.tm_sec;
                        dateComponents.minute = fileInfo.tmu_date.tm_min;
                        dateComponents.hour = fileInfo.tmu_date.tm_hour;
                        dateComponents.day = fileInfo.tmu_date.tm_mday;
                        dateComponents.month = fileInfo.tmu_date.tm_mon + 1;
                        dateComponents.year = fileInfo.tmu_date.tm_year;
                        
                        NSDate *date = [gregorianCalendar dateFromComponents:dateComponents];
                        NSDictionary *attributes = [NSDictionary dictionaryWithObject:date forKey:NSFileModificationDate];
                        
                        [theFileManager setAttributes:attributes ofItemAtPath:absolutePath error:nil];
                    }
                }   // end if (fp)
            }
            
            unzCloseCurrentFile(_unzFile);
            errorCode = unzGoToNextFile(_unzFile);
        }
        
    }   //  end while (UNZ_OK == errorCode && ![operation isCancelled])
    
    return succeeded;
}

- (BOOL)unzipClose {
    _zipFilePath = nil;
	_password = nil;
    
    BOOL succeeded = NO;
    
	if (_unzFile) {
		succeeded = (UNZ_OK == unzClose(_unzFile));
        _unzFile = NULL;
    }
	return succeeded;
}

#pragma mark get NSDate object for 1980-01-01

- (NSDate *)date1980 {
	NSDateComponents *comps = [[NSDateComponents alloc] init];
	[comps setDay:1];
	[comps setMonth:1];
	[comps setYear:1980];
	NSCalendar *gregorianCalendar = [[NSCalendar alloc]
							 initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
	NSDate *date = [gregorianCalendar dateFromComponents:comps];
	
	return date;
}


@end
