/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
 * Copyright (C) 2010 Tim Moore
 * Copyright (C) 2012 David Callu
 *
 * 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_BUFFERINDEXBINDING
#define OSG_BUFFERINDEXBINDING 1

#include <osg/Array>
#include <osg/Export>
#include <osg/BufferObject>
#include <osg/StateAttribute>

#ifndef GL_TRANSFORM_FEEDBACK_BUFFER
    #define GL_TRANSFORM_FEEDBACK_BUFFER      0x8C8E
#endif


namespace osg {

class State;

/** Encapsulate binding buffer objects to index targets. This
 * specifically supports the uniform buffer and transform feedback
 * targets.
 */

// Common implementation superclass
class OSG_EXPORT BufferIndexBinding : public StateAttribute
{
 protected:
    BufferIndexBinding(GLenum target, GLuint index);
    BufferIndexBinding(GLenum target, GLuint index, BufferObject* bo, GLintptr offset,
                       GLsizeiptr size);
    BufferIndexBinding(const BufferIndexBinding& rhs, const CopyOp& copyop=CopyOp::SHALLOW_COPY);
 public:
    // The member value is part of the key to this state attribute in
    // the State class. Using the index target, we can seperately
    // track the bindings for many different index targets.
    virtual unsigned getMember() const { return static_cast<unsigned int>(_index); }

    GLenum getTarget() const { return _target; }
    /** Get the index target.
     */
    GLuint getIndex() const { return _index; }
    /** Set the buffer object that will be bound to the index target.
     */
    void setBufferObject(BufferObject *bo) { _bufferObject = bo; }
    /** Get the buffer object to be bound.
     */
    BufferObject* getBufferObject() const { return _bufferObject.get(); }
    /** Set the starting offset into the buffer object for data for
    the indexed target. Note: the required  alignment on the offset
    may be quite large (e.g., 256 bytes on NVidia 8600M). This
    should be checked with glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT...).
    */
    void setOffset(GLintptr offset) { _offset = offset; }
    GLintptr getOffset() const { return _offset; }
    /** Set the size of data for the indexed target.
     */
    void setSize(GLsizeiptr size) { _size = size; }
    GLsizeiptr getSize() const { return _size; }
    virtual void apply(State& state) const;
 protected:
    virtual ~BufferIndexBinding();
    const GLenum _target;
    const GLuint _index;
    ref_ptr<BufferObject> _bufferObject;
    GLintptr _offset;
    GLsizeiptr _size;
};

/** StateAttribute for binding a uniform buffer index target.
 */
class OSG_EXPORT UniformBufferBinding : public BufferIndexBinding
{
 public:
    UniformBufferBinding();
    UniformBufferBinding(GLuint index);
    /** Create a binding for a uniform buffer index target.
     *  @param index the index target
     *  @param bo associated buffer object
     *  @param offset offset into buffer object
     *  @param size size of data in buffer object
     */
    UniformBufferBinding(GLuint index, BufferObject* bo, GLintptr offset, GLsizeiptr size);
    UniformBufferBinding(const UniformBufferBinding& rhs, const CopyOp& copyop=CopyOp::SHALLOW_COPY);
    META_StateAttribute(osg, UniformBufferBinding, UNIFORMBUFFERBINDING);

    virtual int compare(const StateAttribute& bb) const
    {
        COMPARE_StateAttribute_Types(UniformBufferBinding, bb)

        COMPARE_StateAttribute_Parameter(_target)
        COMPARE_StateAttribute_Parameter(_index)
        COMPARE_StateAttribute_Parameter(_bufferObject)
        COMPARE_StateAttribute_Parameter(_offset)
        COMPARE_StateAttribute_Parameter(_size)
        return 0;
    }
};

/** StateAttribute for binding a transform feedback index target.
 */
class OSG_EXPORT TransformFeedbackBufferBinding : public BufferIndexBinding
{
 public:
    TransformFeedbackBufferBinding(GLuint index = 0);
    TransformFeedbackBufferBinding(GLuint index, BufferObject* bo, GLintptr offset, GLsizeiptr size);
    TransformFeedbackBufferBinding(const TransformFeedbackBufferBinding& rhs, const CopyOp& copyop=CopyOp::SHALLOW_COPY);
    META_StateAttribute(osg, TransformFeedbackBufferBinding, TRANSFORMFEEDBACKBUFFERBINDING);

    virtual int compare(const StateAttribute& bb) const
    {
        COMPARE_StateAttribute_Types(TransformFeedbackBufferBinding, bb)

        COMPARE_StateAttribute_Parameter(_target)
        COMPARE_StateAttribute_Parameter(_index)
        COMPARE_StateAttribute_Parameter(_bufferObject)
        COMPARE_StateAttribute_Parameter(_offset)
        COMPARE_StateAttribute_Parameter(_size)
        return 0;
    }
};

/** StateAttribute for binding a atomic counter buffer index target.
 */
class OSG_EXPORT AtomicCounterBufferBinding : public BufferIndexBinding
{
 public:
    AtomicCounterBufferBinding(GLuint index=0);
    /** Create a binding for a atomic counter buffer index target.
     *  @param index the index target
     *  @param bo associated buffer object
     *  @param offset offset into buffer object
     *  @param size size of data in buffer object
     */
    AtomicCounterBufferBinding(GLuint index, BufferObject* bo, GLintptr offset, GLsizeiptr size);
    AtomicCounterBufferBinding(const AtomicCounterBufferBinding& rhs, const CopyOp& copyop=CopyOp::SHALLOW_COPY);
    META_StateAttribute(osg, AtomicCounterBufferBinding, ATOMICCOUNTERBUFFERBINDING);

    void readData(osg::State & state, osg::UIntArray & uintArray) const;

    virtual int compare(const StateAttribute& bb) const
    {
        COMPARE_StateAttribute_Types(AtomicCounterBufferBinding, bb)

        COMPARE_StateAttribute_Parameter(_target)
        COMPARE_StateAttribute_Parameter(_index)
        COMPARE_StateAttribute_Parameter(_bufferObject)
        COMPARE_StateAttribute_Parameter(_offset)
        COMPARE_StateAttribute_Parameter(_size)
        return 0;
    }
};

} // namespace osg

#endif
