/* -LICENSE-START-
 ** Copyright (c) 2016 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-
 */

#include "platform.h"

struct FourCCNameMapping
{
	INT32_UNSIGNED fourcc;
	const char* name;
};

// Display mode mappings
static FourCCNameMapping kDisplayModeMappings[] =
{
	{ bmdModeNTSC,			"525i59.94 NTSC" },
	{ bmdModeNTSC2398,		"525i47.96 NTSC" },
	{ bmdModePAL,			"625i50 PAL" },
	{ bmdModeNTSCp,			"525p29.97 NTSC" },
	{ bmdModePALp,			"625p25 PAL" },

	{ bmdModeHD1080p2398,	"1080p23.98" },
	{ bmdModeHD1080p24,		"1080p24" },
	{ bmdModeHD1080p25,		"1080p25" },
	{ bmdModeHD1080p2997,	"1080p29.97" },
	{ bmdModeHD1080p30,		"1080p30" },
	{ bmdModeHD1080i50,		"1080i50" },
	{ bmdModeHD1080i5994,	"1080i59.94" },
	{ bmdModeHD1080i6000,	"1080i60" },
	{ bmdModeHD1080p50,		"1080p50" },
	{ bmdModeHD1080p5994,	"1080p59.94" },
	{ bmdModeHD1080p6000,	"1080p60" },

	{ bmdModeHD720p50,		"720p50" },
	{ bmdModeHD720p5994,	"720p59.94" },
	{ bmdModeHD720p60,		"720p60" },

	{ bmdMode2k2398,		"2K 23.98p" },
	{ bmdMode2k24,			"2K 24p" },
	{ bmdMode2k25,			"2K 25p" },

	{ bmdMode2kDCI2398,		"2K DCI 23.98p" },
	{ bmdMode2kDCI24,		"2K DCI 24p" },
	{ bmdMode2kDCI25,		"2K DCI 25p" },

	{ bmdMode4K2160p2398,	"2160p23.98" },
	{ bmdMode4K2160p24,		"2160p24" },
	{ bmdMode4K2160p25,		"2160p25" },
	{ bmdMode4K2160p2997,	"2160p29.97" },
	{ bmdMode4K2160p30,		"2160p30" },
	{ bmdMode4K2160p50,		"2160p50" },
	{ bmdMode4K2160p5994,	"2160p59.94" },
	{ bmdMode4K2160p60,		"2160p60" },

	{ bmdMode4kDCI2398,		"4K DCI 23.98p" },
	{ bmdMode4kDCI24,		"4K DCI 24p" },
	{ bmdMode4kDCI25,		"4K DCI 25p" },

	{ 0, NULL }
};

// Pixel format mappings
static FourCCNameMapping kPixelFormatMappings[] =
{
	{ 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,	"12-bit RGBXLE" },
	{ bmdFormat10BitRGBX,	"10-bit RGBX" },
	{ bmdFormatH265,		"H.265" },

	{ 0, NULL }
};

// Duplex mode mappings
static FourCCNameMapping kDuplexModeMappings[] =
{
	{ bmdDuplexStatusFullDuplex,		"full-duplex" },
	{ bmdDuplexStatusHalfDuplex,		"half-duplex" },
	{ bmdDuplexStatusSimplex,			"simplex" },
	{ bmdDuplexStatusInactive,			"inactive" },

	{ 0, NULL }
};

static const char* getFourCCName(FourCCNameMapping* mappings, INT32_UNSIGNED fourcc)
{
	while (mappings->name != NULL)
	{
		if (mappings->fourcc == fourcc)
			return mappings->name;
		++mappings;
	}

	return "Unknown";
}

static void printHex(INT8_UNSIGNED* buffer, INT32_UNSIGNED size)
{
	for (INT32_UNSIGNED i = 0; i < size; )
	{
		printf("%02x ", buffer[i]);
		++i;
		if ((i % 16) == 0)
			printf("\n");
	}
}

static void printStatus(IDeckLinkStatus* deckLinkStatus, BMDDeckLinkStatusID statusId)
{
	HRESULT        result;
	INT64_SIGNED   intVal;
	BOOL           boolVal;
	INT8_UNSIGNED* bytesVal = NULL;
	INT32_UNSIGNED bytesSize = 0;

	switch (statusId)
	{
		case bmdDeckLinkStatusDetectedVideoInputMode:
		case bmdDeckLinkStatusDetectedVideoInputFlags:
		case bmdDeckLinkStatusCurrentVideoInputMode:
		case bmdDeckLinkStatusCurrentVideoInputPixelFormat:
		case bmdDeckLinkStatusCurrentVideoInputFlags:
		case bmdDeckLinkStatusCurrentVideoOutputMode:
		case bmdDeckLinkStatusCurrentVideoOutputFlags:
		case bmdDeckLinkStatusPCIExpressLinkWidth:
		case bmdDeckLinkStatusPCIExpressLinkSpeed:
		case bmdDeckLinkStatusLastVideoOutputPixelFormat:
		case bmdDeckLinkStatusReferenceSignalMode:
		case bmdDeckLinkStatusDuplexMode:
		case bmdDeckLinkStatusBusy:
			result = deckLinkStatus->GetInt(statusId, &intVal);
			break;

		case bmdDeckLinkStatusVideoInputSignalLocked:
		case bmdDeckLinkStatusReferenceSignalLocked:
			result = deckLinkStatus->GetFlag(statusId, &boolVal);
			break;

		case bmdDeckLinkStatusReceivedEDID:
			result = deckLinkStatus->GetBytes(statusId, NULL, &bytesSize);
			if (result == S_OK)
			{
				bytesVal = (INT8_UNSIGNED*)malloc(bytesSize * sizeof(INT8_UNSIGNED));
				if (bytesVal)
					result = deckLinkStatus->GetBytes(statusId, bytesVal, &bytesSize);
				else
					result = E_OUTOFMEMORY;
			}
			break;

		default:
			fprintf(stderr, "Unknown status ID: %08x", statusId);
			return;
	}

	if (FAILED(result))
	{
		/*
		 * Failed to retrieve the status value. Don't complain as this is
		 * expected for different hardware configurations.
		 *
		 * e.g.
		 * A device that doesn't support automatic mode detection will fail
		 * a request for bmdDeckLinkStatusDetectedVideoInputMode.
		 */
		return;
	}

	switch (statusId)
	{
		case bmdDeckLinkStatusDetectedVideoInputMode:
			if (result == S_FALSE)
				break;

			printf("%-40s %s\n", "Detected Video Input Mode:",
				getFourCCName(kDisplayModeMappings, (BMDDisplayMode)intVal));
			break;

		case bmdDeckLinkStatusDetectedVideoInputFlags:
			if (result == S_FALSE)
				break;

			printf("%-40s %08x\n", "Detected Video Input Flags:",
				(BMDDeckLinkVideoStatusFlags)intVal);
			break;

		case bmdDeckLinkStatusCurrentVideoInputMode:
			printf("%-40s %s\n", "Current Video Input Mode:",
				getFourCCName(kDisplayModeMappings, (BMDDisplayMode)intVal));
			break;

		case bmdDeckLinkStatusCurrentVideoInputFlags:
			printf("%-40s %08x\n", "Current Video Input Flags:",
				(BMDDeckLinkVideoStatusFlags)intVal);
			break;

		case bmdDeckLinkStatusCurrentVideoInputPixelFormat:
			printf("%-40s %s\n", "Current Video Input Pixel Format:",
				getFourCCName(kPixelFormatMappings, (BMDPixelFormat)intVal));
			break;

		case bmdDeckLinkStatusCurrentVideoOutputMode:
			printf("%-40s %s\n", "Current Video Output Mode:",
				getFourCCName(kDisplayModeMappings, (BMDDisplayMode)intVal));
			break;

		case bmdDeckLinkStatusCurrentVideoOutputFlags:
			printf("%-40s %08x\n", "Current Video Output Flags:",
				(BMDDeckLinkVideoStatusFlags)intVal);
			break;

		case bmdDeckLinkStatusPCIExpressLinkWidth:
			printf("%-40s %ux\n", "PCIe Link Width:",
				(unsigned)intVal);
			break;

		case bmdDeckLinkStatusPCIExpressLinkSpeed:
			printf("%-40s Gen. %u\n", "PCIe Link Speed:",
				(unsigned)intVal);
			break;

		case bmdDeckLinkStatusLastVideoOutputPixelFormat:
			printf("%-40s %s\n", "Last Video Output Pixel Format:",
				getFourCCName(kPixelFormatMappings, (BMDPixelFormat)intVal));
			break;

		case bmdDeckLinkStatusReferenceSignalMode:
			printf("%-40s %s\n", "Reference Signal Mode:",
				getFourCCName(kDisplayModeMappings, (BMDDisplayMode)intVal));
			break;

		case bmdDeckLinkStatusDuplexMode:
			printf("%-40s %s\n", "Duplex Mode:",
				   getFourCCName(kDuplexModeMappings, (BMDDuplexStatus)intVal));
			break;

		case bmdDeckLinkStatusBusy:
			printf("%-40s %08x\n", "Busy:",
				(BMDDeviceBusyState)intVal);
			break;

		case bmdDeckLinkStatusVideoInputSignalLocked:
			printf("%-40s %s\n", "Video Input Signal Locked:",
				boolVal ? "yes" : "no");
			break;

		case bmdDeckLinkStatusReferenceSignalLocked:
			printf("%-40s %s\n", "Reference Signal Locked:",
				boolVal ? "yes" : "no");
			break;

		case bmdDeckLinkStatusReceivedEDID:
			if (result == S_FALSE)
			{
				printf("%-40s %s\n", "EDID Received:", "Not Available");
				break;
			}
			printf("EDID Received:\n");
			printHex(bytesVal, bytesSize);
			break;

		default:
			break;
	}
}

class NotificationCallback : public IDeckLinkNotificationCallback
{
public:
	NotificationCallback(IDeckLinkStatus* deckLinkStatus)
	{
		m_deckLinkStatus = deckLinkStatus;
		m_deckLinkStatus->AddRef();
	}

	virtual ~NotificationCallback()
	{
		m_deckLinkStatus->Release();
	}

	// Implement the IDeckLinkNotificationCallback interface
	HRESULT STDMETHODCALLTYPE Notify(BMDNotifications topic, INT64_UNSIGNED param1, INT64_UNSIGNED param2)
	{
		// Check whether the notification we received as a status notification
		if (topic != bmdStatusChanged)
			return S_OK;

		// Print the updated status value
		BMDDeckLinkStatusID statusId = (BMDDeckLinkStatusID)param1;
		printStatus(m_deckLinkStatus, statusId);

		return S_OK;
	}

	// IUnknown needs only a dummy implementation
	HRESULT	STDMETHODCALLTYPE QueryInterface (REFIID iid, LPVOID *ppv)
	{
		return E_NOINTERFACE;
	}
	
	ULONG STDMETHODCALLTYPE AddRef()
	{
		return 1;
	}
	
	ULONG STDMETHODCALLTYPE Release()
	{
		return 1;
	}
private:
	IDeckLinkStatus*    m_deckLinkStatus;
};

int main(int argc, const char * argv[])
{
	IDeckLinkIterator*      deckLinkIterator     = NULL;
	IDeckLink*              deckLink             = NULL;
	IDeckLinkStatus*        deckLinkStatus       = NULL;
	IDeckLinkNotification*  deckLinkNotification = NULL;
	NotificationCallback*   notificationCallback = NULL;
	HRESULT                 result;
	
	Initialize();

	// 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 first DeckLink device
	result = deckLinkIterator->Next(&deckLink);
	if (result != S_OK)
	{
		fprintf(stderr, "Could not find DeckLink device - result = %08x\n", result);
		goto bail;
	}

	// Obtain the status interface
	result = deckLink->QueryInterface(IID_IDeckLinkStatus, (void**)&deckLinkStatus);
	if (result != S_OK)
	{
		fprintf(stderr, "Could not obtain the IDeckLinkStatus interface - result = %08x\n", result);
		goto bail;
	}

	// Print general status values
	printStatus(deckLinkStatus, bmdDeckLinkStatusBusy);
	printStatus(deckLinkStatus, bmdDeckLinkStatusDuplexMode);
	printStatus(deckLinkStatus, bmdDeckLinkStatusPCIExpressLinkWidth);
	printStatus(deckLinkStatus, bmdDeckLinkStatusPCIExpressLinkSpeed);

	// Print video input status values
	printStatus(deckLinkStatus, bmdDeckLinkStatusVideoInputSignalLocked);
	printStatus(deckLinkStatus, bmdDeckLinkStatusDetectedVideoInputMode);
	printStatus(deckLinkStatus, bmdDeckLinkStatusDetectedVideoInputFlags);
	printStatus(deckLinkStatus, bmdDeckLinkStatusCurrentVideoInputMode);
	printStatus(deckLinkStatus, bmdDeckLinkStatusCurrentVideoInputFlags);
	printStatus(deckLinkStatus, bmdDeckLinkStatusCurrentVideoInputPixelFormat);

	// Print video output status values
	printStatus(deckLinkStatus, bmdDeckLinkStatusCurrentVideoOutputMode);
	printStatus(deckLinkStatus, bmdDeckLinkStatusCurrentVideoOutputFlags);
	printStatus(deckLinkStatus, bmdDeckLinkStatusLastVideoOutputPixelFormat);
	printStatus(deckLinkStatus, bmdDeckLinkStatusReferenceSignalLocked);
	printStatus(deckLinkStatus, bmdDeckLinkStatusReferenceSignalMode);
	printStatus(deckLinkStatus, bmdDeckLinkStatusReceivedEDID);

	// Obtain the notification interface
	result = deckLink->QueryInterface(IID_IDeckLinkNotification, (void**)&deckLinkNotification);
	if (result != S_OK)
	{
		fprintf(stderr, "Could not obtain the IDeckLinkNotification interface - result = %08x\n", result);
		goto bail;
	}

	notificationCallback = new NotificationCallback(deckLinkStatus);
	if (notificationCallback == NULL)
	{
		fprintf(stderr, "Could not create notification callback object\n");
		goto bail;
	}

	// Register for notification changes
	result = deckLinkNotification->Subscribe(bmdStatusChanged, notificationCallback);
	if (result != S_OK)
	{
		fprintf(stderr, "Could not subscribe to the status change notification - result = %08x\n", result);
		goto bail;
	}

	// Wait until user presses Enter
	printf("Monitoring... Press <RETURN> to exit\n");
	
	getchar();
	
	printf("Exiting.\n");

	// Release resources
bail:

	// Release the notification interface
	if (deckLinkNotification != NULL)
	{
		if (notificationCallback != NULL)
			deckLinkNotification->Unsubscribe(bmdStatusChanged, notificationCallback);
		deckLinkNotification->Release();
	}

	// Release the notification callback
	if (notificationCallback != NULL)
		notificationCallback->Release();

	// Release the status interface
	if (deckLinkStatus)
		deckLinkStatus->Release();

	// Release the DeckLink object
	if (deckLink)
		deckLink->Release();

	// Release the DeckLink iterator
	if (deckLinkIterator)
		deckLinkIterator->Release();

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