/*
 ** iwadpicker_cocoa.mm
 **
 ** Implements Mac OS X native IWAD Picker.
 **
 **---------------------------------------------------------------------------
 ** Copyright 2010 Braden Obrzut
 ** All rights reserved.
 **
 ** Redistribution and use in source and binary forms, with or without
 ** modification, are permitted provided that the following conditions
 ** are met:
 **
 ** 1. Redistributions of source code must retain the above copyright
 **    notice, this list of conditions and the following disclaimer.
 ** 2. Redistributions in binary form must reproduce the above copyright
 **    notice, this list of conditions and the following disclaimer in the
 **    documentation and/or other materials provided with the distribution.
 ** 3. The name of the author may not be used to endorse or promote products
 **    derived from this software without specific prior written permission.
 **
 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 **---------------------------------------------------------------------------
 **
 */

#include "cmdlib.h"
#include "version.h"
#include "c_cvars.h"
#include "m_argv.h"
#include "gameconfigfile.h"
#include "engineerrors.h"
#include "i_interface.h"

#include <Cocoa/Cocoa.h>
#include <wordexp.h>


CVAR(String, osx_additional_parameters, "", CVAR_ARCHIVE | CVAR_NOSET | CVAR_GLOBALCONFIG);

enum
{
	COLUMN_IWAD,
	COLUMN_GAME,

	NUM_COLUMNS
};

static const char* const tableHeaders[NUM_COLUMNS] = { "IWAD", "Game" };

// Class to convert the IWAD data into a form that Cocoa can use.
@interface IWADTableData : NSObject<NSTableViewDataSource>
{
	NSMutableArray *data;
}

- (void)dealloc;
- (IWADTableData *)init:(WadStuff *) wads num:(int) numwads;

- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView;
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex;
@end

@implementation IWADTableData

- (void)dealloc
{
	[data release];

	[super dealloc];
}

- (IWADTableData *)init:(WadStuff *) wads num:(int) numwads
{
	data = [[NSMutableArray alloc] initWithCapacity:numwads];

	for(int i = 0;i < numwads;i++)
	{
		NSMutableDictionary *record = [[NSMutableDictionary alloc] initWithCapacity:NUM_COLUMNS];
		const char* filename = strrchr(wads[i].Path, '/');
		if(filename == NULL)
			filename = wads[i].Path;
		else
			filename++;
		[record setObject:[NSString stringWithUTF8String:filename] forKey:[NSString stringWithUTF8String:tableHeaders[COLUMN_IWAD]]];
		[record setObject:[NSString stringWithUTF8String:wads[i].Name] forKey:[NSString stringWithUTF8String:tableHeaders[COLUMN_GAME]]];
		[data addObject:record];
		[record release];
	}

	return self;
}

- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
	return [data count];
}

- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
	NSParameterAssert(rowIndex >= 0 && (unsigned int) rowIndex < [data count]);
	NSMutableDictionary *record = [data objectAtIndex:rowIndex];
	return [record objectForKey:[aTableColumn identifier]];
}

@end

static NSDictionary* GetKnownFileTypes()
{
	return [NSDictionary dictionaryWithObjectsAndKeys:
		@"-file"    , @"wad",
		@"-file"    , @"pk3",
		@"-file"    , @"zip",
		@"-file"    , @"pk7",
		@"-file"    , @"7z",
		@"-deh"     , @"deh",
		@"-bex"     , @"bex",
		@"-exec"    , @"cfg",
		@"-playdemo", @"lmp",
		nil];
}

static NSArray* GetKnownExtensions()
{
	return [GetKnownFileTypes() allKeys];
}

@interface NSMutableString(AppendKnownFileType)
- (void)appendKnownFileType:(NSString *)filePath;
@end

@implementation NSMutableString(AppendKnownFileType)
- (void)appendKnownFileType:(NSString *)filePath
{
	NSString* extension = [[filePath pathExtension] lowercaseString];
	NSString* parameter = [GetKnownFileTypes() objectForKey:extension];

	if (nil == parameter)
	{
		return;
	}

	[self appendFormat:@"%@ \"%@\" ", parameter, filePath];
}
@end

// So we can listen for button actions and such we need to have an Obj-C class.
@interface IWADPicker : NSObject
{
	NSApplication *app;
	NSWindow *window;
	NSButton *okButton;
	NSButton *cancelButton;
	NSButton *browseButton;
	NSTextField *parametersTextField;
	bool cancelled;
}

- (void)buttonPressed:(id) sender;
- (void)browseButtonPressed:(id) sender;
- (void)doubleClicked:(id) sender;
- (void)makeLabel:(NSTextField *)label withString:(const char*) str;
- (int)pickIWad:(WadStuff *)wads num:(int) numwads showWindow:(bool) showwin defaultWad:(int) defaultiwad;
- (NSString*)commandLineParameters;
- (void)menuActionSent:(NSNotification*)notification;
@end

@implementation IWADPicker

- (void)buttonPressed:(id) sender
{
	if(sender == cancelButton)
		cancelled = true;

	[window orderOut:self];
	[app stopModal];
}

- (void)browseButtonPressed:(id) sender
{
	NSOpenPanel* openPanel = [NSOpenPanel openPanel];
	[openPanel setAllowsMultipleSelection:YES];
	[openPanel setCanChooseFiles:YES];
	[openPanel setCanChooseDirectories:YES];
	[openPanel setResolvesAliases:YES];
	[openPanel setAllowedFileTypes:GetKnownExtensions()];

	if (NSModalResponseOK == [openPanel runModal])
	{
		NSArray* files = [openPanel URLs];
		NSMutableString* parameters = [NSMutableString string];

		for (NSUInteger i = 0, ei = [files count]; i < ei; ++i)
		{
			NSString* filePath = [[files objectAtIndex:i] path];
			BOOL isDirectory = false;

			if ([[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory] && isDirectory)
			{
				[parameters appendFormat:@"-file \"%@\" ", filePath];
			}
			else
			{
				[parameters appendKnownFileType:filePath];
			}
		}

		if ([parameters length] > 0)
		{
			NSString* newParameters = [parametersTextField stringValue];

			if ([newParameters length] > 0
				&& NO == [newParameters hasSuffix:@" "])
			{
				newParameters = [newParameters stringByAppendingString:@" "];
			}

			newParameters = [newParameters stringByAppendingString:parameters];

			[parametersTextField setStringValue: newParameters];
		}
	}
}

- (void)doubleClicked:(id) sender
{
	if ([sender clickedRow] >= 0)
	{
		[window orderOut:self];
		[app stopModal];
	}
}

// Apparently labels in Cocoa are uneditable text fields, so lets make this a
// little more automated.
- (void)makeLabel:(NSTextField *)label withString:(const char*) str
{
	[label setStringValue:[NSString stringWithUTF8String:str]];
	[label setBezeled:NO];
	[label setDrawsBackground:NO];
	[label setEditable:NO];
	[label setSelectable:NO];
}

- (int)pickIWad:(WadStuff *)wads num:(int) numwads showWindow:(bool) showwin defaultWad:(int) defaultiwad
{
	cancelled = false;

	app = [NSApplication sharedApplication];
	id windowTitle = [NSString stringWithFormat:@"%s %s", GAMENAME, GetVersionString()];

	NSRect frame = NSMakeRect(0, 0, 440, 450);
	window = [[NSWindow alloc] initWithContentRect:frame styleMask:NSWindowStyleMaskTitled backing:NSBackingStoreBuffered defer:NO];
	[window setTitle:windowTitle];

	NSTextField *description = [[NSTextField alloc] initWithFrame:NSMakeRect(18, 384, 402, 50)];
	[self makeLabel:description withString:GAMENAME " found more than one IWAD\nSelect from the list below to determine which one to use:"];
	[[window contentView] addSubview:description];
	[description release];

	NSScrollView *iwadScroller = [[NSScrollView alloc] initWithFrame:NSMakeRect(20, 135, 402, 256)];
	NSTableView *iwadTable = [[NSTableView alloc] initWithFrame:[iwadScroller bounds]];
	IWADTableData *tableData = [[IWADTableData alloc] init:wads num:numwads];
	for(int i = 0;i < NUM_COLUMNS;i++)
	{
		NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:[NSString stringWithUTF8String:tableHeaders[i]]];
		[[column headerCell] setStringValue:[column identifier]];
		if(i == 0)
			[column setMaxWidth:110];
		[column setEditable:NO];
		[column setResizingMask:NSTableColumnAutoresizingMask];
		[iwadTable addTableColumn:column];
		[column release];
	}
	[iwadScroller setDocumentView:iwadTable];
	[iwadScroller setHasVerticalScroller:YES];
	[iwadTable setDataSource:tableData];
	[iwadTable sizeToFit];
	[iwadTable setDoubleAction:@selector(doubleClicked:)];
	[iwadTable setTarget:self];
	NSIndexSet *selection = [[NSIndexSet alloc] initWithIndex:defaultiwad];
	[iwadTable selectRowIndexes:selection byExtendingSelection:NO];
	[selection release];
	[iwadTable scrollRowToVisible:defaultiwad];
	[[window contentView] addSubview:iwadScroller];
	[iwadTable release];
	[iwadScroller release];

	NSTextField *additionalParametersLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(18, 108, 144, 17)];
	[self makeLabel:additionalParametersLabel withString:"Additional Parameters:"];
	[[window contentView] addSubview:additionalParametersLabel];
	parametersTextField = [[NSTextField alloc] initWithFrame:NSMakeRect(20, 48, 402, 54)];
	[parametersTextField setStringValue:[NSString stringWithUTF8String:osx_additional_parameters]];
	[[window contentView] addSubview:parametersTextField];

	// Doesn't look like the SDL version implements this so lets not show it.
	/*NSButton *dontAsk = [[NSButton alloc] initWithFrame:NSMakeRect(18, 18, 178, 18)];
	[dontAsk setTitle:[NSString stringWithCString:"Don't ask me this again"]];
	[dontAsk setButtonType:NSSwitchButton];
	[dontAsk setState:(showwin ? NSOffState : NSOnState)];
	[[window contentView] addSubview:dontAsk];*/

	okButton = [[NSButton alloc] initWithFrame:NSMakeRect(236, 8, 96, 32)];
	[okButton setTitle:@"OK"];
	[okButton setBezelStyle:NSRoundedBezelStyle];
	[okButton setAction:@selector(buttonPressed:)];
	[okButton setTarget:self];
	[okButton setKeyEquivalent:@"\r"];
	[[window contentView] addSubview:okButton];

	cancelButton = [[NSButton alloc] initWithFrame:NSMakeRect(332, 8, 96, 32)];
	[cancelButton setTitle:@"Cancel"];
	[cancelButton setBezelStyle:NSRoundedBezelStyle];
	[cancelButton setAction:@selector(buttonPressed:)];
	[cancelButton setTarget:self];
	[cancelButton setKeyEquivalent:@"\033"];
	[[window contentView] addSubview:cancelButton];

	browseButton = [[NSButton alloc] initWithFrame:NSMakeRect(14, 8, 96, 32)];
	[browseButton setTitle:@"Browse..."];
	[browseButton setBezelStyle:NSRoundedBezelStyle];
	[browseButton setAction:@selector(browseButtonPressed:)];
	[browseButton setTarget:self];
	[[window contentView] addSubview:browseButton];

	NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
	[center addObserver:self selector:@selector(menuActionSent:) name:NSMenuDidSendActionNotification object:nil];

	[window center];
	[app runModalForWindow:window];

	[center removeObserver:self name:NSMenuDidSendActionNotification object:nil];

	[window release];
	[okButton release];
	[cancelButton release];
	[browseButton release];

	return cancelled ? -1 : [iwadTable selectedRow];
}

- (NSString*)commandLineParameters
{
	return [parametersTextField stringValue];
}

- (void)menuActionSent:(NSNotification*)notification
{
	NSDictionary* userInfo = [notification userInfo];
	NSMenuItem* menuItem = [userInfo valueForKey:@"MenuItem"];

	if ( @selector(terminate:) == [menuItem action] )
	{
		throw CExitEvent(0);
	}
}

@end


EXTERN_CVAR(String, defaultiwad)

static NSString* GetArchitectureString()
{
#ifdef __i386__
	return @"i386";
#elif defined __x86_64__
	return @"x86_64";
#elif defined __aarch64__
	return @"arm64";
#endif
}

static void RestartWithParameters(const WadStuff& wad, NSString* parameters)
{
	assert(nil != parameters);

	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

	@try
	{
		NSString* executablePath = [NSString stringWithUTF8String:Args->GetArg(0)];
		NSString* escapedParameters = [parameters stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
		NSString* cvarArgument = [NSString stringWithFormat:@"+osx_additional_parameters \"%@\"", escapedParameters];

		NSMutableArray* const arguments = [[NSMutableArray alloc] init];
		[arguments addObject:@"-arch"];
		[arguments addObject:GetArchitectureString()];
		[arguments addObject:executablePath];
		[arguments addObject:@"-iwad"];
		[arguments addObject:[NSString stringWithUTF8String:wad.Path]];
		[arguments addObject:@"+defaultiwad"];
		[arguments addObject:[NSString stringWithUTF8String:wad.Name]];
		[arguments addObject:cvarArgument];

		for (int i = 1, count = Args->NumArgs(); i < count; ++i)
		{
			NSString* currentParameter = [NSString stringWithUTF8String:Args->GetArg(i)];
			[arguments addObject:currentParameter];
		}

		wordexp_t expansion = {};

		if (0 == wordexp([parameters UTF8String], &expansion, 0))
		{
			for (size_t i = 0; i < expansion.we_wordc; ++i)
			{
				NSString* argumentString = [NSString stringWithCString:expansion.we_wordv[i]
															  encoding:NSUTF8StringEncoding];
				[arguments addObject:argumentString];
			}

			wordfree(&expansion);
		}

		[NSTask launchedTaskWithLaunchPath:@"/usr/bin/arch"
								 arguments:arguments];

		_exit(0); // to avoid atexit()'s functions
	}
	@catch (NSException* e)
	{
		NSLog(@"Cannot restart: %@", [e reason]);
	}

	[pool release];
}

// Simple wrapper so we can call this from outside.
int I_PickIWad_Cocoa (WadStuff *wads, int numwads, bool showwin, int defaultiwad)
{
	NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

	IWADPicker *picker = [IWADPicker alloc];
	int ret = [picker pickIWad:wads num:numwads showWindow:showwin defaultWad:defaultiwad];

	if (ret >= 0)
	{
		NSString* parametersToAppend = [picker commandLineParameters];

		if (0 != [parametersToAppend length])
		{
			RestartWithParameters(wads[ret], parametersToAppend);
		}
	}

	[pool release];

	return ret;
}
