/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield 
*
* This library is open source and may be redistributed and/or modified under  
* the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or 
* (at your option) any later version.  The full license is in LICENSE file
* included with this distribution, and on the openscenegraph.org website.
* 
* 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 
* OpenSceneGraph Public License for more details.
*/

#include <stdlib.h>
#include <float.h>
#include <limits.h>

#include <iomanip>
#include <sstream>

#include <osgDB/FileNameUtils>

#include <osg/Version>
#include <osg/Geometry>
#include <osg/TexMat>
#include <osg/Texture2D>
#include <osg/TextureRectangle>
#include <osg/io_utils>

#include <osgViewer/Viewer>
#include <osgViewer/ViewerEventHandlers>

namespace osgViewer
{

/*
** WindowSizeHandler
*/

WindowSizeHandler::WindowSizeHandler() :
    _keyEventToggleFullscreen('f'),
    _toggleFullscreen(true),
    _keyEventWindowedResolutionUp('>'),
    _keyEventWindowedResolutionDown('<'),
    _changeWindowedResolution(true),
    _currentResolutionIndex(-1)
{
    _resolutionList.push_back(osg::Vec2(640, 480));
    _resolutionList.push_back(osg::Vec2(800, 600));
    _resolutionList.push_back(osg::Vec2(1024, 768));
    _resolutionList.push_back(osg::Vec2(1152, 864));
    _resolutionList.push_back(osg::Vec2(1280, 720));
    _resolutionList.push_back(osg::Vec2(1280, 768));
    _resolutionList.push_back(osg::Vec2(1280, 1024));
    _resolutionList.push_back(osg::Vec2(1440, 900));
    _resolutionList.push_back(osg::Vec2(1400, 1050));
    _resolutionList.push_back(osg::Vec2(1600, 900));
    _resolutionList.push_back(osg::Vec2(1600, 1024));
    _resolutionList.push_back(osg::Vec2(1600, 1200));
    _resolutionList.push_back(osg::Vec2(1680, 1050));
    _resolutionList.push_back(osg::Vec2(1920, 1080));
    _resolutionList.push_back(osg::Vec2(1920, 1200));
    _resolutionList.push_back(osg::Vec2(2048, 1536));
    _resolutionList.push_back(osg::Vec2(2560, 2048));
    _resolutionList.push_back(osg::Vec2(3200, 2400));
    _resolutionList.push_back(osg::Vec2(3840, 2400));
}

void WindowSizeHandler::getUsage(osg::ApplicationUsage &usage) const
{
    usage.addKeyboardMouseBinding(reinterpret_cast<const char*>(&_keyEventToggleFullscreen), "Toggle full screen.");
    usage.addKeyboardMouseBinding(reinterpret_cast<const char*>(&_keyEventWindowedResolutionUp), "Increase the screen resolution (in windowed mode).");
    usage.addKeyboardMouseBinding(reinterpret_cast<const char*>(&_keyEventWindowedResolutionDown), "Decrease the screen resolution (in windowed mode).");
}

bool WindowSizeHandler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa)
{
    osgViewer::View* view = dynamic_cast<osgViewer::View*>(&aa);
    if (!view) return false;
    
    osgViewer::ViewerBase* viewer = view->getViewerBase();

    if (viewer == NULL)
    {
        return false;
    }

    if (ea.getHandled()) return false;

    switch(ea.getEventType())
    {
        case(osgGA::GUIEventAdapter::KEYUP):
        {
            if (_toggleFullscreen == true && ea.getKey() == _keyEventToggleFullscreen)
            {

                // sleep to allow any viewer rendering threads to complete before we
                // resize the window
                OpenThreads::Thread::microSleep(100000);

                osgViewer::Viewer::Windows windows;
                viewer->getWindows(windows);
                for(osgViewer::Viewer::Windows::iterator itr = windows.begin();
                    itr != windows.end();
                    ++itr)
                {
                    toggleFullscreen(*itr);
                }

                aa.requestRedraw();
                return true;
            }
            else if (_changeWindowedResolution == true && ea.getKey() == _keyEventWindowedResolutionUp)
            {
                // sleep to allow any viewer rendering threads to complete before we
                // resize the window
                OpenThreads::Thread::microSleep(100000);

                // Increase resolution
                osgViewer::Viewer::Windows    windows;
                viewer->getWindows(windows);
                for(osgViewer::Viewer::Windows::iterator itr = windows.begin();
                    itr != windows.end();
                    ++itr)
                {
                    changeWindowedResolution(*itr, true);
                }

                aa.requestRedraw();
                return true;
            }
            else if (_changeWindowedResolution == true && ea.getKey() == _keyEventWindowedResolutionDown)
            {
                // sleep to allow any viewer rendering threads to complete before we
                // resize the window
                OpenThreads::Thread::microSleep(100000);

                // Decrease resolution
                osgViewer::Viewer::Windows    windows;
                viewer->getWindows(windows);
                for(osgViewer::Viewer::Windows::iterator itr = windows.begin();
                    itr != windows.end();
                    ++itr)
                {
                    changeWindowedResolution(*itr, false);
                }

                aa.requestRedraw();
                return true;
            }
            break;
        }
    default:
        break;
    }
    return false;
}

void WindowSizeHandler::toggleFullscreen(osgViewer::GraphicsWindow *window)
{
    osg::GraphicsContext::WindowingSystemInterface    *wsi = osg::GraphicsContext::getWindowingSystemInterface();

    if (wsi == NULL) 
    {
        OSG_NOTICE << "Error, no WindowSystemInterface available, cannot toggle window fullscreen." << std::endl;
        return;
    }

    unsigned int    screenWidth;
    unsigned int    screenHeight;

    wsi->getScreenResolution(*(window->getTraits()), screenWidth, screenHeight);

    int x;
    int y;
    int width;
    int height;

    window->getWindowRectangle(x, y, width, height);

    bool    isFullScreen = x == 0 && y == 0 && width == (int)screenWidth && height == (int)screenHeight;

    if (isFullScreen)
    {
        osg::Vec2    resolution;

        if (_currentResolutionIndex == -1)
        {
            _currentResolutionIndex = getNearestResolution(screenWidth, screenHeight, screenWidth / 2, screenHeight / 2);
        }
        resolution = _resolutionList[_currentResolutionIndex];
        window->setWindowDecoration(true);
        window->setWindowRectangle((screenWidth - (int)resolution.x()) / 2, (screenHeight - (int)resolution.y()) / 2, (int)resolution.x(), (int)resolution.y());
        OSG_INFO << "Screen resolution = " << (int)resolution.x() << "x" << (int)resolution.y() << std::endl;
    }
    else
    {
        window->setWindowDecoration(false);
        window->setWindowRectangle(0, 0, screenWidth, screenHeight);
    }

    window->grabFocusIfPointerInWindow();
}

void WindowSizeHandler::changeWindowedResolution(osgViewer::GraphicsWindow *window, bool increase)
{
    osg::GraphicsContext::WindowingSystemInterface    *wsi = osg::GraphicsContext::getWindowingSystemInterface();

    if (wsi == NULL) 
    {
        OSG_NOTICE << "Error, no WindowSystemInterface available, cannot toggle window fullscreen." << std::endl;
        return;
    }

    unsigned int    screenWidth;
    unsigned int    screenHeight;

    wsi->getScreenResolution(*(window->getTraits()), screenWidth, screenHeight);

    int x;
    int y;
    int width;
    int height;

    window->getWindowRectangle(x, y, width, height);

    bool    isFullScreen = x == 0 && y == 0 && width == (int)screenWidth && height == (int)screenHeight;

    if (window->getWindowDecoration() == true || isFullScreen == false)
    {
        osg::Vec2    resolution;

        if (_currentResolutionIndex == -1)
        {
            _currentResolutionIndex = getNearestResolution(screenWidth, screenHeight, width, height);
        }

        if (increase == true)
        {
            // Find the next resolution
            for (int i = _currentResolutionIndex + 1; i < (int)_resolutionList.size(); ++i)
            {
                if ((unsigned int)_resolutionList[i].x() <= screenWidth && (unsigned int)_resolutionList[i].y() <= screenHeight)
                {
                    _currentResolutionIndex = i;
                    break;
                }
            }
        }
        else
        {
            // Find the previous resolution
            for (int i = _currentResolutionIndex - 1; i >= 0; --i)
            {
                if ((unsigned int)_resolutionList[i].x() <= screenWidth && (unsigned int)_resolutionList[i].y() <= screenHeight)
                {
                    _currentResolutionIndex = i;
                    break;
                }
            }
        }

        resolution = _resolutionList[_currentResolutionIndex];
        window->setWindowDecoration(true);
        window->setWindowRectangle((screenWidth - (int)resolution.x()) / 2, (screenHeight - (int)resolution.y()) / 2, (int)resolution.x(), (int)resolution.y());
        OSG_INFO << "Screen resolution = " << (int)resolution.x() << "x" << (int)resolution.y() << std::endl;

        window->grabFocusIfPointerInWindow();
    }
}

unsigned int WindowSizeHandler::getNearestResolution(int screenWidth, int screenHeight, int width, int height) const
{
    unsigned int    position = 0;
    unsigned int    result = 0;
    int                delta = INT_MAX;

    for (std::vector<osg::Vec2>::const_iterator it = _resolutionList.begin();
        it != _resolutionList.end();
        ++it, ++position)
    {
        if ((int)it->x() <= screenWidth && (int)it->y() <= screenHeight)
        {
            int tmp = static_cast<int>(osg::absolute((width * height) - (it->x() * it->y())));

            if (tmp < delta)
            {
                delta = tmp;
                result = position;
            }
        }
    }
    return (result);
}

/*
** ThreadingHandler
*/

ThreadingHandler::ThreadingHandler() :
    _keyEventChangeThreadingModel('m'),
    _changeThreadingModel(true),
    _keyEventChangeEndBarrierPosition('e'),
    _changeEndBarrierPosition(true)
{
    _tickOrLastKeyPress = osg::Timer::instance()->tick();
}

void ThreadingHandler::getUsage(osg::ApplicationUsage &usage) const
{
    usage.addKeyboardMouseBinding(reinterpret_cast<const char*>(&_keyEventChangeThreadingModel), "Toggle threading model.");
    usage.addKeyboardMouseBinding(reinterpret_cast<const char*>(&_keyEventChangeEndBarrierPosition), "Toggle the placement of the end of frame barrier.");
}

bool ThreadingHandler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa)
{
    osgViewer::View* view = dynamic_cast<osgViewer::View*>(&aa);
    if (!view) return false;
    
    osgViewer::ViewerBase* viewerBase = view->getViewerBase();
    osgViewer::Viewer* viewer = dynamic_cast<Viewer*>(viewerBase);

    if (viewerBase == NULL)
    {
        return false;
    }

    if (ea.getHandled()) return false;

    switch(ea.getEventType())
    {
        case(osgGA::GUIEventAdapter::KEYUP):
        {
            double    delta = osg::Timer::instance()->delta_s(_tickOrLastKeyPress, osg::Timer::instance()->tick());

            if (_changeThreadingModel == true && ea.getKey() == _keyEventChangeThreadingModel && delta > 1.0)
            {

                _tickOrLastKeyPress = osg::Timer::instance()->tick();

                switch(viewerBase->getThreadingModel())
                {
                case(osgViewer::ViewerBase::SingleThreaded):
                    viewerBase->setThreadingModel(osgViewer::ViewerBase::CullDrawThreadPerContext);
                    OSG_NOTICE<<"Threading model 'CullDrawThreadPerContext' selected."<<std::endl;
                    break;
                case(osgViewer::ViewerBase::CullDrawThreadPerContext):
                    viewerBase->setThreadingModel(osgViewer::ViewerBase::DrawThreadPerContext);
                    OSG_NOTICE<<"Threading model 'DrawThreadPerContext' selected."<<std::endl;
                    break;
                case(osgViewer::ViewerBase::DrawThreadPerContext):
                    viewerBase->setThreadingModel(osgViewer::ViewerBase::CullThreadPerCameraDrawThreadPerContext);
                    OSG_NOTICE<<"Threading model 'CullThreadPerCameraDrawThreadPerContext' selected."<<std::endl;
                    break;
                case(osgViewer::ViewerBase::CullThreadPerCameraDrawThreadPerContext):
                    viewerBase->setThreadingModel(osgViewer::ViewerBase::SingleThreaded);
                    OSG_NOTICE<<"Threading model 'SingleThreaded' selected."<<std::endl;
                    break;
#if 1                    
                case(osgViewer::ViewerBase::AutomaticSelection):
                    viewerBase->setThreadingModel(osgViewer::ViewerBase::SingleThreaded);
                    OSG_NOTICE<<"Threading model 'SingleThreaded' selected."<<std::endl;
#else                    
                case(osgViewer::ViewerBase::AutomaticSelection):
                    viewerBase->setThreadingModel(viewer->suggestBestThreadingModel());
                    OSG_NOTICE<<"Threading model 'AutomaticSelection' selected."<<std::endl;
#endif
                    break;
                }

                aa.requestRedraw();
                return true;
            }
            if (viewer && _changeEndBarrierPosition == true && ea.getKey() == _keyEventChangeEndBarrierPosition)
            {
                switch(viewer->getEndBarrierPosition())
                {
                case(osgViewer::Viewer::BeforeSwapBuffers):
                    viewer->setEndBarrierPosition(osgViewer::Viewer::AfterSwapBuffers);
                    OSG_NOTICE<<"Threading end of frame barrier position 'AfterSwapBuffers' selected."<<std::endl;
                    break;
                case(osgViewer::Viewer::AfterSwapBuffers):
                    viewer->setEndBarrierPosition(osgViewer::Viewer::BeforeSwapBuffers);
                    OSG_NOTICE<<"Threading end of frame barrier position 'BeforeSwapBuffers' selected."<<std::endl;
                    break;
                }

                aa.requestRedraw();
                return true;
            }
            break;
        }
    default:
        break;
    }
    return false;
}

RecordCameraPathHandler::RecordCameraPathHandler(const std::string& filename, float fps):
    _filename(filename),
    _autoinc( -1 ),
    _keyEventToggleRecord('z'),
    _keyEventTogglePlayback('Z'),
    _currentlyRecording(false),
    _currentlyPlaying(false),
    _delta(0.0f),
    _animStartTime(0),
    _lastFrameTime(osg::Timer::instance()->tick())
{
    const char* str = getenv("OSG_RECORD_CAMERA_PATH_FPS");
    if (str)
    {
        _interval = 1.0f / osg::asciiToDouble(str);
    }
    else
    {
        _interval = 1.0f / fps;
    }
}

void RecordCameraPathHandler::getUsage(osg::ApplicationUsage &usage) const
{
    usage.addKeyboardMouseBinding(reinterpret_cast<const char*>(&_keyEventToggleRecord), "Toggle camera path recording.");
    usage.addKeyboardMouseBinding(reinterpret_cast<const char*>(&_keyEventTogglePlayback), "Toggle camera path playback.");
}

bool RecordCameraPathHandler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa)
{
    osgViewer::View* view = dynamic_cast<osgViewer::View*>(&aa);

    if (view == NULL)
    {
        return false;
    }

    if(ea.getEventType()==osgGA::GUIEventAdapter::FRAME)
    {
        // Calculate our current delta (difference) in time between the last frame and
        // current frame, regardless of whether we actually store a ControlPoint...
        osg::Timer_t time = osg::Timer::instance()->tick();
        double delta = osg::Timer::instance()->delta_s(_lastFrameTime, time);
        _lastFrameTime = time;

        // If our internal _delta is finally large enough to warrant a ControlPoint
        // insertion, do so now. Be sure and reset the internal _delta, so we can start
        // calculating when the next insert should happen.
        if (_animPath.valid() && _currentlyRecording && _delta >= _interval)
        {
            const osg::Matrixd& m = view->getCamera()->getInverseViewMatrix();
            double animationPathTime = osg::Timer::instance()->delta_s(_animStartTime, time);            
            _animPath->insert(animationPathTime, osg::AnimationPath::ControlPoint(m.getTrans(), m.getRotate()));
            _delta = 0.0f;

            if (_fout)
            {
                _animPath->write(_animPath->getTimeControlPointMap().find(animationPathTime), _fout);
                _fout.flush();
            }

        }
        else _delta += delta;
        
        return true;
    }

    if (ea.getHandled()) return false;

    switch(ea.getEventType())
    {
        case(osgGA::GUIEventAdapter::KEYUP):
        {
            // The user has requested to toggle recording.
            if (ea.getKey() ==_keyEventToggleRecord)
            {
                // The user has requested to BEGIN recording.
                if (!_currentlyRecording)
                {
                    _currentlyRecording = true;
                    _animStartTime = osg::Timer::instance()->tick();
                    _animPath = new osg::AnimationPath();
                    
                    if (!_filename.empty())
                    {
                        std::stringstream ss;
                        ss << osgDB::getNameLessExtension(_filename);
                        if ( _autoinc != -1 )
                        {
                            ss << "_"<<std::setfill( '0' ) << std::setw( 2 ) << _autoinc;
                            _autoinc++;
                        }
                        ss << "."<<osgDB::getFileExtension(_filename);
                        
                        OSG_NOTICE << "Recording camera path to file " << ss.str() << std::endl;
                        _fout.open( ss.str().c_str() );

                        // make sure doubles are not trucated by default stream precision = 6
                        _fout.precision( 15 );
                    }
                    else
                    {
                        OSG_NOTICE<<"Recording camera path."<<std::endl;
                    }
                }

                // The user has requested to STOP recording, write the file!
                else
                {
                    _currentlyRecording = false;
                    _delta = 0.0f;

                    if (_fout) _fout.close();
                }

                return true;
            }

            // The user has requested to toggle playback. You'll notice in the code below that
            // we take over the current manipulator; it was originally recommended that we
            // check for a KeySwitchManipulator, create one if not present, and then add this
            // to either the newly created one or the existing one. However, the code do that was
            // EXTREMELY dirty, so I opted for a simpler solution. At a later date, someone may
            // want to implement the original recommendation (which is in a mailing list reply
            // from June 1st by Robert in a thread called "osgviewer Camera Animation (preliminary)".
            else if (ea.getKey() == _keyEventTogglePlayback)
            {
                if (_currentlyRecording)
                {
                    _currentlyRecording = false;
                    _delta = 0.0f;

                    if (_animPath.valid() && !_animPath->empty())
                    {
                        // In the future this will need to be written continuously, rather
                        // than all at once.
                        osgDB::ofstream out(_filename.c_str());
                        OSG_NOTICE<<"Writing camera file: "<<_filename<<std::endl;
                        _animPath->write(out);
                        out.close();
                    }
                    else
                    {
                        OSG_NOTICE<<"No animation path to write out."<<std::endl;
                    }
                }

                // The user has requested to BEGIN playback.
                if (!_currentlyPlaying)
                {
                     if (_animPath.valid() && !_animPath->empty())
                     {                    
                        _animPathManipulator = new osgGA::AnimationPathManipulator(_animPath.get());
                        _animPathManipulator->home(ea,aa);


                        // If we successfully found our _filename file, set it and keep a copy
                        // around of the original CameraManipulator to restore later.
                        if (_animPathManipulator.valid() && _animPathManipulator->valid())
                        {
                            _oldManipulator = view->getCameraManipulator();
                            view->setCameraManipulator(_animPathManipulator.get());
                            _currentlyPlaying = true;
                        }
                     }
                }
                
                // The user has requested to STOP playback.
                else
                {
                    // Restore the old manipulator if necessary and stop playback.
                    if(_oldManipulator.valid()) view->setCameraManipulator(_oldManipulator.get());
                    _currentlyPlaying = false;
                    _oldManipulator = 0;
                }

                return true;
            }        

            break;
        }
    default:
        break;
    }

    return false;
}

LODScaleHandler::LODScaleHandler():
    _keyEventIncreaseLODScale('*'),
    _keyEventDecreaseLODScale('/')
{
}

bool LODScaleHandler::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
{
    osgViewer::View* view = dynamic_cast<osgViewer::View*>(&aa);
    osg::Camera* camera = view ? view->getCamera() : 0;
    if (!camera) return false;

    if (ea.getHandled()) return false;

    switch(ea.getEventType())
    {
        case(osgGA::GUIEventAdapter::KEYUP):
        {
            if (ea.getKey() == _keyEventIncreaseLODScale)
            {
                camera->setLODScale(camera->getLODScale()*1.1);
                OSG_NOTICE<<"LODScale = "<<camera->getLODScale()<<std::endl;

                aa.requestRedraw();
                return true;
            }

            else if (ea.getKey() == _keyEventDecreaseLODScale)
            {
                camera->setLODScale(camera->getLODScale()/1.1);
                OSG_NOTICE<<"LODScale = "<<camera->getLODScale()<<std::endl;

                aa.requestRedraw();
                return true;
            }

            break;
        }
    default:
        break;
    }

    return false;
}

void LODScaleHandler::getUsage(osg::ApplicationUsage& usage) const
{
    {
        std::ostringstream ostr;
        ostr<<char(_keyEventIncreaseLODScale);
        usage.addKeyboardMouseBinding(ostr.str(),"Increase LODScale.");
    }
    
    {
        std::ostringstream ostr;
        ostr<<char(_keyEventDecreaseLODScale);
        usage.addKeyboardMouseBinding(ostr.str(),"Decrease LODScale.");
    }
}

ToggleSyncToVBlankHandler::ToggleSyncToVBlankHandler():
    _keyEventToggleSyncToVBlank('v')
{
}

bool ToggleSyncToVBlankHandler::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
{
    osgViewer::View* view = dynamic_cast<osgViewer::View*>(&aa);
    if (!view) return false;
    
    osgViewer::ViewerBase* viewer = view->getViewerBase();

    if (viewer == NULL)
    {
        return false;
    }
    
    if (ea.getHandled()) return false;

    switch(ea.getEventType())
    {
        case(osgGA::GUIEventAdapter::KEYUP):
        {
            if (ea.getKey() == _keyEventToggleSyncToVBlank)
            {
                // Increase resolution
                osgViewer::Viewer::Windows    windows;

                viewer->getWindows(windows);
                for(osgViewer::Viewer::Windows::iterator itr = windows.begin();
                    itr != windows.end();
                    ++itr)
                {
                    (*itr)->setSyncToVBlank( !(*itr)->getSyncToVBlank() );
                }

                aa.requestRedraw();
                return true;
            }

            break;
        }
    default:
        break;
    }

    return false;
}


void ToggleSyncToVBlankHandler::getUsage(osg::ApplicationUsage& usage) const
{
    {
        std::ostringstream ostr;
        ostr<<char(_keyEventToggleSyncToVBlank);
        usage.addKeyboardMouseBinding(ostr.str(),"Toggle SyncToVBlank.");
    }
}


InteractiveImageHandler::InteractiveImageHandler(osg::Image* image) :
    _image(image),
    _texture(0),
    _fullscreen(false),
    _camera(0)
{
}

InteractiveImageHandler::InteractiveImageHandler(osg::Image* image, osg::Texture2D* texture, osg::Camera* camera) :
    _image(image),
    _texture(texture),
    _fullscreen(true),
    _camera(camera)
{
    if (_camera.valid() && _camera->getViewport())
    {
        // Send an initial resize event (with the same size) so the image can
        // resize itself initially.
        double width = _camera->getViewport()->width();
        double height = _camera->getViewport()->height();

        resize(width, height);
    }
}

bool InteractiveImageHandler::computeIntersections(osgViewer::View* view, float x,float y, const osg::NodePath& nodePath, osgUtil::LineSegmentIntersector::Intersections& intersections,osg::Node::NodeMask traversalMask) const 
{
    float local_x, local_y = 0.0;
    const osg::Camera* camera;
    if (_fullscreen)
    {
        if (!_camera) return false;
        camera = _camera.get();
        local_x = x;
        local_y = y;
    }
    else
    {
        if (!view->getCamera() || nodePath.empty()) return false;
        camera = view->getCameraContainingPosition(x, y, local_x, local_y);
        if (!camera) camera = view->getCamera();
    }

    osg::ref_ptr< osgUtil::LineSegmentIntersector > picker;
    if (_fullscreen)
    {
        picker = new osgUtil::LineSegmentIntersector(osgUtil::Intersector::WINDOW, local_x, local_y);
    }
    else
    {
        osg::Matrixd matrix;
        if (nodePath.size()>1)
        {
            osg::NodePath prunedNodePath(nodePath.begin(),nodePath.end()-1);
            matrix = osg::computeLocalToWorld(prunedNodePath);
        }

        matrix.postMult(camera->getViewMatrix());
        matrix.postMult(camera->getProjectionMatrix());

        double zNear = -1.0;
        double zFar = 1.0;
        if (camera->getViewport())
        {
            matrix.postMult(camera->getViewport()->computeWindowMatrix());
            zNear = 0.0;
            zFar = 1.0;
        }

        osg::Matrixd inverse;
        inverse.invert(matrix);

        osg::Vec3d startVertex = osg::Vec3d(local_x,local_y,zNear) * inverse;
        osg::Vec3d endVertex = osg::Vec3d(local_x,local_y,zFar) * inverse;

        picker = new osgUtil::LineSegmentIntersector(osgUtil::Intersector::MODEL, startVertex, endVertex);
    }

    osgUtil::IntersectionVisitor iv(picker.get());
    iv.setTraversalMask(traversalMask);

    if (_fullscreen)
    {
        const_cast<osg::Camera*>(camera)->accept(iv);
    }
    else
    {
        nodePath.back()->accept(iv);
    }

    if (picker->containsIntersections())
    {
        intersections = picker->getIntersections();
        return true;
    }
    else
    {
        intersections.clear();
        return false;
    }
}

bool InteractiveImageHandler::mousePosition(osgViewer::View* view, osg::NodeVisitor* nv, const osgGA::GUIEventAdapter& ea, int& x, int &y) const
{
    osgUtil::LineSegmentIntersector::Intersections intersections;
    bool foundIntersection = view==0 ? false :
        (nv==0 ? view->computeIntersections(ea.getX(), ea.getY(), intersections) :
                 //view->computeIntersections(ea.getX(), ea.getY(), nv->getNodePath(), intersections));
                 computeIntersections(view, ea.getX(), ea.getY(), nv->getNodePath(), intersections));

    if (foundIntersection)
    {
        osg::Vec2 tc(0.5f,0.5f);

        // use the nearest intersection                 
        const osgUtil::LineSegmentIntersector::Intersection& intersection = *(intersections.begin());
        osg::Drawable* drawable = intersection.drawable.get();
        osg::Geometry* geometry = drawable ? drawable->asGeometry() : 0;
        osg::Vec3Array* vertices = geometry ? dynamic_cast<osg::Vec3Array*>(geometry->getVertexArray()) : 0;
        if (vertices)
        {
            // get the vertex indices.
            const osgUtil::LineSegmentIntersector::Intersection::IndexList& indices = intersection.indexList;
            const osgUtil::LineSegmentIntersector::Intersection::RatioList& ratios = intersection.ratioList;

            if (indices.size()==3 && ratios.size()==3)
            {
                unsigned int i1 = indices[0];
                unsigned int i2 = indices[1];
                unsigned int i3 = indices[2];

                float r1 = ratios[0];
                float r2 = ratios[1];
                float r3 = ratios[2];

                osg::Array* texcoords = (geometry->getNumTexCoordArrays()>0) ? geometry->getTexCoordArray(0) : 0;
                osg::Vec2Array* texcoords_Vec2Array = dynamic_cast<osg::Vec2Array*>(texcoords);
                if (texcoords_Vec2Array)
                {
                    // we have tex coord array so now we can compute the final tex coord at the point of intersection.                                
                    osg::Vec2 tc1 = (*texcoords_Vec2Array)[i1];
                    osg::Vec2 tc2 = (*texcoords_Vec2Array)[i2];
                    osg::Vec2 tc3 = (*texcoords_Vec2Array)[i3];
                    tc = tc1*r1 + tc2*r2 + tc3*r3;
                }
            }

            osg::TexMat* activeTexMat = 0;
            osg::Texture* activeTexture = 0;

            if (drawable->getStateSet())
            {
                osg::TexMat* texMat = dynamic_cast<osg::TexMat*>(drawable->getStateSet()->getTextureAttribute(0,osg::StateAttribute::TEXMAT));
                if (texMat) activeTexMat = texMat;

                osg::Texture* texture = dynamic_cast<osg::Texture*>(drawable->getStateSet()->getTextureAttribute(0,osg::StateAttribute::TEXTURE));
                if (texture) activeTexture = texture;
            }

            if (activeTexMat)
            {
                osg::Vec4 tc_transformed = osg::Vec4(tc.x(), tc.y(), 0.0f,0.0f) * activeTexMat->getMatrix();
                tc.x() = tc_transformed.x();
                tc.y() = tc_transformed.y();
            }

            if (dynamic_cast<osg::TextureRectangle*>(activeTexture))
            {
                x = int( tc.x() );
                y = int( tc.y() );
            }
            else if (_image.valid())
            {
                x = int( float(_image->s()) * tc.x() );
                y = int( float(_image->t()) * tc.y() );
            }

            return true;
        }

    }

    return false;
}


bool InteractiveImageHandler::handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter& aa, osg::Object*, osg::NodeVisitor* nv)
{
    if (ea.getHandled()) return false;
    
    if (!_image) return false;

    switch(ea.getEventType())
    {
        case(osgGA::GUIEventAdapter::MOVE):
        case(osgGA::GUIEventAdapter::DRAG):
        case(osgGA::GUIEventAdapter::PUSH):
        case(osgGA::GUIEventAdapter::RELEASE):
        {
            osgViewer::View* view = dynamic_cast<osgViewer::View*>(&aa);
            int x,y;
            if (mousePosition(view, nv, ea, x, y))
            {
                return _image->sendPointerEvent(x, y, ea.getButtonMask());
            }
            break;
        }
        case(osgGA::GUIEventAdapter::KEYDOWN):
        case(osgGA::GUIEventAdapter::KEYUP):
        {
            osgViewer::View* view = dynamic_cast<osgViewer::View*>(&aa);
            int x,y;
            bool sendKeyEvent = mousePosition(view, nv, ea, x, y);
        
            if (sendKeyEvent)
            {
                return _image->sendKeyEvent(ea.getKey(), ea.getEventType()==osgGA::GUIEventAdapter::KEYDOWN);
            }          
        }
        case (osgGA::GUIEventAdapter::RESIZE):
        {
            if (_fullscreen && _camera.valid())
            {
                _camera->setViewport(0, 0, ea.getWindowWidth(), ea.getWindowHeight());

                resize(ea.getWindowWidth(), ea.getWindowHeight());
                return true;
            }
        }

        default:
            return false;
    }
    return false;
}

bool InteractiveImageHandler::cull(osg::NodeVisitor* nv, osg::Drawable*, osg::RenderInfo*) const
{
    if (_image.valid())
    {
        _image->setFrameLastRendered(nv->getFrameStamp());
    }

    return false;
}

void InteractiveImageHandler::resize(int width, int height)
{
    if (_image.valid())
    {
        _image->scaleImage(width, height, 1);
    }

    // Make sure the texture does not rescale the image because 
    // it thinks it should still be the previous size...
    if (_texture.valid())
        _texture->setTextureSize(width, height);
}

}
