//
//  FSFileOperation.m
//  Downloader
//
//  Created by LiDong on 13-3-18.
//  Copyright (c) 2013年 LiDong. All rights reserved.
//

#import "FSFileOperation.h"
#import "FSFileItem.h"
#import "ZipCoder.h"
#import "FSOperationManager.h"

#define kOperationBufferSize SIZE_1_MB

@implementation FSFileOperation

@synthesize operationType = _operationType;
@synthesize ignoreOnce = _ignoreOnce;
@synthesize delegate = _delegate;

- (id)copyWithZone:(NSZone *)zone {
    FSFileOperation *newOperation = [[[self class] allocWithZone:zone] initWithOperationType:_operationType];
    
    if(newOperation) {
        newOperation->_delegate = _delegate;
        [newOperation->_srcPaths setArray:_srcPaths];
        newOperation->_srcCompletionHandler = [_srcCompletionHandler copy];
    }
    return newOperation;
}

- (id)initWithOperationType:(OPERATION_TYPE)type {
    if (self = [super init]) {
        _operationType = type;
        _srcPaths = [[NSMutableArray alloc] initWithCapacity:100];
        _condition = [[NSCondition alloc] init];
    }
    return self;
}

- (void)dealloc {
    if (_buffer) {
        free(_buffer);
    }
}

- (BOOL)canPaste {
    if (OPERATION_COPY == _operationType || OPERATION_CUT == _operationType) {
        return ([_srcPaths count] > 0);
    }
    return NO;
}

- (void)setFileItems:(NSArray *)fileItems {
    [_srcPaths removeAllObjects];
    
    for (FSFileItem *item in fileItems) {
        NSString *path = [[item path] copy];
        
        [_srcPaths addObject:path];
    }
}

- (void)setDstPath:(NSString *)dstPath {
    if ([dstPath length] > 0) {
        BOOL isDirectory = NO;
        
        if ([theFileManager fileExistsAtPath:dstPath isDirectory:&isDirectory] && isDirectory) {
            _dstDirectory = [dstPath copy];
        } else {
            _dstDirectory = [[dstPath stringByDeletingLastPathComponent] copy];
        }
    } else {
        _dstDirectory = nil;
    }
}

- (void)setSrcCompletionHandler:(FSCompletionHandler)handler {
    if (handler) {
        _srcCompletionHandler = [handler copy];
    } else {
        _srcCompletionHandler = NULL;
    }
}

- (void)setDstCompletionHandler:(FSCompletionHandler)handler {
    if (handler) {
        _dstCompletionHandler = [handler copy];
    } else {
        _dstCompletionHandler = NULL;
    }
}

- (void)operationDidComplete {
    const BOOL completed = ![self isCancelled];
    
    if (_srcCompletionHandler) {
        _srcCompletionHandler(completed);
    }
    if (_dstCompletionHandler) {
        _dstCompletionHandler(completed);
    }
    [_delegate fileOperationDidEnd:self];
}

- (void)start {
    const dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    dispatch_sync(mainQueue, ^{
        [_delegate fileOperationDidBegin:self];
    });
    
    __weak __typeof(self) weakSelf = self;
    
    [self setCompletionBlock:^{
        __strong __typeof(weakSelf) strongSelf = weakSelf;
        
        dispatch_sync(mainQueue, ^{
            [strongSelf operationDidComplete];
        });
    }];
    
    [super start];
}

- (void)askForReplacingFileAtPath:(NSString *)path {
    DLog(@"PATH: %@", path);
    NSString *title = nil;
    
    if (OPERATION_COPY == _operationType) {
        title = LS(@"COPYING_FILES");
    } else if (OPERATION_CUT == _operationType) {
        title = LS(@"MOVING_FILES");
    }
    
    NSString *message = [[NSString alloc] initWithFormat:LS(@"REPLACE_FILES_FORMATTER"), [path lastPathComponent]];
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:message delegate:self cancelButtonTitle:LS(@"IGNORE") otherButtonTitles:LS(@"REPLACE"), nil];
    
    [alert setTag:1];
    [alert show];
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    if (1 == [alertView tag]) {
        [self setIgnoreOnce:(0 == buttonIndex)];
        
        [_condition lock];
        [_condition signal];
        [_condition unlock];
    }
}

- (void)removeFileItemAtPath:(NSString *)path  {
    @autoreleasepool {
        BOOL isDirectory = NO;
        
        if ([theFileManager fileExistsAtPath:path isDirectory:&isDirectory]) {
            if (isDirectory) {
                NSArray *fileNames = [theFileManager contentsOfDirectoryAtPath:path error:NULL];
                const NSInteger numberOfFiles = [fileNames count];
                
                for (NSInteger i = 0; i < numberOfFiles && ![self isCancelled]; ++ i) {
                    NSString *fileName = [fileNames objectAtIndex:i];
                    NSString *srcPath = [path stringByAppendingPathComponent:fileName];
                    
                    [self removeFileItemAtPath:srcPath];
                }
            }
            
            if (![self isCancelled]) {
                [theFileManager removeItemAtPath:path error:NULL];
            }
        }
    }
}

- (void)copyFileItemFromPath:(NSString *)srcPath toPath:(NSString *)dstPath  {
    @autoreleasepool {
        BOOL isDirectory = NO;
        
        if ([theFileManager fileExistsAtPath:srcPath isDirectory:&isDirectory]) {
            BOOL ignored = NO;
            
            if ([theFileManager fileExistsAtPath:dstPath]) {
                [self performSelectorOnMainThread:@selector(askForReplacingFileAtPath:) withObject:dstPath waitUntilDone:YES];
                
                [_condition lock];
                [_condition wait];
                [_condition unlock];
                
                ignored = [self ignoreOnce];
                
                if (ignored) {
                    [self setIgnoreOnce:NO];
                } else {
                    [self removeFileItemAtPath:dstPath];
                    if ([self isCancelled]) {
                        ignored = YES;
                    }
                }   // end if (ignored)
                
            }   // end if ([theFileManager fileExistsAtPath:dstPath])
            
            if (!ignored) {
                NSDictionary *attributes = [theFileManager attributesOfItemAtPath:srcPath error:NULL];
                
                if (isDirectory) {
                    if ([theFileManager fileExistsAtPath:dstPath]) {
                        [theFileManager setAttributes:attributes ofItemAtPath:dstPath error:NULL];
                    } else {
                        [theFileManager createDirectoryAtPath:dstPath withIntermediateDirectories:YES attributes:attributes error:NULL];
                    }
                    
                    NSArray *fileNames = [theFileManager contentsOfDirectoryAtPath:srcPath error:NULL];
                    NSInteger numberOfFiles = [fileNames count];
                    
                    for (NSInteger i = 0; i < numberOfFiles && ![self isCancelled]; ++ i) {
                        NSString *fileName = [fileNames objectAtIndex:i];
                        NSString *newSrcPath = [srcPath stringByAppendingPathComponent:fileName];
                        NSString *newDstPath = [dstPath stringByAppendingPathComponent:fileName];
                        
                        [self copyFileItemFromPath:newSrcPath toPath:newDstPath];
                        
                    }   //  end for
                    
                    if (OPERATION_COPY != _operationType && ![self isCancelled]) {
                        [theFileManager removeItemAtPath:srcPath error:NULL];
                    }
                    
                } else {
                    
                    if (OPERATION_COPY == _operationType) {
                        [theFileManager createFileAtPath:dstPath contents:nil attributes:attributes];
                        
                        const char *cSrcPath = [srcPath fileSystemRepresentation];
                        const char *cDstPath = [dstPath fileSystemRepresentation];
                        int src_fd = open(cSrcPath, O_RDONLY);
                        int dst_fd = open(cDstPath, O_WRONLY | O_TRUNC);
                        int size = 0;
                        
                        while(![self isCancelled]) {
                            size = (int)read(src_fd, _buffer, kOperationBufferSize);
                            
                            if (size <= 0 || (int)write(dst_fd, _buffer, size) <= 0) {
                                break;
                            }
                        }   //  end while
                        
                        close(src_fd);
                        close(dst_fd);
                        
                    } else {
                        
                        [theFileManager moveItemAtPath:srcPath toPath:dstPath error:NULL];
                        
                    }   //  end if (OPERATION_COPY == _operationType)
                    
                }   //  end if (isDirectory)
                
            }   // end if (!ignored)
            
        }   //  end if
        
    }   //  end @autoreleasepool
}

- (void)copyingRoutine {
    const NSInteger numberOfSources = [_srcPaths count];
    
    if (NULL == _buffer) {
        _buffer = malloc(kOperationBufferSize);
    }
    
    const dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    for (NSInteger i = 0; i < numberOfSources && ![self isCancelled]; ++ i) {
        NSString *srcPath = [_srcPaths objectAtIndex:i];
        
        if ([_dstDirectory hasPrefix:srcPath]) {
            NSString *srcName = [srcPath lastPathComponent];
            NSString *title = nil;
            NSString *formatter = nil;
            
            if (OPERATION_COPY == _operationType) {
                title = LS(@"COPYING_FILES_FAILED");
                formatter = LS(@"COPYING_RECURSIVELY_FORMATTER");
            } else {
                title = LS(@"MOVING_FILES_FAILED");
                formatter = LS(@"MOVING_RECURSIVELY_FORMATTER");
            }
            
            NSString *message = [[NSString alloc] initWithFormat:formatter, srcName];
            
            dispatch_sync(mainQueue, ^{
                UIAlert(title, message);
            });
            
            } else {
            
            NSString *dstPath = [_dstDirectory stringByAppendingPathComponent:[srcPath lastPathComponent]];
            
            [self copyFileItemFromPath:srcPath toPath:dstPath];
            
        }   //  end if
        
    }   //  end for
    
    if (_buffer) {
        free(_buffer);
        _buffer = NULL;
    }
}

- (void)removingRoutine {
    NSInteger numberOfPaths = [_srcPaths count];
    
    for (NSInteger i = 0; i < numberOfPaths && ![self isCancelled]; ++ i) {
        [self removeFileItemAtPath:[_srcPaths objectAtIndex:i]];
    }
}

- (void)zipRoutine {
    const NSInteger numberOfSources = [_srcPaths count];
    
    if (0 >= numberOfSources) {
        return;
    }
    
    NSString *tempPath = [FSTmpArchiveDirectory stringByAppendingPathComponent:@"Archive.zip"];
    
    if ([theFileManager fileExistsAtPath:FSTmpArchiveDirectory]) {
        [self removeFileItemAtPath:FSTmpArchiveDirectory];
    }
    
    [theFileManager createDirectoryAtPath:FSTmpArchiveDirectory withIntermediateDirectories:YES attributes:nil error:NULL];
    
    if (![self isCancelled]) {
        ZipCoder *zipCoder = [[ZipCoder alloc] init];
        
        [zipCoder zipCreateFilAtPath:tempPath];
        
        for (NSInteger i = 0; i < numberOfSources && ![self isCancelled]; ++ i) {
            NSString *srcPath = [_srcPaths objectAtIndex:i];
            NSString *pathPrefix = [srcPath stringByDeletingLastPathComponent];
            const NSRange pathPrefixRange = { 0, [pathPrefix length] + 1 };
            BOOL isDirectory = NO;
            
            if ([theFileManager fileExistsAtPath:srcPath isDirectory:&isDirectory]) {
                @autoreleasepool {
                    NSString *newName = [srcPath stringByReplacingCharactersInRange:pathPrefixRange withString:NSStringEmpty];
                    
                    if (isDirectory) {
                        [zipCoder zipAddSourceFileAtPath:srcPath newName:[newName stringByAppendingString:@"/"] inOperation:self];
                        
                        NSArray *subPaths = [theFileManager subpathsAtPath:srcPath];
                        const NSInteger numberOfSubPaths = [subPaths count];
                        
                        for (NSInteger i = 0; i < numberOfSubPaths && ![self isCancelled]; ++ i) {
                            NSString *path = [srcPath stringByAppendingPathComponent:[subPaths objectAtIndex:i]];
                            NSDictionary *attributes = [theFileManager attributesOfItemAtPath:path error:NULL];
                            NSString *fileType = [attributes valueForKey:NSFileType];
                            
                            newName = [path stringByReplacingCharactersInRange:pathPrefixRange withString:NSStringEmpty];
                            
                            if ([fileType isEqualToString:NSFileTypeDirectory]) {
                                [zipCoder zipAddSourceFileAtPath:srcPath newName:[newName stringByAppendingString:@"/"] inOperation:self];
                            } else {
                                [zipCoder zipAddSourceFileAtPath:path newName:newName inOperation:self];
                            }   //  end if
                            
                        }   //  end for
                        
                    } else {
                        
                        [zipCoder zipAddSourceFileAtPath:srcPath newName:newName inOperation:self];
                        
                    }   //  end if (isDirectory)
                    
                }   //  end @autoreleasepool
                
            }   //  end if
        }
        
        if (![self isCancelled]) {
            [zipCoder zipClose];
            
            NSString *name = nil;
            
            if (numberOfSources > 1) {
                name = [[[_srcPaths objectAtIndex:0] stringByDeletingLastPathComponent] lastPathComponent];
            } else {
                name = [[[_srcPaths objectAtIndex:0] lastPathComponent] stringByDeletingPathExtension];
            }
            
            if (nil == name) {
                name = @"Archive";
            }
            
            NSString *dstPath = FSPathForNewFileInDirectory(_dstDirectory, name, @"zip");
            
            if (![self isCancelled]) {
                [theFileManager moveItemAtPath:tempPath toPath:dstPath error:NULL];
            }
        }
        
        }
}

- (void)unzipRoutine {
    if ([_srcPaths count] > 0) {
        NSString *srcPath = [_srcPaths objectAtIndex:0];
        NSString *tempPath = FSTmpUnarchiveDirectory;
        
        if ([theFileManager fileExistsAtPath:FSTmpUnarchiveDirectory]) {
            [self removeFileItemAtPath:FSTmpUnarchiveDirectory];
        }
        [theFileManager createDirectoryAtPath:FSTmpUnarchiveDirectory withIntermediateDirectories:YES attributes:nil error:NULL];
        
        if (![self isCancelled]) {
            ZipCoder *zipCoder = [[ZipCoder alloc] init];
            BOOL succeeded = [zipCoder unzipOpenFileAtPath:srcPath];
            
            if (succeeded && ![self isCancelled]) {
                succeeded = [zipCoder unzipFileToPath:tempPath allowOverwrite:YES inOperation:self];
            }
            
            [zipCoder unzipClose];
            
            if (succeeded && ![self isCancelled]) {
                NSArray *contents = [theFileManager contentsOfDirectoryAtPath:tempPath error:NULL];
                NSInteger numberOfContents = [contents count];
                NSString *dstPath = nil;
                
                if (1 < numberOfContents) {
                    NSString *zipFileName = [[srcPath lastPathComponent] stringByDeletingPathExtension];
                    dstPath = FSPathForNewFileInDirectory(_dstDirectory, zipFileName, nil);
                } else if (1 == numberOfContents) {
                    NSString *unzipFileName = [contents objectAtIndex:0];
                    tempPath = [tempPath stringByAppendingPathComponent:unzipFileName];
                    
                    NSString *fileType = [[theFileManager attributesOfItemAtPath:tempPath error:NULL] valueForKey:NSFileType];
                    
                    if ([fileType isEqualToString:NSFileTypeDirectory]) {
                        dstPath = FSPathForNewFileInDirectory(_dstDirectory, unzipFileName, nil);
                    } else {
                        dstPath = FSPathForNewFileInDirectory(_dstDirectory, [unzipFileName stringByDeletingPathExtension], [unzipFileName pathExtension]);
                    }
                    
                }   //  end if (1 < numberOfContents)
                
                if (![self isCancelled]) {
                    if (NULL == _buffer) {
                        _buffer = malloc(kOperationBufferSize);
                    }
                    
                    [self copyFileItemFromPath:tempPath toPath:dstPath];
                    
                    if (_buffer) {
                        free(_buffer);
                        _buffer = NULL;
                        
                    }   //  end if (_buffer)
                    
                }   //  end if (![self isCancelled])
                
            }   //  end if (succeeded && ![self isCancelled])
            
            }   //  end if (![self isCancelled])
        
    }   //  end if ([_srcPaths count] > 0)
}

- (void)main {
    @autoreleasepool {
        if (OPERATION_COPY == _operationType || OPERATION_CUT == _operationType) {
            [self copyingRoutine];
        } else if (OPERATION_REMOVE == _operationType) {
            [self removingRoutine];
        } else if (OPERATION_ZIP == _operationType) {
            [self zipRoutine];
        } else if (OPERATION_UNZIP == _operationType) {
            [self unzipRoutine];
        }
    }
}

@end

