/* -*-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_BUFFEROBJECT
#define OSG_BUFFEROBJECT 1

#include <osg/GL>
#include <osg/Object>
#include <osg/buffered_value>

#ifndef GL_ARB_vertex_buffer_object

    #define GL_ARB_vertex_buffer_object

    // for compatibility with gl.h headers that don't support VBO,
    #if defined(_WIN64)
        typedef __int64 GLintptrARB;
        typedef __int64 GLsizeiptrARB;
    #elif defined(__ia64__) || defined(__x86_64__)
        typedef long int GLintptrARB;
        typedef long int GLsizeiptrARB;
    #else
        typedef int GLintptrARB;
        typedef int GLsizeiptrARB;
    #endif

    #define GL_ARRAY_BUFFER_ARB               0x8892
    #define GL_ELEMENT_ARRAY_BUFFER_ARB       0x8893
    #define GL_ARRAY_BUFFER_BINDING_ARB       0x8894
    #define GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB 0x8895
    #define GL_VERTEX_ARRAY_BUFFER_BINDING_ARB 0x8896
    #define GL_NORMAL_ARRAY_BUFFER_BINDING_ARB 0x8897
    #define GL_COLOR_ARRAY_BUFFER_BINDING_ARB 0x8898
    #define GL_INDEX_ARRAY_BUFFER_BINDING_ARB 0x8899
    #define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING_ARB 0x889A
    #define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING_ARB 0x889B
    #define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING_ARB 0x889C
    #define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING_ARB 0x889D
    #define GL_WEIGHT_ARRAY_BUFFER_BINDING_ARB 0x889E
    #define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB 0x889F
    #define GL_STREAM_DRAW_ARB                0x88E0
    #define GL_STREAM_READ_ARB                0x88E1
    #define GL_STREAM_COPY_ARB                0x88E2
    #define GL_STATIC_DRAW_ARB                0x88E4
    #define GL_STATIC_READ_ARB                0x88E5
    #define GL_STATIC_COPY_ARB                0x88E6
    #define GL_DYNAMIC_DRAW_ARB               0x88E8
    #define GL_DYNAMIC_READ_ARB               0x88E9
    #define GL_DYNAMIC_COPY_ARB               0x88EA
    #define GL_READ_ONLY_ARB                  0x88B8
    #define GL_WRITE_ONLY_ARB                 0x88B9
    #define GL_READ_WRITE_ARB                 0x88BA
    #define GL_BUFFER_SIZE_ARB                0x8764
    #define GL_BUFFER_USAGE_ARB               0x8765
    #define GL_BUFFER_ACCESS_ARB              0x88BB
    #define GL_BUFFER_MAPPED_ARB              0x88BC
    #define GL_BUFFER_MAP_POINTER_ARB         0x88BD

#endif

#ifndef GL_VERSION_1_5
    #define GL_STREAM_DRAW                    0x88E0
    #define GL_STREAM_READ                    0x88E1
    #define GL_STREAM_COPY                    0x88E2
    #define GL_STATIC_DRAW                    0x88E4
    #define GL_STATIC_READ                    0x88E5
    #define GL_STATIC_COPY                    0x88E6
    #define GL_DYNAMIC_DRAW                   0x88E8
    #define GL_DYNAMIC_READ                   0x88E9
    #define GL_DYNAMIC_COPY                   0x88EA
#endif

#ifndef GL_VERSION_2_1
    #define GL_PIXEL_PACK_BUFFER              0x88EB
    #define GL_PIXEL_UNPACK_BUFFER            0x88EC
    #define GL_PIXEL_PACK_BUFFER_BINDING      0x88ED
    #define GL_PIXEL_UNPACK_BUFFER_BINDING    0x88EF
#endif


#ifndef GL_ARB_pixel_buffer_object
    #define GL_PIXEL_PACK_BUFFER_ARB            0x88EB
    #define GL_PIXEL_UNPACK_BUFFER_ARB          0x88EC
    #define GL_PIXEL_PACK_BUFFER_BINDING_ARB    0x88ED
    #define GL_PIXEL_UNPACK_BUFFER_BINDING_ARB  0x88EF
#endif

namespace osg 
{

class State;

class OSG_EXPORT BufferObject : public Object
{
    public:

        BufferObject();

        /** Copy constructor using CopyOp to manage deep vs shallow copy.*/
        BufferObject(const BufferObject& bo,const CopyOp& copyop=CopyOp::SHALLOW_COPY);

        virtual bool isSameKindAs(const Object* obj) const { return dynamic_cast<const BufferObject*>(obj)!=NULL; }
        virtual const char* libraryName() const { return "osg"; }
        virtual const char* className() const { return "BufferObject"; }

        /** Set what type of usage the buffer object will have. Options are: 
          *          GL_STREAM_DRAW, GL_STREAM_READ, GL_STREAM_COPY, 
          *          GL_STATIC_DRAW, GL_STATIC_READ, GL_STATIC_COPY, 
          *          GL_DYNAMIC_DRAW, GL_DYNAMIC_READ, or GL_DYNAMIC_COPY.
          */
        void setUsage(GLenum usage) { _usage = usage; }
        
        /** Get the type of usage the buffer object has been set up for.*/
        GLenum getUsage() const { return _usage; }

        struct BufferEntry
        {
            BufferEntry(): dataSize(0),offset(0) {}
            BufferEntry(const BufferEntry& be): modifiedCount(be.modifiedCount),dataSize(be.dataSize),offset(be.offset) {}
        
            BufferEntry& operator = (const BufferEntry& be) { modifiedCount=be.modifiedCount; dataSize=be.dataSize; offset=be.offset; return *this; }

            mutable buffered_value<unsigned int>    modifiedCount;
            mutable unsigned int                    dataSize;
            mutable unsigned int                    offset;
        };

        inline bool isBufferObjectSupported(unsigned int contextID) const { return getExtensions(contextID,true)->isBufferObjectSupported(); }
        inline bool isPBOSupported(unsigned int contextID) const { return getExtensions(contextID,true)->isPBOSupported(); }

        inline GLuint& buffer(unsigned int contextID) const { return _bufferObjectList[contextID]; }
        
        inline void bindBuffer(unsigned int contextID) const
        { 
            Extensions* extensions = getExtensions(contextID,true);
            extensions->glBindBuffer(_target,_bufferObjectList[contextID]);
        }

        virtual void unbindBuffer(unsigned int contextID) const
        { 
            Extensions* extensions = getExtensions(contextID,true);
            extensions->glBindBuffer(_target,0);
        }

        inline void dirty() { _compiledList.setAllElementsTo(0); }

        bool isDirty(unsigned int contextID) const { return _compiledList[contextID]==0; }

        virtual void compileBuffer(State& state) const = 0;
        
        /** Resize any per context GLObject buffers to specified size. */
        virtual void resizeGLObjectBuffers(unsigned int maxSize);

        /** If State is non-zero, this function releases OpenGL objects for
          * the specified graphics context. Otherwise, releases OpenGL objects
          * for all graphics contexts. */
        void releaseGLObjects(State* state=0) const;


        /** Use deleteVertexBufferObject instead of glDeleteBuffers to allow
          * OpenGL buffer objects to be cached until they can be deleted
          * by the OpenGL context in which they were created, specified
          * by contextID.*/
        static void deleteBufferObject(unsigned int contextID,GLuint globj);

        /** flush all the cached display list which need to be deleted
          * in the OpenGL context related to contextID.*/
        static void flushDeletedBufferObjects(unsigned int contextID,double /*currentTime*/, double& availableTime);

        /** dicard all the cached display list which need to be deleted
          * in the OpenGL context related to contextID.
          * Note, unlike flush no OpenGL calls are made, instead the handles are all removed.
          * this call is useful for when an OpenGL context has been destroyed. */
        static void discardDeletedBufferObjects(unsigned int contextID);

        /** Extensions class which encapsulates the querying of extensions and
        * associated function pointers, and provide convenience wrappers to
        * check for the extensions or use the associated functions.*/
        class OSG_EXPORT Extensions : public osg::Referenced
        {
        public:
            Extensions(unsigned int contextID);

            Extensions(const Extensions& rhs);

            void lowestCommonDenominator(const Extensions& rhs);

            void setupGLExtensions(unsigned int contextID);

            bool isBufferObjectSupported() const { return _glGenBuffers!=0; }
            bool isPBOSupported() const { return _isPBOSupported; }

            void glGenBuffers (GLsizei n, GLuint *buffers) const;
            void glBindBuffer (GLenum target, GLuint buffer) const;
            void glBufferData (GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage) const;
            void glBufferSubData (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid *data) const;
            void glDeleteBuffers (GLsizei n, const GLuint *buffers) const;
            GLboolean glIsBuffer (GLuint buffer) const;
            void glGetBufferSubData (GLenum target, GLintptrARB offset, GLsizeiptrARB size, GLvoid *data) const;
            GLvoid* glMapBuffer (GLenum target, GLenum access) const;
            GLboolean glUnmapBuffer (GLenum target) const;
            void glGetBufferParameteriv (GLenum target, GLenum pname, GLint *params) const;
            void glGetBufferPointerv (GLenum target, GLenum pname, GLvoid* *params) const;

        protected:

            typedef void (APIENTRY * GenBuffersProc) (GLsizei n, GLuint *buffers);
            typedef void (APIENTRY * BindBufferProc) (GLenum target, GLuint buffer);
            typedef void (APIENTRY * BufferDataProc) (GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage);
            typedef void (APIENTRY * BufferSubDataProc) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid *data);
            typedef void (APIENTRY * DeleteBuffersProc) (GLsizei n, const GLuint *buffers);
            typedef GLboolean (APIENTRY * IsBufferProc) (GLuint buffer);
            typedef void (APIENTRY * GetBufferSubDataProc) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, GLvoid *data);
            typedef GLvoid* (APIENTRY * MapBufferProc) (GLenum target, GLenum access);
            typedef GLboolean (APIENTRY * UnmapBufferProc) (GLenum target);
            typedef void (APIENTRY * GetBufferParameterivProc) (GLenum target, GLenum pname, GLint *params);
            typedef void (APIENTRY * GetBufferPointervProc) (GLenum target, GLenum pname, GLvoid* *params);

            GenBuffersProc          _glGenBuffers;
            BindBufferProc          _glBindBuffer;
            BufferDataProc          _glBufferData;
            BufferSubDataProc       _glBufferSubData;
            DeleteBuffersProc       _glDeleteBuffers;
            IsBufferProc            _glIsBuffer;
            GetBufferSubDataProc    _glGetBufferSubData;
            MapBufferProc           _glMapBuffer;
            UnmapBufferProc         _glUnmapBuffer;
            GetBufferParameterivProc _glGetBufferParameteriv;
            GetBufferPointervProc   _glGetBufferPointerv;

            bool _isPBOSupported;
        };

        /** Function to call to get the extension of a specified context.
        * If the Extension object for that context has not yet been created  
        * and the 'createIfNotInitalized' flag been set to false then returns NULL.
        * If 'createIfNotInitalized' is true then the Extensions object is 
        * automatically created.  However, in this case the extension object is
        * only created with the graphics context associated with ContextID..*/
        static Extensions* getExtensions(unsigned int contextID,bool createIfNotInitalized);

        /** setExtensions allows users to override the extensions across graphics contexts.
        * typically used when you have different extensions supported across graphics pipes
        * but need to ensure that they all use the same low common denominator extensions.*/
        static void setExtensions(unsigned int contextID,Extensions* extensions);

    protected:
    
        virtual ~BufferObject();
        
        typedef osg::buffered_value<GLuint> GLObjectList;
        typedef osg::buffered_value<unsigned int> CompiledList;

        mutable GLObjectList    _bufferObjectList;
        mutable CompiledList    _compiledList;

        GLenum                  _target;
        GLenum                  _usage;
        mutable unsigned int    _totalSize;
};

class Array;
class OSG_EXPORT VertexBufferObject : public BufferObject
{
    public:

        VertexBufferObject();

        /** Copy constructor using CopyOp to manage deep vs shallow copy.*/
        VertexBufferObject(const VertexBufferObject& vbo,const CopyOp& copyop=CopyOp::SHALLOW_COPY);

        META_Object(osg,VertexBufferObject);
        
        typedef std::pair< BufferEntry, Array* > BufferEntryArrayPair;
        typedef std::vector< BufferEntryArrayPair > BufferEntryArrayPairs;

        unsigned int addArray(osg::Array* array);
        void removeArray(osg::Array* array);

        void setArray(unsigned int i, Array* array);
        Array* getArray(unsigned int i) { return _bufferEntryArrayPairs[i].second; }
        const Array* getArray(unsigned int i) const { return _bufferEntryArrayPairs[i].second; }
        
        const GLvoid* getOffset(unsigned int i) const { return (const GLvoid*)(((char *)0)+(_bufferEntryArrayPairs[i].first.offset)); }

        virtual void compileBuffer(State& state) const;

        /** Resize any per context GLObject buffers to specified size. */
        virtual void resizeGLObjectBuffers(unsigned int maxSize);

    protected:
    
        virtual ~VertexBufferObject();
        
        BufferEntryArrayPairs _bufferEntryArrayPairs;
};

class DrawElements;
class OSG_EXPORT ElementBufferObject : public BufferObject
{
    public:

        ElementBufferObject();

        /** Copy constructor using CopyOp to manage deep vs shallow copy.*/
        ElementBufferObject(const ElementBufferObject& pbo,const CopyOp& copyop=CopyOp::SHALLOW_COPY);

        META_Object(osg,ElementBufferObject);
        
        typedef std::pair< BufferEntry, DrawElements* > BufferEntryDrawElementsPair;
        typedef std::vector< BufferEntryDrawElementsPair > BufferEntryDrawElementsPairs;

        unsigned int addDrawElements(osg::DrawElements* PrimitiveSet);
        void removeDrawElements(osg::DrawElements* PrimitiveSet);

        void setDrawElements(unsigned int i, DrawElements* PrimitiveSet);
        DrawElements* getDrawElements(unsigned int i) { return _bufferEntryDrawElementsPairs[i].second; }
        const DrawElements* getDrawElements(unsigned int i) const { return _bufferEntryDrawElementsPairs[i].second; }
        
        const GLvoid* getOffset(unsigned int i) const { return (const GLvoid*)(((char *)0)+(_bufferEntryDrawElementsPairs[i].first.offset)); }

        virtual void compileBuffer(State& state) const;

        /** Resize any per context GLObject buffers to specified size. */
        virtual void resizeGLObjectBuffers(unsigned int maxSize);

    protected:
    
        virtual ~ElementBufferObject();
        
        BufferEntryDrawElementsPairs _bufferEntryDrawElementsPairs;
};

class Image;
class OSG_EXPORT PixelBufferObject : public BufferObject
{
    public:

        PixelBufferObject(osg::Image* image=0);

        /** Copy constructor using CopyOp to manage deep vs shallow copy.*/
        PixelBufferObject(const PixelBufferObject& pbo,const CopyOp& copyop=CopyOp::SHALLOW_COPY);

        META_Object(osg,PixelBufferObject);
        
        typedef std::pair< BufferEntry, Image* > BufferEntryImagePair;

        void setImage(osg::Image* image);

        Image* getImage() { return _bufferEntryImagePair.second; }
        const Image* getImage() const { return _bufferEntryImagePair.second; }
        
        unsigned int offset() const { return _bufferEntryImagePair.first.offset; }
        
        virtual void compileBuffer(State& state) const;

        /** Resize any per context GLObject buffers to specified size. */
        virtual void resizeGLObjectBuffers(unsigned int maxSize);

    protected:
    
        virtual ~PixelBufferObject();
        
        BufferEntryImagePair _bufferEntryImagePair;
};

/**
 * This object represent a general class of pixel buffer objects,
 * which are capable of allocating buffer object (memory)
 * on the GPU. The memory can then be used either for CPU-GPU
 * pixel transfer or directly for GPU-GPU transfer, without CPU intervention.
 **/
class OSG_EXPORT PixelDataBufferObject : public BufferObject
{
    public:
        PixelDataBufferObject();
        PixelDataBufferObject(const PixelDataBufferObject& pbo, const CopyOp& copyop=CopyOp::SHALLOW_COPY);

        META_Object(osg, PixelDataBufferObject);

        //! Set new size of the buffer object. This will reallocate the memory on the next compile
        inline void setDataSize(unsigned int size) { _bufferData.dataSize = size; dirty(); }

        //! Get data size of the used buffer 
        inline unsigned int getDataSize() { return _bufferData.dataSize; }

        //! Compile the buffer (reallocate the memory if buffer is dirty)
        virtual void compileBuffer(State& state) const;

        //! Bind the buffer in read mode, which means that data can be downloaded from the buffer (note: GL_PIXEL_UNPACK_BUFFER_ARB)
        virtual void bindBufferInReadMode(State& state);

        //! Bind the buffer in write mode, which means following OpenGL instructions will write data into the buffer (note: GL_PIXEL_PACK_BUFFER_ARB)
        virtual void bindBufferInWriteMode(State& state);

        //! Unbind the buffer 
        virtual void unbindBuffer(unsigned int contextID) const;

        /** Resize any per context GLObject buffers to specified size. */
        virtual void resizeGLObjectBuffers(unsigned int maxSize);

        enum Mode
        {
            //! A normal mode of this data buffer
            NONE = 0,

            //! Buffer is in read mode (@see bindBufferInReadMode)
            READ = 1,

            //! Buffer is in write mode (@see bindBufferInWriteMode)
            WRITE = 2
        };

        Mode getMode(unsigned int contextID) const { return (Mode)_mode[contextID]; }

    protected:

        virtual ~PixelDataBufferObject();

        BufferEntry _bufferData;

        typedef osg::buffered_value<unsigned int> ModeList;
        
        mutable ModeList _mode;
        
};


}

#endif
