//=============================================================================
// Copyright (c) 2001-2021 FLIR Systems, Inc. All Rights Reserved.
//
// This software is the confidential and proprietary information of FLIR
// Integrated Imaging Solutions, Inc. ("Confidential Information"). You
// shall not disclose such Confidential Information and shall use it only in
// accordance with the terms of the license agreement you entered into
// with FLIR Integrated Imaging Solutions, Inc. (FLIR).
//
// FLIR MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
// SOFTWARE, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE, OR NON-INFRINGEMENT. FLIR SHALL NOT BE LIABLE FOR ANY DAMAGES
// SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
// THIS SOFTWARE OR ITS DERIVATIVES.
//=============================================================================

/**
 *	@example EnumerationEvents.cpp
 *
 *	@brief EnumerationEvents.cpp explores arrival and removal events on
 *	interfaces and the system. It relies on information provided in the
 *	Enumeration, Acquisition, and NodeMapInfo examples.
 *
 *	It can also be helpful to familiarize yourself with the NodeMapCallback
 *	example, as nodemap callbacks follow the same general procedure as
 *	events, but with a few less steps.
 *
 *	This example creates two user-defined classes: InterfaceEventHandlerImpl and
 *	SystemEventHandlerImpl. These child classes allow the user to define properties,
 *	parameters, and the event itself while the parent classes - DeviceArrivalEventHandler,
 *	DeviceRemovalEventHandler, and InterfaceEventHandler - allow the child classes to interface
 *	with Spinnaker.
 */

#include "Spinnaker.h"
#include "SpinGenApi/SpinnakerGenApi.h"
#include <iostream>
#include <sstream>
#if _MSC_VER != 1600
#include <mutex>
#endif

using namespace Spinnaker;
using namespace Spinnaker::GenApi;
using namespace Spinnaker::GenICam;
using namespace std;

// This class defines the properties and functions for device arrivals and removals
// on an interface. Take special note of the signatures of the OnDeviceArrival()
// and OnDeviceRemoval() methods. Also, enumeration event handlers must inherit at least
// one of the three event types - DeviceArrivalEventHandler, DeviceRemovalEventHandler, and InterfaceEventHandler
// if they are to be registered to interfaces, the system, or both.
class InterfaceEventHandlerImpl : public InterfaceEventHandler
{
  public:
    //
    // Set the constructor and destructor
    //
    // *** NOTES ***
    // When constructing a generic InterfaceEventHandler to be registered to the system,
    // the handler will not have knowledge of which interface triggered the event callbacks.
    // On the other hand, InterfaceEventHandlerImpl does not need knowledge about the system if we
    // are constructing it to be registered to a specific interface.
    //
    InterfaceEventHandlerImpl(SystemPtr system)
        : m_system(system), m_interface(nullptr), m_interfaceID(""), m_registerToSystem(true){};

    InterfaceEventHandlerImpl(InterfacePtr iface, std::string interfaceID)
        : m_system(nullptr), m_interface(iface), m_interfaceID(interfaceID), m_registerToSystem(false){};

    ~InterfaceEventHandlerImpl(){};

    // Helper function to print the number of devices on an interface event registered to the system.
    // Note: Using strings in event print statements prevents text from overlapping in the console output
    void PrintGenericHandlerMessage(const unsigned long long deviceCount)
    {
        cout << "Generic interface event handler:\n";
        const bool singular = deviceCount == 1;
        const string genericMsg = "\tThere " + string(singular ? "is " : "are ") + to_string(deviceCount) +
                                  string(singular ? " device " : " devices ") + "on the system.\n";
        cout << genericMsg << endl;
    }

    // This function defines the arrival event handler on an interface. It prints out
    // the device serial number of the camera arriving and the interface
    // number. The argument is the serial number of the camera that triggered
    // the arrival event.
    // If the event handler was constructed to be registered to the system as a generic
    // InterfaceEventHandler, then we just retrieve the number of cameras
    // currently connected on the system and print it out.
    void OnDeviceArrival(uint64_t deviceSerialNumber)
    {
        if (m_registerToSystem)
        {
            const auto deviceCount = static_cast<unsigned long long>(m_system->GetCameras().GetSize());
            PrintGenericHandlerMessage(deviceCount);
        }
        else
        {
            cout << "Interface event handler:\n";
            const std::string arrivalMsg = "\tDevice " + std::to_string(deviceSerialNumber) +
                                           " has arrived on interface '" + m_interfaceID + "'.\n";
            cout << arrivalMsg << endl;
        }
    }

    // This function defines removal event handlers on an interface. It prints out the
    // device serial number of the camera being removed and the interface
    // number. The argument is the serial number of the camera that triggered
    // the removal event.
    // If the event handler was constructed to be registered to the system as a generic
    // InterfaceEventHandler, then we just retrieve the number of cameras
    // currently connected on the system and print it out.
    void OnDeviceRemoval(uint64_t deviceSerialNumber)
    {
        if (m_registerToSystem)
        {
            try
            {
                // At this point the interface that the camera is connected to
                // may have been removed as well, so we need to handle
                // potential exceptions when calling GetCameras().
                const auto deviceCount = static_cast<unsigned long long>(m_system->GetCameras().GetSize());
                PrintGenericHandlerMessage(deviceCount);
            }
            catch (Spinnaker::Exception& e)
            {
                cout << "Error updating cameras: " << e.what() << endl;
            }
        }
        else
        {
            cout << "Interface event handler:\n";
            const std::string removalMsg = "\tDevice " + std::to_string(deviceSerialNumber) +
                                           " was removed from interface '" + m_interfaceID + "'.\n";
            cout << removalMsg << endl;
        }
    }

    // This function returns the interface ID that the interface event handler is bound to.
    std::string GetInterfaceId()
    {
        return m_interfaceID;
    }

  private:
    SystemPtr m_system;
    InterfacePtr m_interface;
    std::string m_interfaceID;

    bool m_registerToSystem;
};

// This class defines the properties and methods for interface arrivals and removals
// on the system. Take special note of the signatures of the OnInterfaceArrival()
// and OnInterfaceRemoval() methods. Interface enumeration event handlers must inherit from
// SystemEventHandler.
class SystemEventHandlerImpl : public SystemEventHandler
{
  public:
    SystemEventHandlerImpl(SystemPtr system) : m_system(system)
    {
#if _MSC_VER == 1600
        // Initialize the mutex for Visual Studio 2010
        eventHandlersMutex = CreateMutex(NULL, false, NULL);
#endif
    };

    ~SystemEventHandlerImpl(){};

    // Helper function to lock the eventhandler mutex as std::mutex is not available for vs 2010
    void LockEventHandlerMutex()
    {
#if _MSC_VER == 1600
        WaitForSingleObject(eventHandlersMutex, INFINITE);
#else
        eventHandlersMutex.lock();
#endif
    }

    // Helper function to unlock the eventhandler mutex as std::mutex is not available for vs 2010
    void UnlockEventHandlerMutex()
    {
#if _MSC_VER == 1600
        ReleaseMutex(eventHandlersMutex);
#else
        eventHandlersMutex.unlock();
#endif
    }

    // This method defines the interface arrival event on the system.
    // It first prints the ID of the arriving interface, then
    // registers the interface event on the newly arrived interface.
    //
    // *** NOTES ***
    // Only arrival events for GEV interfaces are currently supported.
    void OnInterfaceArrival(std::string interfaceID)
    {
        cout << "System event handler:\n";
        const string arrivalMsg = "\tInterface '" + interfaceID + "' has arrived on the system.\n";
        cout << arrivalMsg << endl;

        // UpdateInterfaceList() only updates newly arrived or newly removed interfaces.
        // In particular, after this call:
        //
        // - Any pre-existing interfaces will still be valid.
        // - Any pointers to newly removed interfaces will be invalid.
        //
        // *** NOTES ***
        // - Invalid pointers will be re-validated if the interface comes back (arrives) with the same interface ID.
        //   If the interface ID changes, you must get a new pointer from the updated interface list.
        //
        // - Interface indices used to access an interface with GetInterfaces() may change after updating the interface
        //   list. The interface at a particular index cannot be expected to remain at that index after calling
        //   UpdateInterfaceList().
        m_system->UpdateInterfaceList();

        InterfaceList interfaceList = m_system->GetInterfaces();
        unsigned int numInterfaces = interfaceList.GetSize();

        // Here we go through the new interface list to find the arrived interface
        // and display the cameras connected to the new interface.
        for (unsigned int ifaceIdx = 0; ifaceIdx < numInterfaces; ifaceIdx++)
        {
            // Select interface
            InterfacePtr pInterface = interfaceList.GetByIndex(ifaceIdx);
            INodeMap& nodeMap = pInterface->GetTLNodeMap();

            const CStringPtr interfaceIDNode = nodeMap.GetNode("InterfaceID");
            // Ensure the node is valid
            if (!IsAvailable(interfaceIDNode) || !IsReadable(interfaceIDNode))
            {
                continue;
            }

            if (strcmp(interfaceID.c_str(), interfaceIDNode->GetValue().c_str()) != 0)
            {
                continue;
            }

            CameraList cameraList = pInterface->GetCameras();
            unsigned int numCameras = cameraList.GetSize();
            for (unsigned int camIdx = 0; camIdx < numCameras; camIdx++)
            {
                CameraPtr pCam = cameraList.GetByIndex(camIdx);
                INodeMap& nodeMapTLDevice = pCam->GetTLDeviceNodeMap();
                CStringPtr ptrStringSerial = nodeMapTLDevice.GetNode("DeviceSerialNumber");
                if (IsAvailable(ptrStringSerial) && IsReadable(ptrStringSerial))
                {
                    gcstring deviceSerialNumber = ptrStringSerial->GetValue();
                    const string deviceMsg = "\tDevice " + string(deviceSerialNumber) + " is connected to interface '" +
                                             interfaceID + "'.\n";
                    cout << deviceMsg << endl;
                }
            }

            // Create interface event
            {
                LockEventHandlerMutex();

                try
                {
                    shared_ptr<InterfaceEventHandlerImpl> interfaceEventHandler =
                        shared_ptr<InterfaceEventHandlerImpl>(new InterfaceEventHandlerImpl(pInterface, interfaceID));
                    m_interfaceEventHandlers.push_back(interfaceEventHandler);

                    // Register interface event
                    pInterface->RegisterEventHandler(*interfaceEventHandler);
                    const string registerMsg = "Event handler registered to interface '" + interfaceID + "'...\n";
                    cout << registerMsg << endl;
                }
                catch (exception& e)
                {
                    cout << "Error registering interface event handler to '" << interfaceID << "' :" << e.what()
                         << endl;
                }

                UnlockEventHandlerMutex();
            }
        }
    }

    // This method defines the interface removal event on the system.
    // It prints the ID of the interface removed.
    //
    // *** NOTES ***
    // Only removal events for GEV interfaces are currently supported.
    void OnInterfaceRemoval(std::string interfaceID)
    {
        cout << "System event handler:\n";
        const string removalMsg = "\tInterface '" + interfaceID + "' was removed from the system.\n";
        cout << removalMsg << endl;

        // Interface indices used to access an interface with GetInterfaces() may change after updating the interface
        // list. The interface at a particular index cannot be expected to remain at that index after calling
        // UpdateInterfaceList().
        m_system->UpdateInterfaceList();

        // Find the event handler that was registered to the removed interface and delete it.
        // Interface events are automatically unregistered when the interface is removed so it is not necessary to
        // manually unregister them.
        {
            LockEventHandlerMutex();
            try
            {
                unsigned int handlerIdx = 0;
                for (handlerIdx = 0; handlerIdx < m_interfaceEventHandlers.size(); handlerIdx++)
                {
                    if (m_interfaceEventHandlers[handlerIdx]->GetInterfaceId() == interfaceID)
                    {
                        m_interfaceEventHandlers.erase(m_interfaceEventHandlers.begin() + handlerIdx);
                        break;
                    }
                }
            }
            catch (exception& e)
            {
                cout << "Error erasing event hander from interface '" << interfaceID << "' :" << e.what() << endl;
            }

            UnlockEventHandlerMutex();
        }
    }

    void RegisterInterfaceEventToSystem()
    {
        if (!m_interfaceEventHandlerOnSystem)
        {
            //
            // Create interface event handler for the system
            //
            // *** NOTES ***
            // The InterfaceEventHandler has been constructed to accept a system object in
            // order to print the number of cameras on the system.
            //
            m_interfaceEventHandlerOnSystem =
                shared_ptr<InterfaceEventHandlerImpl>(new InterfaceEventHandlerImpl(m_system));
        }

        //
        // Register interface event handler for the system
        //
        // *** NOTES ***
        // Arrival, removal, and interface event handlers can all be registered to
        // interfaces or the system. Do not think that interface event handlers can only be
        // registered to an interface. An interface event is merely a combination
        // of an arrival and a removal event.
        // Only arrival and removal events for GEV interfaces are currently supported.
        //
        // *** LATER ***
        // Arrival, removal, and interface event handlers must all be unregistered manually.
        // This must be done prior to releasing the system and while they are still
        // in scope.
        //
        m_system->RegisterInterfaceEventHandler(*m_interfaceEventHandlerOnSystem);
        cout << "Interface event handler registered on the system..." << endl;
    }

    void UnregisterInterfaceEventFromSystem()
    {
        //
        // Unregister interface event handler from system object
        //
        // *** NOTES ***
        // It is important to unregister all arrival, removal, and interface event handlers
        // registered to the system.
        //
        if (m_interfaceEventHandlerOnSystem)
        {
            m_system->UnregisterInterfaceEventHandler(*m_interfaceEventHandlerOnSystem);

            cout << "Interface event handler unregistered from system..." << endl;

            m_interfaceEventHandlerOnSystem = shared_ptr<InterfaceEventHandlerImpl>();
        }
    }

    void RegisterAllInterfaceEvents()
    {
        {
            LockEventHandlerMutex();

            if (!m_interfaceEventHandlers.empty())
            {
                m_interfaceEventHandlers.clear();
            }

            UnlockEventHandlerMutex();
        }

        InterfaceList interfaceList = m_system->GetInterfaces();
        unsigned int numInterfaces = interfaceList.GetSize();

        //
        // Create and register interface event handler to each interface
        //
        // *** NOTES ***
        // The process of event handler creation and registration on interfaces is similar
        // to the process of event handler creation and registration on the system. The
        // class for interfaces has been constructed to accept an interface and an
        // interface number (this is just to separate the interfaces).
        //
        // *** LATER ***
        // Arrival, removal, and interface event handlers must all be unregistered manually.
        // This must be done prior to releasing the system and while they are still
        // in scope.
        //
        for (unsigned int i = 0; i < numInterfaces; i++)
        {
            // Select interface
            InterfacePtr pInterface = interfaceList.GetByIndex(i);
            INodeMap& nodeMap = pInterface->GetTLNodeMap();

            const CStringPtr interfaceIDNode = nodeMap.GetNode("InterfaceID");
            // Ensure the node is valid
            if (!IsAvailable(interfaceIDNode) || !IsReadable(interfaceIDNode))
            {
                continue;
            }

            std::string interfaceID = std::string(interfaceIDNode->GetValue().c_str());

            {
                LockEventHandlerMutex();
                try
                {
                    // Create interface event handler
                    shared_ptr<InterfaceEventHandlerImpl> interfaceEventHandler =
                        shared_ptr<InterfaceEventHandlerImpl>(new InterfaceEventHandlerImpl(pInterface, interfaceID));
                    m_interfaceEventHandlers.push_back(interfaceEventHandler);

                    // Register interface event handler
                    pInterface->RegisterEventHandler(*m_interfaceEventHandlers[i]);

                    cout << "Event handler registered to interface '" << interfaceID << "'..." << endl;
                }
                catch (exception& e)
                {
                    cout << "Error registering event hander to interface '" << interfaceID << "' :" << e.what() << endl;
                }

                UnlockEventHandlerMutex();
            }
        }
        cout << endl;
    }

    void UnregisterAllInterfaceEvents()
    {
        InterfaceList interfaceList = m_system->GetInterfaces(false);
        unsigned int numInterfaces = interfaceList.GetSize();

        //
        // Unregister interface event handler from each interface
        //
        // *** NOTES ***
        // It is important to unregister all arrival, removal, and interface event handlers
        // from all interfaces that they may be registered to.
        //
        for (unsigned int i = 0; i < numInterfaces; i++)
        {
            // Select interface
            InterfacePtr pInterface = interfaceList.GetByIndex(i);
            INodeMap& nodeMap = pInterface->GetTLNodeMap();

            const CStringPtr interfaceIDNode = nodeMap.GetNode("InterfaceID");
            // Ensure the node is valid
            if (!IsAvailable(interfaceIDNode) || !IsReadable(interfaceIDNode))
            {
                continue;
            }

            std::string interfaceID = std::string(interfaceIDNode->GetValue().c_str());

            {
                LockEventHandlerMutex();

                try
                {
                    // Ensure that that the event is unregistered from the matching interface
                    for (unsigned int j = 0; j < m_interfaceEventHandlers.size(); j++)
                    {
                        if (interfaceID == m_interfaceEventHandlers[j]->GetInterfaceId())
                        {
                            pInterface->UnregisterEventHandler(*m_interfaceEventHandlers[j]);
                        }
                    }
                }
                catch (exception& e)
                {
                    cout << "Error unreigstering event hander to interface '" << interfaceID << "' :" << e.what()
                         << endl;
                }

                UnlockEventHandlerMutex();
            }
        }

        {
            LockEventHandlerMutex();

            m_interfaceEventHandlers.clear();

            cout << "Event handler unregistered from interfaces..." << endl;

            UnlockEventHandlerMutex();
        }
    }

  private:
    SystemPtr m_system;
    shared_ptr<InterfaceEventHandlerImpl> m_interfaceEventHandlerOnSystem;
    vector<shared_ptr<InterfaceEventHandlerImpl>> m_interfaceEventHandlers;

#if _MSC_VER == 1600
    HANDLE eventHandlersMutex;
#else
    std::mutex eventHandlersMutex;
#endif
};

// This function checks if GEV enumeration is enabled on the system.
void CheckGevEnabled(SystemPtr& pSystem)
{
    // Retrieve the System TL Nodemap and EnumerateGEVInterfaces node
    INodeMap& nodeMap = pSystem->GetTLNodeMap();
    const CBooleanPtr enumerateGevInterfacesNode = nodeMap.GetNode("EnumerateGEVInterfaces");

    // Ensure the node is valid
    if (IsAvailable(enumerateGevInterfacesNode) && IsReadable(enumerateGevInterfacesNode))
    {
        const bool gevEnabled = enumerateGevInterfacesNode->GetValue();

        // Check if node is enabled
        if (!gevEnabled)
        {
            cout << endl
                 << "WARNING: GEV Enumeration is disabled." << endl
                 << "If you intend to use GigE cameras please run the EnableGEVInterfaces shortcut" << endl
                 << "or set EnumerateGEVInterfaces to true and relaunch your application." << endl
                 << endl;
        }
        else
        {
            cout << "EnumerateGEVInterfaces is enabled. Continuing.." << endl;
        }
    }
    else
    {
        cout << "EnumerateGEVInterfaces node is unavailable" << endl;
    }
}

// Example entry point; this function sets up the example to act appropriately
// upon arrival and removal events; please see Enumeration example for more
// in-depth comments on preparing and cleaning up the system.
int main(int /*argc*/, char** /*argv*/)
{
    // Print application build information
    cout << "Application build date: " << __DATE__ << " " << __TIME__ << endl << endl;

    // Retrieve singleton reference to system object
    SystemPtr system = System::GetInstance();

    // Print out current library version
    const LibraryVersion spinnakerLibraryVersion = system->GetLibraryVersion();
    cout << "Spinnaker library version: " << spinnakerLibraryVersion.major << "." << spinnakerLibraryVersion.minor
         << "." << spinnakerLibraryVersion.type << "." << spinnakerLibraryVersion.build << endl
         << endl;

    // Check if GEV enumeration is enabled.
    CheckGevEnabled(system);

    // Retrieve list of cameras from the system
    CameraList camList = system->GetCameras();

    unsigned int numCameras = camList.GetSize();

    cout << "Number of cameras detected: " << numCameras << endl << endl;

    //
    // Retrieve list of interfaces from the system
    //
    // *** NOTES ***
    // macOS interfaces are only registered if they are active.
    // This example will have varied outcomes depending on the order
    // in which we receive the interface and the device arrival events.
    // In most cases on macOS, since the interface and the device arrive
    // at the same time, we will see the interface arrival event and
    // the interface will already have one device connected to it.
    // This means we will not see another device arrival event.
    // Device removal events however, are guaranteed to be fired before
    // interface removal events.
    //
    InterfaceList interfaceList = system->GetInterfaces();

    unsigned int numInterfaces = interfaceList.GetSize();

    cout << "Number of interfaces detected: " << numInterfaces << endl << endl;

    cout << endl << "*** CONFIGURING ENUMERATION EVENTS ***" << endl << endl;

    //
    // Create system event handler
    //
    // *** NOTES ***
    // The SystemEventHandlerImpl has been constructed to accept a system object in
    // order to register/unregister events to/from the system object
    //
    SystemEventHandlerImpl systemEventHandler(system);

    //
    // Register system event to the system
    //
    // *** NOTES ***
    // A system event is merely a combination of an interface arrival and an
    // interface removal event.
    // This feature is currently only supported for GEV interface arrivals and removals.
    //
    // *** LATER ***
    // Interface arrival and removal events must all be unregistered manually.
    // This must be done prior to releasing the system and while they are still
    // in scope.
    //
    system->RegisterEventHandler(systemEventHandler);

    systemEventHandler.RegisterInterfaceEventToSystem();
    systemEventHandler.RegisterAllInterfaceEvents();

    // Wait for user to plug in and/or remove camera devices
    cout << endl << "Ready! Remove/Plug in cameras to test or press Enter to exit..." << endl << endl;
    getchar();

    systemEventHandler.UnregisterAllInterfaceEvents();
    systemEventHandler.UnregisterInterfaceEventFromSystem();

    //
    // Unregister system event handler from system object
    //
    // *** NOTES ***
    // It is important to unregister all interface arrival and removal event handlers
    // registered to the system.
    //
    system->UnregisterEventHandler(systemEventHandler);

    cout << "System event handler unregistered from system..." << endl;

    // Clear camera list before releasing system
    camList.Clear();

    // Clear interface list before releasing system
    interfaceList.Clear();

    // Release system
    system->ReleaseInstance();

    cout << endl << "Done! Press Enter to exit..." << endl;
    getchar();

    return 0;
}
