#import <Foundation/Foundation.h>
#import "Archive.h"
#import "FileInfo.h"
#import "NSString+Custom.h"
#import "NSObject+Custom.h"
#import "Preferences.h"

#define X_EXE_NOT_FOUND	@"ExecutableNotFoundException"

/**
 * This dictionary holds all the file extensions that Archive and its subclasses
 * are able to decompress. Archive subclasses register themselves with 
 * the Archive class.
 */
static NSMutableDictionary *_fileExtMappings = nil;

@interface Archive (PrivateAPI)
- (void)sortElementsBySortOrder:(int) sortOrder selector:(SEL)selector;
@end

@implementation Archive : NSObject

+ (void)initialize
{
	if (_fileExtMappings == nil)
	{
		_fileExtMappings = [[NSMutableDictionary alloc] init];
	}
}

/**
 * All subclasses must register the file extensions they support with the Archive
 * class. This faciliates dynamic addition of Archive subclasses and helps
 * the app discover who's responsible for certain file extensions.
 */
+ (void)registerFileExtension:(NSString *)extension forArchiveClass:(Class)clazz
{
	[_fileExtMappings setObject:clazz forKey:extension];
}

+ (NSDictionary *)classForFileExtension:(NSString *)fileExtension;
{
	return [_fileExtMappings objectForKey:fileExtension];
}

+ (NSArray *)allFileExtensions;
{
	return [_fileExtMappings allKeys];
}

/**
 * Returns YES if this kind of archives contains information about the compression ratio of
 * the archive, else NO.
 */
+ (BOOL)hasRatio
{
	return NO;
}

/**
 * Returns YES if this kind of archiver can uncopress files 'flat' i.e. without directory
 * information, else NO.
 */
+ (BOOL)canExtractWithoutFullPath
{
	return YES;
}

+ (Archive *)newWithPath:(NSString *)path
{
    NSParameterAssert(path != nil);
    return [[[self alloc] initWithPath:path] autorelease];
}

- (id)initWithPath:(NSString *)path
{
    [super init];
    _path = [path retain];
    _elements = nil;
	_sortOrder = NSOrderedAscending;
	_sortAttribute = NSNotFound;
    return self;
}

- (void)dealloc
{
    [_path release];
    [_elements release];
	[super dealloc];
}

- (NSString *)path
{
	return _path;
}

//------------------------------------------------------------------------------
// managing our elements
//------------------------------------------------------------------------------
- (NSArray *)elements
{
    if (_elements == nil)
    {
        [self setElements:[self listContents]];
    }
    return _elements;
}

- (void)setElements:(NSArray *)elements
{
	ASSIGN(_elements, elements);
}

- (int)elementCount
{
    return [[self elements] count];
}

- (FileInfo *)elementAtIndex:(int)index
{
    return [[self elements] objectAtIndex:index];
}

//------------------------------------------------------------------------------
// sorting our elements
//------------------------------------------------------------------------------
- (void)sortByPath
{
	NSArray * sortedElements = nil;
	
	// make sure we start by ordering in descending order when this column is clicked first
	if (_sortAttribute != SortByPath)
	{
		_sortAttribute = SortByPath;
		_sortOrder = NSOrderedDescending;
	}
	
	if (_sortOrder == NSOrderedAscending)
	{
		sortedElements = [[self elements]
			sortedArrayUsingSelector:@selector(comparePathDescending:)];
		_sortOrder = NSOrderedDescending;
	}
	else
	{
		sortedElements = [[self elements]
			sortedArrayUsingSelector:@selector(comparePathAscending:)];
		_sortOrder = NSOrderedAscending;
	}
	[self setElements:sortedElements];
}

- (void)sortBySize
{
	NSArray *sortedElements;

	// make sure we start by ordering in descending order when this column is clicked first
	if (_sortAttribute != SortBySize)
	{
		_sortAttribute = SortBySize;
		_sortOrder = NSOrderedDescending;
	}

	if (_sortOrder == NSOrderedAscending)
	{
		sortedElements = [[self elements]
			sortedArrayUsingSelector:@selector(compareSizeDescending:)];
		_sortOrder = NSOrderedDescending;
	}
	else
	{
		sortedElements = [[self elements]
			sortedArrayUsingSelector:@selector(compareSizeAscending:)];
		_sortOrder = NSOrderedAscending;
	}
		
	[self setElements:sortedElements];
}

- (void)sortByFilename
{
	NSArray *sortedElements;
	
	// make sure we start by ordering in descending order when this column is clicked first
	if (_sortAttribute != SortByFilename)
	{
		_sortAttribute = SortByFilename;
		_sortOrder = NSOrderedDescending;
	}

	if (_sortOrder == NSOrderedAscending)
	{
		sortedElements = [[self elements]
			sortedArrayUsingSelector:@selector(compareFilenameDescending:)];
		_sortOrder = NSOrderedDescending;
	}
	else
	{
		sortedElements = [[self elements]
			sortedArrayUsingSelector:@selector(compareFilenameAscending:)];
		_sortOrder = NSOrderedAscending;
	}
	[self setElements:sortedElements];
}

- (void)sortByDate
{
	NSArray *sortedElements;
	
	// make sure we start by ordering in descending order when this column is clicked first
	if (_sortAttribute != SortByDate)
	{
		_sortAttribute = SortByDate;
		_sortOrder = NSOrderedDescending;
	}

	if (_sortOrder == NSOrderedAscending)
	{
		sortedElements = [[self elements]
			sortedArrayUsingSelector:@selector(compareDateDescending:)];
		_sortOrder = NSOrderedDescending;
	}
	else
	{
		sortedElements = [[self elements]
			sortedArrayUsingSelector:@selector(compareDateAscending:)];
		_sortOrder = NSOrderedAscending;
	}
	[self setElements:sortedElements];
}

- (void)sortByRatio
{
	NSArray *sortedElements;
	
	// make sure we start by ordering in descending order when this column is clicked first
	if (_sortAttribute != SortByRatio)
	{
		_sortAttribute = SortByRatio;
		_sortOrder = NSOrderedDescending;
	}

	if (_sortOrder == NSOrderedAscending)
	{
		sortedElements = [[self elements]
			sortedArrayUsingSelector:@selector(compareRatioDescending:)];
		_sortOrder = NSOrderedDescending;
	}
	else
	{
		sortedElements = [[self elements]
			sortedArrayUsingSelector:@selector(compareRatioAscending:)];
		_sortOrder = NSOrderedAscending;
	}
	[self setElements:sortedElements];
}

- (NSComparisonResult)sortOrder
{
	return _sortOrder;
}

//------------------------------------------------------------------------------
// expanding the archive
//------------------------------------------------------------------------------
- (int)expandFiles:(NSArray *)files withPathInfo:(BOOL)usePathInfo toPath:(NSString *)path
{
	[self methodIsAbstract:_cmd];
	// shut up the compiler
	return NSNotFound;
}

- (NSData *)dataByRunningUnachiverWithArguments:(NSArray *)args
{
    NSData *inData;
    NSFileHandle *readHandle;
    NSPipe *pipe;
    NSTask *task;
    NSMutableData *result;
    
	NSParameterAssert(args != nil);
	NSParameterAssert([[self class] executableDoesExist]);

    pipe = [NSPipe pipe];
    readHandle = [pipe fileHandleForReading];

    task = [[NSTask alloc] init];
    [task setLaunchPath:[[self class] unarchiveExecutable]];	
	[task setArguments:args];
    [task setStandardOutput:pipe];
    [task launch];

    result = [NSMutableData dataWithCapacity:1024];
    while ((inData = [readHandle availableData]) && [inData length])
    {
        [result appendData:inData];
    }
    [task release];
    
    return result;
}

- (int)runUnarchiverWithArguments:(NSArray *)args;
{
	return [[self class] runUnarchiverWithArguments:args inDirectory:nil];
}

+ (int)runUnarchiverWithArguments:(NSArray *)args inDirectory:(NSString *)workDir
{
	int result;
	NSTask *task;

	NSParameterAssert([self executableDoesExist]);
	
	task = [[NSTask alloc] init];
	[task setLaunchPath:[self unarchiveExecutable]];
	[task setArguments:args];
	if (workDir != nil)
	{
		[task setCurrentDirectoryPath:workDir];
	}
	[task launch];
	[task waitUntilExit];
	
	result = [task terminationStatus];
	[task release];
	return result;
}

- (NSArray *)listContents
{
	[self methodIsAbstract:_cmd];
	// shut up the compiler
	return nil;
}

+ (BOOL)executableDoesExist
{
	NSString *exePath;
	
	exePath = [self unarchiveExecutable];
	if (exePath == nil) 
	{
		return NO;
	}
	return [[NSFileManager defaultManager] isExecutableFileAtPath:exePath];
}

/**
 * Returns the full path to the executable that's used to extract the archive. This method
 * raises an exception indicating that subclasses have to override it.
 */
+ (NSString *)unarchiveExecutable
{
	[self methodIsAbstract:_cmd];
	// shut up the compiler
	return nil;
}

/**
 * Returns the user presentable name for this kind of archive. This method raises an exception
 * indicating that subclasses have to override it.
 */
+ (NSString *)archiveType
{
	[self methodIsAbstract:_cmd];
	return nil;
}

@end
