/* -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 "DeckLinkAPI.h"
#include <stdio.h>
#include <list>
#include <map>
#include <string>
#include "platform.h"

#define kMaxHeaderLength 128

// List of known pixel formats and their matching display names
static const std::list<std::pair<BMDPixelFormat, std::string>> gPixelFormats =
{
	{ bmdFormat8BitYUV,     "8-bit YUV" },
	{ bmdFormat10BitYUV,    "10-bit YUV" },
	{ bmdFormat8BitARGB,    "8-bit ARGB" },
	{ bmdFormat8BitBGRA,    "8-bit BGRA" },
	{ bmdFormat10BitRGB,    "10-bit RGB" },
	{ bmdFormat12BitRGB,    "12-bit RGB" },
	{ bmdFormat12BitRGBLE,  "12-bit RGBLE" },
	{ bmdFormat10BitRGBXLE, "10-bit RGBXLE" },
	{ bmdFormat10BitRGBX,   "10-bit RGBX" },
};

static const std::list<std::pair<BMDVideoConnection, std::string>> gConnections =
{
	{ bmdVideoConnectionUnspecified, "Unspecified Connection" },
	{ bmdVideoConnectionSDI,         "SDI" },
	{ bmdVideoConnectionHDMI,        "HDMI" },
	{ bmdVideoConnectionOpticalSDI,  "Optical SDI" },
	{ bmdVideoConnectionComponent,   "Component" },
	{ bmdVideoConnectionComposite,   "Composite" },
	{ bmdVideoConnectionSVideo,      "S-Video" },
	{ bmdVideoConnectionEthernet,	 "Ethernet" },
	{ bmdVideoConnectionOpticalEthernet,  "Optical Ethernet" },
};

static const std::list<std::pair<BMDSupportedVideoModeFlags, std::string>> gSDILinks =
{
	{ bmdSupportedVideoModeSDISingleLink,	"Single-Link" },
	{ bmdSupportedVideoModeSDIDualLink,		"Dual-Link" },
	{ bmdSupportedVideoModeSDIQuadLink,		"Quad-Link" },
};

static const std::map<uint32_t, const char*> gDuplexModes =
{
	{ bmdDuplexInactive, 	"Inactive" },
	{ bmdDuplexFull, 		"Full" },
	{ bmdDuplexSimplex, 	"Simplex" },
	{ bmdDuplexHalf, 		"Half" },
};

static const std::list<std::pair<BMDVideoOutputConversionMode, std::string>> gOutputConversions =
{
	{ bmdNoVideoOutputConversion,                             "No Conversion" },
	{ bmdVideoOutputLetterboxDownconversion,                  "Down-Conversion Letterbox (Software)" },
	{ bmdVideoOutputAnamorphicDownconversion,                 "Down-Conversion Anamorphic (Software)" },
	{ bmdVideoOutputHD720toHD1080Conversion,                  "Cross-Conversion 720 to 1080 (Software)" },
	{ bmdVideoOutputHardwareLetterboxDownconversion,          "Down-Conversion Letterbox (Hardware)" },
	{ bmdVideoOutputHardwareAnamorphicDownconversion,         "Down-Conversion Anamorphic (Hardware)" },
	{ bmdVideoOutputHardwareCenterCutDownconversion,          "Down-Conversion Center Cut (Hardware)" },
	{ bmdVideoOutputHardware720p1080pCrossconversion,         "Cross-Conversion 720p to/from 1080i (Hardware)" },
	{ bmdVideoOutputHardwareAnamorphic720pUpconversion,       "Up-Conversion to 720p Anamorphic (Hardware)" },
	{ bmdVideoOutputHardwareAnamorphic1080iUpconversion,      "Up-Conversion to 1080i Anamorphic (Hardware)" },
	{ bmdVideoOutputHardwareAnamorphic149To720pUpconversion,  "Up-Conversion to 720p 14:9 Zoom (Hardware)" },
	{ bmdVideoOutputHardwareAnamorphic149To1080iUpconversion, "Up-Conversion to 1080i 14:9 Zoom (Hardware)" },
	{ bmdVideoOutputHardwarePillarbox720pUpconversion,        "Up-Conversion to 720p Pillarbox (Hardware)" },
	{ bmdVideoOutputHardwarePillarbox1080iUpconversion,       "Up-Conversion to 1080i Pillarbox (Hardware)" },
};

static const std::list<std::pair<BMDVideoInputConversionMode, std::string>> gInputConversions =
{
	{ bmdNoVideoInputConversion,                       "No Conversion" },
	{ bmdVideoInputLetterboxDownconversionFromHD1080,  "Down-Conversion from 1080 Letterbox (Software)" },
	{ bmdVideoInputAnamorphicDownconversionFromHD1080, "Down-Conversion from 1080 Anamorphic (Software)" },
	{ bmdVideoInputLetterboxDownconversionFromHD720,   "Down-Conversion from 720 Letterbox (Software)" },
	{ bmdVideoInputAnamorphicDownconversionFromHD720,  "Down-Conversion from 720 Anamorphic (Software)" },
	{ bmdVideoInputLetterboxUpconversion,              "Up-Conversion 16:9 Zoom (Software)" },
	{ bmdVideoInputAnamorphicUpconversion,             "Up-Conversion Anamorphic (Software)" },
};

enum PrintFlags : uint32_t
{
	kPrintDisplayModeConnections = (1 << 0),
	kPrintDisplayModeConversions = (1 << 1),
};

void	parse_arguments(int argc, char** argv, uint32_t& printFlags);
void	print_attributes (IDeckLink* deckLink, bool showConnectorAttributes);
void	mode_name(IDeckLinkDisplayMode *displayMode, std::string& modeName);
void	print_output_mode(IDeckLinkOutput* deckLinkOutput, BMDVideoConnection connection, BMDVideoOutputConversionMode conversion, BMDSupportedVideoModeFlags flags, IDeckLinkDisplayMode *displayMode, const char*& header);
void	print_output_modes_for_setup (IDeckLinkOutput* deckLinkOutput, BMDVideoConnection connection, BMDVideoOutputConversionMode conversion, BMDSupportedVideoModeFlags flags, const char* header);
void	print_output_modes (IDeckLink* deckLink, uint32_t printFlags);
void	print_input_mode(IDeckLinkInput* deckLinkInput, BMDVideoConnection connection, BMDVideoInputConversionMode conversion, BMDSupportedVideoModeFlags flags, IDeckLinkDisplayMode* displayMode, const char* nameSuffix, const char*& header);
void	print_input_modes_for_setup (IDeckLinkInput* deckLinkInput, BMDVideoConnection connection, BMDVideoInputConversionMode conversion, BMDSupportedVideoModeFlags flags, const char* nameSuffix, const char* header);
void	print_input_modes (IDeckLink* deckLink, uint32_t printFlags);


int		main (int argc, char** argv)
{
	IDeckLinkIterator*			deckLinkIterator;
	IDeckLinkAPIInformation*	deckLinkAPIInformation;
	IDeckLink*					deckLink;
	IDeckLinkProfileAttributes*	deckLinkAttributes = NULL;
	int							numDevices = 0;
	uint32_t					printFlags = 0;
	HRESULT						result;

	parse_arguments(argc, argv, printFlags);
	
	// 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");
		return 1;
	}
	
	// We can get the version of the API like this:
	result = deckLinkIterator->QueryInterface(IID_IDeckLinkAPIInformation, (void**)&deckLinkAPIInformation);
	if (result == S_OK)
	{
		int64_t			deckLinkVersion;
		int				dlVerMajor, dlVerMinor, dlVerPoint;
		
		// We can also use the BMDDeckLinkAPIVersion flag with GetString
		deckLinkAPIInformation->GetInt(BMDDeckLinkAPIVersion, &deckLinkVersion);
		
		dlVerMajor = (deckLinkVersion & 0xFF000000) >> 24;
		dlVerMinor = (deckLinkVersion & 0x00FF0000) >> 16;
		dlVerPoint = (deckLinkVersion & 0x0000FF00) >> 8;
		
		printf("DeckLinkAPI version: %d.%d.%d\n", dlVerMajor, dlVerMinor, dlVerPoint);
		
		deckLinkAPIInformation->Release();
	}
	
	// Enumerate all cards in this system
	while (deckLinkIterator->Next(&deckLink) == S_OK)
	{
		dlstring_t deviceNameString;
		
		// Increment the total number of DeckLink cards found
		numDevices++;
		if (numDevices > 1)
			printf("\n\n");
		
		// *** Print the model name of the DeckLink card
		result = deckLink->GetModelName(&deviceNameString);
		if (result == S_OK)
		{
			std::string deviceName = DlToStdString(deviceNameString);
			printf("=============== %s ===============\n\n", deviceName.c_str());
			DeleteString(deviceNameString);
		}
		
		// Products with multiple subdevices might not be usable if a subdevice is inactive for the current profile
		bool showIOinfo = true;
		result = deckLink->QueryInterface(IID_IDeckLinkProfileAttributes, (void**)&deckLinkAttributes);
		if (result != S_OK)
		{
			fprintf(stderr, "Could not obtain the IDeckLinkProfileAttributes interface - result = %08x\n", result);
			continue;
		}

		int64_t duplexMode;
		if (deckLinkAttributes->GetInt(BMDDeckLinkDuplex, &duplexMode) == S_OK && duplexMode == bmdDuplexInactive)
		{
			printf("Sub-device has no active connectors for current profile\n\n");
			showIOinfo = false;
		}

		int64_t videoIOSupport;
		result = deckLinkAttributes->GetInt(BMDDeckLinkVideoIOSupport, &videoIOSupport);
		if (result != S_OK)
		{
			fprintf(stderr, "Could not get BMDDeckLinkVideoIOSupport attribute - result = %08x\n", result);
			continue;
		}

		deckLinkAttributes->Release();
		
		print_attributes(deckLink, showIOinfo);
		
		if (showIOinfo)
		{
			if (videoIOSupport & bmdDeviceSupportsPlayback)
			{
				// ** List the video output display modes supported by the card
				print_output_modes(deckLink, printFlags);
			}

			if (videoIOSupport & bmdDeviceSupportsCapture)
			{
				// ** List the video input display modes supported by the card
				print_input_modes(deckLink, printFlags);
			}			
		}
		
		// Release the IDeckLink instance when we've finished with it to prevent leaks
		deckLink->Release();
	}
	
	deckLinkIterator->Release();	

	// If no DeckLink cards were found in the system, inform the user
	if (numDevices == 0)
		printf("No Blackmagic Design devices were found.\n");
	printf("\n");
	
	return 0;
}

void	parse_arguments(int argc, char** argv, uint32_t& printFlags)
{
	for (int i = 1; i < argc; ++i)
	{
		if (strcmp(argv[i], "--connections") == 0 || strcmp(argv[i], "-c") == 0)
			printFlags |= kPrintDisplayModeConnections;
		else if (strcmp(argv[i], "--conversions") == 0|| strcmp(argv[i], "-v") == 0)
			printFlags |= kPrintDisplayModeConversions;
		else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0)
		{
			printf("%s [options]\n"
			       "\n"
			       "Options:\n"
			       "    -h, --help           Display this help message\n"
			       "    -c, --connections    Display the supported modes for each connection type\n"
			       "    -v, --conversions    Display the supported modes for each conversion type\n", argv[0]);
			exit(0);
		}
		else
		{
			fprintf(stderr, "Unknown argument: %s\n", argv[i]);
			exit(1);
		}
	}
}

void	print_attributes (IDeckLink* deckLink, bool showConnectorAttributes)
{
	IDeckLinkProfileAttributes*			deckLinkAttributes = NULL;
	dlbool_t							supported;
	int64_t								value;
	dlstring_t							stringValue;
	HRESULT								result;
	
	// Query the DeckLink for its attributes interface
	result = deckLink->QueryInterface(IID_IDeckLinkProfileAttributes, (void**)&deckLinkAttributes);
	if (result != S_OK)
	{
		fprintf(stderr, "Could not obtain the IDeckLinkProfileAttributes interface - result = %08x\n", result);
		goto bail;
	}
	
	// List attributes and their value
	printf("Attribute list:\n");
	
	result = deckLinkAttributes->GetInt(BMDDeckLinkDeviceInterface, &value);
	if (result == S_OK)
	{
		switch(value)
		{
			case bmdDeviceInterfacePCI:
				printf(" %-40s %s\n", "Device Interface:",  "PCI");
				break;
			case bmdDeviceInterfaceUSB:
				printf(" %-40s %s\n", "Device Interface:",  "USB");
				break;
			case bmdDeviceInterfaceThunderbolt:
				printf(" %-40s %s\n", "Device Interface",  "Thunderbolt");
				break;
		}
	}
	else
	{
		fprintf(stderr, "Could not query the device interface attribute- result = %08x\n", result);
	}

	result = deckLinkAttributes->GetInt(BMDDeckLinkPersistentID, &value);
	if (result == S_OK)
	{
		printf(" %-40s %llx\n", "Device Persistent ID:",  value);
	}
	else
	{
		printf(" %-40s %s\n", "Device Persistent ID:",  "Not Supported on this device");
	}

	result = deckLinkAttributes->GetInt(BMDDeckLinkTopologicalID, &value);
	if (result == S_OK)
	{
		printf(" %-40s %llx\n", "Device Topological ID:",  value);
	}
	else
	{
		printf(" %-40s %s\n", "Device Topological ID:",  "Not Supported on this device");
	}

	result = deckLinkAttributes->GetInt(BMDDeckLinkNumberOfSubDevices, &value);
	if (result == S_OK)
	{
		printf(" %-40s %lld\n", "Number of sub-devices:",  value);
		if (value != 0)
		{
			result = deckLinkAttributes->GetInt(BMDDeckLinkSubDeviceIndex, &value);
			if (result == S_OK)
			{
				printf(" %-40s %lld\n", "Sub-device index:",  value);
			}
			else
			{
				fprintf(stderr, "Could not query the sub-device index attribute- result = %08x\n", result);
			}
		}
	}
	else
	{
		fprintf(stderr, "Could not query the number of sub-device attribute- result = %08x\n", result);
	}
	
	if (! showConnectorAttributes)
		goto bail;
	
	result = deckLinkAttributes->GetFlag(BMDDeckLinkHasSerialPort, &supported);
	if (result == S_OK)
	{
		printf(" %-40s %s\n", "Serial port present:",  supported ? "Yes" : "No");
		if (supported)
		{
			dlstring_t name;
			result = deckLinkAttributes->GetString(BMDDeckLinkSerialPortDeviceName, &name);
			if (result == S_OK)
			{
				std::string	portName = DlToStdString(name);
				printf(" %-40s %s\n", "Serial port name:",  portName.c_str());
				
				DeleteString(name);
			}
			else
			{
				fprintf(stderr, "Could not query the serial port name attribute- result = %08x\n", result);
			}
		}
		
	}
	else
	{
		fprintf(stderr, "Could not query the serial port presence attribute- result = %08x\n", result);
	}
	
	result = deckLinkAttributes->GetInt(BMDDeckLinkMaximumAudioChannels, &value);
	if (result == S_OK)
	{
		printf(" %-40s %lld\n", "Number of audio channels:",  value);
	}
	else
	{
		fprintf(stderr, "Could not query the number of supported audio channels attribute- result = %08x\n", result);
	}

	result = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsInputFormatDetection, &supported);
	if (result == S_OK)
	{
		printf(" %-40s %s\n", "Input mode detection supported ?", supported ? "Yes" : "No");
	}
	else
	{
		fprintf(stderr, "Could not query the input mode detection attribute- result = %08x\n", result);
	}

	result = deckLinkAttributes->GetInt(BMDDeckLinkDuplex, &value);
	if (result == S_OK)
	{
		printf(" %-40s %s\n", "Duplex Mode:", gDuplexModes.at(value));
	}
	else
	{
		fprintf(stderr, "Could not query the of sub-device duplex attribute- result = %08x\n", result);
	}
	
	result = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsInternalKeying, &supported);
	if (result == S_OK)
	{
		printf(" %-40s %s\n", "Internal keying supported ?", supported ? "Yes" : "No");
	}
	else
	{
		fprintf(stderr, "Could not query the internal keying attribute- result = %08x\n", result);
	}
	
	result = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsExternalKeying, &supported);
	if (result == S_OK)
	{
		printf(" %-40s %s\n", "External keying supported ?", supported ? "Yes" : "No");
	}
	else
	{
		fprintf(stderr, "Could not query the external keying attribute- result = %08x\n", result);
	}

	result = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsHDMITimecode, &supported);
	if (result == S_OK)
	{
		printf(" %-40s %s\n", "HDMI timecode support:", supported ? "LTC" : "None");
	}
	else
	{
		fprintf(stderr, "Could not query the HDMI timecode support attribute- result = %08x\n", result);
	}
	
	result = deckLinkAttributes->GetString(BMDDeckLinkEthernetMACAddress, &stringValue);
	if (result == S_OK)
	{
		auto stdString = DlToStdString(stringValue);
		DeleteString(stringValue);
		printf(" %-40s %s\n", "MAC Address:", stdString.c_str());
	}
	else if (result == E_NOTIMPL)
	{
		// Not present on product
	}
	else
	{
		fprintf(stderr, "Could not query BMDDeckLinkEthernetMACAddress. result = %08x\n", result);
	}
	
bail:
	printf("\n");
	if(deckLinkAttributes != NULL)
		deckLinkAttributes->Release();
}

void mode_name(IDeckLinkDisplayMode *displayMode, std::string& modeName)
{
	HRESULT					result;
	dlstring_t				displayModeString;

	result = displayMode->GetName(&displayModeString);
	if (result != S_OK)
	{
		modeName.clear();
		return;
	}

	modeName = DlToStdString(displayModeString);
	DeleteString(displayModeString);
}

void print_output_mode(IDeckLinkOutput* deckLinkOutput, BMDVideoConnection connection, BMDVideoOutputConversionMode conversion, BMDSupportedVideoModeFlags flags, IDeckLinkDisplayMode *displayMode, const char*& header)
{
	std::string				modeName;
	int						modeWidth;
	int						modeHeight;
	BMDTimeValue			frameRateDuration;
	BMDTimeScale			frameRateScale;
	bool					printedMode = false;
	int						unsupportedCount = 0;
	BMDDisplayMode			requestedMode = displayMode->GetDisplayMode();
	BMDDisplayMode			actualMode = requestedMode;

	// Print the supported pixel formats for this display mode
	for (auto pixelFormat: gPixelFormats)
	{
		dlbool_t supported;
		BMDDisplayMode retMode;

		if (deckLinkOutput->DoesSupportVideoMode(connection, requestedMode, pixelFormat.first, conversion, flags, &retMode, &supported) == S_OK && supported)
		{
			if (retMode != bmdModeUnknown)
				actualMode = retMode;

			if (header)
			{
				printf("%s\n", header);
				header = NULL;
			}

			if (! printedMode)
			{
				// Mode properties
				mode_name(displayMode, modeName);
				displayMode->GetFrameRate(&frameRateDuration, &frameRateScale);
				bool requested3D = (flags & bmdSupportedVideoModeDualStream3D) != 0;
				bool converted = actualMode != requestedMode;
				modeWidth = displayMode->GetWidth();
				modeHeight = displayMode->GetHeight();
				
				std::string tag;
				if (requested3D)
					tag = "3D";
				if (converted)
					tag += "*";
				
				printf(" %-20s %-12s \t %4d x %4d \t %7g FPS\t", modeName.c_str(), tag.c_str(), modeWidth, modeHeight, (double)frameRateScale / (double)frameRateDuration);
				
				// Show pixel formats already found to be unsupported
				while (unsupportedCount--)
				{
					printf("%-15s", "------");
				}
				
				printedMode = true;
			}
			
			printf("%-15s", pixelFormat.second.c_str());
		}
		else
		{
			if (printedMode)
				printf("%-15s", "------");
			else
				unsupportedCount++;
		}
	}
	
	if (printedMode)
	{
		
		if (actualMode != requestedMode)
		{
			// Append converted-to mode
			if (deckLinkOutput->GetDisplayMode(actualMode, &displayMode) == S_OK)
			{
				mode_name(displayMode, modeName);
				displayMode->Release();
				printf("* Converted to %s", modeName.c_str());
			}
		}
		
		printf("\n");
	}
}

void	print_output_modes_for_setup (IDeckLinkOutput* deckLinkOutput, BMDVideoConnection connection, BMDVideoOutputConversionMode conversion, BMDSupportedVideoModeFlags flags, const char* header)
{
	IDeckLinkDisplayModeIterator*	displayModeIterator = NULL;
	IDeckLinkDisplayMode*			displayMode = NULL;
	HRESULT							result;
	
	// Obtain an IDeckLinkDisplayModeIterator to enumerate the display modes supported on output
	result = deckLinkOutput->GetDisplayModeIterator(&displayModeIterator);
	if (result != S_OK)
	{
		fprintf(stderr, "Could not obtain the video output display mode iterator - result = %08x\n", result);
		return;
	}
	
	while (displayModeIterator->Next(&displayMode) == S_OK)
	{
		print_output_mode(deckLinkOutput, connection, conversion, flags, displayMode, header);
		displayMode->Release();
	}
	
	displayModeIterator->Release();
}

void	print_output_modes (IDeckLink* deckLink, uint32_t printFlags)
{
	IDeckLinkOutput*					deckLinkOutput = NULL;
	IDeckLinkProfileAttributes*			deckLinkAttributes = NULL;
	HRESULT								result;
	int64_t								ports;
	dlbool_t							keyingSupported;
	char								header[kMaxHeaderLength];

	// Query the DeckLink for its output interface
	result = deckLink->QueryInterface(IID_IDeckLinkOutput, (void**)&deckLinkOutput);
	if (result != S_OK)
	{
		fprintf(stderr, "Could not obtain the IDeckLinkOutput interface - result = %08x\n", result);
		goto bail;
	}
	
	result = deckLink->QueryInterface(IID_IDeckLinkProfileAttributes, (void**)&deckLinkAttributes);
	if (result != S_OK)
	{
		fprintf(stderr, "Could not obtain the IDeckLinkAttributes interface - result = %08x\n", result);
		goto bail;
	}

	result = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsInternalKeying, &keyingSupported);

	if (result != S_OK || !keyingSupported)
		result = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsExternalKeying, &keyingSupported);

	// List all supported output display modes
	printf("Supported video output display modes:\n");

	result = deckLinkAttributes->GetInt(BMDDeckLinkVideoOutputConnections, &ports);

	if (result != S_OK)
		goto bail;

	for (const auto& connection: gConnections)
	{
		if (connection.first == bmdVideoConnectionUnspecified || (ports & connection.first))
		{
			if (((printFlags & kPrintDisplayModeConnections) == 0) != (connection.first == bmdVideoConnectionUnspecified))
				continue;

			for (const auto& conversion: gOutputConversions)
			{
				if (((printFlags & kPrintDisplayModeConversions) == 0) != (conversion.first == bmdNoVideoOutputConversion))
					continue;

				if (connection.first == bmdVideoConnectionSDI || connection.first == bmdVideoConnectionOpticalSDI)
				{
					for (std::list<std::pair<BMDSupportedVideoModeFlags, std::string>>::const_reverse_iterator links = gSDILinks.rbegin(); links != gSDILinks.rend(); links++)
					{
						dlbool_t multilinkSupported = false;
						if (links->first & bmdSupportedVideoModeSDIDualLink)
						{
							if (FAILED(deckLinkAttributes->GetFlag(BMDDeckLinkSupportsDualLinkSDI, &multilinkSupported)) || ! multilinkSupported)
								continue;
						}
						else if (links->first & bmdSupportedVideoModeSDIQuadLink)
						{
							if (FAILED(deckLinkAttributes->GetFlag(BMDDeckLinkSupportsQuadLinkSDI, &multilinkSupported)) || ! multilinkSupported)
								continue;
						}

						if (conversion.first == bmdNoVideoOutputConversion)
							snprintf(header, kMaxHeaderLength, "\n%s %s output:", connection.second.c_str(), links->second.c_str());
						else
							snprintf(header, kMaxHeaderLength, "\n%s %s %s output:", connection.second.c_str(), links->second.c_str(), conversion.second.c_str());
						print_output_modes_for_setup(deckLinkOutput, connection.first, conversion.first, links->first, header);

						if (multilinkSupported)
							print_output_modes_for_setup(deckLinkOutput, connection.first, conversion.first, (BMDSupportedVideoModeFlags)(links->first | bmdSupportedVideoModeDualStream3D), header);
					}

					if (keyingSupported && conversion.first == bmdNoVideoOutputConversion)
					{
						snprintf(header, kMaxHeaderLength, "\n%s fill and key outputs:", connection.second.c_str());
						print_output_modes_for_setup(deckLinkOutput, connection.first, conversion.first, bmdSupportedVideoModeKeying, header);
					}
				}
				else
				{
					if (conversion.first == bmdNoVideoOutputConversion)
						snprintf(header, kMaxHeaderLength, "\n%s output:", connection.second.c_str());
					else
						snprintf(header, kMaxHeaderLength, "\n%s %s output:", connection.second.c_str(), conversion.second.c_str());

					print_output_modes_for_setup(deckLinkOutput, connection.first, conversion.first, bmdSupportedVideoModeDefault, header);
					print_output_modes_for_setup(deckLinkOutput, connection.first, conversion.first, bmdSupportedVideoModeDualStream3D, header);
				}
			}
		 }
	}
	
bail:
	if (deckLinkAttributes)
		deckLinkAttributes->Release();

	if (deckLinkOutput)
		deckLinkOutput->Release();

	printf("\n");
}

void print_input_mode(IDeckLinkInput* deckLinkInput, BMDVideoConnection connection, BMDVideoInputConversionMode conversion, BMDSupportedVideoModeFlags flags, IDeckLinkDisplayMode* displayMode, const char* nameSuffix, const char*& header)
{
	std::string				modeName;
	int						modeWidth;
	int						modeHeight;
	BMDTimeValue			frameRateDuration;
	BMDTimeScale			frameRateScale;
	bool					printedMode = false;
	int						unsupportedCount = 0;
	BMDDisplayMode			requestedMode = displayMode->GetDisplayMode();
	BMDDisplayMode			actualMode = requestedMode;

	// Print the supported pixel formats for this display mode
	for (auto pixelFromat: gPixelFormats)
	{
		dlbool_t supported;
		BMDDisplayMode retMode;

		if (deckLinkInput->DoesSupportVideoMode(connection, requestedMode, pixelFromat.first, conversion, flags, &retMode, &supported) == S_OK && supported)
		{
			if (retMode != bmdModeUnknown)
				actualMode = retMode;

			if (header)
			{
				printf("%s\n", header);
				header = NULL;
			}

			if (! printedMode)
			{
				// Mode properties
				mode_name(displayMode, modeName);
				modeWidth = displayMode->GetWidth();
				modeHeight = displayMode->GetHeight();
				displayMode->GetFrameRate(&frameRateDuration, &frameRateScale);
				
				printf(" %-20s %-12s \t %4d x %4d \t %7g FPS\t", modeName.c_str(), nameSuffix, modeWidth, modeHeight, (double)frameRateScale / (double)frameRateDuration);
				
				// Show pixel formats already found to be unsupported
				while (unsupportedCount--)
				{
					printf("%-15s", "------");
				}
				
				printedMode = true;
			}
			
			printf("%-15s", pixelFromat.second.c_str());
		}
		else
		{
			if (printedMode)
				printf("%-15s", "------");
			else
				unsupportedCount++;
		}
	}
	
	if (printedMode)
	{
		if (actualMode != requestedMode)
		{
			// Append converted-to mode
			if (deckLinkInput->GetDisplayMode(actualMode, &displayMode) == S_OK)
			{
				mode_name(displayMode, modeName);
				displayMode->Release();
				printf("* Converted to %s", modeName.c_str());
			}
		}

		printf("\n");
	}
}

void	print_input_modes_for_setup (IDeckLinkInput* deckLinkInput, BMDVideoConnection connection, BMDVideoInputConversionMode conversion, BMDSupportedVideoModeFlags flags, const char* nameSuffix, const char* header)
{
	IDeckLinkDisplayModeIterator*   displayModeIterator = NULL;
	IDeckLinkDisplayMode*           displayMode = NULL;
	HRESULT							result;
	
	// Obtain an IDeckLinkDisplayModeIterator to enumerate the display modes supported on input
	result = deckLinkInput->GetDisplayModeIterator(&displayModeIterator);
	if (result != S_OK)
	{
		fprintf(stderr, "Could not obtain the video input display mode iterator - result = %08x\n", result);
		return;
	}
	
	while (displayModeIterator->Next(&displayMode) == S_OK)
	{
		print_input_mode(deckLinkInput, connection, conversion, flags, displayMode, nameSuffix, header);
		displayMode->Release();
	}
	
	displayModeIterator->Release();
}

void	print_input_modes (IDeckLink* deckLink, uint32_t printFlags)
{
	IDeckLinkInput*					deckLinkInput = NULL;
	IDeckLinkProfileAttributes*		deckLinkAttributes = NULL;
	HRESULT							result;
	int64_t							ports;
	char							header[kMaxHeaderLength];

	// Query the DeckLink for its input interface
	result = deckLink->QueryInterface(IID_IDeckLinkInput, (void**)&deckLinkInput);
	if (result != S_OK)
	{
		fprintf(stderr, "Could not obtain the IDeckLinkInput interface - result = %08x\n", result);
		goto bail;
	}
	
	result = deckLink->QueryInterface(IID_IDeckLinkProfileAttributes, (void**)&deckLinkAttributes);
	if (result != S_OK)
	{
		fprintf(stderr, "Could not obtain the IDeckLinkAttributes interface - result = %08x\n", result);
		goto bail;
	}

	result = deckLinkAttributes->GetInt(BMDDeckLinkVideoInputConnections, &ports);

	if (result != S_OK)
		goto bail;


	printf("Supported video input display modes:\n");
	for (auto connection: gConnections)
	{
		if (connection.first == bmdVideoConnectionUnspecified || (ports & connection.first))
		{
			if (((printFlags & kPrintDisplayModeConnections) == 0) != (connection.first == bmdVideoConnectionUnspecified))
				continue;

			for (const auto& conversion: gInputConversions)
			{
				if (((printFlags & kPrintDisplayModeConversions) == 0) != (conversion.first == bmdNoVideoInputConversion))
					continue;

				if (conversion.first == bmdNoVideoInputConversion)
					snprintf(header, kMaxHeaderLength, "\n%s input:", connection.second.c_str());
				else
					snprintf(header, kMaxHeaderLength, "\n%s %s input:", connection.second.c_str(), conversion.second.c_str());

				print_input_modes_for_setup(deckLinkInput, connection.first, conversion.first, bmdSupportedVideoModeDefault, "", header);
				print_input_modes_for_setup(deckLinkInput, connection.first, conversion.first, bmdSupportedVideoModeDualStream3D, "3D", header);
			}
		}
	}
		
bail:
	if (deckLinkAttributes)
		deckLinkAttributes->Release();

	if (deckLinkInput)
		deckLinkInput->Release();

	printf("\n");
}

