#include "..\Public\UE4ComClient.h"

#ifndef PLATFORM_WINDOWS
#include "../altona_lite/types.hpp"
#endif

namespace Ventuz
{
namespace UE4
{

/************************************************************************/
/*                                                                      */
/* UE4 Client                                                           */
/*                                                                      */
/************************************************************************/

void UE4ComClient::LockShared()
{
    EnterCriticalSection( &critSec );
#ifdef PLATFORM_WINDOWS
    //check(sharedLocked == 0);
#else
    ASSERT( sharedLocked == 0 );
#endif
    while ( !sharedLocked )
        sharedLocked = ( WAIT_OBJECT_0 == WaitForSingleObject( mutex, INFINITE ) );
}

void UE4ComClient::UnlockShared()
{
#ifdef PLATFORM_WINDOWS
    //check(sharedLocked == 1);
#else
    ASSERT( sharedLocked == 1 );
#endif
    ReleaseMutex( mutex );
    sharedLocked = 0;
    LeaveCriticalSection( &critSec );
}

bool UE4ComClient::IsServerAlive()
{
    if ( !monitorMutexServer )
        return true;

    bool result = WaitForSingleObject( monitorMutexServer, 0 ) == WAIT_ABANDONED;

    if ( result )
    {
        CloseHandle( monitorMutexServer );
        monitorMutexServer = false;
    }

    return !result;
}

void UE4ComClient::DestroyHandles()
{
    if ( sharedLocked )
        UnlockShared();

    if ( mapping )
        CloseHandle( mapping );
    if ( shared )
        UnmapViewOfFile( shared );
    if ( mutex )
        CloseHandle( mutex ); 
    if ( monitorMutexServer )
        CloseHandle( monitorMutexServer );
    if ( monitorMutexClient )
        CloseHandle( monitorMutexClient );
    if ( dataSentFromVentuzEvent )
        CloseHandle( dataSentFromVentuzEvent );
    if ( toVentuzPipe )
        CloseHandle( toVentuzPipe );
    if ( fromVentuzPipe )
        CloseHandle( fromVentuzPipe );

    for (int x = 0; x < (int)Ventuz::UE4::SyncEvent::SyncEventCount; x++)
    {
        if (syncEvents[x])
        {
            CloseHandle(syncEvents[x]);
            syncEvents[x]=0;
        }
    }

    mapping = 0;
    shared = nullptr;
    mutex = 0;
    monitorMutexServer = 0;
    monitorMutexClient = 0;
    dataSentFromVentuzEvent = 0;
    toVentuzPipe = nullptr;
    fromVentuzPipe = nullptr;

    streamHandle = -1;
}

UE4ComClient::UE4ComClient()
{
    toVentuzOverlapped.Offset = 0xffffffff;
    toVentuzOverlapped.OffsetHigh = 0xffffffff;

    InitializeCriticalSection( &critSec );
}

UE4ComClient::~UE4ComClient()
{
    Disconnect();
    DeleteCriticalSection( &critSec );
}

bool UE4ComClient::IsConnected()
{
    return streamHandle < MAX_UE4_STREAMS&& mapping&& shared&& mutex&& monitorMutexClient&& monitorMutexServer&& lastKnownState == ConnectionState::Connected;
}

FromVentuzClock UE4ComClient::GetClusterClock()
{
    return clusterClock;
}

Ventuz::UE4::ConnectionError UE4ComClient::UpdateConnection( const GUID& ventuzLaunchID, bool liveLink )
{
    Mutex mtx( &critSec );
    if ( !mapping || !shared || !mutex ) // do we have a connection?
    {
        mapping = OpenFileMappingW( FILE_MAP_READ | FILE_MAP_WRITE, 0, UE4ComName "_Shared" );

        if ( mapping )
            shared = (ComShared*)MapViewOfFile( mapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof( ComShared ) );

        mutex = OpenMutexW( SYNCHRONIZE, 0, UE4ComName "_Mutex" );

        if ( !mapping || !shared || !mutex )
        {
            DestroyHandles();
            return ConnectionError::ServerNotFound;
        }
    }

    if ( !IsServerAlive() )
    {
        DestroyHandles();
        streamHandle = -1;
        lastKnownState = ConnectionState::Disconnected;
        return ConnectionError::ServerNotFound;
    }

    if ( streamHandle >= MAX_UE4_STREAMS ) // get a stream handle if we can
    {
        LockShared();

        for ( int x = 0; x < MAX_UE4_STREAMS; x++ )
        {
            if ( shared->channels[ x ].state == ConnectionState::Disconnected )
            {
                streamHandle = x;
                shared->channels[ x ].state = ConnectionState::ConnectionRequest;
                shared->channels[ x ].ventuzLaunchID = ventuzLaunchID;
                shared->channels[ x ].isLiveLink = liveLink;

                GUID connectionID;
                CoCreateGuid( &connectionID );
                shared->channels[ x ].internalComID = connectionID; // unique id for the connection mutex

                WCHAR guidBuffer[ 256 ];
                StringFromGUID2( connectionID, guidBuffer, 256 );

                WCHAR buffer[ 256 ];
                wsprintf( buffer, UE4ComName L"_MonitorMutexClient_%s\0", guidBuffer );
                monitorMutexClient = CreateMutexW( 0, 1, buffer );

                if ( !monitorMutexClient )
                {
                    DestroyHandles();
                    shared->channels[ x ].state = ConnectionState::Disconnected;
                }

                wsprintf(buffer, L"\\\\.\\pipe\\" UE4ComName L"_ToVentuzPipe_%s\0", guidBuffer);
                toVentuzPipe = CreateNamedPipe(buffer, PIPE_ACCESS_OUTBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED, PIPE_TYPE_MESSAGE | PIPE_REJECT_REMOTE_CLIENTS, 1, 1024 * 64, 0, 1000, nullptr);

                if (!toVentuzPipe)
                {
                    DestroyHandles();
                    shared->channels[x].state = ConnectionState::Disconnected;
                }

                break;
            }
        }

        UnlockShared();

        if ( streamHandle >= MAX_UE4_STREAMS )
            return ConnectionError::ServerFull;
    }

    // check stream status
    LockShared();
    auto state = shared->channels[ streamHandle ].state;
    switch ( state )
    {
    case Ventuz::UE4::ConnectionState::ConnectionRequest: // handled by the server
        break;
    case Ventuz::UE4::ConnectionState::Connecting:
    {
        WCHAR guidBuffer[ 256 ];
        StringFromGUID2( shared->channels[ streamHandle ].internalComID, guidBuffer, 256 );

        WCHAR buffer[ 256 ];
        wsprintf( buffer, UE4ComName L"_MonitorMutexServer_%s\0", guidBuffer );
        monitorMutexServer = OpenMutexW( SYNCHRONIZE, 0, buffer );

        auto& sharedInfo = shared->channels[ streamHandle ];

        wsprintf( buffer, UE4ComName L"_DataSentFromVentuzEvent_%s\0", guidBuffer );
        dataSentFromVentuzEvent = OpenEventW( EVENT_MODIFY_STATE | SYNCHRONIZE, 0, buffer );

        wsprintf(buffer, L"\\\\.\\pipe\\" UE4ComName L"_FromVentuzPipe_%s\0", guidBuffer);
        fromVentuzPipe = CreateFile(buffer, GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr);

        bool eventsCreated=true;
        for (int x = 0; x < (int)Ventuz::UE4::SyncEvent::SyncEventCount; x++)
        {
            wsprintf(buffer, UE4ComName L"_syncEvent_%d_%s\0", x, guidBuffer);
            syncEvents[x] = OpenEventW(EVENT_MODIFY_STATE | SYNCHRONIZE, 0, buffer);
            if (!syncEvents[x])
                eventsCreated=false;
        }

        shared->channels[streamHandle].pluginVersion = (6 << 0) + (0 << 9) + (0 << 16); // 6<<0 0<<8 8<<16 (major, minor, patch, 00)
        if ( !monitorMutexServer || !dataSentFromVentuzEvent || !fromVentuzPipe || !eventsCreated)
        {
            sharedInfo.state = Ventuz::UE4::ConnectionState::Disconnected;
            DestroyHandles();
        }
        else
            sharedInfo.state = Ventuz::UE4::ConnectionState::Connected;
    }
    break;
    case Ventuz::UE4::ConnectionState::Connected: // handled by the server
        break;
    case Ventuz::UE4::ConnectionState::Disconnected:
    case Ventuz::UE4::ConnectionState::Disconnecting: // booted off the server
        shared->channels[ streamHandle ].state = Ventuz::UE4::ConnectionState::Disconnected;
        DestroyHandles();
        streamHandle = -1;
        break;
    default:
        break;
    }
    UnlockShared();

    lastKnownState = state;

    return ConnectionError::OK;
}

ConnectionError UE4ComClient::AncToVentuz( AncID fourcc, void* data, int size )
{
    if (!toVentuzPipe)
        return ConnectionError::NoConnection;

    unsigned char* message = new unsigned char[size + 4];
    ((AncID*)message)[0] = fourcc;
    memcpy(message + 4, data, size);

    DWORD outBytes = 0;

    toVentuzOverlapped.hEvent = 0;

    bool result = WriteFile(toVentuzPipe, message, size + 4, nullptr, &toVentuzOverlapped);
    if (!result)
        result = GetLastError() == ERROR_IO_PENDING;
    return result ? ConnectionError::OK : ConnectionError::NoConnection;
}

ConnectionError UE4ComClient::SendDataToVentuz()
{
    Mutex mtx( &critSec );
    if ( streamHandle >= MAX_UE4_STREAMS )
        return ConnectionError::InvalidStream;

    /*
        WaitForSingleObject( serverStartFrameEvent, INFINITE );
        ResetEvent( serverStartFrameEvent );
    */

    // update changed transform values by guid
    for ( const GUID& transform : sharedData.updatedTransformValues )
        SendTransformValue( transform, (float*)&sharedData.transformValuesByGUID[ transform ] );

    sharedData.updatedTransformValues.clear();

/*
    LockShared();
    auto& videoData = shared->channels[ streamHandle ];
    bool result = videoData.FinalizeToVentuz( toVentuz );
    UnlockShared();
*/

    return ConnectionError::OK;
}

ConnectionError UE4ComClient::EndFrame()
{
    Mutex mtx( &critSec );
    if ( streamHandle >= MAX_UE4_STREAMS )
        return ConnectionError::InvalidStream;

    return ConnectionError::OK;
}

ConnectionError UE4ComClient::Disconnect()
{
    DestroyHandles();

    Mutex mtx( &critSec );
    if ( streamHandle >= MAX_UE4_STREAMS )
        return ConnectionError::InvalidStream;

    LockShared();
    shared->channels[ streamHandle ].state = ConnectionState::Disconnected;
    UnlockShared();

    return ConnectionError::OK;
}

ConnectionError UE4ComClient::ParseANC(std::function<void(void*, const std::vector<FieldDescriptor>&)> fieldDescCallback, std::unordered_map<GUID, std::string>& fromVentuzFieldListRequests, std::function<void(RenderQuality quality) > qualityLevelCallback)
{
    Mutex mtx( &critSec );
    if ( streamHandle >= MAX_UE4_STREAMS )
        return ConnectionError::InvalidStream;
    WaitForEvent(SyncEvent::VentuzAfterValidation, true);

    DWORD messageSize = 0;
    while (PeekNamedPipe(fromVentuzPipe, nullptr, 0, nullptr, nullptr, &messageSize))
    {
        if (!messageSize)
            break;
        int messagePos = 0;
        unsigned char* message = new unsigned char[messageSize];

        bool success = false;
        do
        {
            DWORD numRead = 0;
            success = ReadFile(fromVentuzPipe, message + messagePos, messageSize, &numRead, nullptr);

            if (!success && GetLastError() != ERROR_MORE_DATA)
                break;

            messagePos += numRead;
        } while (!success);

        if (!success)
        {
            delete[] message;
            continue;
        }

        uint hash;
        bool hashChanged = sharedData.HandleMessage(message, messageSize, hash);

        AncID fourcc = *((AncID*)message);
        unsigned char* data = message + 4;
        int length = messageSize - 4;

        switch (fourcc)
        {
        case AncID::SetResolution:
            resolutionChanged = displayWidth != ((int*)data)[0] || displayHeight != ((int*)data)[1];
            displayWidth = newWidth = ((int*)data)[0];
            displayHeight = newHeight = ((int*)data)[1];
            break;
        case AncID::NodeFieldDescriptor:
            {
                unsigned char* dataEnd = data + length;

                GUID requestID = *(GUID*)data;

                if (fieldListRequests.find(requestID) == fieldListRequests.end())
                    break;

                void* targetObject = fieldListRequests[requestID];
                fieldListRequests.erase(requestID);

                data += sizeof(GUID);

                std::vector<FieldDescriptor> fields;

                while (data < dataEnd)
                {
                    FieldDescriptor desc;
                    desc.type = (E2EFieldType)data[0];
                    desc.fromVentuz = data[1] != 0;

                    data += 2;
                    unsigned char* nameStart = data;
                    desc.fieldName = std::string((char*)nameStart);

                    while (data < dataEnd && *data)
                        data++;

                    data++;
                    fields.emplace_back(desc);
                }

                fieldDescCallback(targetObject, fields);

                break;
            }
        case AncID::RequestNodeFields:
            {
                GUID requestID = *(GUID*)data;
                std::string nodeName((char*)data + sizeof(GUID));
                fromVentuzFieldListRequests[requestID] = nodeName;
                break;
            }
        case AncID::RenderQuality:
            qualityLevelCallback(((RenderQuality*)data)[0]);
            break;
        case AncID::ClusterClock:
            clusterClock = ((FromVentuzClock*)data)[0];
            break;
        default:
            break;
        }

        delete[] message;
    }

    return ConnectionError::OK;
}

std::vector<std::string> UE4ComClient::GetInputBundles()
{
    Mutex mtx( &critSec );

    auto bundleCopy = sharedData.inputBundles;

    sharedData.inputBundles.clear();

    return bundleCopy;
}

ConnectionError UE4ComClient::ProcessEventsFromVentuz( const std::function<void( uint, uint )>& callback )
{
    Mutex mtx( &critSec );
    if ( streamHandle >= MAX_UE4_STREAMS )
        return ConnectionError::InvalidStream;

    for ( auto& event : sharedData.events )
    {
        callback( event.paramHash, event.argument );
    }
    sharedData.events.clear();

    return ConnectionError::OK;
}

void UE4ComClient::ResetStoredValues()
{
    sharedData.Reset();
}

bool UE4ComClient::HasResolutionChanged( int& width, int& height )
{
    if ( !resolutionChanged )
        return false;

    width = newWidth;
    height = newHeight;
    return true;
}

ConnectionError UE4ComClient::SendIntValue( uint hash, const int& value )
{
    Ventuz::UE4::FromVentuzInt data;

    data.paramHash = hash;
    data.value = value;

    return AncToVentuz( Ventuz::UE4::AncID::IntValue, &data, sizeof( data ) );
}

ConnectionError UE4ComClient::SendFloatValue( uint hash, const float& value )
{
    Ventuz::UE4::FromVentuzFloat data;

    data.paramHash = hash;
    data.value = value;

    return AncToVentuz( Ventuz::UE4::AncID::FloatValue, &data, sizeof( data ) );
}

ConnectionError UE4ComClient::SendStringValue( uint hash, const std::string& value )
{
    int dataSize = sizeof( uint ) + (int)value.length() + 1;
    unsigned char* data = new unsigned char[ dataSize ];

    ( (uint*)data )[ 0 ] = hash;
    memcpy( data + sizeof( uint ), value.c_str(), value.length() );
    data[ dataSize - 1 ] = 0;

    ConnectionError result = AncToVentuz( Ventuz::UE4::AncID::StringValue, data, dataSize );
    delete[] data;

    return result;
}

ConnectionError UE4ComClient::SendBoolValue( uint hash, const bool value )
{
    Ventuz::UE4::FromVentuzBool data;

    data.paramHash = hash;
    data.value = value;

    return AncToVentuz( Ventuz::UE4::AncID::BoolValue, &data, sizeof( data ) );
}

ConnectionError UE4ComClient::SendTransformValue( uint hash, const float matrix[ 4 ][ 4 ] )
{
    Ventuz::UE4::FromVentuzMatrix data;

    data.paramHash = hash;
    data.nodeID = GUID{};
    memcpy( data.value, matrix, 16 * sizeof( float ) );

    return AncToVentuz( Ventuz::UE4::AncID::TransformValue, &data, sizeof( data ) );
}

ConnectionError UE4ComClient::SendTransformValue( const GUID& nodeID, const float matrix[ 16 ] )
{
    Ventuz::UE4::FromVentuzMatrix data;

    data.paramHash = 0;
    data.nodeID = nodeID;
    memcpy( data.value, matrix, 16 * sizeof( float ) );

    return AncToVentuz( Ventuz::UE4::AncID::TransformValue, &data, sizeof( data ) );
}

ConnectionError UE4ComClient::TriggerEvent( uint eventNameHash, int argument )
{
    Ventuz::UE4::FromVentuzEvent data;

    data.paramHash = eventNameHash;
    data.argument = argument;

    return AncToVentuz( Ventuz::UE4::AncID::Event, &data, sizeof( data ) );
}

ConnectionError UE4ComClient::SendTextureShareList( const std::vector<std::string>& list )
{
    int dataSize = sizeof( int );

    for ( auto& shareName : list )
        dataSize += (int)shareName.length() + 1;

    unsigned char* data = new unsigned char[ dataSize ];

    ( (int*)data )[ 0 ] = (int)list.size();

    char* dataPos = (char*)data + sizeof( int );

    for ( auto& shareName : list )
    {
        int stringSize = (int)shareName.length();

        memcpy( dataPos, shareName.c_str(), stringSize );
        dataPos[ stringSize ] = 0;
        dataPos += stringSize + 1;
    }

    ConnectionError result = AncToVentuz( Ventuz::UE4::AncID::TextureShareNames, data, dataSize );

    delete[] data;

    return result;
}

Ventuz::UE4::ConnectionError UE4ComClient::SendFieldList( const GUID& requestID, const std::vector<FieldDescriptor>& fields )
{
    int dataSize = sizeof( GUID );
    for ( const auto& field : fields )
    {
        dataSize += 2; //type, fromVentuz
        dataSize += (int)field.fieldName.size() + 1;
    }

    unsigned char* dataToSend = new unsigned char[ dataSize ];
    ( *(GUID*)dataToSend ) = requestID;
    unsigned char* currDataPos = dataToSend + sizeof( GUID );

    for ( const auto& field : fields )
    {
        int nameLength = (int)field.fieldName.size();

        currDataPos[ 0 ] = (unsigned char)field.type;
        currDataPos[ 1 ] = field.fromVentuz;
        memcpy( currDataPos + 2, field.fieldName.c_str(), nameLength );
        currDataPos[ 2 + nameLength ] = 0;
        currDataPos += 3 + nameLength;
    }

    auto result = AncToVentuz( AncID::NodeFieldDescriptor, dataToSend, dataSize );
    delete[] dataToSend;
    return result;
}

Ventuz::UE4::ConnectionError UE4ComClient::SendFrameCount( unsigned int frameCount )
{
    return AncToVentuz( Ventuz::UE4::AncID::FrameCount, &frameCount, sizeof( unsigned int ) );
}

Ventuz::UE4::ConnectionError UE4ComClient::SendIntArray( uint hash, const int* array, int count )
{
    unsigned char* data = new unsigned char[ 4 + count * sizeof( int ) ];
    ( (uint*)data )[ 0 ] = hash;
    memcpy( data + 4, array, count * sizeof( int ) );
    auto result = AncToVentuz( AncID::IntArray, data, 4 + count * sizeof( int ) );
    delete[] data;
    return result;
}

Ventuz::UE4::ConnectionError UE4ComClient::SendFloatArray( uint hash, const float* array, int count )
{
    unsigned char* data = new unsigned char[ 4 + count * sizeof( float ) ];
    ( (uint*)data )[ 0 ] = hash;
    memcpy( data + 4, array, count * sizeof( float ) );
    auto result = AncToVentuz( AncID::FloatArray, data, 4 + count * sizeof( float ) );
    delete[] data;
    return result;
}

Ventuz::UE4::ConnectionError UE4ComClient::SendStringArray( uint hash, const std::vector<std::string>& array )
{
    int size = 4;
    for ( int x = 0; x < array.size(); x++ )
        size += (int)(array[ x ].length() + 1);

    unsigned char* data = new unsigned char[ size ];
    ( (uint*)data )[ 0 ] = hash;
    unsigned char* copyTarget = data + 4;

    for ( int x = 0; x < array.size(); x++ )
    {
        size_t length = array[ x ].length();
        memcpy( copyTarget, array[ x ].c_str(), length );
        copyTarget[ length ] = 0;
        copyTarget += length + 1;
    }

    auto result = AncToVentuz( AncID::StringArray, data, size );
    delete[] data;
    return result;
}

Ventuz::UE4::ConnectionError UE4ComClient::SendBoolArray( uint hash, const bool* array, int count )
{
    unsigned char* data = new unsigned char[ 4 + count * sizeof( bool ) ];
    ( (uint*)data )[ 0 ] = hash;
    memcpy( data + 4, array, count * sizeof( bool ) );
    auto result = AncToVentuz( AncID::BoolArray, data, 4 + count * sizeof( bool ) );
    delete[] data;
    return result;
}

Ventuz::UE4::ConnectionError UE4ComClient::RequestFieldListUpdate( const std::string& requestedNodeName, void* requesterNode )
{
    GUID requestId;
    CoCreateGuid( &requestId );
    fieldListRequests[ requestId ] = requesterNode;

    int dataSize = sizeof( GUID ) + (int)requestedNodeName.length() + 1;
    unsigned char* data = new unsigned char[ dataSize ];
    data[ dataSize - 1 ] = 0;
    *( (GUID*)data ) = requestId;
    memcpy( data + sizeof( GUID ), requestedNodeName.c_str(), requestedNodeName.length() );

    auto result = AncToVentuz( Ventuz::UE4::AncID::RequestNodeFields, data, dataSize );
    delete[] data;

    return result;
}

Ventuz::UE4::ConnectionError UE4ComClient::SetViewports(int x1, int y1, int x2, int y2, int zWidth, int zHeight)
{
    ViewportData data;
    data.x1 = x1;
    data.y1 = y1;
    data.x2 = x2;
    data.y2 = y2;
    data.gViewWidth = zWidth;
    data.gViewHeight = zHeight;
    return AncToVentuz(AncID::Viewports, &data, sizeof(ViewportData));
}

void UE4ComClient::SetRunStatus(bool runStatus)
{
    if (streamHandle >= MAX_UE4_STREAMS)
        return;

    LockShared();
    shared->channels[streamHandle].isPlaying = runStatus;
    UnlockShared();
}

float UE4ComClient::GetCameraAspect()
{
    if (displayWidth == 0 || displayHeight == 0)
        return sharedData.projectionMatrix[5] / sharedData.projectionMatrix[0];

    return displayWidth / (float)displayHeight;
}

void UE4ComClient::MarkResolutionDirty()
{
    displayWidth = 0;
    displayHeight = 0;
}

void UE4ComClient::SetSyncEvent(SyncEvent event)
{
    SetEvent(syncEvents[(int)event]);
}

void UE4ComClient::WaitForEvent(SyncEvent event, bool reset)
{
    WaitForSingleObject(syncEvents[(int)event], 200);
    if (reset)
        ResetEvent(syncEvents[(int)event]);
}

}
}

uint64_t GetGUIDHash( const GUID& guid )
{
    uint* g = (uint*)&guid;
    return ( uint64_t( g[ 0 ] ) | ( uint64_t( g[ 1 ] ) << 32 ) ) ^ ( uint64_t( g[ 2 ] ) | ( uint64_t( g[ 3 ] ) << 32 ) );
}
