#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 = nullptr;
  }

  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;

    SendUnrealVersion( versionMajor, versionMinor, versionPatch );
  }
  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::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, std::function<void( const DataKeyValuePair& )> hashChangeCallback )
{
  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, hashChangeCallback );

    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;
}

Ventuz::UE4::ConnectionError UE4ComClient::SendUnrealVersion( int major, int minor, int patch )
{
  Ventuz::UE4::VersionData data;

  data.majorVersion = major;
  data.minorVersion = minor;
  data.patchVersion = patch;

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

}

ConnectionError UE4ComClient::SendTextureShareList( const std::vector<GUID>& list )
{
  int dataSize = sizeof( int ) + (int)list.size() * sizeof( GUID );

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

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

  GUID* guids = (GUID*)( data + sizeof( int ) );

  for ( int x = 0; x < list.size(); x++ )
    guids[x] = list[x];

  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::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 ) );
}

Ventuz::UE4::ConnectionError UE4ComClient::SetAlphaType( int alpha )
{
  return AncToVentuz( AncID::AlphaType, &alpha, 4 );
}

Ventuz::UE4::ConnectionError UE4ComClient::SetErrorMessage( const std::string& message )
{
  return AncToVentuz( Ventuz::UE4::AncID::ErrorMessage, (void*)message.c_str(), (int)message.size() );
}

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] );
}
*/

void UE4ComClient::SetUnrealVersion( int major, int minor, int patch )
{
  versionMajor = major;
  versionMinor = minor;
  versionPatch = patch;
}

}
}

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 ) );
}
