#pragma once

// ue4 windows header magic
#if PLATFORM_WINDOWS
#include "Windows/AllowWindowsPlatformTypes.h"
#endif

#include "UE4ComCommon.h"
#include <vector>
#include <string>
#include <functional>
#include <map>
#include <unordered_map>

// ue4 windows header magic
#if PLATFORM_WINDOWS
#include "Windows/HideWindowsPlatformTypes.h"
#endif

namespace Ventuz
{
namespace UE4
{

struct FieldDescriptor
{
    E2EFieldType type;
    bool fromVentuz;
    std::string fieldName;
};

class Mutex
{
    LPCRITICAL_SECTION lpCritSec;

public:

    Mutex( LPCRITICAL_SECTION lpCritSec )
        : lpCritSec( lpCritSec )
    {
        EnterCriticalSection( lpCritSec );
    }
    ~Mutex()
    {
        LeaveCriticalSection( lpCritSec );
    }
};

class UE4ComClient
{
    ComShared* shared = nullptr; // shared memory space
    HANDLE mapping = 0;       // handle to the file mapping
    HANDLE mutex = 0;       // mutex created by server; used by server and client to sync communication
    HANDLE monitorMutexServer = 0;       // used to determine if the server is still alive
    HANDLE monitorMutexClient = 0;       // used by the server to determine if the client is still alive

    HANDLE dataSentFromVentuzEvent = 0;
    HANDLE syncEvents[(int)Ventuz::UE4::SyncEvent::SyncEventCount]{};

    uint streamHandle = -1;      // Server stream handle. Also doubles as a connection indicator.                              
    bool sharedLocked = false;

    bool IsServerAlive();
    void DestroyHandles();

    void LockShared();
    void UnlockShared();

    ConnectionState lastKnownState = ConnectionState::Disconnected;

    CRITICAL_SECTION critSec;

    bool resolutionChanged = false;
    int newWidth;
    int newHeight;
    int displayWidth{};
    int displayHeight{};
    FromVentuzClock clusterClock{};

/*
    Ancillary toVentuz;
*/

    ConnectionError AncToVentuz( AncID fourcc, void* data, int size );

    OVERLAPPED toVentuzOverlapped{};
    HANDLE fromVentuzPipe = nullptr;
    HANDLE toVentuzPipe = nullptr;

    std::unordered_map<GUID, void*> fieldListRequests;

public:

    UE4ComClient();
    virtual ~UE4ComClient();

    SharedDatabase sharedData;

    bool IsConnected();
    FromVentuzClock GetClusterClock();

    ConnectionError UpdateConnection( const GUID& ventuzLaunchID, bool liveLink );

    ConnectionError SendDataToVentuz();
    ConnectionError EndFrame();
    ConnectionError Disconnect();

    ConnectionError ParseANC( std::function<void( void*, const std::vector<FieldDescriptor>& )> fieldDescCallback, std::unordered_map<GUID, std::string>& fromVentuzFieldListRequests, std::function<void(RenderQuality quality) > qualityLevelCallback);

    std::vector<std::string> GetInputBundles();

    ConnectionError ProcessEventsFromVentuz( const std::function<void( uint, uint )>& callback );

    void ResetStoredValues();

    bool HasResolutionChanged( int& width, int& height );

    ConnectionError SendIntValue( uint hash, const int& value );
    ConnectionError SendFloatValue( uint hash, const float& value );
    ConnectionError SendStringValue( uint hash, const std::string& value );
    ConnectionError SendBoolValue( uint hash, const bool value );
    ConnectionError SendTransformValue( uint hash, const float matrix[ 4 ][ 4 ] );
    ConnectionError SendTransformValue( const GUID& nodeID, const float matrix[ 16 ] );
    ConnectionError TriggerEvent( uint eventNameHash, int argument );
    ConnectionError SendTextureShareList( const std::vector<std::string>& list );
    ConnectionError SendFieldList( const GUID& requestID, const std::vector<FieldDescriptor>& list );
    ConnectionError SendFrameCount( unsigned int frameCount );
    ConnectionError SendIntArray( uint hash, const int* array, int count );
    ConnectionError SendFloatArray( uint hash, const float* array, int count );
    ConnectionError SendStringArray( uint hash, const std::vector<std::string>& array );
    ConnectionError SendBoolArray( uint hash, const bool* array, int count );

    ConnectionError RequestFieldListUpdate( const std::string& requestedNodeName, void* requesterNode );

    ConnectionError SetViewports(int x1, int y1, int x2, int y2, int zWidth, int zHeight);

    void SetRunStatus(bool runStatus);

    float GetCameraAspect();
    void MarkResolutionDirty();

    void SetSyncEvent(SyncEvent event);
    void WaitForEvent(SyncEvent event, bool reset);
};

}

}
