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

#import "ZipArchive.h"
#import "zlib.h"
#import "zconf.h"

#import "libs.h"	// 2013/01/31 AIG-Soft

@interface ZipArchive (Private)
-(void) OutputErrorMessage:(NSString*) msg;
-(BOOL) OverWrite:(NSString*) file;
-(NSDate*) Date1980;
@end



@implementation ZipArchive

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

-(void) dealloc
{
	[self CloseZipFile2];
}

-(BOOL) CreateZipFile2:(NSString*) zipFile
{
	_password = nil;
	_zipFile = zipOpen( (const char*)[zipFile UTF8String], 0 );
	if( !_zipFile ) return NO;
	return YES;
}

-(BOOL) CreateZipFile2:(NSString*) zipFile Password:(NSString*) password
{
#ifdef ENABLE_PASSWORD
	_password = password;
#else	// パスワードは無効
	_password = nil;
#endif
	_zipFile = zipOpen( (const char*)[zipFile UTF8String], 0 );
	if( !_zipFile ) return NO;
	return YES;
}


// 2015/09/17 AIG-Soft追加
-(BOOL) appendZipFile2:(NSString*) zipFile
{
	_password = nil;
	_zipFile = zipOpen( (const char*)[zipFile UTF8String], APPEND_STATUS_ADDINZIP );
	if( !_zipFile ) return NO;
	return YES;
}

-(BOOL) appendZipFile2:(NSString*) zipFile Password:(NSString*) password
{
#ifdef ENABLE_PASSWORD
	_password = password;
#else	// パスワードは無効
	_password = nil;
#endif
	_zipFile = zipOpen( (const char*)[zipFile UTF8String], APPEND_STATUS_ADDINZIP );
	if( !_zipFile ) return NO;
	return YES;
}

// 2012/12/18 AIG-Soft追加
// file : zipファイル名(フルパス名)
// newname : 圧縮するファイルのフルパス名
// 将来的にはカレント相当以下のディレクトリは有効にしたいが、今回はなし
-(BOOL) addFileToZipWithFilenameOnly:(NSString*)file newname:(NSString*)newname
{
	setFfilenameOnly(1);
	BOOL ret=[self addFileToZip:file newname:newname];
	setFfilenameOnly(0);
	return (ret);
}

// フルパス名用
/// @return NO=エラー , YES=正常終了
-(BOOL) addFileToZip:(NSString *)file newname:(NSString *)newname;
{
	nslog(@"addFileToZip:%@ ← %@",file,newname);
	if( !_zipFile ) return NO;
//	tm_zip filetime;
	time_t current;
	time( &current );
	
	zip_fileinfo zipInfo = {0};
//	zipInfo.dosDate = (ULong) current;
	
	// 2012/12/16 for iOS v6 ↓は削除されたメソッドだから
//	NSDictionary* attr = [[NSFileManager defaultManager] fileAttributesAtPath:file traverseLink:YES];
	// traverseLink:YES 〜 シンボリックリンクの時、そのリンク先を調べる
	NSError *error;
	NSDictionary* attr = [[NSFileManager defaultManager] attributesOfItemAtPath:newname error:&error];
	nslog(@"attr=%@/error=%@",attr,error);
	if( attr )
	{
		NSDate* fileDate = (NSDate*)attr[NSFileModificationDate];
		if( fileDate )
		{
			// some application does use dosDate, but tmz_date instead
		//	zipInfo.dosDate = [fileDate timeIntervalSinceDate:[self Date1980] ];
			NSCalendar* currCalendar = [NSCalendar currentCalendar];
			uint flags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit ;
			NSDateComponents* dc = [currCalendar components:flags fromDate:fileDate];
			zipInfo.tmz_date.tm_sec  = (uInt)[dc second];
			zipInfo.tmz_date.tm_min  = (uInt)[dc minute];
			zipInfo.tmz_date.tm_hour = (uInt)[dc hour];
			zipInfo.tmz_date.tm_mday = (uInt)[dc day];
			zipInfo.tmz_date.tm_mon  = (uInt)[dc month] - 1;
			zipInfo.tmz_date.tm_year = (uInt)[dc year];
//			nslog(@"%04d/%02d/%02d %02d:%02d:%02d",zipInfo.tmz_date.tm_year,zipInfo.tmz_date.tm_mon,zipInfo.tmz_date.tm_mday,zipInfo.tmz_date.tm_hour,zipInfo.tmz_date.tm_min,zipInfo.tmz_date.tm_sec);
		}
	}
	
	int ret ;
	NSData* data = nil;
	if( [_password length] == 0 ) {
		nslog(@"パスワード無し圧縮");
		ret = zipOpenNewFileInZip( _zipFile,
								  (const char *)[newname UTF8String],
								  &zipInfo,
								  NULL,0,
								  NULL,0,
								  NULL,		//comment
								  Z_DEFLATED,
								  Z_DEFAULT_COMPRESSION );
	} else {
		nslog(@"パスワード付き圧縮");
//		nslog(@"パスワード=%@->%s",_password,[_password cStringUsingEncoding:NSASCIIStringEncoding]);
		data = [ NSData dataWithContentsOfFile:file];
		uLong crcValue = crc32( 0L,NULL, 0L );
		crcValue = crc32( crcValue, (const Bytef*)[data bytes], (uInt)[data length] );
		ret = zipOpenNewFileInZip3( _zipFile,
								  (const char *)[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( ret!=Z_OK )
	{
		return NO;
	}
	if( data==nil )
	{
		// 2013/07/21 オリジナルのバグ修正
//		data = [ NSData dataWithContentsOfFile:file];
		nslog(@"ファイルのNSData化:file=%@",newname);
		data = [ NSData dataWithContentsOfFile:newname];
	}
	size_t dataLen = [data length];
	nslog(@"サイズ=%zu",dataLen);
	
	ret = zipWriteInFileInZip( _zipFile, (const void*)[data bytes], (unsigned int)dataLen);
	if( ret!=Z_OK )	{
		nslog(@"圧縮失敗(write)");
		return NO;
	}
	ret = zipCloseFileInZip( _zipFile );
	if ( ret!=Z_OK )	{
		nslog(@"圧縮失敗(close)");
		return NO;
	}
	nslog(@"圧縮成功");
	return YES;
}

-(BOOL) CloseZipFile2
{
	_password = nil;
	if( _zipFile==NULL ) return NO;
	BOOL ret =  zipClose( _zipFile,NULL )==Z_OK?YES:NO;
	_zipFile = NULL;
	return ret;
}

-(BOOL) UnzipOpenFile:(NSString*) zipFile
{
	_unzFile = unzOpen( (const char*)[zipFile UTF8String] );
	if( _unzFile )
	{
		unz_global_info  globalInfo = {0};
		if( unzGetGlobalInfo(_unzFile, &globalInfo )==UNZ_OK )
		{
			// 2012/12/16 for iOS
			nslog(@"%ld entries in the zip file",globalInfo.number_entry);
		}
	}
	return _unzFile!=NULL;
}

-(BOOL) UnzipOpenFile:(NSString*) zipFile Password:(NSString*) password
{
	_password = password;
	return [self UnzipOpenFile:zipFile];
}

// ZIPが展開対象外ファイルを含むかどうか調べる
// リターン値 : YES=ない , NO=ある もしくは 展開できないファイルが有る
// 2013/11/07 AIG-Soft
-(BOOL)checkInZipFile:(NSString*)path exceptExt:(NSString *)ecext
{
	BOOL success = YES;
	int ret = unzGoToFirstFile( _unzFile );
	do {
		if( [_password length]==0 ) {
			ret = unzOpenCurrentFile( _unzFile );
		} else {
			ret = unzOpenCurrentFilePassword( _unzFile, [_password cStringUsingEncoding:NSASCIIStringEncoding] );
		}
		if( ret!=UNZ_OK )
		{
			[self OutputErrorMessage:@"Error occurs"];
			success = NO;
			break;
		}
		unz_file_info	fileInfo ={0};
		ret = unzGetCurrentFileInfo(_unzFile, &fileInfo, NULL, 0, NULL, 0, NULL, 0);
		if( ret!=UNZ_OK )
		{
			[self OutputErrorMessage:@"Error occurs while getting file info"];
			success = NO;
			unzCloseCurrentFile( _unzFile );
			break;
		}
		
		char *filename = (char *)malloc( fileInfo.size_filename +1 );
		unzGetCurrentFileInfo(_unzFile, &fileInfo, filename, fileInfo.size_filename + 1, NULL, 0, NULL, 0);
		filename[fileInfo.size_filename] = '\0';

		unzCloseCurrentFile( _unzFile ); // 実体を読むことはないので閉じてしまう
		
		NSString *ext=[@(filename) lastPathComponent];
		nslog(@"filename=%s,ext=%@",filename,ext);
		free(filename);
		
		// 排除拡張子チェック
		if (cmpRegularExpression(ext,ecext)==YES) {
			nslog(@"展開対象外ファイル:%@:指定=%@",ext,ecext);
			success=NO; // 展開外ファイルが有った
			break;
		}
		ret = unzGoToNextFile( _unzFile ); // 次のファイルへ
	} while ( ret==UNZ_OK && UNZ_OK!=UNZ_END_OF_LIST_OF_FILE );
	// 展開外ファイルはなかった
	return success;
}

// 指定パスにZipを解凍する
// 除外拡張子指定付き: // 2013/01/31 AIG-Soft
-(BOOL)UnzipFileTo:(NSString*)path overWrite:(BOOL)overwrite exceptExt:(NSString *)ecext
{
	printFunc();
	nslog(@"path=%@",path);
	nslog(@"except ext=%@",ecext);
	
	BOOL success = YES;
	int ret = unzGoToFirstFile( _unzFile );
	unsigned char buffer[4096] = {0};
	NSFileManager* fman = [NSFileManager defaultManager];
	if( ret!=UNZ_OK )
	{
		[self OutputErrorMessage:@"Failed"];
	}
	
	do {
		if( [_password length]==0 ) {
			ret = unzOpenCurrentFile( _unzFile );
		} else {
			ret = unzOpenCurrentFilePassword( _unzFile, [_password cStringUsingEncoding:NSASCIIStringEncoding] );
		}
		if( ret!=UNZ_OK )
		{
			[self OutputErrorMessage:@"Error occurs"];
			success = NO;
			break;
		}
		// reading data and write to file
		int read ;
		unz_file_info	fileInfo ={0};
		ret = unzGetCurrentFileInfo(_unzFile, &fileInfo, NULL, 0, NULL, 0, NULL, 0);
		if( ret!=UNZ_OK )
		{
			[self OutputErrorMessage:@"Error occurs while getting file info"];
			success = NO;
			unzCloseCurrentFile( _unzFile );
			break;
		}
		char *filename = (char *)malloc(fileInfo.size_filename+1);
		NSLog(@"fileInfo.size_filename =%lu",fileInfo.size_filename );
		unzGetCurrentFileInfo(_unzFile, &fileInfo, filename, fileInfo.size_filename + 1, NULL, 0, NULL, 0);
		filename[fileInfo.size_filename] = '\0';

		// check if it contains directory
		// 2012/12/16 for iOS v6
		// 2015/09/07 SHIFT-JISファイル名対応
#if DEBUG
		{
		NSLog(@"filename=%s",filename);
		UByte *p=(UByte *)filename;
		for (int i=0;i<strlen(filename);i++) {
			fprintf(stdout,"%02x,",*p++);
		}
		fprintf(stdout,"\n");
		}
		// zipのファイル名は、作られた環境に依存するようである。
		// Windows7上で作ったファイル名はSHIFT-JISになっている。
		// 故に、ここではその文字コードを認識して変換しなければならない・・・のだが、簡略化のため、双方の文字コードで変換してみて、有効な方を使う。
		// NSUTF8StringEncodingはUTF8として無効な場合はnilが返るようである。
#endif
		NSString * strPathUTF8 = [NSString stringWithCString:filename encoding:NSUTF8StringEncoding];
		NSString * strPathSJIS = [NSString stringWithCString:filename encoding:NSShiftJISStringEncoding];
		NSString * strPath=(strPathUTF8==nil? strPathSJIS:strPathUTF8);
		nslog(@"strPath=%@",strPath);
		BOOL isDirectory = NO;
		if ( filename[fileInfo.size_filename-1]=='/' || filename[fileInfo.size_filename-1]=='\\') {
			isDirectory = YES;
		}
		free( filename );
		if( [strPath rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"/\\"]].location!=NSNotFound )	{
			// contains a path
			strPath = [strPath stringByReplacingOccurrencesOfString:@"\\" withString:@"/"];
		}
		NSString* fullPath = [path stringByAppendingPathComponent:strPath];
		
		if ( isDirectory ) {
			[fman createDirectoryAtPath:fullPath withIntermediateDirectories:YES attributes:nil error:nil];
		} else {
			[fman createDirectoryAtPath:[fullPath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil];
		}
		if ( [fman fileExistsAtPath:fullPath] && !isDirectory && !overwrite ) {
			if( ![self OverWrite:fullPath] )
			{
				unzCloseCurrentFile( _unzFile );
				ret = unzGoToNextFile( _unzFile );
				continue;
			}
		}
		
		// 排除拡張子チェック
		// 2013/01/31 AIG-Soft
		NSString *ext=[fullPath lastPathComponent];
		if (cmpRegularExpression(ext,ecext)==NO) {
			FILE *fp = fopen( (const char*)[fullPath UTF8String], "wb");
			while ( fp ) {
				read=unzReadCurrentFile(_unzFile, buffer, 4096);
				if( read > 0 ) {
					fwrite(buffer, read, 1, fp );
				}
				else
				if ( read<0 ) {
					[self OutputErrorMessage:@"Failed to reading zip file"];
					break;
				}
				else {
					break;
				}
			} // while
			if ( fp ) {
				fclose( fp );
				// set the orignal datetime property
				NSDate* orgDate = nil;
				
				//{{ thanks to brad.eaton for the solution
				NSDateComponents *dc = [[NSDateComponents alloc] init];
				dc.second = fileInfo.tmu_date.tm_sec;
				dc.minute = fileInfo.tmu_date.tm_min;
				dc.hour   = fileInfo.tmu_date.tm_hour;
				dc.day    = fileInfo.tmu_date.tm_mday;
				dc.month  = fileInfo.tmu_date.tm_mon+1;
				dc.year   = fileInfo.tmu_date.tm_year;
				NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
				orgDate = [gregorian dateFromComponents:dc] ;
				//}}
								
				NSDictionary* attr = @{NSFileModificationDate: orgDate}; //[[NSFileManager defaultManager] fileAttributesAtPath:fullPath traverseLink:YES];
				if ( attr ) {
					//		[attr  setValue:orgDate forKey:NSFileCreationDate];
					if( ![[NSFileManager defaultManager] setAttributes:attr ofItemAtPath:fullPath error:nil] ) {
						// cann't set attributes 
						nslog(@"Failed to set attributes");
					}
				}
			}
		} // if (ext)
#ifdef DEBUG
		else {
			nslog(@"展開対象外ファイル:%@:指定=%@",ext,ecext);
		}
#endif
		unzCloseCurrentFile( _unzFile );
		ret = unzGoToNextFile( _unzFile );
	} while ( ret==UNZ_OK && UNZ_OK!=UNZ_END_OF_LIST_OF_FILE );
	return success;
}

// 指定パスにZipを解凍する
// 常時全ファイル
-(BOOL)UnzipFileTo:(NSString*)path overWrite:(BOOL)overwrite
{
	// オーバーライドでの実装に変更
	// 2013/01/31 AIG-Soft
	return [self UnzipFileTo:path overWrite:overwrite exceptExt:@""];
}

-(BOOL) UnzipCloseFile
{
	_password = nil;
	if ( _unzFile ) {
		return unzClose( _unzFile )==UNZ_OK;
	}
	return YES;
}

#pragma mark wrapper for delegate
-(void) OutputErrorMessage:(NSString*) msg
{
	if( _delegate && [_delegate respondsToSelector:@selector(ErrorMessage)] ) {
		[_delegate ErrorMessage:msg];
	}
}

-(BOOL) OverWrite:(NSString*) file
{
	if( _delegate && [_delegate respondsToSelector:@selector(OverWriteOperation)] ) {
		return [_delegate OverWriteOperation:file];
	}
	return YES;
}

#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 *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
	NSDate *date = [gregorian dateFromComponents:comps];
	
	return date;
}

@end


