/* -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 (the 
 ** "Software") to use, reproduce, display, distribute, sub-license, 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, in 
 ** accordance with:
 ** 
 ** (1) if the Software is obtained from Blackmagic Design, the End User License 
 ** Agreement for the Software Development Kit (“EULA”) available at 
 ** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
 ** 
 ** (2) if the Software is obtained from any third party, such licensing terms 
 ** as notified by that third party,
 ** 
 ** and all subject to the following:
 ** 
 ** (3) 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.
 ** 
 ** (4) 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.
 ** 
 ** A copy of the Software is available free of charge at 
 ** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
 ** 
 ** -LICENSE-END-
 */

#include <cstring>
#include <cstdio>
#include <string>
#include <vector>
#include <tuple>
#include <functional>
#include "platform.h"
#include "DeckLinkAPI.h"


// Video input connector map 
const std::vector<std::tuple<BMDVideoConnection, std::string>> kVideoInputConnections =
{
	std::make_tuple(bmdVideoConnectionSDI, "SDI video connection"),
	std::make_tuple(bmdVideoConnectionHDMI, "HDMI video connection"),
	std::make_tuple(bmdVideoConnectionOpticalSDI, "Optical SDI video connection"),
	std::make_tuple(bmdVideoConnectionComponent, "Component video connection"),
	std::make_tuple(bmdVideoConnectionComposite, "Composite video connection"),
	std::make_tuple(bmdVideoConnectionSVideo, "S-Video connection"),
	std::make_tuple(bmdVideoConnectionEthernet, "Ethernet connection"),
	std::make_tuple(bmdVideoConnectionOpticalEthernet, "Optical Ethernet connection"),
};
enum { 
	kVideoInputConnectionsFlag = 0, 
	kVideoInputConnectionsString 
};

// Audio input conector map 
const std::vector<std::tuple<BMDAudioConnection, std::string>> kAudioInputConnections =
{
	std::make_tuple(bmdAudioConnectionEmbedded, "Embedded SDI/HDMI audio connection"),
	std::make_tuple(bmdAudioConnectionAESEBU, "AES/EBU audio connection"),
	std::make_tuple(bmdAudioConnectionAnalog, "Analog audio connection"),
	std::make_tuple(bmdAudioConnectionAnalogXLR, "Analog XLR audio connection"),
	std::make_tuple(bmdAudioConnectionAnalogRCA, "Analog RCA audio connection"),
	std::make_tuple(bmdAudioConnectionMicrophone, "Analog microphone audio connection"),
	std::make_tuple(bmdAudioConnectionHeadphones, "Analog headphone audio connection"),
};
enum { 
	kAudioInputConnectionsFlag = 0, 
	kAudioInputConnectionsString 
};

// Link Configuration - Link configuration, Link configuration valid function, display string
const std::vector<std::tuple<BMDLinkConfiguration, std::function<bool(IDeckLinkProfileAttributes*)>, std::string>> kLinkConfiguration =
{
	std::make_tuple(bmdLinkConfigurationSingleLink, [](IDeckLinkProfileAttributes* dla) -> bool { return true; }, "Single-link SDI video connection"),
	std::make_tuple(bmdLinkConfigurationDualLink, [](IDeckLinkProfileAttributes* dla) -> bool { dlbool_t flag; return (dla->GetFlag(BMDDeckLinkSupportsDualLinkSDI, &flag) == S_OK) && flag; }, "Dual-link SDI video connection"),
	std::make_tuple(bmdLinkConfigurationQuadLink, [](IDeckLinkProfileAttributes* dla) -> bool { dlbool_t flag; return (dla->GetFlag(BMDDeckLinkSupportsQuadLinkSDI, &flag) == S_OK) && flag; }, "Quad-link SDI video connection"),
};
enum { 
	kLinkConfigurationValue = 0, 
	kLinkConfigurationValidFunction, 
	kLinkConfigurationString 
};

// Video Output Mode - Capture pass through mode, idle video output mode, 444 Video output
const std::vector<std::tuple<BMDDeckLinkCapturePassthroughMode, BMDIdleVideoOutputOperation, bool>> kVideoOutputMode
{
	std::make_tuple(bmdDeckLinkCapturePassthroughModeDirect,		bmdIdleVideoOutputBlack,		false),
	std::make_tuple(bmdDeckLinkCapturePassthroughModeCleanSwitch,	bmdIdleVideoOutputBlack,		false),
	std::make_tuple(bmdDeckLinkCapturePassthroughModeDisabled,		bmdIdleVideoOutputBlack,		false),
	std::make_tuple(bmdDeckLinkCapturePassthroughModeDisabled,		bmdIdleVideoOutputLastFrame,	false),
	std::make_tuple(bmdDeckLinkCapturePassthroughModeDirect,		bmdIdleVideoOutputBlack,		true),
	std::make_tuple(bmdDeckLinkCapturePassthroughModeCleanSwitch,	bmdIdleVideoOutputBlack,		true),
	std::make_tuple(bmdDeckLinkCapturePassthroughModeDisabled,		bmdIdleVideoOutputBlack,		true),
	std::make_tuple(bmdDeckLinkCapturePassthroughModeDisabled,		bmdIdleVideoOutputLastFrame,	true),
};
enum { 
	kVideoOutputCapturePassthroughValue = 0, 
	kVideoOutputIdleModeValue, 
	kVideoOutputSDI444Value 
};

// Ethernet PTP Encapsulation type
const std::vector<std::tuple<bool, std::string>> kEthernetPTPEncapsulation
{
	std::make_tuple(false,	"Ethernet"),
	std::make_tuple(true,	"UDP"),
};
enum {
	kEthernetPTPEncapsulationFlag = 0,
	kEthernetPTPEncapsulationString
};

std::string GetColorModelString(bool is444)
{
	return is444 ? "RGB444" : "YUV422";
}

const char* GetPTPFollowerOnlyString(bool followerOnly)
{
	return followerOnly ? "Follower only" : "Follower or leader";
}

std::string GetIdleOutputString(BMDIdleVideoOutputOperation idleOutputMode)
{
	return (idleOutputMode == bmdIdleVideoOutputBlack) ? "Black Frame" : "Last Frame";
}

std::string GetPassThroughModeString(BMDDeckLinkCapturePassthroughMode passThroughMode)
{
	switch (passThroughMode)
	{
	case bmdDeckLinkCapturePassthroughModeDirect:
		return "Direct";
	case bmdDeckLinkCapturePassthroughModeCleanSwitch:
		return "Clean Switch";
	case bmdDeckLinkCapturePassthroughModeDisabled:
	default:
		return "Disabled";
	}
}

bool SupportsEthernetConnection(IDeckLinkProfileAttributes* deckLinkAttributes)
{
	int64_t supportedVideoConnections;
	
	if (deckLinkAttributes == NULL)
		return false;

	if (deckLinkAttributes->GetInt(BMDDeckLinkVideoOutputConnections, &supportedVideoConnections) == S_OK)
	{
		if (supportedVideoConnections & (bmdVideoConnectionEthernet | bmdVideoConnectionOpticalEthernet))
			return true;
	}
		
	if (deckLinkAttributes->GetInt(BMDDeckLinkVideoInputConnections, &supportedVideoConnections) == S_OK)
	{
		if (supportedVideoConnections & (bmdVideoConnectionEthernet | bmdVideoConnectionOpticalEthernet))
			return true;
	}
		
	return false;
}

std::string	GetDisplayNameAttribute(IDeckLinkProfileAttributes* deckLinkAttributes)
{
	dlstring_t deckLinkName;
	std::string retString;

	if (deckLinkAttributes->GetString(BMDDeckLinkDisplayName, &deckLinkName) == S_OK)
	{
		retString = DlToStdString(deckLinkName);
		DeleteString(deckLinkName);
	}
	else
		retString = "";

	return retString;
}

void DisplayUsage(IDeckLinkConfiguration* deckLinkConfiguration, IDeckLinkProfileAttributes* deckLinkAttributes, std::vector<std::string>& displayNames)
{
	int64_t videoIOSupport;

	std::tuple<BMDDeckLinkCapturePassthroughMode, BMDIdleVideoOutputOperation, bool> currentVideoOutputMode;

	fprintf(stderr,
		"\n"
		"Usage: DeviceConfigure -d <device id> [OPTIONS]\n"
		"\n"
		"    -h/?: help\n"
		"    -d <device id>:\n"
		);

	{
		std::string selectedDisplayName = (deckLinkAttributes != NULL) ? GetDisplayNameAttribute(deckLinkAttributes) : "";

		// Loop through all available devices
		for (size_t i = 0; i < displayNames.size(); i++)
		{
			fprintf(stderr,
				"       %c%2d:  %s\n",
				(displayNames[i] == selectedDisplayName) ? '*' : ' ',
				(int)i,
				displayNames[i].c_str()
				);
		}
	}

	if ((deckLinkConfiguration == NULL) || (deckLinkAttributes == NULL))
	{
		fprintf(stderr,
			"        No DeckLink device selected\n"
			"\n"
			"Options:\n"
		);
	}

	// Get Video IO support for selected device
	if (deckLinkAttributes == NULL || deckLinkAttributes->GetInt(BMDDeckLinkVideoIOSupport, &videoIOSupport) != S_OK)
		videoIOSupport = 0;

	fprintf(stderr, "    -l <link width id>:\n");
	if (deckLinkConfiguration && deckLinkAttributes)
	{
		int64_t supportedVideoConnections;

		if ((deckLinkAttributes->GetInt(BMDDeckLinkVideoOutputConnections, &supportedVideoConnections) == S_OK)
			&& (((BMDVideoConnection)supportedVideoConnections & bmdVideoConnectionSDI) != 0))
		{
			int64_t currentLinkConfig;
			if (deckLinkConfiguration->GetInt(bmdDeckLinkConfigSDIOutputLinkConfiguration, &currentLinkConfig) != S_OK)
				currentLinkConfig = (int64_t) bmdLinkConfigurationSingleLink;

			// Iterate through all available link configurations on the delected DeckLink device
			for (size_t i = 0; i < kLinkConfiguration.size(); i++)
			{
				if (std::get<kLinkConfigurationValidFunction>(kLinkConfiguration[i])(deckLinkAttributes))
				{

					fprintf(stderr,
						"       %c%2d:  %s\n",
						((BMDLinkConfiguration)currentLinkConfig == std::get<kLinkConfigurationValue>(kLinkConfiguration[i])) ? '*' : ' ',
						(int)i,
						(std::get<kLinkConfigurationString>(kLinkConfiguration[i])).c_str()
						);
				}
			}
		}
		else
			fprintf(stderr, "       No SDI connections on the selected device\n");
	}

	fprintf(stderr, "    -v <video input connector id>:\n");

	if (deckLinkConfiguration && deckLinkAttributes)
	{
		if ((BMDVideoIOSupport)videoIOSupport & bmdDeviceSupportsCapture)
		{
			int64_t currentVideoInputConnection;
			int64_t supportedVideoInputConnections;

			// Get current video input connector configuration
			if (deckLinkConfiguration->GetInt(bmdDeckLinkConfigVideoInputConnection, &currentVideoInputConnection) != S_OK)
				currentVideoInputConnection = 0;

			if (deckLinkAttributes->GetInt(BMDDeckLinkVideoInputConnections,&supportedVideoInputConnections) == S_OK)
			{
				for (size_t i = 0; i < kVideoInputConnections.size(); i++)
				{
					if (((BMDVideoConnection)supportedVideoInputConnections & std::get<kVideoInputConnectionsFlag>(kVideoInputConnections[i])) != 0)
						fprintf(stderr,
						"       %c%2d:  %s\n",
						((BMDVideoConnection)currentVideoInputConnection & std::get<kVideoInputConnectionsFlag>(kVideoInputConnections[i])) ? '*' : ' ',
						(int)i,
						(std::get<kVideoInputConnectionsString>(kVideoInputConnections[i])).c_str()
						);

				}
			}
			else
				fprintf(stderr, "       Unable to get video input connections\n");
		}
		else
			fprintf(stderr, "       Capture operation is not supported by the selected device\n");
	}

	fprintf(stderr, "    -a <audio input connector id>:\n");

	if (deckLinkConfiguration && deckLinkAttributes)
	{
		if ((BMDVideoIOSupport)videoIOSupport & bmdDeviceSupportsCapture)
		{
			int64_t currentAudioInputConnection;
			int64_t supportedAudioInputConnections;

			// Get current audio input connector configuration
			if (deckLinkConfiguration->GetInt(bmdDeckLinkConfigAudioInputConnection, &currentAudioInputConnection) != S_OK)
				currentAudioInputConnection = 0;

			if (deckLinkAttributes->GetInt(BMDDeckLinkAudioInputConnections, &supportedAudioInputConnections) == S_OK)
			{
				for (size_t i = 0; i < kAudioInputConnections.size(); i++)
				{
					if (((BMDAudioConnection)supportedAudioInputConnections & std::get<kAudioInputConnectionsFlag>(kAudioInputConnections[i])) != 0)
						fprintf(stderr,
						"       %c%2d:  %s\n",
						((BMDAudioConnection)currentAudioInputConnection & std::get<kAudioInputConnectionsFlag>(kAudioInputConnections[i])) ? '*' : ' ',
						(int)i,
						(std::get<kAudioInputConnectionsString>(kAudioInputConnections[i])).c_str()
						);

				}
			}
			else
				fprintf(stderr, "       Unable to get audio input connections\n");
		}
		else
			fprintf(stderr, "       Capture operation is not supported by the selected device\n");
	}
	
	fprintf(stderr, "    -o <video output mode id>:\n");

	if (deckLinkConfiguration && deckLinkAttributes)
	{
		if((BMDVideoIOSupport)videoIOSupport && bmdDeviceSupportsPlayback)
		{
			dlbool_t dummyFlag;
			int64_t dummyInt;

			// Check whether idle, passthrough and 444 output mode supported for the device
			bool supportsIdleOutput = (deckLinkAttributes->GetFlag(BMDDeckLinkSupportsIdleOutput, &dummyFlag) == S_OK) && dummyFlag;
			bool supportsSDI444Output = deckLinkConfiguration->GetFlag(bmdDeckLinkConfig444SDIVideoOutput, &dummyFlag) == S_OK;
			bool supportsCapturePassThrough = (deckLinkConfiguration->GetInt(bmdDeckLinkConfigCapturePassThroughMode, &dummyInt) == S_OK);

			{
				int64_t currentPassThroughMode;
				int64_t currentIdleMode;
				dlbool_t currentSDI444OutputMode;

				if (deckLinkConfiguration->GetInt(bmdDeckLinkConfigCapturePassThroughMode, &currentPassThroughMode) != S_OK)
					currentPassThroughMode = (int64_t)bmdDeckLinkCapturePassthroughModeDisabled;

				if (deckLinkConfiguration->GetInt(bmdDeckLinkConfigVideoOutputIdleOperation, &currentIdleMode) != S_OK)
					currentIdleMode = (int64_t)bmdIdleVideoOutputBlack;

				if (deckLinkConfiguration->GetFlag(bmdDeckLinkConfig444SDIVideoOutput, &currentSDI444OutputMode) != S_OK)
					currentSDI444OutputMode = (dlbool_t)false;

				currentVideoOutputMode = std::make_tuple((BMDDeckLinkCapturePassthroughMode)currentPassThroughMode, (BMDIdleVideoOutputOperation)currentIdleMode, (currentSDI444OutputMode != (dlbool_t)false));
			}

			fprintf(stderr,
				"        ID   %-15s\t%-20s\t%-20s\n",
				"Color space",
				"Pass-through mode",
				"Idle output"
				);

			for (size_t i = 0; i < kVideoOutputMode.size(); i++)
			{
				if ((!supportsIdleOutput && (std::get<kVideoOutputIdleModeValue>(kVideoOutputMode[i]) == bmdIdleVideoOutputLastFrame)) ||
					(!supportsSDI444Output && std::get<kVideoOutputSDI444Value>(kVideoOutputMode[i])) ||
					(!supportsCapturePassThrough && (std::get<kVideoOutputCapturePassthroughValue>(kVideoOutputMode[i]) != bmdDeckLinkCapturePassthroughModeDisabled)))
					continue;

				fprintf(stderr,
					"       %c%2d:  %-15s\t%-20s\t%-20s\n",
					(kVideoOutputMode[i] == currentVideoOutputMode) ? '*' : ' ',
					(int)i,
					GetColorModelString(std::get<kVideoOutputSDI444Value>(kVideoOutputMode[i])).c_str(),
					GetPassThroughModeString(std::get<kVideoOutputCapturePassthroughValue>(kVideoOutputMode[i])).c_str(),
					GetIdleOutputString(std::get<kVideoOutputIdleModeValue>(kVideoOutputMode[i])).c_str()
					);

			}
		}
		else
			fprintf(stderr, "       Playback operation is not supported by the selected device\n");
	}

	fprintf(stderr, "    -n <new device label>\n");
	
	if (deckLinkConfiguration && deckLinkAttributes)
	{
		int64_t persistentID;

		// Check whether Persistent ID exists, if so we can set device label
		if (deckLinkAttributes->GetInt(BMDDeckLinkPersistentID, &persistentID) == S_OK)
		{
			// Get current device label
			dlstring_t deviceLabelStr;
			std::string deviceLabel;

			if (deckLinkConfiguration->GetString(bmdDeckLinkConfigDeviceInformationLabel, &deviceLabelStr) == S_OK)
			{
				deviceLabel = DlToStdString(deviceLabelStr);
				DeleteString(deviceLabelStr);
			}
			else
				deviceLabel = "";

			fprintf(stderr,
				"       * %s\n",
				deviceLabel.empty() ? "<Label not set>" : deviceLabel.c_str()
				);
		}
		else
			fprintf(stderr, "       Selected device does not have persistent ID\n");
	}
	
	if (SupportsEthernetConnection(deckLinkAttributes))
	{
		printf("    -pf <PTP follower-only>\n");
		
		if (deckLinkConfiguration)
		{
			// Get current PTP follower-only value
			dlbool_t current;
			if (deckLinkConfiguration->GetFlag(bmdDeckLinkConfigEthernetPTPFollowerOnly, &current) != S_OK)
				current = (dlbool_t)false;
			printf("       %s 0:  %s\n", ! current  ? "*" : " ", GetPTPFollowerOnlyString(0));
			printf("       %s 1:  %s\n", current ? "*" : " ", GetPTPFollowerOnlyString(1));
		}

		printf("    -pe <PTP encapsulation>\n");
		
		if (deckLinkConfiguration)
		{
			// Get current PTP encapsulation value
			dlbool_t currentPTPEncapsulation;
			if (deckLinkConfiguration->GetFlag(bmdDeckLinkConfigEthernetPTPUseUDPEncapsulation, &currentPTPEncapsulation) != S_OK)
				currentPTPEncapsulation = (dlbool_t)false;
			
			for (size_t i = 0; i < kEthernetPTPEncapsulation.size(); ++i)
			{
				printf("       %c%2zu:  %s\n",
					   (bool)currentPTPEncapsulation == std::get<kEthernetPTPEncapsulationFlag>(kEthernetPTPEncapsulation[i]) ? '*' : ' ',
					   i,
					   std::get<kEthernetPTPEncapsulationString>(kEthernetPTPEncapsulation[i]).c_str());
			}
		}
		
		printf("    -p1 <PTP Priority 1>\n");
		
		if (deckLinkConfiguration)
		{
			// Get PTP Priority 1 value
			int64_t ptpPriority1;
			if (deckLinkConfiguration->GetInt(bmdDeckLinkConfigEthernetPTPPriority1, &ptpPriority1) == S_OK)
			{
				printf("       * %lld\n", ptpPriority1);
			}
		}

		printf("    -p2 <PTP Priority 2>\n");
		
		if (deckLinkConfiguration)
		{
			// Get PTP Priority 2 value
			int64_t ptpPriority2;
			if (deckLinkConfiguration->GetInt(bmdDeckLinkConfigEthernetPTPPriority2, &ptpPriority2) == S_OK)
			{
				printf("       * %lld\n", ptpPriority2);
			}
		}
		
		printf("    -pd <PTP Domain>\n");
		
		if (deckLinkConfiguration)
		{
			// Get PTP Domain value
			int64_t ptpDomain;
			if (deckLinkConfiguration->GetInt(bmdDeckLinkConfigEthernetPTPDomain, &ptpDomain) == S_OK)
			{
				printf("       * %lld\n", ptpDomain);
			}
		}
	}

	fprintf(stderr,
		"\n"
		"    * = Current configuration\n"
		"\n"
		"Reconfigure DeckLink device. eg. modify label for device 1:\n"
		"\n"
		"    ./DeviceConfigure -d 1 -n \"New Label\"\n\n"
	);

}


int main(int argc, char* argv[])
{
	// Configuration settings
	bool						displayHelp						= false;
	int							deckLinkIndex					= -1;
	int							linkConfigurationIndex			= -1;
	int							videoInputConnectorIndex		= -1;
	int							audioInputConnectorIndex		= -1;
	int							videoOutputModeIndex			= -1;
	std::string					newDeviceLabel;
	int							ethernetPTPFollowerOnly			= -1;
	int							ethernetPTPEncapsulationIndex	= -1;
	int							ethernetPTPPriority1			= -1;
	int							ethernetPTPPriority2			= -1;
	int							ethernetPTPDomain				= -1;

	int							idx;
	bool						supportsEthernetPTP				= false;

	IDeckLinkIterator*			deckLinkIterator		= NULL;
	IDeckLink*					deckLink				= NULL;
	IDeckLink*					selectedDeckLink		= NULL;
	IDeckLinkProfileAttributes*	deckLinkAttributes		= NULL;
	IDeckLinkConfiguration*		deckLinkConfiguration	= NULL;

	std::vector<std::string>	deckLinkDisplayNames;

	HRESULT						result;
	
	// Process the command line arguments
	for (int i = 1; i < argc; i++)
	{
		if (strcmp(argv[i], "-d") == 0)
		{
			++i;
			if (i == argc)
				displayHelp = true;
			else
			{
				deckLinkIndex = atoi(argv[i]);
				if (argc == 3)
					displayHelp = true;
			}
		}
		else if (strcmp(argv[i], "-l") == 0)
			linkConfigurationIndex = atoi(argv[++i]);

		else if (strcmp(argv[i], "-v") == 0)
			videoInputConnectorIndex = atoi(argv[++i]);

		else if (strcmp(argv[i], "-a") == 0)
			audioInputConnectorIndex = atoi(argv[++i]);

		else if (strcmp(argv[i], "-o") == 0)
			videoOutputModeIndex = atoi(argv[++i]);

		else if (strcmp(argv[i], "-n") == 0)
			newDeviceLabel = std::string(argv[++i]);

		else if (strcmp(argv[i], "-pf") == 0)
			ethernetPTPFollowerOnly = atoi(argv[++i]);
		
		else if (strcmp(argv[i], "-pe") == 0)
			ethernetPTPEncapsulationIndex = atoi(argv[++i]);

		else if (strcmp(argv[i], "-p1") == 0)
			ethernetPTPPriority1 = atoi(argv[++i]);

		else if (strcmp(argv[i], "-p2") == 0)
			ethernetPTPPriority2 = atoi(argv[++i]);

		else if (strcmp(argv[i], "-pd") == 0)
			ethernetPTPDomain = atoi(argv[++i]);

		else if ((strcmp(argv[i], "?") == 0) || (strcmp(argv[i], "-h") == 0))
			displayHelp = true;

		else
		{
			// Unknown argument on command line
			fprintf(stderr, "Unknown argument %s\n", argv[i]);
			displayHelp = true;
		}
	}

	if (deckLinkIndex < 0)
	{
		fprintf(stderr, "You must select a device\n");
		displayHelp = true;
	}

	// Create an IDeckLinkIterator object to enumerate all DeckLink cards in the system
	result = GetDeckLinkIterator(&deckLinkIterator);
	if (result != S_OK)
	{
		fprintf(stderr, "A DeckLink iterator could not be created.  The DeckLink drivers may not be installed.\n");
		goto bail;
	}
	
	// Obtain the required DeckLink device
	idx = 0;

	while ((result = deckLinkIterator->Next(&deckLink)) == S_OK)
	{
		dlstring_t deckLinkName;

		result = deckLink->GetDisplayName(&deckLinkName);
		if (result == S_OK)
		{
			deckLinkDisplayNames.push_back(DlToStdString(deckLinkName));
			DeleteString(deckLinkName);
		}

		if (idx++ == deckLinkIndex)
			selectedDeckLink = deckLink;
		else
			deckLink->Release();
	}

	if (selectedDeckLink != NULL)
	{
		if (selectedDeckLink->QueryInterface(IID_IDeckLinkConfiguration, (void**)&deckLinkConfiguration) != S_OK)
		{
			fprintf(stderr, "Unable to query IDeckLinkConfiguration interface\n");
			goto bail;
		}

		if (selectedDeckLink->QueryInterface(IID_IDeckLinkProfileAttributes, (void**)&deckLinkAttributes) != S_OK)
		{
			fprintf(stderr, "Unable to query IDeckLinkProfileAttributes interface\n");
			goto bail;
		}
		
		supportsEthernetPTP = SupportsEthernetConnection(deckLinkAttributes);
	}
	else
		displayHelp = true;

	if (displayHelp)
	{
		DisplayUsage(deckLinkConfiguration, deckLinkAttributes, deckLinkDisplayNames);
		goto bail;
	}

	// Set link width configuration
	if (linkConfigurationIndex != -1)
	{
		if (std::get<kLinkConfigurationValidFunction>(kLinkConfiguration[linkConfigurationIndex])(deckLinkAttributes))
		{
			if (deckLinkConfiguration->SetInt(bmdDeckLinkConfigSDIOutputLinkConfiguration, (int64_t)std::get<kLinkConfigurationValue>(kLinkConfiguration[linkConfigurationIndex])) != S_OK)
			{
				fprintf(stderr, "Unable to set SDI output Link Configuration\n");
				goto bail;
			}
		}
		else
		{
			fprintf(stderr, "Link width not supported by device\n");
			goto bail;
		}
	}
	
	// Set video input connection configuration
	if (videoInputConnectorIndex != -1)
	{
		int64_t supportedVideoInputConnections;

		if ((deckLinkAttributes->GetInt(BMDDeckLinkVideoInputConnections, &supportedVideoInputConnections) != S_OK)
			|| (((BMDVideoConnection)supportedVideoInputConnections & std::get<kVideoInputConnectionsFlag>(kVideoInputConnections[videoInputConnectorIndex])) == 0))
		{
			fprintf(stderr, "Invalid video input connector for device\n");
			goto bail;
		}
		else if (deckLinkConfiguration->SetInt(bmdDeckLinkConfigVideoInputConnection, (int64_t)std::get<kVideoInputConnectionsFlag>(kVideoInputConnections[videoInputConnectorIndex])) != S_OK)
		{
			fprintf(stderr, "Unable to set video input connector\n");
			goto bail;
		}
	}

	// Set audio input connection configuration
	if (audioInputConnectorIndex != -1)
	{
		int64_t supportedAudioInputConnections;

		if ((deckLinkAttributes->GetInt(BMDDeckLinkAudioInputConnections, &supportedAudioInputConnections) != S_OK) 
			|| (((BMDAudioConnection)supportedAudioInputConnections & std::get<kAudioInputConnectionsFlag>(kAudioInputConnections[audioInputConnectorIndex])) == 0))
		{
			fprintf(stderr, "Invalid audio input connector for device\n");
			goto bail;
		}
		else if (deckLinkConfiguration->SetInt(bmdDeckLinkConfigAudioInputConnection, (int64_t)std::get<kAudioInputConnectionsFlag>(kAudioInputConnections[audioInputConnectorIndex])) != S_OK)
		{
			fprintf(stderr, "Unable to set audio input connector\n");
			goto bail;
		}
	}
	
	// Set video output mode configurations
	if (videoOutputModeIndex != -1)
	{
		dlbool_t dummyFlag;
		int64_t dummyInt;

		bool supportsIdleOutput = (deckLinkAttributes->GetFlag(BMDDeckLinkSupportsIdleOutput, &dummyFlag) == S_OK) && dummyFlag;
		bool supportsSDI444Output = deckLinkConfiguration->GetFlag(bmdDeckLinkConfig444SDIVideoOutput, &dummyFlag) == S_OK;
		bool supportsCapturePassThrough = (deckLinkConfiguration->GetInt(bmdDeckLinkConfigCapturePassThroughMode, &dummyInt) == S_OK);

		if (supportsCapturePassThrough && (deckLinkConfiguration->SetInt(bmdDeckLinkConfigCapturePassThroughMode, (int64_t)std::get<kVideoOutputCapturePassthroughValue>(kVideoOutputMode[videoOutputModeIndex])) == E_FAIL))
		{
			fprintf(stderr, "Unable to set video capture passthrough mode\n");
			goto bail;
		}

		if (supportsIdleOutput && (deckLinkConfiguration->SetInt(bmdDeckLinkConfigVideoOutputIdleOperation, (int64_t)std::get<kVideoOutputIdleModeValue>(kVideoOutputMode[videoOutputModeIndex])) != S_OK))
		{
			fprintf(stderr, "Unable to set video output idle mode\n");
			goto bail;
		}

		if (supportsSDI444Output && (deckLinkConfiguration->SetFlag(bmdDeckLinkConfig444SDIVideoOutput, (dlbool_t)std::get<kVideoOutputSDI444Value>(kVideoOutputMode[videoOutputModeIndex])) == E_FAIL))
		{
			fprintf(stderr, "Unable to set video output 444 mode\n");
			goto bail;
		}
	}

	// Set new device label
	if (!newDeviceLabel.empty())
	{
		int64_t persistentID;

		if (deckLinkAttributes->GetInt(BMDDeckLinkPersistentID, &persistentID) != S_OK)
		{
			fprintf(stderr, "Unable to set new display label, device has no persistent ID\n");
			goto bail;
		}
		else
		{
			dlstring_t deviceLabelStr = StdToDlString(newDeviceLabel);
			result = deckLinkConfiguration->SetString(bmdDeckLinkConfigDeviceInformationLabel, deviceLabelStr);
			DeleteString(deviceLabelStr);

			if (result != S_OK)
			{
				fprintf(stderr, "Unable to set device label\n");
				goto bail;
			}
		}
	}
	
	if (supportsEthernetPTP)
	{
		if (ethernetPTPFollowerOnly != -1)
		{
			if (FAILED(deckLinkConfiguration->SetFlag(bmdDeckLinkConfigEthernetPTPFollowerOnly, (dlbool_t)ethernetPTPFollowerOnly)))
			{
				fprintf(stderr, "Unable to set Ethernet PTP follower-only\n");
				goto bail;
			}
		}
		
		if (ethernetPTPEncapsulationIndex != -1)
		{
			if (deckLinkConfiguration->SetFlag(bmdDeckLinkConfigEthernetPTPUseUDPEncapsulation,
											   (dlbool_t)std::get<kEthernetPTPEncapsulationFlag>(kEthernetPTPEncapsulation[ethernetPTPEncapsulationIndex])) == E_FAIL)
			{
				fprintf(stderr, "Unable to set Ethernet PTP encapsulation\n");
				goto bail;
			}
		}
		
		if (ethernetPTPPriority1 != -1)
		{
			if (deckLinkConfiguration->SetInt(bmdDeckLinkConfigEthernetPTPPriority1, ethernetPTPPriority1) == E_FAIL)
			{
				fprintf(stderr, "Unable to set Ethernet PTP priority 1\n");
				goto bail;
			}
		}

		if (ethernetPTPPriority2 != -1)
		{
			if (deckLinkConfiguration->SetInt(bmdDeckLinkConfigEthernetPTPPriority2, ethernetPTPPriority2) == E_FAIL)
			{
				fprintf(stderr, "Unable to set Ethernet PTP priority 2\n");
				goto bail;
			}
		}
		
		if (ethernetPTPDomain != -1)
		{
			if (deckLinkConfiguration->SetInt(bmdDeckLinkConfigEthernetPTPDomain, ethernetPTPDomain) == E_FAIL)
			{
				fprintf(stderr, "Unable to set Ethernet PTP domain\n");
				goto bail;
			}
		}
	}

	// OK to write new configuration - print configuration
	fprintf(stderr, "Updating device with configuration:\n - Device display name: %s\n", GetDisplayNameAttribute(deckLinkAttributes).c_str());
	if (linkConfigurationIndex != -1)
		fprintf(stderr, " - Link width: %s\n", (std::get<kLinkConfigurationString>(kLinkConfiguration[linkConfigurationIndex])).c_str());
	if (videoInputConnectorIndex != -1)
		fprintf(stderr, " - Video Input Connector: %s\n", (std::get<kVideoInputConnectionsString>(kVideoInputConnections[videoInputConnectorIndex])).c_str());
	if (audioInputConnectorIndex != -1)
		fprintf(stderr, " - Audio Input Connector: %s\n", (std::get<kAudioInputConnectionsString>(kAudioInputConnections[audioInputConnectorIndex])).c_str());
	if (videoOutputModeIndex != -1)
	{
		fprintf(stderr, " - Video Output Color mode: %s\n", GetColorModelString(std::get<kVideoOutputSDI444Value>(kVideoOutputMode[videoOutputModeIndex])).c_str());
		fprintf(stderr, " - Video Output Pass-through mode: %s\n", GetPassThroughModeString(std::get<kVideoOutputCapturePassthroughValue>(kVideoOutputMode[videoOutputModeIndex])).c_str());
		fprintf(stderr, " - Video Output Idle mode: %s\n", GetIdleOutputString(std::get<kVideoOutputIdleModeValue>(kVideoOutputMode[videoOutputModeIndex])).c_str());
	}
	if (supportsEthernetPTP)
	{
		if (ethernetPTPFollowerOnly != -1)
			printf(" - Ethernet PTP Follower-Only: %s\n", GetPTPFollowerOnlyString(ethernetPTPFollowerOnly));
		if (ethernetPTPEncapsulationIndex != -1)
			printf(" - Ethernet PTP Encapsulation: %s\n",
					(std::get<kEthernetPTPEncapsulationString>(kEthernetPTPEncapsulation[ethernetPTPEncapsulationIndex])).c_str());
		if (ethernetPTPPriority1 != -1)
			printf(" - Ethernet PTP Priority 1: %d\n", ethernetPTPPriority1);
		if (ethernetPTPPriority2 != -1)
			printf(" - Ethernet PTP Priority 2: %d\n", ethernetPTPPriority2);
		if (ethernetPTPDomain != -1)
			printf(" - Ethernet PTP Domain: %d\n", ethernetPTPDomain);
	}

	// Write configuration to preferences
	result = deckLinkConfiguration->WriteConfigurationToPreferences();
	switch (result)
	{
		case S_OK :
			fprintf(stderr, "Done.\n");
			break;

		case S_FALSE :
			fprintf(stderr, "No configuration to save, use ?/-h option for help\n");
			break;

		case E_ACCESSDENIED :
			fprintf(stderr, "Unable to save configuration, insufficient privileges to write to system preferences\n");
			break;

		case E_FAIL:
			fprintf(stderr, "Unable to save configuration, an unexpected failure occurred\n");
			break;
	}

bail:
	if (deckLinkConfiguration != NULL)
	{
		deckLinkConfiguration->Release();
		deckLinkConfiguration = NULL;
	}

	if (deckLinkAttributes != NULL)
	{
		deckLinkAttributes->Release();
		deckLinkAttributes = NULL;
	}

	if (selectedDeckLink != NULL)
	{
		selectedDeckLink->Release();
		selectedDeckLink = NULL;
	}

	if (deckLinkIterator != NULL)
	{
		deckLinkIterator->Release();
		deckLinkIterator = NULL;
	}

	return(result == S_OK) ? 0 : 1;
}

