//
//  libavg - Media Playback Engine. 
//  Copyright (C) 2003-2014 Ulrich von Zadow
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
//  Current versions can be found at www.libavg.de
//

#include "Player.h"

#include "../avgconfigwrapper.h"

#include "AVGNode.h"
#include "DivNode.h"
#include "WordsNode.h"
#include "VideoNode.h"
#include "CameraNode.h"
#include "ImageNode.h"
#include "SoundNode.h"
#include "LineNode.h"
#include "RectNode.h"
#include "CurveNode.h"
#include "PolyLineNode.h"
#include "PolygonNode.h"
#include "CircleNode.h"
#include "MeshNode.h"
#include "FontStyle.h"
#include "PluginManager.h"
#include "TextEngine.h"
#include "TestHelper.h"
#include "MainCanvas.h"
#include "OffscreenCanvas.h"
#include "TrackerInputDevice.h"
#include "SDLDisplayEngine.h"
#include "MultitouchInputDevice.h"
#include "TUIOInputDevice.h"
#include "OGLSurface.h"
#ifdef __APPLE__
    #include "AppleTrackpadInputDevice.h"
#endif
#if defined(_WIN32) && defined(SM_DIGITIZER)
    #include "Win7TouchInputDevice.h"
#endif
#ifdef AVG_ENABLE_MTDEV
    #include "LibMTDevInputDevice.h"
#endif
#if defined(HAVE_XI2_1) || defined(HAVE_XI2_2) 
    #include "XInputMTInputDevice.h"
#endif
#include "Contact.h"
#include "KeyEvent.h"
#include "MouseEvent.h"
#include "EventDispatcher.h"
#include "PublisherDefinition.h"
#include "BitmapManager.h"

#include "../base/FileHelper.h"
#include "../base/StringHelper.h"
#include "../base/OSHelper.h"
#include "../base/Exception.h"
#include "../base/Logger.h"
#include "../base/ConfigMgr.h"
#include "../base/XMLHelper.h"
#include "../base/ScopeTimer.h"
#include "../base/WorkerThread.h"
#include "../base/DAG.h"

#include "../graphics/BitmapLoader.h"
#include "../graphics/ShaderRegistry.h"
#include "../graphics/Display.h"

#include "../imaging/Camera.h"

#include "../audio/AudioEngine.h"

#include <libxml/xmlmemory.h>

#ifdef _WIN32
#include <direct.h>
#define getcwd _getcwd
#endif

#include <iostream>

#ifdef __linux
#include <fenv.h>
#endif

#include <glib-object.h>
#include <typeinfo>

using namespace std;
using namespace boost;

namespace avg {

Player * Player::s_pPlayer=0;

Player::Player()
    : Publisher("Player"),
      m_pDisplayEngine(),
      m_bDisplayEngineBroken(false),
      m_bIsTraversingTree(false),
      m_pMultitouchInputDevice(),
      m_bInHandleTimers(false),
      m_bCurrentTimeoutDeleted(false),
      m_bKeepWindowOpen(false),
      m_bStopOnEscape(true),
      m_bIsPlaying(false),
      m_bFakeFPS(false),
      m_FakeFPS(0),
      m_FrameTime(0),
      m_Volume(1),
      m_bPythonAvailable(true),
      m_pLastMouseEvent(new MouseEvent(Event::CURSOR_MOTION, false, false, false, 
            IntPoint(-1, -1), MouseEvent::NO_BUTTON, glm::vec2(-1, -1), 0)),
      m_EventHookPyFunc(Py_None),
      m_bMouseEnabled(true)
{
    string sDummy;
#ifdef _WIN32
    if (getEnv("AVG_WIN_CRASH_SILENTLY", sDummy)) {
        DWORD dwMode = SetErrorMode(SEM_NOGPFAULTERRORBOX);
        SetErrorMode(dwMode | SEM_NOGPFAULTERRORBOX);
    }
#endif
#ifdef __linux
// Turning this on causes fp exceptions in the linux nvidia drivers.
//    feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
#endif
    setAffinityMask(true);

    if (s_pPlayer) {
        throw Exception(AVG_ERR_UNKNOWN, "Player has already been instantiated.");
    }
    ThreadProfiler* pProfiler = ThreadProfiler::get();
    pProfiler->setName("main");

    SDLDisplayEngine::initSDL();
    initConfig();

    FontStyle::registerType();
    Node::registerType();
    AreaNode::registerType();
    RasterNode::registerType();
    VectorNode::registerType();
    FilledVectorNode::registerType();

    DivNode::registerType();
    CanvasNode::registerType();
    OffscreenCanvasNode::registerType();
    AVGNode::registerType();
    ImageNode::registerType();
    WordsNode::registerType();
    VideoNode::registerType();
    CameraNode::registerType();
    SoundNode::registerType();
    LineNode::registerType();
    RectNode::registerType();
    CurveNode::registerType();
    PolyLineNode::registerType();
    PolygonNode::registerType();
    CircleNode::registerType();
    MeshNode::registerType();

    Contact::registerType();

    m_pTestHelper = TestHelperPtr(new TestHelper());

    s_pPlayer = this;

    m_CurDirName = getCWD();
    if (getEnv("AVG_BREAK_ON_IMPORT", sDummy)) {
        debugBreak();
    }
}

void deletePlayer()
{
    delete Player::s_pPlayer;
    Player::s_pPlayer = 0;
}

Player::~Player()
{
    m_pMainCanvas = MainCanvasPtr();
    if (m_pDisplayEngine) {
        m_pDisplayEngine->teardown();
    }
    SDLDisplayEngine::quitSDL();
}

Player* Player::get()
{
    if (!s_pPlayer) {
        s_pPlayer = new Player();
        atexit(deletePlayer);
    }
    return s_pPlayer;
}

bool Player::exists()
{
    return s_pPlayer != 0;
}

void Player::setResolution(bool bFullscreen, int width, int height, int bpp)
{
    errorIfPlaying("Player.setResolution");
    m_DP.m_bFullscreen = bFullscreen;
    if (bpp) {
        m_DP.m_BPP = bpp;
    }
    if (width) {
        m_DP.m_WindowSize.x = width;
    }
    if (height) {
        m_DP.m_WindowSize.y = height;
    }
}

bool Player::isFullscreen()
{
    return m_DP.m_bFullscreen;
}

void Player::setWindowFrame(bool bHasWindowFrame)
{
    errorIfPlaying("Player.setWindowFrame");
    m_DP.m_bHasWindowFrame = bHasWindowFrame;
}

void Player::setWindowPos(int x, int y)
{
    errorIfPlaying("Player.setWindowPos");
    m_DP.m_Pos.x = x;
    m_DP.m_Pos.y = y;
}

void Player::setWindowTitle(const string& sTitle)
{
    m_pDisplayEngine->setWindowTitle(sTitle);
}

void Player::useGLES(bool bGLES)
{
    errorIfPlaying("Player.useGLES");
    m_GLConfig.m_bGLES = bGLES;
#ifdef AVG_ENABLE_EGL
    m_GLConfig.m_bGLES = true;
#endif
    BitmapLoader::init(!m_GLConfig.m_bGLES);
}

void Player::setOGLOptions(bool bUsePOTTextures, bool bUsePixelBuffers, 
        int multiSampleSamples, GLConfig::ShaderUsage shaderUsage,
        bool bUseDebugContext)
{
    errorIfPlaying("Player.setOGLOptions");
    m_GLConfig.m_bUsePOTTextures = bUsePOTTextures;
    m_GLConfig.m_bUsePixelBuffers = bUsePixelBuffers;
    setMultiSampleSamples(multiSampleSamples);
    m_GLConfig.m_ShaderUsage = shaderUsage;
    m_GLConfig.m_bUseDebugContext = bUseDebugContext;
}

void Player::setMultiSampleSamples(int multiSampleSamples)
{
    errorIfPlaying("Player.setMultiSampleSamples");
    if (multiSampleSamples < 1) {
        throw Exception(AVG_ERR_OUT_OF_RANGE,
                "MultiSampleSamples must be 1 or greater (was " +
                toString(multiSampleSamples) + ").");
    }
    m_GLConfig.m_MultiSampleSamples = multiSampleSamples;
}

void Player::setAudioOptions(int samplerate, int channels)
{
    errorIfPlaying("Player.setAudioOptions");
    m_AP.m_SampleRate = samplerate;
    m_AP.m_Channels = channels;
}

void Player::enableGLErrorChecks(bool bEnable)
{
    GLContext::enableErrorChecks(bEnable);
}
        
glm::vec2 Player::getScreenResolution()
{
    return glm::vec2(Display::get()->getScreenResolution());
}

float Player::getPixelsPerMM()
{
    return Display::get()->getPixelsPerMM();
}

glm::vec2 Player::getPhysicalScreenDimensions()
{
    return Display::get()->getPhysicalScreenDimensions();
}

void Player::assumePixelsPerMM(float ppmm)
{
    Display::get()->assumePixelsPerMM(ppmm);
}

CanvasPtr Player::loadFile(const string& sFilename)
{
    errorIfPlaying("Player.loadFile");
    NodePtr pNode = loadMainNodeFromFile(sFilename);
    if (m_pMainCanvas) {
        cleanup(false);
    }

    initMainCanvas(pNode);
    
    return m_pMainCanvas;
}

CanvasPtr Player::loadString(const string& sAVG)
{
    errorIfPlaying("Player.loadString");
    if (m_pMainCanvas) {
        cleanup(false);
    }

    NodePtr pNode = loadMainNodeFromString(sAVG);
    initMainCanvas(pNode);

    return m_pMainCanvas;
}

OffscreenCanvasPtr Player::loadCanvasFile(const string& sFilename)
{
    NodePtr pNode = loadMainNodeFromFile(sFilename);
    return registerOffscreenCanvas(pNode);
}

OffscreenCanvasPtr Player::loadCanvasString(const string& sAVG)
{
    NodePtr pNode = loadMainNodeFromString(sAVG);
    return registerOffscreenCanvas(pNode);
}

CanvasPtr Player::createMainCanvas(const py::dict& params)
{
    errorIfPlaying("Player.createMainCanvas");
    if (m_pMainCanvas) {
        cleanup(false);
    }

    NodePtr pNode = createNode("avg", params);
    initMainCanvas(pNode);

    return m_pMainCanvas;
}

OffscreenCanvasPtr Player::createCanvas(const py::dict& params)
{
    NodePtr pNode = createNode("canvas", params);
    return registerOffscreenCanvas(pNode);
}

void Player::deleteCanvas(const string& sID)
{
    vector<OffscreenCanvasPtr>::iterator it;
    for (it = m_pCanvases.begin(); it != m_pCanvases.end(); ++it) {
        if ((*it)->getID() == sID) {
            if ((*it)->getNumDependentCanvases() > 0) {
                throw (Exception(AVG_ERR_INVALID_ARGS,
                        string("deleteCanvas: Canvas with id ")+sID
                        +" is still referenced."));
            }
            (*it)->stopPlayback(false);
            m_pCanvases.erase(it);
            return;
        }
    }
    throw(Exception(AVG_ERR_OUT_OF_RANGE,
            string("deleteCanvas: Canvas with id ")+sID+" does not exist."));
}

CanvasPtr Player::getMainCanvas() const
{
    return m_pMainCanvas;
}

OffscreenCanvasPtr Player::getCanvas(const string& sID) const
{
    OffscreenCanvasPtr pCanvas = findCanvas(sID);
    if (pCanvas) {
        return pCanvas;
    } else {
        throw (Exception(AVG_ERR_INVALID_ARGS,
                string("Player::getCanvas(): No canvas with id '")+sID+"' exists."));
    }
}

void Player::newCanvasDependency()
{
    DAG dag;
    for (unsigned i = 0; i < m_pCanvases.size(); ++i) {
        set<long> dependentCanvasSet;
        OffscreenCanvasPtr pCanvas = m_pCanvases[i];
        const vector<CanvasPtr>& pDependents = pCanvas->getDependentCanvases();
        for (unsigned j = 0; j < pDependents.size(); ++j) {
            dependentCanvasSet.insert(pDependents[j]->getHash());
        }
        dag.addNode(pCanvas->getHash(), dependentCanvasSet);
    }
    dag.addNode(m_pMainCanvas->getHash(), set<long>());

    vector<long> sortedCanvasIDs;
    try {
        dag.sort(sortedCanvasIDs);
    } catch (Exception&) {
        throw Exception(AVG_ERR_INVALID_ARGS, "Circular dependency between canvases.");
    }

    vector<OffscreenCanvasPtr> pTempCanvases = m_pCanvases;
    m_pCanvases.clear();
    for (unsigned i = 0; i < sortedCanvasIDs.size(); ++i) {
        long canvasID = sortedCanvasIDs[i];
        for (unsigned j = 0; j < pTempCanvases.size(); ++j) {
            OffscreenCanvasPtr pCandidateCanvas = pTempCanvases[j];
            if (pCandidateCanvas->getHash() == canvasID) {
                m_pCanvases.push_back(pCandidateCanvas);
                break;
            }
        }
    }
}

NodePtr Player::loadMainNodeFromFile(const string& sFilename)
{
    string sRealFilename;
    AVG_TRACE(Logger::category::MEMORY, Logger::severity::INFO,
           "Player::loadFile(" << sFilename << ")");

    // When loading an avg file, assets are loaded from a directory relative
    // to the file.
    char szBuf[1024];
    char * pBuf = getcwd(szBuf, 1024);
    if (sFilename[0] == '/') {
        sRealFilename = sFilename;
    } else {
        m_CurDirName = string(pBuf)+"/";
        sRealFilename = m_CurDirName+sFilename;
    }
    m_CurDirName = sRealFilename.substr(0, sRealFilename.rfind('/')+1);

    string sAVG;
    readWholeFile(sRealFilename, sAVG);
    NodePtr pNode = internalLoad(sAVG, sRealFilename);

    // Reset the directory to load assets from to the current dir.
    m_CurDirName = string(pBuf)+"/";
    return pNode;
}

NodePtr Player::loadMainNodeFromString(const string& sAVG)
{
    AVG_TRACE(Logger::category::MEMORY, Logger::severity::INFO, "Player::loadString()");

    string sEffectiveDoc = removeStartEndSpaces(sAVG);
    NodePtr pNode = internalLoad(sEffectiveDoc, "");
    return pNode;
}

void Player::play()
{
    try {
        if (!m_pMainCanvas) {
            throw Exception(AVG_ERR_NO_NODE, "Play called, but no xml file loaded.");
        }
        initPlayback();
        notifySubscribers("PLAYBACK_START");
        try {
            ThreadProfiler::get()->start();
            doFrame(true);
            while (!m_bStopping) {
                doFrame(false);
            }
            notifySubscribers("PLAYBACK_END");
        } catch (...) {
            cleanup(true);
            m_bDisplayEngineBroken = true;
            throw;
        }
        cleanup(false);
        AVG_TRACE(Logger::category::PLAYER, Logger::severity::INFO, "Playback ended.");
    } catch (Exception& ex) {
        m_bIsPlaying = false;
        AVG_LOG_ERROR(ex.getStr());
        throw;
    }
}

void Player::stop()
{
    if (m_bIsPlaying) {
        m_bStopping = true;
    } else {
        cleanup(false);
    }
}

bool Player::isStopping()
{
    return m_bStopping;
}

void Player::initPlayback(const std::string& sShaderPath)
{
    m_bIsPlaying = true;
    AVG_TRACE(Logger::category::PLAYER, Logger::severity::INFO, "Playback started.");
    initGraphics(sShaderPath);
    initAudio();
    try {
        for (unsigned i = 0; i < m_pCanvases.size(); ++i) {
            m_pCanvases[i]->initPlayback();
        }
        m_pMainCanvas->initPlayback(m_pDisplayEngine);
    } catch (Exception&) {
        cleanup(true);
        m_bDisplayEngineBroken = true;
        throw;
    }
    m_pEventDispatcher->addInputDevice(
            boost::dynamic_pointer_cast<IInputDevice>(m_pDisplayEngine));
    m_pEventDispatcher->addInputDevice(m_pTestHelper);

    m_pDisplayEngine->initRender();
    Display::get()->rereadScreenResolution();
    m_bStopping = false;
    if (m_pMultitouchInputDevice) {
        m_pMultitouchInputDevice->start();
    }

    m_FrameTime = 0;
    m_NumFrames = 0;
}

bool Player::isPlaying()
{
    return m_bIsPlaying;
}

void Player::setFramerate(float rate)
{
    if (m_bIsPlaying) {
        m_pDisplayEngine->setFramerate(rate);
    }
    m_DP.m_Framerate = rate;
    m_DP.m_VBRate = 0;
}

void Player::setVBlankFramerate(int rate)
{
    if (m_bIsPlaying) {
        m_pDisplayEngine->setVBlankRate(rate);
    }
    m_DP.m_Framerate = 0;
    m_DP.m_VBRate = rate;
}

float Player::getEffectiveFramerate()
{
    if (m_bIsPlaying) {
        if (m_bFakeFPS) {
            return m_FakeFPS;
        } else {
            return m_pDisplayEngine->getEffectiveFramerate();
        }
    } else {
        return 0;
    }
}

TestHelper * Player::getTestHelper()
{
    return m_pTestHelper.get();
}

void Player::setFakeFPS(float fps)
{
    if (fabs(fps + 1.0) < 0.0001) {
        // fps = -1
        m_bFakeFPS = false;
    } else {
        m_bFakeFPS = true;
        m_FakeFPS = fps;
    }

    if (AudioEngine::get()) {
        AudioEngine::get()->setAudioEnabled(!m_bFakeFPS);
    }
}

void Player::addInputDevice(IInputDevicePtr pSource)
{
    if (!m_pEventDispatcher) {
        throw Exception(AVG_ERR_UNSUPPORTED,
                "You must use loadFile() before addInputDevice().");
    }
    m_pEventDispatcher->addInputDevice(pSource);
}

long long Player::getFrameTime()
{
    return m_FrameTime;
}

float Player::getFrameDuration()
{
    if (!m_bIsPlaying) {
        throw Exception(AVG_ERR_UNSUPPORTED,
                "Must call Player.play() before getFrameDuration().");
    }
    if (m_bFakeFPS) {
        return 1000.0f/m_FakeFPS;
    } else {
        float framerate = m_pDisplayEngine->getEffectiveFramerate();
        if (framerate > 0) {
            return 1000.f/framerate;
        } else {
            return 0;
        }
    }
}

TrackerInputDevice * Player::getTracker()
{
    TrackerInputDevice* pTracker = dynamic_cast<TrackerInputDevice*>(
            m_pMultitouchInputDevice.get());
    return pTracker;
}

void Player::enableMultitouch()
{
    if (!m_bIsPlaying) {
        throw Exception(AVG_ERR_UNSUPPORTED,
                "Must call Player.play() before enableMultitouch().");
    }

    string sDriver;
    getEnv("AVG_MULTITOUCH_DRIVER", sDriver);
    if (sDriver == "") {
#if defined(_WIN32) && defined(SM_DIGITIZER)
        sDriver = "WIN7TOUCH";
#elif defined(HAVE_XI2_1) || defined(HAVE_XI2_2) 
        sDriver = "XINPUT";
#elif defined (AVG_ENABLE_MTDEV)
        sDriver = "LINUXMTDEV";
#else
        AVG_LOG_WARNING("Valid values for AVG_MULTITOUCH_DRIVER are WIN7TOUCH, XINPUT, LINUXMTDEV, TRACKER, TUIO and APPLETRACKPAD.");
        throw Exception(AVG_ERR_MT_INIT,
                "Multitouch support: No default driver available. Set AVG_MULTITOUCH_DRIVER.");
#endif
    }
    if (sDriver == "TUIO") {
        m_pMultitouchInputDevice = IInputDevicePtr(new TUIOInputDevice);
#if defined(_WIN32) && defined(SM_DIGITIZER)
    } else if (sDriver == "WIN7TOUCH") {
        m_pMultitouchInputDevice = IInputDevicePtr(new Win7TouchInputDevice);
#endif
    } else if (sDriver == "XINPUT" || sDriver == "XINPUT21") {
#if defined(HAVE_XI2_1) || defined(HAVE_XI2_2) 
        m_pMultitouchInputDevice =  IInputDevicePtr(new XInputMTInputDevice);
#else
        throw Exception(AVG_ERR_MT_INIT,
                "XInput multitouch event source: Support not configured.'");
#endif
#ifdef AVG_ENABLE_MTDEV
    } else if (sDriver == "LINUXMTDEV") {
        m_pMultitouchInputDevice = IInputDevicePtr(new LibMTDevInputDevice);
#endif
#ifdef __APPLE__
    } else if (sDriver == "APPLETRACKPAD") {
        m_pMultitouchInputDevice = IInputDevicePtr(new AppleTrackpadInputDevice);
#endif
    } else if (sDriver == "TRACKER") {
        m_pMultitouchInputDevice = IInputDevicePtr(new TrackerInputDevice);
    } else {
        AVG_LOG_WARNING("Valid values for AVG_MULTITOUCH_DRIVER are WIN7TOUCH, XINPUT, LINUXMTDEV, TRACKER, TUIO and APPLETRACKPAD.");
        throw Exception(AVG_ERR_UNSUPPORTED, string("Unsupported multitouch driver '")+
                sDriver +"'.");
    }
    if (m_bIsPlaying) {
        try {
            m_pMultitouchInputDevice->start();
        } catch (Exception&) {
            m_pMultitouchInputDevice = IInputDevicePtr();
            throw;
        }
    }
    addInputDevice(m_pMultitouchInputDevice);
}

void Player::enableMouse(bool enabled)
{
    m_bMouseEnabled = enabled;
    
    if (m_pEventDispatcher) {
        m_pEventDispatcher->enableMouse(enabled);
    }
}

bool Player::isMultitouchAvailable() const
{
    if (m_bIsPlaying) {
        return m_pMultitouchInputDevice != 0;
    } else {
        throw Exception(AVG_ERR_UNSUPPORTED,
                "Must call Player.play() before isMultitouchAvailable().");
    }
}

void Player::setEventCapture(NodePtr pNode, int cursorID=MOUSECURSORID)
{
    std::map<int, EventCaptureInfoPtr>::iterator it =
            m_EventCaptureInfoMap.find(cursorID);
    if (it != m_EventCaptureInfoMap.end()) {
        EventCaptureInfoPtr pCaptureInfo = it->second;
        NodePtr pOldNode = pCaptureInfo->m_pNode;
        if (pOldNode->getState() != Node::NS_UNCONNECTED) {
            if (pOldNode == pNode) {
                pCaptureInfo->m_CaptureCount++;
            } else {
                throw Exception(AVG_ERR_INVALID_CAPTURE, "setEventCapture called for '"
                        + pNode->getID() + "', but cursor already captured by '"
                        + pOldNode->getID() + "'.");
            }
        }
    } else {
        m_EventCaptureInfoMap[cursorID] = EventCaptureInfoPtr(
                new EventCaptureInfo(pNode));
    }
}

void Player::releaseEventCapture(int cursorID)
{
    std::map<int, EventCaptureInfoPtr>::iterator it =
            m_EventCaptureInfoMap.find(cursorID);
    if (it == m_EventCaptureInfoMap.end() || 
            (it->second->m_pNode->getState() == Node::NS_UNCONNECTED))
    {
        throw Exception(AVG_ERR_INVALID_CAPTURE,
                "releaseEventCapture called, but cursor not captured.");
    } else {
        it->second->m_CaptureCount--;
        if (it->second->m_CaptureCount == 0) {
            m_EventCaptureInfoMap.erase(cursorID);
        }
    }
}

bool Player::isCaptured(int cursorID)
{
    std::map<int, EventCaptureInfoPtr>::iterator it =
            m_EventCaptureInfoMap.find(cursorID);
    return (it != m_EventCaptureInfoMap.end());
}

void Player::removeDeadEventCaptures()
{
    std::map<int, EventCaptureInfoPtr>::iterator it;
    for (it = m_EventCaptureInfoMap.begin(); it != m_EventCaptureInfoMap.end();) {
        std::map<int, EventCaptureInfoPtr>::iterator lastIt = it;
        it++;
        if (lastIt->second->m_pNode->getState() == Node::NS_UNCONNECTED) {
            m_EventCaptureInfoMap.erase(lastIt);
        }
    }
}

int Player::setInterval(int time, PyObject * pyfunc)
{
    return internalSetTimeout(time, pyfunc, true);
}

int Player::setTimeout(int time, PyObject * pyfunc)
{
    return internalSetTimeout(time, pyfunc, false);
}

int Player::setOnFrameHandler(PyObject * pyfunc)
{
    avgDeprecationWarning("1.8", "Player.setOnFrameHandler", 
            "Player.subscribe(Player.ON_FRAME)");
    return internalSetTimeout(0, pyfunc, true);
}

bool Player::clearInterval(int id)
{
    vector<Timeout*>::iterator it;
    for (it = m_PendingTimeouts.begin(); it != m_PendingTimeouts.end(); it++) {
        if (id == (*it)->getID()) {
            if (it == m_PendingTimeouts.begin() && m_bInHandleTimers) {
                m_bCurrentTimeoutDeleted = true;
            }
            delete *it;
            m_PendingTimeouts.erase(it);
            return true;
        }
    }
    for (it = m_NewTimeouts.begin(); it != m_NewTimeouts.end(); it++) {
        if (id == (*it)->getID()) {
            delete *it;
            m_NewTimeouts.erase(it);
            return true;
        }
    }
    return false;
}

void Player::callFromThread(PyObject * pyfunc)
{
    lock_guard lock(m_AsyncCallMutex);
    Timeout* pTimeout = new Timeout(0, pyfunc, false, getFrameTime());
    m_AsyncCalls.push_back(pTimeout);
}

MouseEventPtr Player::getMouseState() const
{
    return m_pLastMouseEvent;
}

EventPtr Player::getCurrentEvent() const
{
    if (!m_pCurrentEvent) {
        throw Exception(AVG_ERR_UNSUPPORTED, "No current event.");
    }
    return m_pCurrentEvent;
}

void Player::setMousePos(const IntPoint& pos)
{
    m_pDisplayEngine->setMousePos(pos);
}

int Player::getKeyModifierState() const
{
    return m_pDisplayEngine->getKeyModifierState();
}

BitmapPtr Player::screenshot()
{
    if (!m_bIsPlaying) {
        throw Exception(AVG_ERR_UNSUPPORTED,
                "Must call Player.play() before screenshot().");
    }
    if (GLContext::getMain()->isGLES()) {
        // Some GLES implementations invalidate the buffer after eglSwapBuffers.
        // The only way we can get at the contents at this point is to rerender them.
        m_pMainCanvas->render(m_pDisplayEngine->getWindowSize(), false);
    }
    return m_pDisplayEngine->screenshot();
}

void Player::showCursor(bool bShow)
{
    if (m_pDisplayEngine) {
        m_pDisplayEngine->showCursor(bShow);
    }
    m_DP.m_bShowCursor = bShow;
}

bool Player::isCursorShown()
{
    return m_DP.m_bShowCursor;
}

void Player::setCursor(const Bitmap* pBmp, IntPoint hotSpot)
{
    IntPoint size = pBmp->getSize();
    if (size.x % 8 != 0 || size.y % 8 != 0 || pBmp->getPixelFormat() != R8G8B8A8) {
        throw Exception(AVG_ERR_INVALID_ARGS,
                "setCursor: Bitmap size must be divisible by 8 and in RGBA format.");
    }
    int i = -1;
    unsigned char * pData = new unsigned char[size.x*size.y/8];
    unsigned char * pMask = new unsigned char[size.x*size.y/8];
    Pixel32 * pLine = (Pixel32*)(pBmp->getPixels());
    int stride = pBmp->getStride()/4;
    for (int y = 0; y < size.y; ++y) {
        Pixel32 * pPixel = pLine;
        for (int x = 0; x < size.x; ++x) {
            if (x % 8 == 0) {
                i++;
                pData[i] = 0;
                pMask[i] = 0;
            } else {
                pData[i] <<= 1;
                pMask[i] <<= 1;
            }
            if (pPixel->getA() > 127) {
                pMask[i] |= 0x01;
                if (pPixel->getR() < 128) {
                    // Black Pixel
                    pData[i] |= 0x01;
                }
            }
            pPixel++;
        }
        pLine += stride;
    }
    SDL_Cursor * pCursor = SDL_CreateCursor(pData, pMask, size.x, size.y,
            hotSpot.x, hotSpot.y);
    SDL_SetCursor(pCursor);
    delete[] pData;
    delete[] pMask;
}

NodePtr Player::getElementByID(const std::string& sID)
{
    if (m_pMainCanvas) {
        return m_pMainCanvas->getElementByID(sID);
    } else {
        return NodePtr();
    }
}

AVGNodePtr Player::getRootNode()
{
    if (m_pMainCanvas) {
        return dynamic_pointer_cast<AVGNode>(m_pMainCanvas->getRootNode());
    } else {
        return AVGNodePtr();
    }
}

string Player::getCurDirName()
{
    return m_CurDirName;
}

std::string Player::getRootMediaDir()
{
    string sMediaDir;
    if (m_pMainCanvas) {
        sMediaDir = m_pMainCanvas->getRootNode()->getEffectiveMediaDir();
    } else {
        sMediaDir = m_CurDirName;
    }
    return sMediaDir;
}

void Player::disablePython()
{
    m_bPythonAvailable = false;
}

void Player::startTraversingTree()
{
    AVG_ASSERT(!m_bIsTraversingTree);
    m_bIsTraversingTree = true;
}

void Player::endTraversingTree()
{
    AVG_ASSERT(m_bIsTraversingTree);
    m_bIsTraversingTree = false;
}

bool Player::isTraversingTree() const
{
    return m_bIsTraversingTree;
}

void Player::registerFrameEndListener(IFrameEndListener* pListener)
{
    AVG_ASSERT(m_pMainCanvas);
    m_pMainCanvas->registerFrameEndListener(pListener);
}

void Player::unregisterFrameEndListener(IFrameEndListener* pListener)
{
    if (m_pMainCanvas) {
        m_pMainCanvas->unregisterFrameEndListener(pListener);
    }
}

void Player::registerPlaybackEndListener(IPlaybackEndListener* pListener)
{
    AVG_ASSERT(m_pMainCanvas);
    m_pMainCanvas->registerPlaybackEndListener(pListener);
}

void Player::unregisterPlaybackEndListener(IPlaybackEndListener* pListener)
{
    if (m_pMainCanvas) {
        m_pMainCanvas->unregisterPlaybackEndListener(pListener);
    }
}

void Player::registerPreRenderListener(IPreRenderListener* pListener)
{
    AVG_ASSERT(m_pMainCanvas);
    m_pMainCanvas->registerPreRenderListener(pListener);
}

void Player::unregisterPreRenderListener(IPreRenderListener* pListener)
{
    if (m_pMainCanvas) {
        m_pMainCanvas->unregisterPreRenderListener(pListener);
    }
}

bool Player::handleEvent(EventPtr pEvent)
{
    AVG_ASSERT(pEvent);
    EventPtr pLastEvent = m_pCurrentEvent;
    m_pCurrentEvent = pEvent;
    if (MouseEventPtr pMouseEvent = boost::dynamic_pointer_cast<MouseEvent>(pEvent)) {
        m_pLastMouseEvent = pMouseEvent;
    }

    if (CursorEventPtr pCursorEvent = boost::dynamic_pointer_cast<CursorEvent>(pEvent)) {
        if (pEvent->getType() == Event::CURSOR_OUT ||
                pEvent->getType() == Event::CURSOR_OVER)
        {
            pEvent->trace();
            pCursorEvent->getNode()->handleEvent(pEvent);
        } else {
            handleCursorEvent(pCursorEvent);
        }
    }
    else if (KeyEventPtr pKeyEvent = boost::dynamic_pointer_cast<KeyEvent>(pEvent))
    {
        pEvent->trace();
        switch (pEvent->getType()) {
            case Event::KEY_DOWN:
                notifySubscribers("KEY_DOWN", pEvent);
                break;
            case Event::KEY_UP:
                notifySubscribers("KEY_UP", pEvent);
                break;
            default:
                AVG_ASSERT(false);
        }
        getRootNode()->handleEvent(pKeyEvent);
        if (getStopOnEscape() && pEvent->getType() == Event::KEY_DOWN
                && pKeyEvent->getKeyCode() == avg::key::KEY_ESCAPE)
        {
            stop();
        }
    }
    else {
        if (pEvent->getType() != Event::QUIT) {
            pEvent->trace();
            getRootNode()->handleEvent(pEvent);
        }
        else {
            stop();
        }
    }
    m_pCurrentEvent = pLastEvent;
    return true;
}

static ProfilingZoneID MainProfilingZone("Player - Total frame time");
static ProfilingZoneID TimersProfilingZone("Player - handleTimers");
static ProfilingZoneID EventsProfilingZone("Dispatch events");
static ProfilingZoneID MainCanvasProfilingZone("Main canvas rendering");
static ProfilingZoneID OffscreenProfilingZone("Offscreen rendering");

void Player::doFrame(bool bFirstFrame)
{
    {
        ScopeTimer Timer(MainProfilingZone);
        if (!bFirstFrame) {
            m_NumFrames++;
            if (m_bFakeFPS) {
                m_FrameTime = (long long)((m_NumFrames*1000.0)/m_FakeFPS);
            } else {
                m_FrameTime = m_pDisplayEngine->getDisplayTime();
            }
            {
                ScopeTimer Timer(TimersProfilingZone);
                handleTimers();
            }
            {
                ScopeTimer Timer(EventsProfilingZone);
                m_pEventDispatcher->dispatch();
                sendFakeEvents();
                removeDeadEventCaptures();
            }
        }
        for (unsigned i = 0; i < m_pCanvases.size(); ++i) {
            ScopeTimer Timer(OffscreenProfilingZone);
            dispatchOffscreenRendering(m_pCanvases[i].get());
        }
        {
            ScopeTimer Timer(MainCanvasProfilingZone);
            m_pMainCanvas->doFrame(m_bPythonAvailable);
        }
        GLContext::mandatoryCheckError("End of frame");
        if (m_bPythonAvailable) {
            Py_BEGIN_ALLOW_THREADS;
            try {
                endFrame();
            } catch(...) {
                Py_BLOCK_THREADS;
                throw;
            }
            Py_END_ALLOW_THREADS;
        } else {
            endFrame();
        }
    }
    ThreadProfiler::get()->reset();
    if (m_NumFrames == 5) {
        ThreadProfiler::get()->restart();
    }
}

void Player::endFrame()
{
    m_pDisplayEngine->frameWait();
    m_pDisplayEngine->swapBuffers();
    m_pDisplayEngine->checkJitter();
}

float Player::getFramerate()
{
    if (!m_pDisplayEngine) {
        return m_DP.m_Framerate;
    }
    return m_pDisplayEngine->getFramerate();
}

float Player::getVideoRefreshRate()
{
    return Display::get()->getRefreshRate();
}

size_t Player::getVideoMemInstalled()
{
    if (!m_pDisplayEngine) {
        throw Exception(AVG_ERR_UNSUPPORTED,
                "Player.getVideoMemInstalled must be called after Player.play().");
    }
    return GLContext::getMain()->getVideoMemInstalled();
}

size_t Player::getVideoMemUsed()
{
    if (!m_pDisplayEngine) {
        throw Exception(AVG_ERR_UNSUPPORTED,
                "Player.getVideoMemUsed must be called after Player.play().");
    }
    return GLContext::getMain()->getVideoMemUsed();
}

void Player::setGamma(float red, float green, float blue)
{
    if (m_pDisplayEngine) {
        m_pDisplayEngine->setGamma(red, green, blue);
    }
    m_DP.m_Gamma[0] = red;
    m_DP.m_Gamma[1] = green;
    m_DP.m_Gamma[2] = blue;
}

void Player::initConfig()
{
    // Get data from config files.
    ConfigMgr* pMgr = ConfigMgr::get();

    m_DP.m_BPP = atoi(pMgr->getOption("scr", "bpp")->c_str());
    if (m_DP.m_BPP != 15 && m_DP.m_BPP != 16 && m_DP.m_BPP != 24 && m_DP.m_BPP != 32) {
        AVG_LOG_ERROR("BPP must be 15, 16, 24 or 32. Current value is "
                << m_DP.m_BPP << ". Aborting." );
        exit(-1);
    }
    m_DP.m_bFullscreen = pMgr->getBoolOption("scr", "fullscreen", false);

    m_DP.m_WindowSize.x = atoi(pMgr->getOption("scr", "windowwidth")->c_str());
    m_DP.m_WindowSize.y = atoi(pMgr->getOption("scr", "windowheight")->c_str());

    if (m_DP.m_bFullscreen && (m_DP.m_WindowSize != IntPoint(0, 0))) {
        AVG_LOG_ERROR("Can't set fullscreen and window size at once. Aborting.");
        exit(-1);
    }
    if (m_DP.m_WindowSize.x != 0 && m_DP.m_WindowSize.y != 0) {
        AVG_LOG_ERROR("Can't set window width and height at once");
        AVG_LOG_ERROR("(aspect ratio is determined by avg file). Aborting.");
        exit(-1);
    }

    m_AP.m_Channels = atoi(pMgr->getOption("aud", "channels")->c_str());
    m_AP.m_SampleRate = atoi(pMgr->getOption("aud", "samplerate")->c_str());
    m_AP.m_OutputBufferSamples =
            atoi(pMgr->getOption("aud", "outputbuffersamples")->c_str());

    m_GLConfig.m_bGLES = pMgr->getBoolOption("scr", "gles", false);
    m_GLConfig.m_bUsePOTTextures = pMgr->getBoolOption("scr", "usepow2textures", false);

    m_GLConfig.m_bUsePixelBuffers = pMgr->getBoolOption("scr", "usepixelbuffers", true);
    int multiSampleSamples = pMgr->getIntOption("scr", "multisamplesamples", 8);
    if (multiSampleSamples < 1) {
        AVG_LOG_ERROR("multisamplesamples must be >= 1. Aborting")
        exit(-1);
    }
    m_GLConfig.m_MultiSampleSamples = multiSampleSamples;

    string sShaderUsage;
    pMgr->getStringOption("scr", "shaderusage", "auto", sShaderUsage);
    if (sShaderUsage == "full") {
        m_GLConfig.m_ShaderUsage = GLConfig::FULL;
    } else if (sShaderUsage == "minimal") {
        m_GLConfig.m_ShaderUsage = GLConfig::MINIMAL;
    } else if (sShaderUsage == "auto") {
        m_GLConfig.m_ShaderUsage = GLConfig::AUTO;
    } else {
        throw Exception(AVG_ERR_OUT_OF_RANGE,
               "avgrc parameter shaderusage must be full, minimal, fragmentonly or auto");
    }
    string sDummy;
    m_GLConfig.m_bUseDebugContext = getEnv("AVG_USE_DEBUG_GL_CONTEXT", sDummy);
#ifdef AVG_ENABLE_EGL
    m_GLConfig.m_bGLES = true;
#endif
    BitmapLoader::init(!m_GLConfig.m_bGLES);

    pMgr->getGammaOption("scr", "gamma", m_DP.m_Gamma);
}

void Player::initGraphics(const string& sShaderPath)
{
    if (!Display::isInitialized()) {
        ConfigMgr* pMgr = ConfigMgr::get();
        float dotsPerMM = float(atof(pMgr->getOption("scr", "dotspermm")->c_str()));
        Display::get()->assumePixelsPerMM(dotsPerMM);
    }
    // Init display configuration.
    AVG_TRACE(Logger::category::CONFIG, Logger::severity::INFO,
            "Display bpp: " << m_DP.m_BPP);

    if (m_bDisplayEngineBroken) {
        m_bDisplayEngineBroken = false;
        m_pDisplayEngine->teardown();
        m_pDisplayEngine = SDLDisplayEnginePtr();
    }

    if (!m_pDisplayEngine) {
        m_pDisplayEngine = SDLDisplayEnginePtr(new SDLDisplayEngine());
    }
    AVG_TRACE(Logger::category::CONFIG, Logger::severity::INFO,
            "Requested OpenGL configuration: ");
    m_GLConfig.log();
    m_DP.m_WindowSize = m_pDisplayEngine->calcWindowSize(m_DP);
    if (m_pDisplayEngine->getWindowSize() != m_DP.m_WindowSize ||
            m_pDisplayEngine->isFullscreen() == true) 
    {
        m_pDisplayEngine->teardown();
        m_pDisplayEngine->init(m_DP, m_GLConfig);
    }
    AVG_TRACE(Logger::category::CONFIG, Logger::severity::INFO,
            "Pixels per mm: " << Display::get()->getPixelsPerMM());
    if (sShaderPath != "") {
        ShaderRegistry::get()->setShaderPath(sShaderPath);
    }
    m_pDisplayEngine->setGamma(1.0, 1.0, 1.0);
    m_GLConfig = GLContext::getCurrent()->getConfig();
}

void Player::initAudio()
{
    AudioEngine* pAudioEngine = AudioEngine::get();
    if (!pAudioEngine) {
        pAudioEngine = new AudioEngine();
    }
    pAudioEngine->init(m_AP, m_Volume);
    pAudioEngine->setAudioEnabled(!m_bFakeFPS);
    pAudioEngine->play();
}

void Player::initMainCanvas(NodePtr pRootNode)
{
    m_pEventDispatcher = EventDispatcherPtr(new EventDispatcher(this, m_bMouseEnabled));
    m_pMainCanvas = MainCanvasPtr(new MainCanvas(this));
    m_pMainCanvas->setRoot(pRootNode);
    m_DP.m_Size = m_pMainCanvas->getSize();

    registerFrameEndListener(BitmapManager::get());
}

NodePtr Player::internalLoad(const string& sAVG, const string& sFilename)
{
    XMLParser parser;
    parser.setDTD(TypeRegistry::get()->getDTD(), "avg.dtd");
    parser.parse(sAVG, sFilename);
    xmlNodePtr xmlNode = parser.getRootNode();
    NodePtr pNode = createNodeFromXml(parser.getDoc(), xmlNode);
    if (!pNode) {
        throw (Exception(AVG_ERR_XML_PARSE,
                "Root node of an avg tree needs to be an <avg> node."));
    }
    return pNode;
}

SDLDisplayEnginePtr Player::safeGetDisplayEngine()
{
    if (!m_pDisplayEngine) {
        m_pDisplayEngine = SDLDisplayEnginePtr(new SDLDisplayEngine());
    }
    return m_pDisplayEngine;

}

NodePtr Player::createNode(const string& sType,
        const py::dict& params, const boost::python::object& self)
{
    DivNodePtr pParentNode;
    py::dict attrs = params;
    py::object parent;
    if (params.has_key("parent")) {
        parent = params["parent"];
        attrs.attr("__delitem__")("parent");
        pParentNode = py::extract<DivNodePtr>(parent);
    }
    NodePtr pNode = dynamic_pointer_cast<Node>(
            TypeRegistry::get()->createObject(sType, attrs));

    // See if the class names of self and pNode match. If they don't, there is a
    // python derived class that's being constructed and we can't set parent here.
    string sSelfClassName = py::extract<string>(
            self.attr("__class__").attr("__name__"));
    py::object pythonClassName = 
            (py::object(pNode).attr("__class__").attr("__name__"));
    string sThisClassName = py::extract<string>(pythonClassName);
    bool bHasDerivedClass = sSelfClassName != sThisClassName && 
            sSelfClassName != "NoneType";
    if (bHasDerivedClass) {
        if (pParentNode) {
            throw Exception(AVG_ERR_UNSUPPORTED,
                    "Can't pass 'parent' parameter to C++ class constructor if there is a derived python class. Use Node.registerInstance() instead.");
        }
        pNode->registerInstance(self.ptr(), pParentNode);
    } else {
        pNode->registerInstance(0, pParentNode);
    } 
    if (parent) {
        attrs["parent"] = parent;
    }
    return pNode;
}

NodePtr Player::createNodeFromXmlString(const string& sXML)
{
    xmlPedanticParserDefault(1);
    xmlDoValidityCheckingDefaultValue =0;

    XMLParser parser;
    parser.setDTD(TypeRegistry::get()->getDTD(), "avg.dtd");
    parser.parse(sXML, "");

//        cvp->error = xmlParserValidityError;
//        cvp->warning = xmlParserValidityWarning;
    xmlNodePtr xmlNode = parser.getRootNode();
    NodePtr pNode = createNodeFromXml(parser.getDoc(), xmlNode);

    return pNode;
}

NodePtr Player::createNodeFromXml(const xmlDocPtr xmlDoc,
        const xmlNodePtr xmlNode)
{
    const char * nodeType = (const char *)xmlNode->name;

    if (!strcmp (nodeType, "text") || !strcmp (nodeType, "comment")) {
        // Ignore whitespace & comments
        return NodePtr();
    }
    NodePtr pCurNode = dynamic_pointer_cast<Node>(
            TypeRegistry::get()->createObject(nodeType, xmlNode));
    if (!strcmp(nodeType, "words")) {
        // TODO: This is an end-run around the generic serialization mechanism
        // that will probably break at some point.
        string s = getXmlChildrenAsString(xmlDoc, xmlNode);
        boost::dynamic_pointer_cast<WordsNode>(pCurNode)->setTextFromNodeValue(s);
    } else {
        // If this is a container, recurse into children
        if (pCurNode->getDefinition()->hasChildren()) {
            xmlNodePtr curXmlChild = xmlNode->xmlChildrenNode;
            while (curXmlChild) {
                NodePtr curChild = createNodeFromXml(xmlDoc, curXmlChild);
                if (curChild) {
                    DivNodePtr pDivNode = boost::dynamic_pointer_cast<DivNode>(pCurNode);
                    pDivNode->appendChild(curChild);
                }
                curXmlChild = curXmlChild->next;
            }
        }
    }
    return pCurNode;
}

OffscreenCanvasPtr Player::registerOffscreenCanvas(NodePtr pNode)
{
    OffscreenCanvasPtr pCanvas(new OffscreenCanvas(this));
    pCanvas->setRoot(pNode);
    if (findCanvas(pCanvas->getID())) {
        throw (Exception(AVG_ERR_INVALID_ARGS,
                string("Duplicate canvas id ")+pCanvas->getID()));
    }
    m_pCanvases.push_back(pCanvas);
    if (m_bIsPlaying) {
        try {
            pCanvas->initPlayback();
        } catch (...) {
            m_pCanvases.pop_back();
            throw;
        }
    }
    return pCanvas;
}

OffscreenCanvasPtr Player::findCanvas(const string& sID) const
{
    for (unsigned i=0; i<m_pCanvases.size(); ++i) {
        if (m_pCanvases[i]->getID() == sID) {
            return m_pCanvases[i];
        }
    }
    return OffscreenCanvasPtr();
}

void Player::sendFakeEvents()
{
    std::map<int, CursorStatePtr>::iterator it;
    for (it = m_pLastCursorStates.begin(); it != m_pLastCursorStates.end(); ++it) {
        CursorStatePtr pState = it->second;
        handleCursorEvent(pState->getLastEvent(), true);
    }
}

void Player::sendOver(const CursorEventPtr pOtherEvent, Event::Type type,
        NodePtr pNode)
{
    if (pNode) {
        CursorEventPtr pNewEvent = pOtherEvent->cloneAs(type);
        pNewEvent->setNode(pNode);
        m_pEventDispatcher->sendEvent(pNewEvent);
    }
}

void Player::handleCursorEvent(CursorEventPtr pEvent, bool bOnlyCheckCursorOver)
{
    // Find all nodes under the cursor.
    vector<NodePtr> pCursorNodes;
    DivNodePtr pEventReceiverNode = pEvent->getInputDevice()->getEventReceiverNode();
    if (!pEventReceiverNode) {
        pEventReceiverNode = getRootNode();
    }
    pEventReceiverNode->getElementsByPos(pEvent->getPos(), pCursorNodes);
    ContactPtr pContact = pEvent->getContact();
    if (pContact && !bOnlyCheckCursorOver) {
        if (!pCursorNodes.empty()) {
            NodePtr pNode = *(pCursorNodes.begin());
            pEvent->setNode(pNode);
        }
        pContact->sendEventToListeners(pEvent);
    }
        
    int cursorID = pEvent->getCursorID();

    // Determine the nodes the event should be sent to.
    vector<NodePtr> pDestNodes = pCursorNodes;
    if (m_EventCaptureInfoMap.find(cursorID) != m_EventCaptureInfoMap.end()) {
        NodeWeakPtr pEventCaptureNode = 
                m_EventCaptureInfoMap[cursorID]->m_pNode;
        if (pEventCaptureNode.expired()) {
            m_EventCaptureInfoMap.erase(cursorID);
        } else {
            pDestNodes = pEventCaptureNode.lock()->getParentChain();
        }
    }

    vector<NodePtr> pLastCursorNodes;
    {
        map<int, CursorStatePtr>::iterator it;
        it = m_pLastCursorStates.find(cursorID);
        if (it != m_pLastCursorStates.end()) {
            pLastCursorNodes = it->second->getNodes();
        }
    }

    // Send out events.
    vector<NodePtr>::const_iterator itLast;
    vector<NodePtr>::iterator itCur;
    for (itLast = pLastCursorNodes.begin(); itLast != pLastCursorNodes.end(); 
            ++itLast)
    {
        NodePtr pLastNode = *itLast;
        for (itCur = pCursorNodes.begin(); itCur != pCursorNodes.end(); ++itCur) {
            if (*itCur == pLastNode) {
                break;
            }
        }
        if (itCur == pCursorNodes.end()) {
            sendOver(pEvent, Event::CURSOR_OUT, pLastNode);
        }
    }

    // Send over events.
    for (itCur = pCursorNodes.begin(); itCur != pCursorNodes.end(); ++itCur) {
        NodePtr pCurNode = *itCur;
        for (itLast = pLastCursorNodes.begin(); itLast != pLastCursorNodes.end();
                ++itLast)
        {
            if (*itLast == pCurNode) {
                break;
            }
        }
        if (itLast == pLastCursorNodes.end()) {
            sendOver(pEvent, Event::CURSOR_OVER, pCurNode);
        }
    }

    if (!bOnlyCheckCursorOver) {
        // Iterate through the nodes and send the event to all of them.
        vector<NodePtr>::iterator it;
        for (it = pDestNodes.begin(); it != pDestNodes.end(); ++it) {
            NodePtr pNode = *it;
            if (pNode->getState() != Node::NS_UNCONNECTED) {
                pEvent->setNode(pNode);
                if (pEvent->getType() != Event::CURSOR_MOTION) {
                    pEvent->trace();
                }
                if (pNode->handleEvent(pEvent) == true) {
                    // stop bubbling
                    break;
                }
            }
        }
    }

    if (pEvent->getType() == Event::CURSOR_UP && pEvent->getSource() != Event::MOUSE) {
        // Cursor has disappeared: send out events.
        vector<NodePtr>::iterator it;
        for (it = pCursorNodes.begin(); it != pCursorNodes.end(); ++it) {
            NodePtr pNode = *it;
            sendOver(pEvent, Event::CURSOR_OUT, pNode);
        }
        m_pLastCursorStates.erase(cursorID);
    } else {
        // Update list of nodes under cursor
        if (m_pLastCursorStates.find(cursorID) != m_pLastCursorStates.end()) {
            m_pLastCursorStates[cursorID]->setInfo(pEvent, pCursorNodes);
        } else {
            m_pLastCursorStates[cursorID] =
                    CursorStatePtr(new CursorState(pEvent, pCursorNodes));
        }
    }
}

void Player::dispatchOffscreenRendering(OffscreenCanvas* pOffscreenCanvas)
{
    if (!pOffscreenCanvas->getAutoRender()) {
        return;
    }
    if (pOffscreenCanvas->hasRegisteredCamera()) {
        pOffscreenCanvas->updateCameraImage();
        while (pOffscreenCanvas->isCameraImageAvailable()) {
            pOffscreenCanvas->doFrame(m_bPythonAvailable);
            pOffscreenCanvas->updateCameraImage();
        }
    } else {
        pOffscreenCanvas->doFrame(m_bPythonAvailable);
        return;
    }
}

void Player::errorIfPlaying(const std::string& sFunc) const
{
    if (m_bIsPlaying) {
        throw Exception(AVG_ERR_UNSUPPORTED,
                sFunc + " must be called before Player.play().");
    }
}

void Player::handleTimers()
{
    vector<Timeout *>::iterator it;
    m_bInHandleTimers = true;

    it = m_PendingTimeouts.begin();
    while (it != m_PendingTimeouts.end() && (*it)->isReady(getFrameTime())
            && !m_bStopping)
    {
        (*it)->fire(getFrameTime());
        if (m_bCurrentTimeoutDeleted) {
            it = m_PendingTimeouts.begin();
        } else {
            if ((*it)->isInterval()) {
                Timeout* pTempTimeout = *it;
                it = m_PendingTimeouts.erase(it);
                m_NewTimeouts.insert(m_NewTimeouts.begin(), pTempTimeout);
            } else {
                delete *it;
                it = m_PendingTimeouts.erase(it);
            }
        }
        m_bCurrentTimeoutDeleted = false;
    }
    for (it = m_NewTimeouts.begin(); it != m_NewTimeouts.end(); ++it) {
        addTimeout(*it);
    }
    m_NewTimeouts.clear();
    
    notifySubscribers("ON_FRAME");
    
    m_bInHandleTimers = false;

    if (m_bPythonAvailable) {
        std::vector<Timeout *> tempAsyncCalls;
        Py_BEGIN_ALLOW_THREADS;
        {
            lock_guard lock(m_AsyncCallMutex);
            tempAsyncCalls = m_AsyncCalls;
            m_AsyncCalls.clear();
        }
        Py_END_ALLOW_THREADS;
        for (it = tempAsyncCalls.begin(); it != tempAsyncCalls.end(); ++it) {
            (*it)->fire(getFrameTime());
            delete *it;
        }
    }
}

SDLDisplayEngine * Player::getDisplayEngine() const
{
    return m_pDisplayEngine.get();
}

void Player::keepWindowOpen()
{
    m_bKeepWindowOpen = true;
}

void Player::setStopOnEscape(bool bStop)
{
    m_bStopOnEscape = bStop;
}

bool Player::getStopOnEscape() const
{
    return m_bStopOnEscape;
}

void Player::setVolume(float volume)
{
    m_Volume = volume;
    if (AudioEngine::get()) {
        AudioEngine::get()->setVolume(m_Volume);
    }
}

float Player::getVolume() const
{
    return m_Volume;
}

string Player::getConfigOption(const string& sSubsys, const string& sName) const
{
    const string* psValue = ConfigMgr::get()->getOption(sSubsys, sName);
    if (!psValue) {
        throw Exception(AVG_ERR_INVALID_ARGS, string("Unknown config option ") + sSubsys
                + ":" + sName);
    } else {
        return *psValue;
    }
}

bool Player::isUsingGLES() const
{
    return m_GLConfig.m_bGLES;
}

bool Player::areFullShadersSupported() const
{
    if (!m_bIsPlaying) {
        throw Exception(AVG_ERR_UNSUPPORTED,
                "Must call Player.play() before areFullShadersSupported().");
    }
    return (m_GLConfig.m_ShaderUsage == GLConfig::FULL);
}

OffscreenCanvasPtr Player::getCanvasFromURL(const std::string& sURL)
{
    if (sURL.substr(0, 7) != "canvas:") {
        throw Exception(AVG_ERR_CANT_PARSE_STRING,
                string("Invalid canvas url :'")+sURL+"'");
    }
    string sCanvasID = sURL.substr(7);
    for (unsigned i=0; i < m_pCanvases.size(); ++i) {
        if (m_pCanvases[i]->getID() == sCanvasID) {
            return m_pCanvases[i];
        }
    }
    throw Exception(AVG_ERR_CANT_PARSE_STRING,
            string("Canvas with url '")+sURL+"' not found.");
}

void Player::cleanup(bool bIsAbort)
{
    // Kill all timeouts.
    vector<Timeout*>::iterator it;
    for (it = m_PendingTimeouts.begin(); it != m_PendingTimeouts.end(); it++) {
        delete *it;
    }
    m_PendingTimeouts.clear();
    m_EventCaptureInfoMap.clear();
    m_pLastCursorStates.clear();
    m_pTestHelper->reset();
    ThreadProfiler::get()->dumpStatistics();
    if (m_pMainCanvas) {
        unregisterFrameEndListener(BitmapManager::get());
        delete BitmapManager::get();
        m_pMainCanvas->stopPlayback(bIsAbort);
        m_pMainCanvas = MainCanvasPtr();
    }

    if (m_pMultitouchInputDevice) {
        m_pMultitouchInputDevice = IInputDevicePtr();
    }
    for (unsigned i = 0; i < m_pCanvases.size(); ++i) {
        m_pCanvases[i]->stopPlayback(bIsAbort);
    }
    m_pCanvases.clear();

    if (m_pDisplayEngine) {
        m_DP.m_WindowSize = IntPoint(0,0);
        if (!m_bKeepWindowOpen) {
            m_pDisplayEngine->deinitRender();
            m_pDisplayEngine->teardown();
            m_pDisplayEngine = SDLDisplayEnginePtr();
        }
    }
    if (AudioEngine::get()) {
        AudioEngine::get()->teardown();
    }
    m_pEventDispatcher = EventDispatcherPtr();
    m_pLastMouseEvent = MouseEventPtr(new MouseEvent(Event::CURSOR_MOTION, false, false, 
            false, IntPoint(-1, -1), MouseEvent::NO_BUTTON, glm::vec2(-1, -1), 0));

    m_FrameTime = 0;
    m_bIsPlaying = false;

    m_CurDirName = getCWD();

    removeSubscribers();
}

int Player::internalSetTimeout(int time, PyObject * pyfunc, bool bIsInterval)
{
    Timeout* pTimeout = new Timeout(time, pyfunc, bIsInterval, getFrameTime());
    if (m_bInHandleTimers) {
        m_NewTimeouts.push_back(pTimeout);
    } else {
        addTimeout(pTimeout);
    }
    return pTimeout->getID();
}


int Player::addTimeout(Timeout* pTimeout)
{
    vector<Timeout*>::iterator it = m_PendingTimeouts.begin();
    while (it != m_PendingTimeouts.end() && (**it)<*pTimeout) {
        it++;
    }
    m_PendingTimeouts.insert(it, pTimeout);
    return pTimeout->getID();
}

void Player::setPluginPath(const string& newPath)
{
    PluginManager::get().setSearchPath(newPath);
}

string Player::getPluginPath() const
{
    return  PluginManager::get().getSearchPath();
}

py::object Player::loadPlugin(const std::string& name)
{
    return PluginManager::get().loadPlugin(name);
}

void Player::setEventHook(PyObject * pyfunc)
{
    if (m_EventHookPyFunc != Py_None) {
        Py_DECREF(m_EventHookPyFunc);
    }

    if (pyfunc != Py_None) {
        Py_INCREF(pyfunc);
    }

    m_EventHookPyFunc = pyfunc;
}

PyObject * Player::getEventHook() const
{
    return m_EventHookPyFunc;
}

Player::EventCaptureInfo::EventCaptureInfo(const NodeWeakPtr& pNode)
    : m_pNode(pNode),
      m_CaptureCount(1)
{
}

}
