/* -LICENSE-START-
 ** Copyright (c) 2014 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 <atomic>
#include "platform.h"

// Print out a DeckLink device's model and ID (if supported)
void PrintDeckLinkDetails (IDeckLink* deckLink)
{
    STRINGOBJ                   deckLinkModelNameString = NULL;
    HRESULT                     result = E_FAIL;
    INT64_SIGNED                deckLinkPersistentId = 0;
    std::string                 deviceName;
    IDeckLinkProfileAttributes* deckLinkAttributes = NULL;
    
    result = deckLink->GetModelName(&deckLinkModelNameString);

    if (result != S_OK)
    {
        fprintf(stderr, "\nDeckLink model information unavailable. Error: %08x", result);
        return;
    }
			
    // Obtain the deckLink device model name
    StringToStdString(deckLinkModelNameString, deviceName);
            
    printf("Model: %s ", deviceName.c_str());
    // Release the deckLink device model name string
    STRINGFREE(deckLinkModelNameString);
            
    result = deckLink->QueryInterface(IID_IDeckLinkProfileAttributes, (void**)&deckLinkAttributes);
    if (result == S_OK)
    {
        // Obtain the deckLink device's unique id, if this feature is supported.
        result = deckLinkAttributes->GetInt(BMDDeckLinkPersistentID, &deckLinkPersistentId);
        if ((result == S_OK) && (deckLinkPersistentId > 0))
        {
            printf("Id: %lld", deckLinkPersistentId);
        }
        deckLinkAttributes->Release();
    }
}


// The device discovery callback class
class DeckLinkDeviceDiscoveryCallback : public IDeckLinkDeviceNotificationCallback
{
    
private:
    // A list of connected DeckLink devices.
    std::list<IDeckLink*>      deckLinkList;
    
    // A mutex is required...
    MUTEX	mutex;

	std::atomic<ULONG>		m_refCount;

	~DeckLinkDeviceDiscoveryCallback(void)
	{
		// A mutex is not required, as UninstallNotifications() has been called guaranteeing that other callbacks will not occur.
		while (!deckLinkList.empty())
		{
			// Release any AddRef'ed DeckLink objects before the program exits.
			deckLinkList.back()->Release();
			deckLinkList.pop_back();
		}

		MutexDestroy(&mutex);
	}

public:
	DeckLinkDeviceDiscoveryCallback() : m_refCount(1)
    {
        MutexInit(&mutex);
    }

	HRESULT     STDMETHODCALLTYPE DeckLinkDeviceArrived (/* in */ IDeckLink* deckLink)
	{
        printf("\nDeckLink device arrived. (%p) ", deckLink);
        
        MutexLock(&mutex);
            PrintDeckLinkDetails(deckLink);
        
            // The IDeckLink object must be kept so that device removal notifications can be received.
            deckLink->AddRef();
            deckLinkList.push_back(deckLink);
        MutexUnlock(&mutex);
        
        printf("\n\n");
        return S_OK;
	}
	
	HRESULT     STDMETHODCALLTYPE DeckLinkDeviceRemoved (/* in */ IDeckLink* deckLink)
	{
        std::list<IDeckLink*>::iterator         elementToRemove;
        
        printf("\nDeckLink device removed. (%p)", deckLink);
        
        MutexLock(&mutex);

            // locate DeckLink object in list and erase it
            elementToRemove = std::find(deckLinkList.begin(), deckLinkList.end(), deckLink);
            if (elementToRemove != deckLinkList.end())
                deckLinkList.erase(elementToRemove);

            // Release the reference on the IDeckLink instance we took in DeckLinkDeviceArrived()
            deckLink->Release();

        MutexUnlock(&mutex);
        
        printf("\n\n");
        return S_OK;
	}
		
	HRESULT		STDMETHODCALLTYPE QueryInterface (REFIID iid, LPVOID *ppv)
	{
        return E_NOINTERFACE;
    }
	
    ULONG		STDMETHODCALLTYPE AddRef ()
    {
		return ++m_refCount;
	}
	
    ULONG		STDMETHODCALLTYPE Release ()
    {
		ULONG newRefValue = --m_refCount;

		if (newRefValue == 0)
			delete this;

		return newRefValue;
    }
    
};


int		main (int argc, char** argv)
{
	IDeckLinkDiscovery*                   deckLinkDiscovery = NULL;
    DeckLinkDeviceDiscoveryCallback*      deckLinkDeviceDiscoveryCallback = NULL;
    
	HRESULT						result;
    INT8_UNSIGNED               returnCode = 1;

    Initialize();
    
    result = GetDeckLinkDiscoveryInstance(&deckLinkDiscovery);
    
    if((result != S_OK) || (deckLinkDiscovery == NULL))
	{
        fprintf(stderr, "Could not get DeckLink discovery instance. The DeckLink drivers may not be installed.\n");
		goto bail;
	}
	
    // Create an instance of notification callback
    deckLinkDeviceDiscoveryCallback = new DeckLinkDeviceDiscoveryCallback();

    if (deckLinkDeviceDiscoveryCallback == NULL)
    {
		fprintf(stderr, "Could not create device discovery callback object\n");
        goto bail;
    }
    
    // Set the device arrival / removal callback object
	if(deckLinkDiscovery->InstallDeviceNotifications(deckLinkDeviceDiscoveryCallback) != S_OK)
	{
		fprintf(stderr, "Could not install device discovery callback object\n");
        goto bail;	
	}
    
    printf("Waiting for DeckLink devices.... Press <RETURN> to exit\n");

    getchar();
    
    printf("Exiting.\n");

    // Uninstall device notifications
	if(deckLinkDiscovery->UninstallDeviceNotifications() != S_OK)
	{
		fprintf(stderr, "Could not uninstall device discovery callback objects\n");
        goto bail;	
	}
	
    // return success
    returnCode = 0;         
    
	// Release resources
bail:

    // Release the DeckLink discovery object
    if(deckLinkDiscovery != NULL)
        deckLinkDiscovery->Release();

    // Release the DeckLink device notification callback object
    if(deckLinkDeviceDiscoveryCallback != NULL)
        deckLinkDeviceDiscoveryCallback->Release();
   
	return returnCode;
}
