/* -*-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_ARRAY
#define OSG_ARRAY 1

#include <osg/MixinVector>

#include <osg/Vec2>
#include <osg/Vec3>
#include <osg/Vec4>
#include <osg/Vec2d>
#include <osg/Vec3d>
#include <osg/Vec4d>
#include <osg/Vec4ub>
#include <osg/Vec2s>
#include <osg/Vec3s>
#include <osg/Vec4s>
#include <osg/Vec2b>
#include <osg/Vec3b>
#include <osg/Vec4b>

#include <osg/BufferObject>

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

namespace osg {

class ArrayVisitor;
class ConstArrayVisitor;

class ValueVisitor;
class ConstValueVisitor;

class OSG_EXPORT Array : public Object
{

    public:
    
        enum Type
        {
            ArrayType = 0,
            ByteArrayType     = 1,
            ShortArrayType    = 2,
            IntArrayType      = 3,
            UByteArrayType    = 4,
            UShortArrayType   = 5,
            UIntArrayType     = 6,
            Vec4ubArrayType   = 7,
            FloatArrayType    = 8,
            Vec2ArrayType     = 9,
            Vec3ArrayType     = 10,
            Vec4ArrayType     = 11,
            Vec2sArrayType    = 12,
            Vec3sArrayType    = 13,
            Vec4sArrayType    = 14,
            Vec2bArrayType    = 15,
            Vec3bArrayType    = 16,
            Vec4bArrayType    = 17,
            DoubleArrayType   = 18,
            Vec2dArrayType    = 19,
            Vec3dArrayType    = 20,
            Vec4dArrayType    = 21            
        };

        Array(Type arrayType=ArrayType,GLint dataSize=0,GLenum dataType=0):
            _arrayType(arrayType),
            _dataSize(dataSize),
            _dataType(dataType),
            _modifiedCount(0),
            _vboOffset(0) {}
    
        Array(const Array& array,const CopyOp& copyop=CopyOp::SHALLOW_COPY):
            Object(array,copyop),
            _arrayType(array._arrayType),
            _dataSize(array._dataSize),
            _dataType(array._dataType),
            _modifiedCount(0),
            _vboOffset(0) {}

        virtual bool isSameKindAs(const Object* obj) const { return dynamic_cast<const Array*>(obj)!=NULL; }
        virtual const char* libraryName() const { return "osg"; }
        virtual const char* className() const;
        
        virtual void accept(ArrayVisitor&) = 0;
        virtual void accept(ConstArrayVisitor&) const = 0;

        virtual void accept(unsigned int index,ValueVisitor&) = 0;
        virtual void accept(unsigned int index,ConstValueVisitor&) const = 0;

        /** Return -1 if lhs element is less than rhs element, 0 if equal,
          * 1 if lhs element is greater than rhs element. */
        virtual int compare(unsigned int lhs,unsigned int rhs) const = 0;

        Type                    getType() const { return _arrayType; }
        GLint                   getDataSize() const { return _dataSize; }
        GLenum                  getDataType() const { return _dataType; }
        virtual const GLvoid*   getDataPointer() const = 0;
        virtual unsigned int    getTotalDataSize() const = 0;
        virtual unsigned int    getNumElements() const = 0;

        /** Frees unused space on this vector - i.e. the difference between size() and max_size() of the underlying vector.*/
        virtual void trim() {}

        /** Dirty the primitive, which increments the modified count, to force buffer objects to update. */
        inline void dirty() { ++_modifiedCount; if (_vbo.valid()) _vbo->dirty(); }
      
        /** Set the modified count value.*/
        inline void setModifiedCount(unsigned int value) { _modifiedCount=value; }

        /** Get modified count value.*/
        inline unsigned int getModifiedCount() const { return _modifiedCount; }

        /** Set the VertexBufferObject.*/
        inline void setVertexBufferObject(osg::VertexBufferObject* vbo)
        {
            if (_vbo == vbo) return;
            
            if (_vbo.valid())
            {
                _vbo->removeArray(this);
            }
            
            _vbo = vbo;
            
            if (_vbo.valid())
            {
                _vbo->addArray(this);
            }
        }
        
        /** Get the VertexBufferObject. If no VBO is assigned returns NULL*/
        inline osg::VertexBufferObject* getVertexBufferObject() { return _vbo.get(); }

        /** Get the const VertexBufferObject. If no VBO is assigned returns NULL*/
        inline const osg::VertexBufferObject* getVertexBufferObject() const { return _vbo.get(); }

        /** Set the offset into the VertexBufferObject, if used.*/
        void setVertexBufferObjectOffset(const GLvoid* offset ) const { _vboOffset = offset; }

        /** Get the offset into the VertexBufferObject, if used.*/
        const GLvoid* getVertexBufferObjectOffset() const { return _vboOffset; }

    protected:
    
        virtual ~Array()
        {
            if (_vbo.valid())
            {
                _vbo->removeArray(this);
            }
        }

        Type                                    _arrayType;
        GLint                                   _dataSize;
        GLenum                                  _dataType;
        unsigned int                            _modifiedCount;
        osg::ref_ptr<osg::VertexBufferObject>   _vbo;
        mutable const GLvoid*                   _vboOffset;
};

template<typename T, Array::Type ARRAYTYPE, int DataSize, int DataType>
class TemplateArray : public Array, public MixinVector<T>
{
    public:
    
        TemplateArray() : Array(ARRAYTYPE,DataSize,DataType) {}

        TemplateArray(const TemplateArray& ta,const CopyOp& copyop=CopyOp::SHALLOW_COPY):
            Array(ta,copyop),
            MixinVector<T>(ta) {}
        
        TemplateArray(unsigned int no) : 
            Array(ARRAYTYPE,DataSize,DataType),
            MixinVector<T>(no) {}

        TemplateArray(unsigned int no,T* ptr) : 
            Array(ARRAYTYPE,DataSize,DataType),
            MixinVector<T>(ptr,ptr+no) {}

        template <class InputIterator>
        TemplateArray(InputIterator first,InputIterator last) : 
            Array(ARRAYTYPE,DataSize,DataType),
            MixinVector<T>(first,last) {}        

        TemplateArray& operator = (const TemplateArray& array)
        {
            if (this==&array) return *this;
            assign(array.begin(),array.end());
            return *this;
        } 

        virtual Object* cloneType() const { return new TemplateArray(); }
        virtual Object* clone(const CopyOp& copyop) const { return new TemplateArray(*this,copyop); }        

        inline virtual void accept(ArrayVisitor& av);
        inline virtual void accept(ConstArrayVisitor& av) const;

        inline virtual void accept(unsigned int index,ValueVisitor& vv);
        inline virtual void accept(unsigned int index,ConstValueVisitor& vv) const;

        virtual int compare(unsigned int lhs,unsigned int rhs) const
        {
            const T& elem_lhs = (*this)[lhs];
            const T& elem_rhs = (*this)[rhs];
            if (elem_lhs<elem_rhs) return -1;
            if (elem_rhs<elem_lhs) return 1;
            return 0;
        }

        /** Frees unused space on this vector - i.e. the difference between size() and max_size() of the underlying vector.*/
        virtual void trim()
        {
            MixinVector<T>( *this ).swap( *this );
        }
        
        virtual const GLvoid*   getDataPointer() const { if (!this->empty()) return &this->front(); else return 0; }
        virtual unsigned int    getTotalDataSize() const { return this->size()*sizeof(T); }
        virtual unsigned int    getNumElements() const { return this->size(); }

        typedef T ElementDataType; // expose T

    protected:
    
        virtual ~TemplateArray() {}
};

class OSG_EXPORT IndexArray : public Array
{

    public:

        IndexArray(Type arrayType=ArrayType,GLint dataSize=0,GLenum dataType=0):
            Array(arrayType,dataSize,dataType) {}
    
        IndexArray(const Array& array,const CopyOp& copyop=CopyOp::SHALLOW_COPY):
            Array(array,copyop) {}

        virtual bool isSameKindAs(const Object* obj) const { return dynamic_cast<const IndexArray*>(obj)!=NULL; }
        
        virtual unsigned int index(unsigned int pos) const = 0;

    protected:
    
        virtual ~IndexArray() {}
};

template<typename T, Array::Type ARRAYTYPE, int DataSize, int DataType>
class TemplateIndexArray : public IndexArray, public MixinVector<T>
{
    public:
    
        TemplateIndexArray() : IndexArray(ARRAYTYPE,DataSize,DataType) {}

        TemplateIndexArray(const TemplateIndexArray& ta,const CopyOp& copyop=CopyOp::SHALLOW_COPY):
            IndexArray(ta,copyop),
            MixinVector<T>(ta) {}
        
        TemplateIndexArray(unsigned int no) : 
            IndexArray(ARRAYTYPE,DataSize,DataType),
            MixinVector<T>(no) {}

        TemplateIndexArray(unsigned int no,T* ptr) : 
            IndexArray(ARRAYTYPE,DataSize,DataType),
            MixinVector<T>(ptr,ptr+no) {}

        template <class InputIterator>
        TemplateIndexArray(InputIterator first,InputIterator last) : 
            IndexArray(ARRAYTYPE,DataSize,DataType),
            MixinVector<T>(first,last) {}        

        TemplateIndexArray& operator = (const TemplateIndexArray& array)
        {
            if (this==&array) return *this;
            assign(array.begin(),array.end());
            return *this;
        } 

        virtual Object* cloneType() const { return new TemplateIndexArray(); }
        virtual Object* clone(const CopyOp& copyop) const { return new TemplateIndexArray(*this,copyop); }        

        inline virtual void accept(ArrayVisitor& av);
        inline virtual void accept(ConstArrayVisitor& av) const;

        inline virtual void accept(unsigned int index,ValueVisitor& vv);
        inline virtual void accept(unsigned int index,ConstValueVisitor& vv) const;

        virtual int compare(unsigned int lhs,unsigned int rhs) const
        {
            const T& elem_lhs = (*this)[lhs];
            const T& elem_rhs = (*this)[rhs];
            if (elem_lhs<elem_rhs) return -1;
            if (elem_rhs<elem_lhs) return 1;
            return 0;
        }

        /** Frees unused space on this vector - i.e. the difference between size() and max_size() of the underlying vector.*/
        virtual void trim()
        {
            MixinVector<T>( *this ).swap( *this );
        }
        
        virtual const GLvoid*   getDataPointer() const { if (!this->empty()) return &this->front(); else return 0; }
        virtual unsigned int    getTotalDataSize() const { return this->size()*sizeof(T); }
        virtual unsigned int    getNumElements() const { return this->size(); }

        virtual unsigned int    index(unsigned int pos) const { return (*this)[pos]; }

        typedef T ElementDataType; // expose T

    protected:
    
        virtual ~TemplateIndexArray() {}
};

typedef TemplateIndexArray<GLbyte,Array::ByteArrayType,1,GL_BYTE>               ByteArray;
typedef TemplateIndexArray<GLshort,Array::ShortArrayType,1,GL_SHORT>            ShortArray;
typedef TemplateIndexArray<GLint,Array::IntArrayType,1,GL_INT>                  IntArray;
typedef TemplateIndexArray<GLubyte,Array::UByteArrayType,1,GL_UNSIGNED_BYTE>    UByteArray;
typedef TemplateIndexArray<GLushort,Array::UShortArrayType,1,GL_UNSIGNED_SHORT> UShortArray;
typedef TemplateIndexArray<GLuint,Array::UIntArrayType,1,GL_UNSIGNED_INT>       UIntArray;

typedef TemplateArray<GLfloat,Array::FloatArrayType,1,GL_FLOAT>                 FloatArray;

typedef TemplateArray<Vec2,Array::Vec2ArrayType,2,GL_FLOAT>                     Vec2Array;
typedef TemplateArray<Vec3,Array::Vec3ArrayType,3,GL_FLOAT>                     Vec3Array;
typedef TemplateArray<Vec4,Array::Vec4ArrayType,4,GL_FLOAT>                     Vec4Array;

typedef TemplateArray<Vec4ub,Array::Vec4ubArrayType,4,GL_UNSIGNED_BYTE>         Vec4ubArray;

typedef TemplateArray<Vec2s,Array::Vec2sArrayType,2,GL_SHORT>                   Vec2sArray;
typedef TemplateArray<Vec3s,Array::Vec3sArrayType,3,GL_SHORT>                   Vec3sArray;
typedef TemplateArray<Vec4s,Array::Vec4sArrayType,4,GL_SHORT>                   Vec4sArray;

typedef TemplateArray<Vec2b,Array::Vec2bArrayType,2,GL_BYTE>                    Vec2bArray;
typedef TemplateArray<Vec3b,Array::Vec3bArrayType,3,GL_BYTE>                    Vec3bArray;
typedef TemplateArray<Vec4b,Array::Vec4bArrayType,4,GL_BYTE>                    Vec4bArray;

typedef TemplateArray<GLdouble,Array::DoubleArrayType,1,GL_DOUBLE>               DoubleArray;
typedef TemplateArray<Vec2d,Array::Vec2dArrayType,2,GL_DOUBLE>                   Vec2dArray;
typedef TemplateArray<Vec3d,Array::Vec3dArrayType,3,GL_DOUBLE>                   Vec3dArray;
typedef TemplateArray<Vec4d,Array::Vec4dArrayType,4,GL_DOUBLE>                   Vec4dArray;


class ArrayVisitor
{
    public:
        ArrayVisitor() {}
        virtual ~ArrayVisitor() {}
        
        virtual void apply(Array&) {}
        virtual void apply(ByteArray&) {}
        virtual void apply(ShortArray&) {}
        virtual void apply(IntArray&) {}
        virtual void apply(UByteArray&) {}
        virtual void apply(UShortArray&) {}
        virtual void apply(UIntArray&) {}
        virtual void apply(FloatArray&) {}
        virtual void apply(DoubleArray&) {}

        virtual void apply(Vec2Array&) {}
        virtual void apply(Vec3Array&) {}
        virtual void apply(Vec4Array&) {}
        
        virtual void apply(Vec4ubArray&) {}

        virtual void apply(Vec2bArray&) {}        
        virtual void apply(Vec3bArray&) {}        
        virtual void apply(Vec4bArray&) {}        

        virtual void apply(Vec2sArray&) {}
        virtual void apply(Vec3sArray&) {}
        virtual void apply(Vec4sArray&) {}

        virtual void apply(Vec2dArray&) {}
        virtual void apply(Vec3dArray&) {}
        virtual void apply(Vec4dArray&) {}
};

class ConstArrayVisitor
{
    public:
        ConstArrayVisitor() {}
        virtual ~ConstArrayVisitor() {}
        
        virtual void apply(const Array&) {}
        virtual void apply(const ByteArray&) {}
        virtual void apply(const ShortArray&) {}
        virtual void apply(const IntArray&) {}
        virtual void apply(const UByteArray&) {}
        virtual void apply(const UShortArray&) {}
        virtual void apply(const UIntArray&) {}
        virtual void apply(const FloatArray&) {}
        virtual void apply(const DoubleArray&) {}

        virtual void apply(const Vec2Array&) {}
        virtual void apply(const Vec3Array&) {}
        virtual void apply(const Vec4Array&) {}

        virtual void apply(const Vec4ubArray&) {}

        virtual void apply(const Vec2bArray&) {}        
        virtual void apply(const Vec3bArray&) {}        
        virtual void apply(const Vec4bArray&) {}        

        virtual void apply(const Vec2sArray&) {}
        virtual void apply(const Vec3sArray&) {}
        virtual void apply(const Vec4sArray&) {}

        virtual void apply(const Vec2dArray&) {}
        virtual void apply(const Vec3dArray&) {}
        virtual void apply(const Vec4dArray&) {}
};


class ValueVisitor
{
    public:
        ValueVisitor() {}
        virtual ~ValueVisitor() {}
        
        virtual void apply(GLbyte&) {}
        virtual void apply(GLshort&) {}
        virtual void apply(GLint&) {}
        virtual void apply(GLushort&) {}
        virtual void apply(GLubyte&) {}
        virtual void apply(GLuint&) {}
        virtual void apply(GLfloat&) {}
        virtual void apply(GLdouble&) {}
        
        
        virtual void apply(Vec2&) {}
        virtual void apply(Vec3&) {}
        virtual void apply(Vec4&) {}
        
        virtual void apply(Vec4ub&) {}

        virtual void apply(Vec2b&) {}        
        virtual void apply(Vec3b&) {}        
        virtual void apply(Vec4b&) {}        

        virtual void apply(Vec2s&) {}
        virtual void apply(Vec3s&) {}
        virtual void apply(Vec4s&) {}

        virtual void apply(Vec2d&) {}
        virtual void apply(Vec3d&) {}
        virtual void apply(Vec4d&) {}
};

class ConstValueVisitor
{
    public:
        ConstValueVisitor() {}
        virtual ~ConstValueVisitor() {}
        
        virtual void apply(const GLbyte&) {}
        virtual void apply(const GLshort&) {}
        virtual void apply(const GLint&) {}
        virtual void apply(const GLushort&) {}
        virtual void apply(const GLubyte&) {}
        virtual void apply(const GLuint&) {}
        virtual void apply(const GLfloat&) {}
        virtual void apply(const GLdouble&) {}

        virtual void apply(const Vec4ub&) {}

        virtual void apply(const Vec2&) {}
        virtual void apply(const Vec3&) {}
        virtual void apply(const Vec4&) {}

        virtual void apply(const Vec2b&) {}
        virtual void apply(const Vec3b&) {}
        virtual void apply(const Vec4b&) {}        
        
        virtual void apply(const Vec2s&) {}
        virtual void apply(const Vec3s&) {}
        virtual void apply(const Vec4s&) {}
        
        virtual void apply(const Vec2d&) {}
        virtual void apply(const Vec3d&) {}
        virtual void apply(const Vec4d&) {}
};

template<typename T, Array::Type ARRAYTYPE, int DataSize, int DataType>
inline void TemplateArray<T,ARRAYTYPE,DataSize,DataType>::accept(ArrayVisitor& av) { av.apply(*this); }

template<typename T, Array::Type ARRAYTYPE, int DataSize, int DataType>
inline void TemplateArray<T,ARRAYTYPE,DataSize,DataType>::accept(ConstArrayVisitor& av) const { av.apply(*this); }

template<typename T, Array::Type ARRAYTYPE, int DataSize, int DataType>
inline void TemplateArray<T,ARRAYTYPE,DataSize,DataType>::accept(unsigned int index,ValueVisitor& vv) { vv.apply( (*this)[index] ); }

template<typename T, Array::Type ARRAYTYPE, int DataSize, int DataType>
inline void TemplateArray<T,ARRAYTYPE,DataSize,DataType>::accept(unsigned int index,ConstValueVisitor& vv) const {  vv.apply( (*this)[index] );}

template<typename T, Array::Type ARRAYTYPE, int DataSize, int DataType>
inline void TemplateIndexArray<T,ARRAYTYPE,DataSize,DataType>::accept(ArrayVisitor& av) { av.apply(*this); }

template<typename T, Array::Type ARRAYTYPE, int DataSize, int DataType>
inline void TemplateIndexArray<T,ARRAYTYPE,DataSize,DataType>::accept(ConstArrayVisitor& av) const { av.apply(*this); }

template<typename T, Array::Type ARRAYTYPE, int DataSize, int DataType>
inline void TemplateIndexArray<T,ARRAYTYPE,DataSize,DataType>::accept(unsigned int index,ValueVisitor& vv) { vv.apply( (*this)[index] ); }

template<typename T, Array::Type ARRAYTYPE, int DataSize, int DataType>
inline void TemplateIndexArray<T,ARRAYTYPE,DataSize,DataType>::accept(unsigned int index,ConstValueVisitor& vv) const {  vv.apply( (*this)[index] );}

}

#endif
