<commit_before>/**
    @author <NAME>

    Copyright 2013 <NAME>. All rights reserved.
    See LICENCE for conditions of distribution and use.

    @file   
    @brief   */

// Go around Windows GDI Polygon (conflicts with the Tundra math library) define by including it first.

#include "Win.h"
#include "EC_Camera.h"

#include "WebRTCRenderer.h"
#include "WebRTCWebSocketClient.h"
#include "WebRTCPeerConnection.h"

#include "CloudRenderingPlugin.h"

#include "Framework.h"
#include "LoggingFunctions.h"
#include "FrameAPI.h"
#include "Profiler.h"
#include "Application.h"
#include "UiAPI.h"
#include "UiMainWindow.h"
#include "UiGraphicsView.h"
#include "InputAPI.h"

#include <QImage>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QGraphicsScene>
#include <QGraphicsItem>
#include <QDebug>

#include "OgreRenderingModule.h"
#include "Renderer.h"
#include "TextureAsset.h"
#include "OgreTextureManager.h"
#include "OgreTexture.h"
#include "OgreHardwarePixelBuffer.h"

#include <OgreD3D9HardwarePixelBuffer.h>
#include <OgreD3D9RenderWindow.h>

#include <d3d9.h>
#include <d3dx9tex.h>

namespace WebRTC
{
    // Renderer
    
    Renderer::Renderer(CloudRenderingPlugin *plugin) :
        LC("[WebRTC::Renderer]: "),
        plugin_(plugin),
        websocket_(new WebRTC::WebSocketClient(plugin)),
        tundraRenderer_(new TundraRenderer(plugin))
    {
        WebRTC::RegisterMetaTypes();
        CloudRenderingProtocol::RegisterMetaTypes();

        connect(websocket_.get(), SIGNAL(Connected()), SLOT(OnServiceConnected()));
        connect(websocket_.get(), SIGNAL(Disconnected()), SLOT(OnServiceDisconnected()));
        connect(websocket_.get(), SIGNAL(ConnectingFailed()), SLOT(OnServiceConnectingFailed()));
        connect(websocket_.get(), SIGNAL(Message(CloudRenderingProtocol::MessageSharedPtr)),
            SLOT(OnServiceMessage(CloudRenderingProtocol::MessageSharedPtr)));
            
        // We are going to be injecting input events when the window is inactive, disable auto releasing keys.
        plugin_->GetFramework()->Input()->SetReleaseInputWhenApplicationInactive(false);
        
        // Connect to service
        serviceHost_ = WebRTC::WebSocketClient::CleanHost(plugin_->GetFramework()->CommandLineParameters("--cloudRenderer").first());
        if (!serviceHost_.isEmpty())
            websocket_->Connect(serviceHost_);
        else
            LogError(LC + "--cloudRenderer <cloudRenderingServiceHost> parameter not defined, cannot connect to service for renderer registration!");
    }
    
    Renderer::~Renderer()
    {
        tundraRenderer_.reset();
        websocket_.reset();
        connections_.clear();
    }
    
    CloudRenderingProtocol::CloudRenderingRoom Renderer::Room() const
    {
        return room_;
    }
    
    WebRTCPeerConnectionPtr Renderer::Peer(const QString &peerId) const
    {
        foreach(WebRTCPeerConnectionPtr peer, connections_)
            if (peer->Id() == peerId)
                return peer;
        return WebRTCPeerConnectionPtr();
    }
    
    WebRTCPeerConnectionPtr Renderer::GetOrCreatePeer(const QString &peerId)
    {
        WebRTCPeerConnectionPtr peer = Peer(peerId);
        if (!peer.get())
        {
            peer = WebRTCPeerConnectionPtr(new WebRTC::PeerConnection(plugin_->GetFramework(), peerId));
            connect(peer.get(), SIGNAL(LocalConnectionDataResolved(WebRTC::SDP, WebRTC::ICECandidateList)), 
                SLOT(OnLocalConnectionDataResolved(WebRTC::SDP, WebRTC::ICECandidateList)), Qt::QueuedConnection);
            connect(peer.get(), SIGNAL(DataChannelMessage(CloudRenderingProtocol::MessageSharedPtr)), 
                SLOT(OnDataChannelMessage(CloudRenderingProtocol::MessageSharedPtr)), Qt::QueuedConnection);
            connect(peer.get(), SIGNAL(DataChannelMessage(const CloudRenderingProtocol::BinaryMessageData&)), 
                SLOT(OnDataChannelMessage(const CloudRenderingProtocol::BinaryMessageData&)), Qt::QueuedConnection);
            connections_ << peer;
        }
        return peer;
    }
    
    void Renderer::OnServiceConnected()
    {
        CloudRenderingProtocol::State::RegistrationMessage *message = new CloudRenderingProtocol::State::RegistrationMessage(
            CloudRenderingProtocol::State::RegistrationMessage::R_Renderer);
        message->deleteLater();
        websocket_->Send(message);
        
        room_.Reset();
    }
    
    void Renderer::OnServiceDisconnected()
    {
        LogInfo(LC + QString("WebSocket connection disconnected from %1").arg(serviceHost_));
        room_.Reset();
    }

    void Renderer::OnServiceConnectingFailed()
    {
        LogError(LC + QString("WebSocket connection failed to Cloud Rendering Service at %1").arg(serviceHost_));
        room_.Reset();
    }

    void Renderer::OnServiceMessage(CloudRenderingProtocol::MessageSharedPtr message)
    {
        switch (message->Channel())
        {
            // Signaling
            case CloudRenderingProtocol::CT_Signaling:
            {
                switch (message->Type())
                {
                    // Offer
                    case CloudRenderingProtocol::MT_Offer:
                    {
                        CloudRenderingProtocol::Signaling::OfferMessage *offer = dynamic_cast<CloudRenderingProtocol::Signaling::OfferMessage*>(message.get());
                        if (offer)
                        {
                            bool sendWebCamera = plugin_->GetFramework()->HasCommandLineParameter("--cloudRenderingSendWebCamera");
                            
                            WebRTCPeerConnectionPtr peer = GetOrCreatePeer(offer->senderId);
                            peer->HandleOfferOrAnswer(offer->sdp, offer->iceCandidates, PeerConnection::ConnectionSettings(false, sendWebCamera, !sendWebCamera, true));
                        }
                        else
                            LogError(LC + "Failed to cast MT_Offer message to OfferMessage*");
                        break;
                    }
                    // Answer
                    case CloudRenderingProtocol::MT_Answer:
                    {
                        CloudRenderingProtocol::Signaling::AnswerMessage *answer = dynamic_cast<CloudRenderingProtocol::Signaling::AnswerMessage*>(message.get());
                        if (answer)
                        {
                            bool sendWebCamera = plugin_->GetFramework()->HasCommandLineParameter("--cloudRenderingSendWebCamera");
                            
                            WebRTCPeerConnectionPtr peer = GetOrCreatePeer(answer->senderId);
                            peer->HandleOfferOrAnswer(answer->sdp, answer->iceCandidates, PeerConnection::ConnectionSettings(false, sendWebCamera, !sendWebCamera, true));
                        }
                        else
                            LogError(LC + "Failed to cast MT_Answer message to AnswerMessage*");
                        break;
                    }
                    // Ice candidates
                    case CloudRenderingProtocol::MT_IceCandidates:
                    {
                        CloudRenderingProtocol::Signaling::IceCandidatesMessage *candidates = dynamic_cast<CloudRenderingProtocol::Signaling::IceCandidatesMessage*>(message.get());
                        if (candidates)
                        {
                            WebRTCPeerConnectionPtr peer = Peer(candidates->senderId);
                            if (peer.get())
                                peer->AddRemoteIceCandidates(candidates->iceCandidates);
                            else
                                LogError(LC + QString("Failed to find sender peer with id %1 for a IceCandidatesMessage.").arg(candidates->senderId));
                        }
                        else
                            LogError(LC + "Failed to cast MT_IceCandidates message to IceCandidatesMessage*");
                        break;
                    }
                }
                break;
            }
            // Room
            case CloudRenderingProtocol::CT_Room:
            {
                switch (message->Type())
                {
                    case CloudRenderingProtocol::MT_RoomAssigned:
                    {
                        room_.Reset();

                        CloudRenderingProtocol::Room::RoomAssignedMessage *assigned = dynamic_cast<CloudRenderingProtocol::Room::RoomAssignedMessage*>(message.get());
                        if (assigned)
                        {
                            if (assigned->error == CloudRenderingProtocol::Room::RoomAssignedMessage::RQE_NoError)
                            {
                                room_.id = assigned->roomId;
                                LogDebug(LC + "Renderer was assigned to room " + room_.id);
                            }
                            else
                                LogError(LC + QString("RoomAssignedMessage sent a error code %1 to renderer, this should never happen as we are not requesting for a room!")
                                    .arg(static_cast<int>(assigned->error)));
                        }
                        else
                            LogError(LC + "Failed to cast MT_RoomAssigned message to RoomAssignedMessage*");
                        break;
                    }
                    case CloudRenderingProtocol::MT_RoomUserJoined:
                    {
                        CloudRenderingProtocol::Room::RoomUserJoinedMessage *joined = dynamic_cast<CloudRenderingProtocol::Room::RoomUserJoinedMessage*>(message.get());
                        if (joined)
                        {
                            LogDebug(LC + "Peers joined to the renderers room");
                            foreach(const QString &joinedPeerId, joined->peerIds)
                            {
                                LogDebug(LC + QString("  peerId = %1").arg(joinedPeerId));
                                room_.AddPeer(joinedPeerId);
                                
                                /// @todo This will be changed to something else in the future, for now send offer to each joining client.
                                bool sendWebCamera = plugin_->GetFramework()->HasCommandLineParameter("--cloudRenderingSendWebCamera");
                                
                                WebRTCPeerConnectionPtr peer = GetOrCreatePeer(joinedPeerId);
                                peer->CreateOffer(PeerConnection::ConnectionSettings(false, sendWebCamera, !sendWebCamera, true));
                            }
                        }
                        else
                            LogError(LC + "Failed to cast MT_RoomUserJoined message to RoomUserJoinedMessage*");
                        break;
                    }
                    case CloudRenderingProtocol::MT_RoomUserLeft:
                    {
                        CloudRenderingProtocol::Room::RoomUserLeftMessage *left = dynamic_cast<CloudRenderingProtocol::Room::RoomUserLeftMessage*>(message.get());
                        if (left)
                        {
                            LogInfo(LC + "Peers left from the renderers room");
                            foreach(const QString &leftPeerId, left->peerIds)
                            {
                                LogInfo(LC + QString("  peerId = %1").arg(leftPeerId));
                                room_.RemovePeer(leftPeerId);
                            }
                        }
                        else
                            LogError(LC + "Failed to cast MT_RoomUserLeft message to RoomUserJoinedMessage*");
                        break;
                    }
                }
                break;
            }
        }
    }
    
    void Renderer::OnDataChannelMessage(CloudRenderingProtocol::MessageSharedPtr message)
    {
        OnDataChannelMessage(dynamic_cast<WebRTC::PeerConnection*>(sender()), message);
    }
    
    void Renderer::OnDataChannelMessage(WebRTC::PeerConnection *sender, CloudRenderingProtocol::MessageSharedPtr message)
    {
        if (!sender)
            return;
            
        switch (message->Channel())
        {
            // Application
            case CloudRenderingProtocol::CT_Application:
            {
                switch (message->Type())
                {
                    case CloudRenderingProtocol::MT_PeerCustomMessage:
                    {
                        CloudRenderingProtocol::Application::PeerCustomMessage *peerMessage = dynamic_cast<CloudRenderingProtocol::Application::PeerCustomMessage*>(message.get());
                        if (peerMessage)
                        {
                            QString type = peerMessage->payload.value("type", "").toString();
                            if (type == "InputKeyboard")
                                PostKeyboardEvent(peerMessage->payload);
                            else if (type == "InputMouse")
                                PostMouseEvent(peerMessage->payload);
                            else
                                LogWarning("Unknown PeerCustomMessage with type " + type);
                        }
                        break;
                    }
                }
            }
        }
    }

    Qt::Key KeyFromHtmlKeyCode(int c)
    {
        switch(c)
        {
            case 8:     return Qt::Key_Backspace;
            case 9:     return Qt::Key_Tab;
            case 13:    return Qt::Key_Enter;
            case 16:    return Qt::Key_Shift;
            case 17:    return Qt::Key_Control;
            case 18:    return Qt::Key_Alt;
            case 19:    return Qt::Key_Pause;
            case 20:    return Qt::Key_CapsLock;
            case 27:    return Qt::Key_Escape;
            
            case 32:    return Qt::Key_Space;
            case 33:    return Qt::Key_PageUp;
            case 34:    return Qt::Key_PageDown;
            
            case 35:    return Qt::Key_End;
            case 36:    return Qt::Key_Home;
            case 37:    return Qt::Key_Left;
            case 38:    return Qt::Key_Up;
            case 39:    return Qt::Key_Right;
            case 40:    return Qt::Key_Down;
            
            case 45:    return Qt::Key_Insert;
            case 46:    return Qt::Key_Delete;
            
            case 48:    return Qt::Key_0;
            case 49:    return Qt::Key_1;
            case 50:    return Qt::Key_2;
            case 51:    return Qt::Key_3;
            case 52:    return Qt::Key_4;
            case 53:    return Qt::Key_5;
            case 54:    return Qt::Key_6;
            case 55:    return Qt::Key_7;
            case 56:    return Qt::Key_8;
            case 57:    return Qt::Key_9;
            
            case 65:    return Qt::Key_A;
            case 66:    return Qt::Key_B;
            case 67:    return Qt::Key_C;
            case 68:    return Qt::Key_D;
            case 69:    return Qt::Key_E;
            case 70:    return Qt::Key_F;
            case 71:    return Qt::Key_G;
            case 72:    return Qt::Key_H;
            case 73:    return Qt::Key_I;
            case 74:    return Qt::Key_J;
            case 75:    return Qt::Key_K;
            case 76:    return Qt::Key_L;
            case 77:    return Qt::Key_M;
            case 78:    return Qt::Key_N;
            case 79:    return Qt::Key_O;
            case 80:    return Qt::Key_P;
            case 81:    return Qt::Key_Q;
            case 82:    return Qt::Key_R;
            case 83:    return Qt::Key_S;
            case 84:    return Qt::Key_T;
            case 85:    return Qt::Key_U;
            case 86:    return Qt::Key_V;
            case 87:    return Qt::Key_W;
            case 88:    return Qt::Key_X;
            case 89:    return Qt::Key_Y;
            case 90:    return Qt::Key_Z;

            //case 91:    return Qt::Key_; // left window key?
            //case 92:    return Qt::Key_; // right window key?
            case 93:    return Qt::Key_Select;

            case 96:    return Qt::Key_0; // Numpad numbers
            case 97:    return Qt::Key_1;
            case 98:    return Qt::Key_2;
            case 99:    return Qt::Key_3;
            case 100:   return Qt::Key_4;
            case 101:   return Qt::Key_5;
            case 102:   return Qt::Key_6;
            case 103:   return Qt::Key_7;
            case 104:   return Qt::Key_8;
            case 105:   return Qt::Key_9;
            
            case 106:   return Qt::Key_Asterisk; // multiply
            case 107:   return Qt::Key_Plus;     // add
            case 109:   return Qt::Key_Minus;    // subtract
            case 110:   return Qt::Key_Comma;    // decimal point
            case 111:   return Qt::Key_Slash;    // divide
            
            case 112:   return Qt::Key_F1;
            case 113:   return Qt::Key_F2;
            case 114:   return Qt::Key_F3;
            case 115:   return Qt::Key_F4;
            case 116:   return Qt::Key_F5;
            case 117:   return Qt::Key_F6;
            case 118:   return Qt::Key_F7;
            case 119:   return Qt::Key_F8;
            case 120:   return Qt::Key_F9;
            case 121:   return Qt::Key_F10;
            case 122:   return Qt::Key_F11;
            case 123:   return Qt::Key_F12;
            
            case 144:   return Qt::Key_NumLock;
            case 145:   return Qt::Key_ScrollLock;
            
            case 186:   return Qt::Key_Semicolon;
            case 187:   return Qt::Key_Equal;
            case 188:   return Qt::Key_Comma;
            //case 189:   return Qt::Key_;     // dash
            case 190:   return Qt::Key_Period;
            case 191:   return Qt::Key_Slash;  // forward slash
            //case 192:   return Qt::Key_;     // grave accent
            
            case 219:   return Qt::Key_BracketLeft;  // open bracket
            case 220:   return Qt::Key_Backslash;
            case 221:   return Qt::Key_BracketRight; // close bracket
            case 222:   return Qt::Key_QuoteLeft;    // single quote
        }
        return Qt::Key_unknown;
    }

    void Renderer::PostKeyboardEvent(const QVariantMap &data)
    {
        UiGraphicsView *view = (plugin_ ? plugin_->GetFramework()->Ui()->GraphicsView() : 0);
        if (!view)
            return;

        // type: 'keyDown' or 'keyUp'
        QEvent::Type type = (data.value("action", "").toString() == "keyDown" ? QEvent::KeyPress : QEvent::KeyRelease);

        // modifiers
        Qt::KeyboardModifiers modifiers = Qt::NoModifier;
        if (data.value("altKey", false).toBool())
            modifiers |= Qt::AltModifier;
        if (data.value("shiftKey", false).toBool())
            modifiers |= Qt::ShiftModifier;
        if (data.value("ctrlKey", false).toBool())
            modifiers |= Qt::ControlModifier;
        if (data.value("metaKey", false).toBool())
            modifiers |= Qt::MetaModifier;
            
        inputState_.keyboardModifiers = modifiers;
            
        Qt::Key key = KeyFromHtmlKeyCode(data.value("key").toInt());
        if (key == Qt::Key_unknown)
        {
            if (IsLogChannelEnabled(LogChannelDebug))
                qWarning() << "Failed to map HTML keyCode" << data.value("key").toInt() << "to a Qt key";
            return;
        }
        
        QString text = (key != Qt::Key_unknown ? QKeySequence(key).toString(QKeySequence::NativeText).toLower() : "");
        if (text == "space")
            text = " ";
        else if (text == "tab")
            text = "    ";
        else if (key == Qt::Key_Shift || key == Qt::Key_Control || key == Qt::Key_Alt || key == Qt::Key_AltGr)
            text = "";
        if (data.value("shiftKey", false).toBool())
            text = text.toUpper();
        
        QKeyEvent e(type, key, modifiers, text);
        if (IsLogChannelEnabled(LogChannelDebug))
            qDebug() << &e;
        QApplication::sendEvent(view, &e);
    }

    void Renderer::PostMouseEvent(const QVariantMap &data)
    {
        UiGraphicsView *view = (plugin_ ? plugin_->GetFramework()->Ui()->GraphicsView() : 0);
        UiMainWindow *window = (plugin_ ? plugin_->GetFramework()->Ui()->MainWindow() : 0);
        if (!view || !window)
            return;

        if (!data.contains("x") || !data.contains("y"))
            return;

        bool ok = false;
        float x = data.value("x").toFloat(&ok);
        if (!ok) return; ok = false;       
        if (x > 1.0f)
        {
            LogWarning(LC + "Received mouse event with x >= 1.0!");
            x = 1.0f;
        }
        float y = data.value("y").toFloat(&ok);
        if (!ok) return;
        if (y > 1.0f)
        {
            LogWarning(LC + "Received mouse event with y >= 1.0!");
            y = 1.0f;
        }

        // type: 'move', 'press' or 'release'
        QString typeStr = data.value("action", "").toString();
        QEvent::Type type = (typeStr == "move" ? QEvent::MouseMove : (typeStr == "press" ? QEvent::MouseButtonPress : (typeStr == "doublepress" ? QEvent::MouseButtonDblClick : QEvent::MouseButtonRelease)));       

        // button
        inputState_.mouseButtons = Qt::NoButton;

        Qt::MouseButton button = Qt::NoButton;
        if (data.value("leftButton", false).toBool())
        {
            if (button == Qt::NoButton)
                button = Qt::LeftButton;
            inputState_.mouseButtons |= Qt::LeftButton;
        }
        if (data.value("rightButton", false).toBool())
        {
            if (button == Qt::NoButton)
                button = Qt::RightButton;
            inputState_.mouseButtons |= Qt::RightButton;
        }
        if (data.value("middleButton", false).toBool())
        {
            if (button == Qt::NoButton)
                button = Qt::MiddleButton;
            inputState_.mouseButtons |= Qt::MiddleButton;
        }

        // Check another extra prop if button could not be resolved.
        if ((type == QEvent::MouseButtonRelease || type == QEvent::MouseButtonPress) && button == Qt::NoButton)
        {
            int releaseExtraCheck = data.value("which", -1).toInt();
            if (releaseExtraCheck == -1)
                releaseExtraCheck = data.value("button", -1).toInt();

            if (releaseExtraCheck == 1)
                button = Qt::LeftButton;
            else if (releaseExtraCheck == 3)
                button = Qt::RightButton;
            else if (releaseExtraCheck == 2)
                button = Qt::MiddleButton;
            if (button != Qt::NoButton)
                inputState_.mouseButtons = button;
        }

        // position from [0.0, 1.0] to application window coordinates
        QRect renderingSurfaceRect = view->geometry();
        QPoint mousePos(renderingSurfaceRect.width() * x, renderingSurfaceRect.height() * y);
        QPoint globalPos(window->geometry().topLeft() + renderingSurfaceRect.topLeft() + mousePos);

        // release or press event: fake a mouse move to this coordinate first
        if (type == QEvent::MouseButtonPress)
        {
            Qt::MouseButton moveButton = (type == QEvent::MouseButtonRelease ? button : Qt::NoButton);
            QMouseEvent *e = QMouseEvent::createExtendedMouseEvent(QEvent::MouseMove, mousePos, globalPos, 
                 moveButton, moveButton, inputState_.keyboardModifiers); 
            QApplication::sendEvent(view->viewport(), e);
            SAFE_DELETE(e);
        }

        QMouseEvent *e = QMouseEvent::createExtendedMouseEvent(type, mousePos, globalPos, button, inputState_.mouseButtons, inputState_.keyboardModifiers); 
        QApplication::sendEvent(view->viewport(), e);
        SAFE_DELETE(e);

        if ((type == QEvent::MouseButtonPress || type == QEvent::MouseButtonDblClick) && plugin_->GetFramework()->Input()->ItemUnderMouse() == 0)
            ClearInputFocus();
    }
    
    void Renderer::ClearInputFocus()
    {
        UiGraphicsView *view = (plugin_ ? plugin_->GetFramework()->Ui()->GraphicsView() : 0);
        if (view && view->scene() && view->scene()->focusItem())
        {
            view->scene()->setFocusItem(0);
            view->scene()->clearFocus();

            if (view->scene()->focusItem())
            {
                QFocusEvent fe(QEvent::FocusOut);
                QApplication::sendEvent(view->scene(), &fe);
            }
        }
    }
    
    void Renderer::OnDataChannelMessage(const CloudRenderingProtocol::BinaryMessageData &data)
    {
        OnDataChannelMessage(dynamic_cast<WebRTC::PeerConnection*>(sender()), data);
    }
    
    void Renderer::OnDataChannelMessage(WebRTC::PeerConnection *sender, const CloudRenderingProtocol::BinaryMessageData &data)<commit_msg>Fix mouse input bugs and add double click.<commit_after>/**
    @author <NAME>

    Copyright 2013 Admin<NAME>. All rights reserved.
    See LICENCE for conditions of distribution and use.

    @file   
    @brief   */

// Go around Windows GDI Polygon (conflicts with the Tundra math library) define by including it first.

#include "Win.h"
#include "EC_Camera.h"

#include "WebRTCRenderer.h"
#include "WebRTCWebSocketClient.h"
#include "WebRTCPeerConnection.h"

#include "CloudRenderingPlugin.h"

#include "Framework.h"
#include "LoggingFunctions.h"
#include "FrameAPI.h"
#include "Profiler.h"
#include "Application.h"
#include "UiAPI.h"
#include "UiMainWindow.h"
#include "UiGraphicsView.h"
#include "InputAPI.h"

#include <QImage>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QGraphicsScene>
#include <QGraphicsItem>
#include <QDebug>

#include "OgreRenderingModule.h"
#include "Renderer.h"
#include "TextureAsset.h"
#include "OgreTextureManager.h"
#include "OgreTexture.h"
#include "OgreHardwarePixelBuffer.h"

#include <OgreD3D9HardwarePixelBuffer.h>
#include <OgreD3D9RenderWindow.h>

#include <d3d9.h>
#include <d3dx9tex.h>

namespace WebRTC
{
    // Renderer
    
    Renderer::Renderer(CloudRenderingPlugin *plugin) :
        LC("[WebRTC::Renderer]: "),
        plugin_(plugin),
        websocket_(new WebRTC::WebSocketClient(plugin)),
        tundraRenderer_(new TundraRenderer(plugin))
    {
        WebRTC::RegisterMetaTypes();
        CloudRenderingProtocol::RegisterMetaTypes();

        connect(websocket_.get(), SIGNAL(Connected()), SLOT(OnServiceConnected()));
        connect(websocket_.get(), SIGNAL(Disconnected()), SLOT(OnServiceDisconnected()));
        connect(websocket_.get(), SIGNAL(ConnectingFailed()), SLOT(OnServiceConnectingFailed()));
        connect(websocket_.get(), SIGNAL(Message(CloudRenderingProtocol::MessageSharedPtr)),
            SLOT(OnServiceMessage(CloudRenderingProtocol::MessageSharedPtr)));
            
        // We are going to be injecting input events when the window is inactive, disable auto releasing keys.
        plugin_->GetFramework()->Input()->SetReleaseInputWhenApplicationInactive(false);
        
        // Connect to service
        serviceHost_ = WebRTC::WebSocketClient::CleanHost(plugin_->GetFramework()->CommandLineParameters("--cloudRenderer").first());
        if (!serviceHost_.isEmpty())
            websocket_->Connect(serviceHost_);
        else
            LogError(LC + "--cloudRenderer <cloudRenderingServiceHost> parameter not defined, cannot connect to service for renderer registration!");
    }
    
    Renderer::~Renderer()
    {
        tundraRenderer_.reset();
        websocket_.reset();
        connections_.clear();
    }
    
    CloudRenderingProtocol::CloudRenderingRoom Renderer::Room() const
    {
        return room_;
    }
    
    WebRTCPeerConnectionPtr Renderer::Peer(const QString &peerId) const
    {
        foreach(WebRTCPeerConnectionPtr peer, connections_)
            if (peer->Id() == peerId)
                return peer;
        return WebRTCPeerConnectionPtr();
    }
    
    WebRTCPeerConnectionPtr Renderer::GetOrCreatePeer(const QString &peerId)
    {
        WebRTCPeerConnectionPtr peer = Peer(peerId);
        if (!peer.get())
        {
            peer = WebRTCPeerConnectionPtr(new WebRTC::PeerConnection(plugin_->GetFramework(), peerId));
            connect(peer.get(), SIGNAL(LocalConnectionDataResolved(WebRTC::SDP, WebRTC::ICECandidateList)), 
                SLOT(OnLocalConnectionDataResolved(WebRTC::SDP, WebRTC::ICECandidateList)), Qt::QueuedConnection);
            connect(peer.get(), SIGNAL(DataChannelMessage(CloudRenderingProtocol::MessageSharedPtr)), 
                SLOT(OnDataChannelMessage(CloudRenderingProtocol::MessageSharedPtr)), Qt::QueuedConnection);
            connect(peer.get(), SIGNAL(DataChannelMessage(const CloudRenderingProtocol::BinaryMessageData&)), 
                SLOT(OnDataChannelMessage(const CloudRenderingProtocol::BinaryMessageData&)), Qt::QueuedConnection);
            connections_ << peer;
        }
        return peer;
    }
    
    void Renderer::OnServiceConnected()
    {
        CloudRenderingProtocol::State::RegistrationMessage *message = new CloudRenderingProtocol::State::RegistrationMessage(
            CloudRenderingProtocol::State::RegistrationMessage::R_Renderer);
        message->deleteLater();
        websocket_->Send(message);
        
        room_.Reset();
    }
    
    void Renderer::OnServiceDisconnected()
    {
        LogInfo(LC + QString("WebSocket connection disconnected from %1").arg(serviceHost_));
        room_.Reset();
    }

    void Renderer::OnServiceConnectingFailed()
    {
        LogError(LC + QString("WebSocket connection failed to Cloud Rendering Service at %1").arg(serviceHost_));
        room_.Reset();
    }

    void Renderer::OnServiceMessage(CloudRenderingProtocol::MessageSharedPtr message)
    {
        switch (message->Channel())
        {
            // Signaling
            case CloudRenderingProtocol::CT_Signaling:
            {
                switch (message->Type())
                {
                    // Offer
                    case CloudRenderingProtocol::MT_Offer:
                    {
                        CloudRenderingProtocol::Signaling::OfferMessage *offer = dynamic_cast<CloudRenderingProtocol::Signaling::OfferMessage*>(message.get());
                        if (offer)
                        {
                            bool sendWebCamera = plugin_->GetFramework()->HasCommandLineParameter("--cloudRenderingSendWebCamera");
                            
                            WebRTCPeerConnectionPtr peer = GetOrCreatePeer(offer->senderId);
                            peer->HandleOfferOrAnswer(offer->sdp, offer->iceCandidates, PeerConnection::ConnectionSettings(false, sendWebCamera, !sendWebCamera, true));
                        }
                        else
                            LogError(LC + "Failed to cast MT_Offer message to OfferMessage*");
                        break;
                    }
                    // Answer
                    case CloudRenderingProtocol::MT_Answer:
                    {
                        CloudRenderingProtocol::Signaling::AnswerMessage *answer = dynamic_cast<CloudRenderingProtocol::Signaling::AnswerMessage*>(message.get());
                        if (answer)
                        {
                            bool sendWebCamera = plugin_->GetFramework()->HasCommandLineParameter("--cloudRenderingSendWebCamera");
                            
                            WebRTCPeerConnectionPtr peer = GetOrCreatePeer(answer->senderId);
                            peer->HandleOfferOrAnswer(answer->sdp, answer->iceCandidates, PeerConnection::ConnectionSettings(false, sendWebCamera, !sendWebCamera, true));
                        }
                        else
                            LogError(LC + "Failed to cast MT_Answer message to AnswerMessage*");
                        break;
                    }
                    // Ice candidates
                    case CloudRenderingProtocol::MT_IceCandidates:
                    {
                        CloudRenderingProtocol::Signaling::IceCandidatesMessage *candidates = dynamic_cast<CloudRenderingProtocol::Signaling::IceCandidatesMessage*>(message.get());
                        if (candidates)
                        {
                            WebRTCPeerConnectionPtr peer = Peer(candidates->senderId);
                            if (peer.get())
                                peer->AddRemoteIceCandidates(candidates->iceCandidates);
                            else
                                LogError(LC + QString("Failed to find sender peer with id %1 for a IceCandidatesMessage.").arg(candidates->senderId));
                        }
                        else
                            LogError(LC + "Failed to cast MT_IceCandidates message to IceCandidatesMessage*");
                        break;
                    }
                }
                break;
            }
            // Room
            case CloudRenderingProtocol::CT_Room:
            {
                switch (message->Type())
                {
                    case CloudRenderingProtocol::MT_RoomAssigned:
                    {
                        room_.Reset();

                        CloudRenderingProtocol::Room::RoomAssignedMessage *assigned = dynamic_cast<CloudRenderingProtocol::Room::RoomAssignedMessage*>(message.get());
                        if (assigned)
                        {
                            if (assigned->error == CloudRenderingProtocol::Room::RoomAssignedMessage::RQE_NoError)
                            {
                                room_.id = assigned->roomId;
                                LogDebug(LC + "Renderer was assigned to room " + room_.id);
                            }
                            else
                                LogError(LC + QString("RoomAssignedMessage sent a error code %1 to renderer, this should never happen as we are not requesting for a room!")
                                    .arg(static_cast<int>(assigned->error)));
                        }
                        else
                            LogError(LC + "Failed to cast MT_RoomAssigned message to RoomAssignedMessage*");
                        break;
                    }
                    case CloudRenderingProtocol::MT_RoomUserJoined:
                    {
                        CloudRenderingProtocol::Room::RoomUserJoinedMessage *joined = dynamic_cast<CloudRenderingProtocol::Room::RoomUserJoinedMessage*>(message.get());
                        if (joined)
                        {
                            LogDebug(LC + "Peers joined to the renderers room");
                            foreach(const QString &joinedPeerId, joined->peerIds)
                            {
                                LogDebug(LC + QString("  peerId = %1").arg(joinedPeerId));
                                room_.AddPeer(joinedPeerId);
                                
                                /// @todo This will be changed to something else in the future, for now send offer to each joining client.
                                bool sendWebCamera = plugin_->GetFramework()->HasCommandLineParameter("--cloudRenderingSendWebCamera");
                                
                                WebRTCPeerConnectionPtr peer = GetOrCreatePeer(joinedPeerId);
                                peer->CreateOffer(PeerConnection::ConnectionSettings(false, sendWebCamera, !sendWebCamera, true));
                            }
                        }
                        else
                            LogError(LC + "Failed to cast MT_RoomUserJoined message to RoomUserJoinedMessage*");
                        break;
                    }
                    case CloudRenderingProtocol::MT_RoomUserLeft:
                    {
                        CloudRenderingProtocol::Room::RoomUserLeftMessage *left = dynamic_cast<CloudRenderingProtocol::Room::RoomUserLeftMessage*>(message.get());
                        if (left)
                        {
                            LogInfo(LC + "Peers left from the renderers room");
                            foreach(const QString &leftPeerId, left->peerIds)
                            {
                                LogInfo(LC + QString("  peerId = %1").arg(leftPeerId));
                                room_.RemovePeer(leftPeerId);
                            }
                        }
                        else
                            LogError(LC + "Failed to cast MT_RoomUserLeft message to RoomUserJoinedMessage*");
                        break;
                    }
                }
                break;
            }
        }
    }
    
    void Renderer::OnDataChannelMessage(CloudRenderingProtocol::MessageSharedPtr message)
    {
        OnDataChannelMessage(dynamic_cast<WebRTC::PeerConnection*>(sender()), message);
    }
    
    void Renderer::OnDataChannelMessage(WebRTC::PeerConnection *sender, CloudRenderingProtocol::MessageSharedPtr message)
    {
        if (!sender)
            return;
            
        switch (message->Channel())
        {
            // Application
            case CloudRenderingProtocol::CT_Application:
            {
                switch (message->Type())
                {
                    case CloudRenderingProtocol::MT_PeerCustomMessage:
                    {
                        CloudRenderingProtocol::Application::PeerCustomMessage *peerMessage = dynamic_cast<CloudRenderingProtocol::Application::PeerCustomMessage*>(message.get());
                        if (peerMessage)
                        {
                            QString type = peerMessage->payload.value("type", "").toString();
                            if (type == "InputKeyboard")
                                PostKeyboardEvent(peerMessage->payload);
                            else if (type == "InputMouse")
                                PostMouseEvent(peerMessage->payload);
                            else
                                LogWarning("Unknown PeerCustomMessage with type " + type);
                        }
                        break;
                    }
                }
            }
        }
    }

    Qt::Key KeyFromHtmlKeyCode(int c)
    {
        switch(c)
        {
            case 8:     return Qt::Key_Backspace;
            case 9:     return Qt::Key_Tab;
            case 13:    return Qt::Key_Enter;
            case 16:    return Qt::Key_Shift;
            case 17:    return Qt::Key_Control;
            case 18:    return Qt::Key_Alt;
            case 19:    return Qt::Key_Pause;
            case 20:    return Qt::Key_CapsLock;
            case 27:    return Qt::Key_Escape;
            
            case 32:    return Qt::Key_Space;
            case 33:    return Qt::Key_PageUp;
            case 34:    return Qt::Key_PageDown;
            
            case 35:    return Qt::Key_End;
            case 36:    return Qt::Key_Home;
            case 37:    return Qt::Key_Left;
            case 38:    return Qt::Key_Up;
            case 39:    return Qt::Key_Right;
            case 40:    return Qt::Key_Down;
            
            case 45:    return Qt::Key_Insert;
            case 46:    return Qt::Key_Delete;
            
            case 48:    return Qt::Key_0;
            case 49:    return Qt::Key_1;
            case 50:    return Qt::Key_2;
            case 51:    return Qt::Key_3;
            case 52:    return Qt::Key_4;
            case 53:    return Qt::Key_5;
            case 54:    return Qt::Key_6;
            case 55:    return Qt::Key_7;
            case 56:    return Qt::Key_8;
            case 57:    return Qt::Key_9;
            
            case 65:    return Qt::Key_A;
            case 66:    return Qt::Key_B;
            case 67:    return Qt::Key_C;
            case 68:    return Qt::Key_D;
            case 69:    return Qt::Key_E;
            case 70:    return Qt::Key_F;
            case 71:    return Qt::Key_G;
            case 72:    return Qt::Key_H;
            case 73:    return Qt::Key_I;
            case 74:    return Qt::Key_J;
            case 75:    return Qt::Key_K;
            case 76:    return Qt::Key_L;
            case 77:    return Qt::Key_M;
            case 78:    return Qt::Key_N;
            case 79:    return Qt::Key_O;
            case 80:    return Qt::Key_P;
            case 81:    return Qt::Key_Q;
            case 82:    return Qt::Key_R;
            case 83:    return Qt::Key_S;
            case 84:    return Qt::Key_T;
            case 85:    return Qt::Key_U;
            case 86:    return Qt::Key_V;
            case 87:    return Qt::Key_W;
            case 88:    return Qt::Key_X;
            case 89:    return Qt::Key_Y;
            case 90:    return Qt::Key_Z;

            //case 91:    return Qt::Key_; // left window key?
            //case 92:    return Qt::Key_; // right window key?
            case 93:    return Qt::Key_Select;

            case 96:    return Qt::Key_0; // Numpad numbers
            case 97:    return Qt::Key_1;
            case 98:    return Qt::Key_2;
            case 99:    return Qt::Key_3;
            case 100:   return Qt::Key_4;
            case 101:   return Qt::Key_5;
            case 102:   return Qt::Key_6;
            case 103:   return Qt::Key_7;
            case 104:   return Qt::Key_8;
            case 105:   return Qt::Key_9;
            
            case 106:   return Qt::Key_Asterisk; // multiply
            case 107:   return Qt::Key_Plus;     // add
            case 109:   return Qt::Key_Minus;    // subtract
            case 110:   return Qt::Key_Comma;    // decimal point
            case 111:   return Qt::Key_Slash;    // divide
            
            case 112:   return Qt::Key_F1;
            case 113:   return Qt::Key_F2;
            case 114:   return Qt::Key_F3;
            case 115:   return Qt::Key_F4;
            case 116:   return Qt::Key_F5;
            case 117:   return Qt::Key_F6;
            case 118:   return Qt::Key_F7;
            case 119:   return Qt::Key_F8;
            case 120:   return Qt::Key_F9;
            case 121:   return Qt::Key_F10;
            case 122:   return Qt::Key_F11;
            case 123:   return Qt::Key_F12;
            
            case 144:   return Qt::Key_NumLock;
            case 145:   return Qt::Key_ScrollLock;
            
            case 186:   return Qt::Key_Semicolon;
            case 187:   return Qt::Key_Equal;
            case 188:   return Qt::Key_Comma;
            //case 189:   return Qt::Key_;     // dash
            case 190:   return Qt::Key_Period;
            case 191:   return Qt::Key_Slash;  // forward slash
            //case 192:   return Qt::Key_;     // grave accent
            
            case 219:   return Qt::Key_BracketLeft;  // open bracket
            case 220:   return Qt::Key_Backslash;
            case 221:   return Qt::Key_BracketRight; // close bracket
            case 222:   return Qt::Key_QuoteLeft;    // single quote
        }
        return Qt::Key_unknown;
    }

    void Renderer::PostKeyboardEvent(const QVariantMap &data)
    {
        UiGraphicsView *view = (plugin_ ? plugin_->GetFramework()->Ui()->GraphicsView() : 0);
        if (!view)
            return;

        // type: 'keyDown' or 'keyUp'
        QEvent::Type type = (data.value("action", "").toString() == "keyDown" ? QEvent::KeyPress : QEvent::KeyRelease);

        // modifiers
        Qt::KeyboardModifiers modifiers = Qt::NoModifier;
        if (data.value("altKey", false).toBool())
            modifiers |= Qt::AltModifier;
        if (data.value("shiftKey", false).toBool())
            modifiers |= Qt::ShiftModifier;
        if (data.value("ctrlKey", false).toBool())
            modifiers |= Qt::ControlModifier;
        if (data.value("metaKey", false).toBool())
            modifiers |= Qt::MetaModifier;
            
        inputState_.keyboardModifiers = modifiers;
            
        Qt::Key key = KeyFromHtmlKeyCode(data.value("key").toInt());
        if (key == Qt::Key_unknown)
        {
            if (IsLogChannelEnabled(LogChannelDebug))
                qWarning() << "Failed to map HTML keyCode" << data.value("key").toInt() << "to a Qt key";
            return;
        }
        
        QString text = (key != Qt::Key_unknown ? QKeySequence(key).toString(QKeySequence::NativeText).toLower() : "");
        if (text == "space")
            text = " ";
        else if (text == "tab")
            text = "    ";
        else if (key == Qt::Key_Shift || key == Qt::Key_Control || key == Qt::Key_Alt || key == Qt::Key_AltGr)
            text = "";
        if (data.value("shiftKey", false).toBool())
            text = text.toUpper();
        
        QKeyEvent e(type, key, modifiers, text);
        if (IsLogChannelEnabled(LogChannelDebug))
            qDebug() << &e;
        QApplication::sendEvent(view, &e);
    }

    void Renderer::PostMouseEvent(const QVariantMap &data)
    {
        UiGraphicsView *view = (plugin_ ? plugin_->GetFramework()->Ui()->GraphicsView() : 0);
        UiMainWindow *window = (plugin_ ? plugin_->GetFramework()->Ui()->MainWindow() : 0);
        if (!view || !window)
            return;

        if (!data.contains("x") || !data.contains("y"))
            return;

        bool ok = false;
        float x = data.value("x").toFloat(&ok);
        if (!ok) return; ok = false;       
        if (x < 0.0f)
        {
            LogWarning(LC + "Received mouse event with x < 0.0 - Clamping to 0.0.");
            x = 0.0f;
        }
        else if (x > 1.0f)
        {
            LogWarning(LC + "Received mouse event with x > 1.0 - Clamping to 1.0.");
            x = 1.0f;
        }
        float y = data.value("y").toFloat(&ok);
        if (!ok) return;
        if (y < 0.0f)
        {
            LogWarning(LC + "Received mouse event with y < 0.0 - Clamping to 0.0");
            x = 0.0f;
        }
        else if (y > 1.0f)
        {
            LogWarning(LC + "Received mouse event with y > 1.0 - Clamping to 1.0");
            y = 1.0f;
        }

        // type: 'move', 'press', 'doublepress' or 'release'
        QString typeStr = data.value("action", "").toString();
        if (typeStr.isEmpty())
            return;
        QEvent::Type type = (typeStr == "move" ? QEvent::MouseMove : (typeStr == "press" ? QEvent::MouseButtonPress : (typeStr == "doublepress" ? QEvent::MouseButtonDblClick : QEvent::MouseButtonRelease)));       

        // buttons
        Qt::MouseButton button = Qt::NoButton;
        inputState_.mouseButtons = Qt::NoButton;

        // Check another extra prop if button could not be resolved.
        if (type == QEvent::MouseButtonRelease || type == QEvent::MouseButtonDblClick || type == QEvent::MouseButtonPress)
        {
            int releaseExtraCheck = data.value("which", -1).toInt();
            if (releaseExtraCheck == -1)
                releaseExtraCheck = data.value("button", -1).toInt();

            if (releaseExtraCheck == 1)
                button = Qt::LeftButton;
            else if (releaseExtraCheck == 3)
                button = Qt::RightButton;
            else if (releaseExtraCheck == 2)
                button = Qt::MiddleButton;
            if (button != Qt::NoButton)
                inputState_.mouseButtons = button;
        }

        // Additionally check all the buttons pressed down at the moment.
        if (button != Qt::LeftButton && data.value("leftButton", false).toBool())
        {
            if (button == Qt::NoButton)
                button = Qt::LeftButton;
            inputState_.mouseButtons |= Qt::LeftButton;
        }
        if (button != Qt::RightButton && data.value("rightButton", false).toBool())
        {
            if (button == Qt::NoButton)
                button = Qt::RightButton;
            inputState_.mouseButtons |= Qt::RightButton;
        }
        if (button != Qt::MiddleButton && data.value("middleButton", false).toBool())
        {
            if (button == Qt::NoButton)
                button = Qt::MiddleButton;
            inputState_.mouseButtons |= Qt::MiddleButton;
        }

        // position from [0.0, 1.0] to application window coordinates
        QRect renderingSurfaceRect = view->geometry();
        QPoint mousePos(renderingSurfaceRect.width() * x, renderingSurfaceRect.height() * y);
        QPoint globalPos(window->geometry().topLeft() + renderingSurfaceRect.topLeft() + mousePos);

        // release or press event: fake a mouse move to this coordinate first
        if (type == QEvent::MouseButtonPress)
        {
            Qt::MouseButton moveButton = (type == QEvent::MouseButtonRelease ? button : Qt::NoButton);
            QMouseEvent *e = QMouseEvent::createExtendedMouseEvent(QEvent::MouseMove, mousePos, globalPos, 
                 moveButton, moveButton, inputState_.keyboardModifiers); 
            QApplication::sendEvent(view->viewport(), e);
            SAFE_DELETE(e);
        }

        QMouseEvent *e = QMouseEvent::createExtendedMouseEvent(type, mousePos, globalPos, button, inputState_.mouseButtons, inputState_.keyboardModifiers); 
        QApplication::sendEvent(view->viewport(), e);
        SAFE_DELETE(e);
        
        // double press: fake a release event to make it register correctly
        if (type == QEvent::MouseButtonDblClick)
        {
            QMouseEvent *e = QMouseEvent::createExtendedMouseEvent(QEvent::MouseButtonRelease, mousePos, globalPos, 
                button, inputState_.mouseButtons, inputState_.keyboardModifiers); 
            QApplication::sendEvent(view->viewport(), e);
            SAFE_DELETE(e);
        }

        if ((type == QEvent::MouseButtonPress || type == QEvent::MouseButtonDblClick) && plugin_->GetFramework()->Input()->ItemUnderMouse() == 0)
            ClearInputFocus();
    }
    
    void Renderer::ClearInputFocus()
    {
        UiGraphicsView *view = (plugin_ ? plugin_->GetFramework()->Ui()->GraphicsView() : 0);
        if (view && view->scene() && view->scene()->focusItem())
        {
            view->scene()->setFocusItem(0);
            view->scene()->clearFocus();

            if (view->scene()->focusItem())
            {
                QFocusEvent fe(QEvent::FocusOut);
                QApplication::sendEvent(view->scene(), &fe);
            }
        }
    }
    
    void Renderer::OnDataChannelMessage(const CloudRenderingProtocol::BinaryMessageData &data)
    {
        OnDataChannelMessage(dynamic_cast<WebRTC::PeerConnection*>(sender()), data);
    }
    
    void Renderer::OnDataChannelMessage(WebRTC::PeerConnection *sender, const CloudRenderingProtocol::BinaryMessageData &data)
{'fraction_non_alphanumeric': 0.10613026063966222, 'fraction_numerical': 0.012745891127202416, 'mean_word_length': 1.7037234610050145, 'pattern_counts': {'":': 0, '<': 73, '<?xml version=': 0, '>': 200, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6068722', 'n_tokens_mistral': 13717, 'n_tokens_neox': 12851, 'n_words': 2829}