/* -LICENSE-START-
** Copyright (c) 2011 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 "stdafx.h"
#include "StreamingPreview.h"
#include "StreamingPreviewDlg.h"

#include "DeckLinkAPI_h.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// CStreamingPreviewDlg dialog




CStreamingPreviewDlg::CStreamingPreviewDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CStreamingPreviewDlg::IDD, pParent), m_refCount(1)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

	m_streamingDiscovery = NULL;
	m_streamingDevice = NULL;
	m_streamingDeviceInput = NULL;

	m_previewWindow = NULL;
	m_decoder = NULL;
}

CStreamingPreviewDlg::~CStreamingPreviewDlg()
{
}

void CStreamingPreviewDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_BUTTON_START_PREVIEW, m_startButton);
	DDX_Control(pDX, IDC_STATIC_CONFIGBOX, m_configBoxStatic);
	DDX_Control(pDX, IDC_COMBO_INPUT_MODE, m_videoInputCombo);
	DDX_Control(pDX, IDC_COMBO_ENCODING_PRESET, m_videoEncodingCombo);
}

BEGIN_MESSAGE_MAP(CStreamingPreviewDlg, CDialog)
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	//}}AFX_MSG_MAP
	ON_BN_CLICKED(IDC_BUTTON_START_PREVIEW, &CStreamingPreviewDlg::OnBnClickedOk)
	ON_CBN_SELENDOK(IDC_COMBO_INPUT_MODE, &CStreamingPreviewDlg::OnInputModeChanged)
	ON_MESSAGE(WM_PREVIEWSTART, &CStreamingPreviewDlg::StartPreview)
	ON_MESSAGE(WM_PREVIEWSTOP, &CStreamingPreviewDlg::StopPreview)
	ON_WM_CLOSE()
END_MESSAGE_MAP()


// CStreamingPreviewDlg message handlers

BOOL CStreamingPreviewDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon

	m_previewWindow = NULL;

	// Presume no devices to begin with:
	UpdateUIForNoDevice();

	// Initialise Blackmagic Streaming API
	HRESULT						result;

	try
	{
		m_decoder = new DecoderMF();
	}
	catch (...)
	{
		MessageBox(_T("This application requires Media Foundation and Windows 7"), _T("Error"));
		goto bail;
	}
	
	result = CoCreateInstance(CLSID_CBMDStreamingDiscovery, NULL, CLSCTX_ALL, IID_IBMDStreamingDiscovery, (void**)&m_streamingDiscovery);
	if (FAILED(result))
	{
		MessageBox(_T("This application requires the Blackmagic Streaming drivers installed.\nPlease install the Blackmagic Streaming drivers to use the features of this application."), _T("Error"));
		goto bail;
	}

	// Note: at this point you may get device notification messages!
	result = m_streamingDiscovery->InstallDeviceNotifications(this);
	if (FAILED(result))
	{
		MessageBox(_T("Failed to install device notifications for the Blackmagic Streaming devices"), _T("Error"));
		goto bail;
	}

	// return TRUE unless you set the focus to a control
	return TRUE;

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

	return FALSE;
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CStreamingPreviewDlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // device context for painting

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// Center icon in client rectangle
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// Draw the icon
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialog::OnPaint();
	}
}

void CStreamingPreviewDlg::OnClose()
{
	if (m_decoder != NULL)
	{
		m_decoder->SetPreviewWindow(NULL);
		delete m_decoder;
		m_decoder = NULL;
	}

	delete m_previewWindow;
	m_previewWindow = NULL;

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

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

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

	CDialog::OnClose();
}

// The system calls this function to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CStreamingPreviewDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}

void CStreamingPreviewDlg::OnBnClickedOk()
{
	OutputDebugString(_T("OnBnClickedOk\n"));

	if (m_streamingDeviceInput)
	{
		if (m_deviceMode == bmdStreamingDeviceEncoding)
		{
			m_streamingDeviceInput->StopCapture();
		}
		else if (m_inputMode != bmdModeUnknown)
		{
			IBMDStreamingVideoEncodingMode* encodingMode = (IBMDStreamingVideoEncodingMode*)m_videoEncodingCombo.GetItemDataPtr(m_videoEncodingCombo.GetCurSel());
			m_streamingDeviceInput->SetVideoEncodingMode(encodingMode);
			m_streamingDeviceInput->StartCapture();
		}
	}
}

afx_msg LRESULT CStreamingPreviewDlg::StartPreview(WPARAM, LPARAM)
{
	if (m_previewWindow)
		return 0;

	IBMDStreamingVideoEncodingMode* encodingMode = (IBMDStreamingVideoEncodingMode*)m_videoEncodingCombo.GetItemDataPtr(m_videoEncodingCombo.GetCurSel());

	int width, height;
	width = encodingMode->GetDestWidth();
	height = encodingMode->GetDestHeight();

	m_previewWindow = PreviewWindow::CreateInstance(PreviewWindowClosedFunction, this);
	if (m_previewWindow)
	{
		m_previewWindow->SetWindowSize(width, height);
		m_decoder->SetPreviewWindow(m_previewWindow);
	}

	return 0;
}

afx_msg LRESULT CStreamingPreviewDlg::StopPreview(WPARAM, LPARAM)
{
	m_decoder->SetPreviewWindow(NULL);
	delete m_previewWindow;
	m_previewWindow = NULL;
	return 0;
}

void CStreamingPreviewDlg::OnInputModeChanged()
{
	if (m_streamingDevice == NULL)
		return;

	UpdateEncodingPresetsUIForInputMode();
}

void CStreamingPreviewDlg::UpdateUIForModeChanges()
{
	CString status = _T(" (unknown)");

	switch (m_deviceMode)
	{
		case bmdStreamingDeviceIdle:
			status = _T(" (idle)");
			break;
		case bmdStreamingDeviceEncoding:
			status = _T(" (encoding)");
			break;
		case bmdStreamingDeviceStopping:
			status = _T(" (stopping)");
			break;
		default:
			return;
	}

	BSTR modelName;
	if (m_streamingDevice->GetModelName(&modelName) != S_OK)
		return;

	CString modelNameCString(modelName);
	CString displayName = _T("Device: ") + modelNameCString + status;
	m_configBoxStatic.SetWindowText(displayName);
	SysFreeString(modelName);

	bool enablePresets = m_deviceMode == bmdStreamingDeviceIdle && m_inputMode != bmdModeUnknown;
	m_videoEncodingCombo.EnableWindow(enablePresets);
	
	bool enableStartStop = (m_deviceMode == bmdStreamingDeviceIdle || m_deviceMode == bmdStreamingDeviceEncoding) && m_inputMode != bmdModeUnknown;
	m_startButton.EnableWindow(enableStartStop);

	bool start = m_deviceMode != bmdStreamingDeviceEncoding;
	m_startButton.SetWindowText(start ? _T("Start Preview") : _T("Stop Preview"));

	// Preview Window. These operations must be done by the main GUI thread so that the preview window takes part in the same GUI event queue
	if (m_deviceMode == bmdStreamingDeviceEncoding)
	{
		if (m_inputMode != bmdModeUnknown)
		{
			// If the app opened and an encoding session was already running then the BMDStreamingAPI won't give us packets from the stream until
			// we tell the API to start.
			m_streamingDeviceInput->StartCapture();

			this->PostMessage(WM_PREVIEWSTART);
		}
	}
	else
		this->PostMessage(WM_PREVIEWSTOP);
}

void CStreamingPreviewDlg::UpdateUIForNewDevice()
{
	BSTR modelName;
	if (m_streamingDevice->GetModelName(&modelName) != S_OK)
	{
		UpdateUIForNoDevice();
		return;
	}

	CString modelNameCString(modelName);
	CString displayName = _T("Device: ") + modelNameCString;
	m_configBoxStatic.SetWindowText(displayName);
	SysFreeString(modelName);

	m_videoInputCombo.ResetContent();
	int index = m_videoInputCombo.AddString(_T("No Input"));
	m_videoInputCombo.SetItemData(index, bmdModeUnknown);

	EncodingPresetsRemoveItems();

	m_startButton.SetWindowText(_T("Start Preview"));

	// Add video input modes:
	IDeckLinkDisplayModeIterator* inputModeIterator;
	if (FAILED(m_streamingDeviceInput->GetVideoInputModeIterator(&inputModeIterator)))
	{
		MessageBox(_T("Failed to get input mode iterator"), _T("error"));
		UpdateUIForNoDevice();
		return;
	}

	BMDDisplayMode currentInputModeValue;
	if (FAILED(m_streamingDeviceInput->GetCurrentDetectedVideoInputMode(&currentInputModeValue)))
	{
		MessageBox(_T("Failed to get current detected input mode"), _T("error"));
		UpdateUIForNoDevice();
		return;
	}

	IDeckLinkDisplayMode* inputMode;
	while (inputModeIterator->Next(&inputMode) == S_OK)
	{
		BSTR modeName;
		if (inputMode->GetName(&modeName) != S_OK)
		{
			inputMode->Release();
			inputModeIterator->Release();
			UpdateUIForNoDevice();
			return;
		}

		CString modeNameCString(modeName);
		index = m_videoInputCombo.AddString(modeNameCString);
		m_videoInputCombo.SetItemData(index, inputMode->GetDisplayMode());
		if (inputMode->GetDisplayMode() == currentInputModeValue)
			m_videoInputCombo.SetCurSel(index);
		SysFreeString(modeName);
		
		inputMode->Release();
	}

	inputModeIterator->Release();

	OnInputModeChanged();
}

void CStreamingPreviewDlg::UpdateUIForNoDevice()
{
	m_deviceMode = bmdStreamingDeviceUnknown;
	m_inputMode = bmdModeUnknown;

	m_configBoxStatic.SetWindowText(_T("No device detected"));

	m_videoInputCombo.ResetContent();
	int index = m_videoInputCombo.AddString(_T("No Input"));
	m_videoInputCombo.SetItemData(index, bmdModeUnknown);
	m_videoInputCombo.EnableWindow(FALSE);

	EncodingPresetsRemoveItems();
	index = m_videoEncodingCombo.AddString(_T("No Input"));
	m_videoEncodingCombo.SetItemDataPtr(index, NULL);
	m_videoEncodingCombo.EnableWindow(FALSE);

	m_startButton.EnableWindow(FALSE);
}

void CStreamingPreviewDlg::UpdateEncodingPresetsUIForInputMode()
{
	BMDDisplayMode inputMode = BMDDisplayMode(m_videoInputCombo.GetItemData(m_videoInputCombo.GetCurSel()));
	EncodingPresetsRemoveItems();

	IBMDStreamingVideoEncodingModePresetIterator* presetIterator;
	
	if (inputMode != bmdModeUnknown && SUCCEEDED(m_streamingDeviceInput->GetVideoEncodingModePresetIterator(inputMode, &presetIterator)))
	{
		IBMDStreamingVideoEncodingMode* encodingMode = NULL;
		BSTR encodingModeName;
		
		while (presetIterator->Next(&encodingMode) == S_OK)
		{
			encodingMode->GetName(&encodingModeName);
			CString encodingModeNameCString(encodingModeName);
			SysFreeString(encodingModeName);
			
			// Add this item to the video input poup menu
			int newIndex = m_videoEncodingCombo.AddString(encodingModeNameCString);
			m_videoEncodingCombo.SetItemDataPtr(newIndex, encodingMode);

			// We don't release the object here, as we hold the reference
			// in the combo box.
		}

		presetIterator->Release();
	}

	m_videoEncodingCombo.SetCurSel(0);
}

void CStreamingPreviewDlg::EncodingPresetsRemoveItems()
{
	int currentCount = m_videoEncodingCombo.GetCount();

	for (int i = 0; i < currentCount; i++)
	{
		IBMDStreamingVideoEncodingMode* encodingMode = (IBMDStreamingVideoEncodingMode*)m_videoEncodingCombo.GetItemDataPtr(i);

		if (encodingMode != NULL)
			encodingMode->Release();
	}

	m_videoEncodingCombo.ResetContent();
}

void CStreamingPreviewDlg::PreviewWindowClosedFunction(void* ctx, PreviewWindow* previewWindow)
{
	OutputDebugString(_T("PreviewWindow closed...\n"));
	CStreamingPreviewDlg* self = (CStreamingPreviewDlg*)ctx;
	self->OnPreviewWindowClosed(previewWindow);
}

void CStreamingPreviewDlg::OnPreviewWindowClosed(PreviewWindow* previewWindow)
{
	if (previewWindow != m_previewWindow)
		return;

	if (m_streamingDeviceInput && m_deviceMode == bmdStreamingDeviceEncoding)
	{
		this->PostMessage(WM_CLOSE);
	}
}

HRESULT CStreamingPreviewDlg::QueryInterface(REFIID iid, LPVOID* ppv)
{
	HRESULT result = E_NOINTERFACE;

	if (ppv == NULL)
		return E_POINTER;
	*ppv = NULL;
	
	if (iid == IID_IUnknown)
	{
		*ppv = static_cast<IUnknown*>(static_cast<IBMDStreamingDeviceNotificationCallback*>(this));
		AddRef();
		result = S_OK;
	}
	else if (iid == IID_IBMDStreamingDeviceNotificationCallback)
	{
		*ppv = static_cast<IBMDStreamingDeviceNotificationCallback*>(this);
		AddRef();
		result = S_OK;
	}
	else if (iid == IID_IBMDStreamingH264InputCallback)
	{
		*ppv = static_cast<IBMDStreamingH264InputCallback*>(this);
		AddRef();
		result = S_OK;
	}
	
	return result;	
}

ULONG CStreamingPreviewDlg::AddRef()
{
	return InterlockedIncrement((LONG*)&m_refCount);
}

ULONG CStreamingPreviewDlg::Release()
{
	ULONG			newRefValue;

	newRefValue = InterlockedDecrement((LONG*)&m_refCount);
	if (newRefValue == 0)
	{
		delete this;
		return 0;
	}

	return newRefValue;
}

HRESULT CStreamingPreviewDlg::StreamingDeviceArrived(IDeckLink* device)
{
	HRESULT			result;
	// These messages will happen on the main loop as a result
	// of the message pump.

	// Check we don't already have a device.
	if (m_streamingDevice != NULL)
	{
		return S_OK;
	}

	// See if it can do input:
	result = device->QueryInterface(IID_IBMDStreamingDeviceInput, (void**)&m_streamingDeviceInput);
	if (FAILED(result))
	{
		// This device doesn't support input. We can ignore this device.
		return S_OK;
	}

	// Ok, we're happy with this device, hold a reference to the device (we
	// also have a reference held from the QueryInterface, too).
	m_streamingDevice = device;
	m_streamingDevice->AddRef();

	// Now install our callbacks. To do this, we must query our own delegate
	// to get it's IUnknown interface, and pass this to the device input interface.
	// It will then query our interface back to a IBMDStreamingH264InputCallback,
	// if that's what it wants.
	// Note, although you may be tempted to cast directly to an IUnknown, it's
	// not particular safe, and is invalid COM.
	IUnknown* ourCallbackDelegate;
	this->QueryInterface(IID_IUnknown, (void**)&ourCallbackDelegate);
	//
	result = m_streamingDeviceInput->SetCallback(ourCallbackDelegate);
	//
	// Finally release ourCallbackDelegate, since we created a reference to it
	// during QueryInterface. The device will hold its own reference.
	ourCallbackDelegate->Release();

	if (FAILED(result))
	{
		m_streamingDevice->Release();
		m_streamingDeviceInput->Release();
		m_streamingDevice = NULL;
		m_streamingDeviceInput = NULL;

		return S_OK;
	}

	UpdateUIForNewDevice();

	return S_OK;
}

HRESULT CStreamingPreviewDlg::StreamingDeviceRemoved(IDeckLink* device)
{
	// These messages will happen on the main loop as a result
	// of the message pump.

	// We only care about removal of the device we are using
	if (device != m_streamingDevice)
		return S_OK;

	m_streamingDeviceInput->SetCallback(NULL);
	m_streamingDeviceInput->Release();
	m_streamingDevice->Release();

	m_streamingDeviceInput = NULL;
	m_streamingDevice = NULL;

	this->PostMessage(WM_PREVIEWSTOP);
	UpdateUIForNoDevice();

	return S_OK;
}

HRESULT CStreamingPreviewDlg::StreamingDeviceModeChanged(IDeckLink* device, BMDStreamingDeviceMode mode)
{
	if (mode == m_deviceMode)
		return S_OK;

	m_deviceMode = mode;

	UpdateUIForModeChanges();

	return S_OK;
}

HRESULT CStreamingPreviewDlg::StreamingDeviceFirmwareUpdateProgress(IDeckLink* device, unsigned char percent)
{
	return S_OK;
}

HRESULT CStreamingPreviewDlg::H264NALPacketArrived(IBMDStreamingH264NALPacket* nalPacket)
{
	if (m_decoder)
		m_decoder->HandleNALPacket(nalPacket);

	return S_OK;
}

HRESULT CStreamingPreviewDlg::H264AudioPacketArrived(IBMDStreamingAudioPacket* audioPacket)
{
	return S_OK;
}

HRESULT CStreamingPreviewDlg::MPEG2TSPacketArrived(IBMDStreamingMPEG2TSPacket* mpeg2TSPacket)
{
	return S_OK;
}

HRESULT CStreamingPreviewDlg::H264VideoInputConnectorScanningChanged(void)
{
	return E_NOTIMPL;
}

HRESULT CStreamingPreviewDlg::H264VideoInputConnectorChanged(void)
{
	return E_NOTIMPL;
}

HRESULT CStreamingPreviewDlg::H264VideoInputModeChanged(void)
{
	if (m_streamingDeviceInput->GetCurrentDetectedVideoInputMode(&m_inputMode) != S_OK)
		MessageBox(_T("Failed to get current detected input mode"), _T("error"));
	else
	{
		for (int i = 0; i < m_videoInputCombo.GetCount(); i++)
		{
			if (m_videoInputCombo.GetItemData(i) == m_inputMode)
			{
				m_videoInputCombo.SetCurSel(i);
				break;
			}
		}

		OnInputModeChanged();
	}

	if (m_inputMode == bmdModeUnknown)
	{
		int index = m_videoInputCombo.AddString(_T("No Input"));
		m_videoInputCombo.SetItemData(index, bmdModeUnknown);
	}

	UpdateUIForModeChanges();

	return S_OK;
}
