/* -*-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.
*/

#ifndef OSG_GRAPHICSTHREAD
#define OSG_GRAPHICSTHREAD 1

#include <osg/State>
#include <OpenThreads/Thread>
#include <OpenThreads/Barrier>
#include <OpenThreads/Condition>

#include <list>

namespace osg {

// forward declare GraphicsContext
class GraphicsContext;    
    
class Block: virtual public osg::Referenced {
    public:
        Block():_released(false) {}

        inline void block()
        {
            OpenThreads::ScopedLock<OpenThreads::Mutex> mutlock(_mut);
            if( !_released )
                _cond.wait(&_mut);
        }

        inline void release()
        {
            OpenThreads::ScopedLock<OpenThreads::Mutex> mutlock(_mut);
            if (!_released)
            {
                _released = true;
                _cond.broadcast();
            }
        }

        inline void reset()
        {
            OpenThreads::ScopedLock<OpenThreads::Mutex> mutlock(_mut);
            _released = false;
        }
        
        inline void set(bool doRelease)
        {
            if (doRelease!=_released)
            {
                if (doRelease) release();
                else reset();
            }
        }

    protected:

        ~Block()
        {
            release();
        }

    private:
        OpenThreads::Mutex _mut;
        OpenThreads::Condition _cond;
        bool _released;
};

/** GraphicsThread is a helper class for running OpenGL GraphicsOperation within a single thread assigned to a specific GraphicsContext.*/
class OSG_EXPORT GraphicsThread : public Referenced, public OpenThreads::Thread
{
    public:
        GraphicsThread();

        /** Base class for implementing GraphicsThread operations.*/
        struct OSG_EXPORT Operation : virtual public Referenced
        {
            Operation(const std::string& name, bool keep):
                _name(name),
                _keep(keep) {}
                
            /** Set the human readable name of the operation.*/
            void setName(const std::string& name) { _name = name; }

            /** Get the human readable name of the operation.*/
            const std::string& getName() const { return _name; }
        
            /** Set whether the operation should be kept once its been applied.*/ 
            void setKeep(bool keep) { _keep = keep; }

            /** Get whether the operation should be kept once its been applied.*/ 
            bool getKeep() const { return _keep; }

            /** if this operation is a barrier then release it.*/
            virtual void release() {}

            /** Do the actual task of this operation.*/ 
            virtual void operator () (GraphicsContext*) {}
            
            std::string _name;
            bool        _keep;
        };

        /** Add operation to end of OperationQueue, this will be 
          * executed by the graphics thread once this operation gets to the head of the queue.*/
        void add(Operation* operation, bool waitForCompletion=false);
        
        /** Remove operation from OperationQueue.*/
        void remove(Operation* operation);

        /** Remove named operation from OperationQueue.*/
        void remove(const std::string& name);

        /** Remove all operations from OperationQueue.*/
        void removeAllOperations();
        
        /** Get the operation currently being run.*/
        osg::ref_ptr<Operation> getCurrentOperation() { return _currentOperation; }

        /** Run does the graphics thread run loop.*/        
        virtual void run();
        
        void setDone(bool done);
        bool getDone() const { return _done; }

        /** Cancel this graphics thread.*/        
        virtual int cancel();

    protected:
    
        virtual ~GraphicsThread();
        
        friend class GraphicsContext;
        GraphicsContext*    _graphicsContext;

        typedef std::list< ref_ptr<Operation> > OperationQueue;

        bool                        _done;

        OpenThreads::Mutex          _operationsMutex;
        osg::ref_ptr<osg::Block>    _operationsBlock;
        OperationQueue              _operations;
        osg::ref_ptr<Operation>     _currentOperation;

};


/** SwapBufferOperation calls swap buffers on the GraphicsContext.*/
struct OSG_EXPORT SwapBuffersOperation : public GraphicsThread::Operation
{
    SwapBuffersOperation():
        GraphicsThread::Operation("SwapBuffers",true) {}

    virtual void operator () (GraphicsContext* context);
};

/** BarrierOperation allows one to syncronize multiple GraphicsThreads with each other.*/
struct OSG_EXPORT BarrierOperation : public GraphicsThread::Operation, public OpenThreads::Barrier
{
    enum PreBlockOp
    {
        NO_OPERATION,
        GL_FLUSH,
        GL_FINISH
    };

    BarrierOperation(int numThreads, PreBlockOp op=NO_OPERATION):
        GraphicsThread::Operation("Barrier", true),
        OpenThreads::Barrier(numThreads),
        _preBlockOp(op) {}

    virtual void release();

    virtual void operator () (GraphicsContext* context);
    
    PreBlockOp _preBlockOp;
};

/** ReleaseContext_Block_MakeCurrentOperation releases the context for another thread to aquire, 
  * then blocks waiting for context to be released, once the block is release the context is re-aqquired.*/
struct OSG_EXPORT ReleaseContext_Block_MakeCurrentOperation : public GraphicsThread::Operation, public Block
{
    ReleaseContext_Block_MakeCurrentOperation():
        GraphicsThread::Operation("ReleaseContext_Block_MakeCurrent", false) {}

    virtual void release();

    virtual void operator () (GraphicsContext* context);
};

}

#endif
