/* -LICENSE-START-
 ** Copyright (c) 2018 Blackmagic Design
 **
 ** Permission is hereby granted, free of charge, to any person or organization
 ** obtaining a copy of the software and accompanying documentation covered by
 ** this license (the "Software") to use, reproduce, display, distribute,
 ** execute, and transmit the Software, and to prepare derivative works of the
 ** Software, and to permit third-parties to whom the Software is furnished to
 ** do so, all subject to the following:
 **
 ** The copyright notices in the Software and this entire statement, including
 ** the above license grant, this restriction and the following disclaimer,
 ** must be included in all copies of the Software, in whole or in part, and
 ** all derivative works of the Software, unless such copies or derivative
 ** works are solely in the form of machine-executable object code generated by
 ** a source language processor.
 **
 ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 ** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
 ** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
 ** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
 ** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 ** DEALINGS IN THE SOFTWARE.
 ** -LICENSE-END-
 */

#import <array>
#import <string>
#import <AppKit/NSAlert.h>
#import <Foundation/Foundation.h>
#import "AppDelegate.h"
#import "CocoaViewController.h"
#import "DeckLinkCoreMediaWriter.h"
#import "DeckLinkMediaCommon.h"

@implementation AppDelegate

static const size_t kMaxTempFilePathLength = 256;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
	using namespace std::placeholders;

	m_selectedDevice	= kInvalidDevice;

	m_deviceState		= kDeviceIOIdle;

	m_deviceManager		= std::shared_ptr<DeviceManager>(new DeviceManager());
	m_deviceDiscovery	= std::unique_ptr<DeckLinkDeviceDiscovery>(new DeckLinkDeviceDiscovery());
	m_viewController	= std::unique_ptr<CocoaViewController>(std::move(new CocoaViewController(self)));
	m_mediaWriter		= std::shared_ptr<DeckLinkMediaWriter>(new DeckLinkCoreMediaWriter());

	m_screenPreviewCallback = com_ptr<IDeckLinkScreenPreviewCallback>(CreateCocoaScreenPreview((__bridge void*)m_previewView));

	m_deviceManager->setMediaWriter(m_mediaWriter);

	// Connect device discovery to device manager
	m_deviceDiscovery->onDeviceArrival(std::bind(&DeviceManager::deckLinkArrived, m_deviceManager.get(), _1));
	m_deviceDiscovery->onDeviceRemoval(std::bind(&DeviceManager::deckLinkRemoved, m_deviceManager.get(), _1));

	// Connect device device manager to view controller
	m_viewController->setDeviceManager(m_deviceManager);
	m_deviceManager->setStatusListener(std::bind(&::CocoaViewController::deviceManagerUpdated, m_viewController.get(), _1, _2));

	// Ensure disabled devices stay disabled when popup button is clicked
	[m_deviceList setAutoenablesItems:NO];
	
	m_deviceDiscovery->start();
}

- (IBAction)deviceSelected:(id)sender
{
	if (m_viewController->selectDevice((intptr_t)[[m_deviceList selectedItem] tag]))
	{
		[self capture:TRUE];
	}
}

- (IBAction)inputSelected:(id)sender
{
	m_viewController->selectInput((BMDVideoConnection)[[m_inputList selectedItem] tag]);
}

- (IBAction)formatSelected:(id)sender
{
	if (m_viewController->selectFormat((BMDDisplayMode)[[m_formatList selectedItem] tag]))
		[self capture:TRUE];
}

- (IBAction)recordClicked:(id)sender
{
	if (m_deviceState == kFileIORunning)
	{
		m_viewController->stopRecording();
		return;
	}

	bool success = false;
	std::string filename;
	std::array<char, kMaxTempFilePathLength> tempDir;

	// Create a unique temporary directory for the captured file
	snprintf(tempDir.data(), kMaxTempFilePathLength, "%s%sXXXXXXXX", [NSTemporaryDirectory() fileSystemRepresentation], kFileCaptureTempFilePrefix);
	if (mkdtemp(tempDir.data()) == nullptr)
		goto bail;

	filename.append(tempDir.data()).append("/movie.mov");

	// Record to temporary file (it will be moved or deleted on completion)
	m_viewController->record(filename);

	success = true;

bail:
	if (!success)
	{
		[self showError:CFSTR("Recording Error") detail:CFSTR("Failed to obtain a temporary file name for media file.")];
	}
}

- (IBAction)formatAutoDetectClicked:(id)sender
{
	if (m_viewController->setFormatAutoDetect([m_formatDetection state]))
		[self capture:TRUE];
}

- (void)applicationWillTerminate:(NSNotification *)aNotification
{
	[self capture:FALSE];
	
	if (m_deviceDiscovery)
		m_deviceDiscovery->stop();
}

- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)app
{
	return TRUE;
}

- (void)updateDevice:(intptr_t)deviceID enabled:(BOOL)enabled
{
	for (NSMenuItem* menuItem in [m_deviceList itemArray])
	{
		if ((intptr_t)[menuItem tag] != deviceID)
			continue;
		
		[menuItem setEnabled:enabled];
		break;
	}
	
	// If this device is currently selected, try to (re)enable capture on it
	if ([m_deviceList selectedTag] == (NSInteger)deviceID)
		[self capture:TRUE];
}

- (void)addDevice:(intptr_t)deviceID displayName:(CFStringRef)displayName enabled:(BOOL)enabled
{
	[[m_deviceList menu] addItemWithTitle:(__bridge_transfer NSString*)displayName action:nil keyEquivalent:@""];
	[[m_deviceList lastItem] setTag:(NSInteger)deviceID];
	
	[self updateDevice:deviceID enabled:enabled];
	[self captureFromAvailableDevice];
}

- (void)removeDevice:(intptr_t)deviceID
{
	NSInteger indexToRemove = [m_deviceList indexOfItemWithTag:(NSInteger)deviceID];
	if (indexToRemove < 0)
		return;

	bool selectedDeviceRemoved = (intptr_t)[[m_deviceList selectedItem] tag] == deviceID;
	
	[m_deviceList removeItemAtIndex:indexToRemove];
	
	if (selectedDeviceRemoved)
	{
		// Selected device was removed, try to select the device first available device
		for (uint32_t i = 0; i < [m_deviceList numberOfItems]; ++i)
		{
			if (m_viewController->selectDevice((intptr_t)[[m_deviceList itemAtIndex:i] tag]))
			{
				[m_deviceList selectItemAtIndex:i];
				break;
			}
		}
	}
	
	if ([m_deviceList numberOfItems] == 0)
	{
		// Last device was removed
		m_selectedDevice = kInvalidDevice;
		m_viewController->selectDevice(m_selectedDevice);
		[self clearDisplayModeList];
		[self clearVideoInputConnectionList];
	}
}

- (void)clearVideoInputConnectionList
{
	[m_inputList removeAllItems];
}

- (void)addVideoInputConnection:(NSInteger)connectionID connectionName:(CFStringRef)connectionName
{
	[[m_inputList menu] addItemWithTitle:(__bridge_transfer NSString*)connectionName action:nil keyEquivalent:@""];
	[[m_inputList lastItem] setTag:connectionID];
}

- (void)clearDisplayModeList
{
	[m_formatList removeAllItems];
}

- (void)addDisplayMode:(NSInteger)displayMode displayModeName:(CFStringRef)displayModeName
{
	[[m_formatList menu] addItemWithTitle:(__bridge_transfer NSString*)displayModeName action:nil keyEquivalent:@""];
	[[m_formatList lastItem] setTag:displayMode];
}

- (void)captureFromAvailableDevice
{
	// If not currently capturing, try to enable capture on the first available device
	if (m_deviceState != kDeviceIORunning && m_deviceState != kFileIORunning)
	{
		for (uint32_t i = 0; i < [m_deviceList numberOfItems]; ++i)
		{
			intptr_t deviceID = [[m_deviceList itemAtIndex:i] tag];
			if (!m_viewController->selectDevice(deviceID))
				continue;

			m_selectedDevice = deviceID;
			m_viewController->setFormatAutoDetect(true);

			// Device is available, so select it and start capturing
			[m_deviceList selectItem:[m_deviceList itemAtIndex:i]];
			[self capture:TRUE];
		}
	}
}

- (void)capture:(BOOL)active
{
	if (m_deviceState != kDeviceIOIdle)
		m_viewController->stopCapturing();
	
	// Check we have a valid display mode for capture
	NSMenuItem* selectedDisplayModeItem = [m_formatList selectedItem];
	if (selectedDisplayModeItem == nil)
		return;
	
	bool supportsformatAutoDetect = false;
	bool formatAutoDetectEnabled = false;
	
	m_viewController->formatAutoDetectState(supportsformatAutoDetect, formatAutoDetectEnabled);
	
	if (supportsformatAutoDetect)
	{
		[m_formatDetection setState:(formatAutoDetectEnabled ? NSOnState : NSOffState)];
		[m_formatDetection setEnabled:TRUE];
	}
	else
	{
		m_viewController->setFormatAutoDetect(false);
		[m_formatDetection setState:NSOffState];
		[m_formatDetection setEnabled:FALSE];
	}
	
	if (active)
		m_viewController->capture((BMDDisplayMode)[[m_formatList selectedItem] tag]);
}

- (void)setSignalPresence:(BOOL)signalPresent
{
	[m_captureSignal	setHidden:signalPresent];

	if (m_deviceState == kFileIORunning)
	{
		// Allow 'Stop' when recording
		[m_recordButton setEnabled:TRUE];
	}
	else
	{
		// Allow 'Record' only when input signal is valid
		[m_recordButton setEnabled:(m_deviceState == kDeviceIORunning && signalPresent)];
	}
}

- (void)setDisplayMode:(NSInteger)displayMode
{
	[m_formatList selectItemWithTag:displayMode];
}

- (void)setVideoInputConnection:(NSInteger)videoInputConnection
{
	[m_inputList selectItemWithTag:videoInputConnection];
}

- (void)saveRecodedFile:(const std::string&)filePath
{
	NSString* filename = @"movie.mov";
	NSURL* tempURL = [NSURL fileURLWithPath:[[NSString alloc] initWithUTF8String:filePath.c_str()]];

	NSSavePanel* panel = [NSSavePanel savePanel];
	[panel setNameFieldStringValue:filename];
	[panel beginSheetModalForWindow:m_window completionHandler:^(NSInteger result)
	{
		NSError *error;
		if (result == NSFileHandlingPanelOKButton)
		{
			[[NSFileManager defaultManager] removeItemAtURL:[panel URL] error:nil];
			if (![[NSFileManager defaultManager] moveItemAtURL:tempURL toURL:[panel URL] error:&error])
				[self showError:CFSTR("File Save Error") detail:CFSTR("Failed to rename recorded file.")];

			// Remove the temporary directory
			if (filePath.length() > 0)
				[[NSFileManager defaultManager] removeItemAtURL:[tempURL URLByDeletingLastPathComponent] error:nil];
		}
		else
		{
			// User cancelled save, remove the temporary file and directory
			if (filePath.length() > 0)
				[[NSFileManager defaultManager] removeItemAtURL:[tempURL URLByDeletingLastPathComponent] error:nil];
		}
	 }];
}

- (void)showError:(CFStringRef)title detail:(CFStringRef)detail
{
	NSAlert *alert = [[NSAlert alloc] init];
	[alert addButtonWithTitle:@"OK"];
	[alert setMessageText:(__bridge_transfer NSString*)title];
	[alert setInformativeText:(__bridge_transfer NSString*)detail];
	[alert setAlertStyle:NSCriticalAlertStyle];
	[alert beginSheetModalForWindow:m_window completionHandler:nil];
}

- (void)showInfo:(CFStringRef)title detail:(CFStringRef)detail
{
	[m_infoText setStringValue: [NSString stringWithFormat:@"%@: %@", title, detail]];
}

- (void)enableUI:(BOOL)enabled
{
	[m_deviceList			setEnabled:enabled];
	[m_inputList			setEnabled:enabled];
	[m_formatList			setEnabled:![m_formatDetection state]];
	[m_recordButton			setEnabled:FALSE];
	[m_formatDetection		setEnabled:enabled];
	[m_captureSignal		setHidden:TRUE];
}

- (void)setDeviceState:(DeviceIOState)deviceState
{
	m_deviceState = deviceState;

	BOOL enableUI		= [[m_deviceList itemArray] count] > 0;
	switch (deviceState)
	{
		case kDeviceIOError:
		case kDeviceIOIdle:
			[self enableUI:enableUI];
			[m_recordButton		setTitle:@"Record"];
			break;
		case kFileIOError:
		case kDeviceIORunning:
			[self enableUI:TRUE];
			[m_recordButton		setTitle:@"Record"];
			[m_recordButton		setEnabled:TRUE];
			break;
		case kFileIORunning:
			[self enableUI:TRUE];
			[m_recordButton		setTitle:@"Stop"];
			[m_recordButton		setEnabled:TRUE];
			break;
		case kDeviceIOUnknown:
		default:
			[self enableUI:FALSE];
			[m_recordButton		setTitle:@"Record"];
			break;
	}
}

- (com_ptr<IDeckLinkScreenPreviewCallback>)screenPreviewCallback
{
	return m_screenPreviewCallback;
}

@end
