/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2003 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_GEOMETRY
#define OSG_GEOMETRY 1

#include <osg/Drawable>
#include <osg/Vec2>
#include <osg/Vec3>
#include <osg/Vec4>
#include <osg/Array>
#include <osg/PrimitiveSet>

namespace osg {


class SG_EXPORT Geometry : public Drawable
{
    public:

        Geometry();

        /** Copy constructor using CopyOp to manage deep vs shallow copy.*/
        Geometry(const Geometry& geometry,const CopyOp& copyop=CopyOp::SHALLOW_COPY);
            
        virtual Object* cloneType() const { return new Geometry(); }
        virtual Object* clone(const CopyOp& copyop) const { return new Geometry(*this,copyop); }        
        virtual bool isSameKindAs(const Object* obj) const { return dynamic_cast<const Geometry*>(obj)!=NULL; }
        virtual const char* libraryName() const { return "osg"; }
        virtual const char* className() const { return "Geometry"; }

        virtual Geometry* asGeometry() { return this; }
        virtual const Geometry* asGeometry() const { return this; }

        enum AttributeBinding
        {
            BIND_OFF=0,
            BIND_OVERALL,
            BIND_PER_PRIMITIVE_SET,
            BIND_PER_PRIMITIVE,
            BIND_PER_VERTEX
        };
               
        struct ArrayData
        {
            ArrayData():
                binding(BIND_OFF),
                normalize(GL_FALSE),
                offset(0) {}
                
            ArrayData(Array* a, AttributeBinding b, GLboolean n = GL_FALSE):
                array(a),
                indices(0),
                binding(b),
                normalize(n),
                offset(0) {}

            ArrayData(Array* a, IndexArray* i, AttributeBinding b, GLboolean n = GL_FALSE):
                array(a),
                indices(i),
                binding(b),
                normalize(n),
                offset(0) {}

            ArrayData& operator = (const ArrayData& rhs)
            {
                array = rhs.array;
                indices = rhs.indices;
                binding = rhs.binding;
                normalize = rhs.normalize;
                offset = rhs.offset;
                return *this;
            }

            ref_ptr<Array>          array;
            ref_ptr<IndexArray>     indices;
            AttributeBinding        binding;
            GLboolean               normalize;
            mutable unsigned int    offset;
        };
        
        struct Vec3ArrayData
        {
            Vec3ArrayData():
                binding(BIND_OFF),
                normalize(GL_FALSE),
                offset(0) {}
                
            Vec3ArrayData(Vec3Array* a, AttributeBinding b, GLboolean n = GL_FALSE):
                array(a),
                indices(0),
                binding(b),
                normalize(n),
                offset(0) {}

            Vec3ArrayData(Vec3Array* a, IndexArray* i, AttributeBinding b, GLboolean n = GL_FALSE):
                array(a),
                indices(i),
                binding(b),
                normalize(n),
                offset(0) {}

            Vec3ArrayData& operator = (const Vec3ArrayData& rhs)
            {
                array = rhs.array;
                indices = rhs.indices;
                binding = rhs.binding;
                normalize = rhs.normalize;
                offset = rhs.offset;
                return *this;
            }

            ref_ptr<Vec3Array>          array;
            ref_ptr<IndexArray>     indices;
            AttributeBinding        binding;
            GLboolean               normalize;
            mutable unsigned int    offset;
        };

        /** static ArrayData which is returned get getTexCoordData(i) const and getVertexAttribData(i) const 
          * when i is out of range.*/
        static const ArrayData s_InvalidArrayData;
        
        typedef std::vector< ArrayData >  ArrayList;
               

        void setVertexArray(Array* array) { _vertexData.array = array; dirtyDisplayList(); dirtyBound();     }
        Array* getVertexArray() { return _vertexData.array.get(); }
        const Array* getVertexArray() const  { return _vertexData.array.get(); }

        void setVertexIndices(IndexArray* array) { _vertexData.indices = array; computeFastPathsUsed(); dirtyDisplayList(); dirtyBound(); }
        IndexArray* getVertexIndices() { return _vertexData.indices.get(); }
        const IndexArray* getVertexIndices() const  { return _vertexData.indices.get(); }

        void setVertexData(const ArrayData& arrayData) { _vertexData = arrayData; }
        ArrayData& getVertexData() { return _vertexData; }
        const ArrayData& getVertexData() const { return _vertexData; }
        

        void setNormalBinding(AttributeBinding ab) { _normalData.binding = ab; dirtyDisplayList(); computeFastPathsUsed(); }
        AttributeBinding getNormalBinding() const { return _normalData.binding; }

        void setNormalArray(Vec3Array* array) { _normalData.array = array; if (!_normalData.array.valid()) _normalData.binding=BIND_OFF; computeFastPathsUsed(); dirtyDisplayList(); }
        Vec3Array* getNormalArray() { return _normalData.array.get(); }
        const Vec3Array* getNormalArray() const { return _normalData.array.get(); }

        void setNormalIndices(IndexArray* array) { _normalData.indices = array; computeFastPathsUsed(); dirtyDisplayList(); }
        IndexArray* getNormalIndices() { return _normalData.indices.get(); }
        const IndexArray* getNormalIndices() const { return _normalData.indices.get(); }

        void setNormalData(const Vec3ArrayData& arrayData) { _normalData = arrayData; }
        Vec3ArrayData& getNormalData() { return _normalData; }
        const Vec3ArrayData& getNormalData() const { return _normalData; }



        void setColorBinding(AttributeBinding ab) { _colorData.binding = ab; computeFastPathsUsed();}
        AttributeBinding getColorBinding() const { return _colorData.binding; }

        void setColorArray(Array* array) { _colorData.array = array;  if (!_colorData.array.valid()) _colorData.binding=BIND_OFF; computeFastPathsUsed(); dirtyDisplayList(); }
        Array* getColorArray() { return _colorData.array.get(); }
        const Array* getColorArray() const { return _colorData.array.get(); }

        void setColorIndices(IndexArray* array) { _colorData.indices = array;  computeFastPathsUsed(); dirtyDisplayList(); }
        IndexArray* getColorIndices() { return _colorData.indices.get(); }
        const IndexArray* getColorIndices() const { return _colorData.indices.get(); }

        void setColorData(const ArrayData& arrayData) { _colorData = arrayData; }
        ArrayData& getColorData() { return _colorData; }
        const ArrayData& getColorData() const { return _colorData; }


        void setSecondaryColorBinding(AttributeBinding ab) { _secondaryColorData.binding = ab; computeFastPathsUsed();}
        AttributeBinding getSecondaryColorBinding() const { return _secondaryColorData.binding; }

        void setSecondaryColorArray(Array* array) { _secondaryColorData.array = array;  if (!_secondaryColorData.array.valid()) _secondaryColorData.binding=BIND_OFF; computeFastPathsUsed(); dirtyDisplayList(); }
        Array* getSecondaryColorArray() { return _secondaryColorData.array.get(); }
        const Array* getSecondaryColorArray() const { return _secondaryColorData.array.get(); }

        void setSecondaryColorIndices(IndexArray* array) { _secondaryColorData.indices = array;  computeFastPathsUsed(); dirtyDisplayList(); }
        IndexArray* getSecondaryColorIndices() { return _secondaryColorData.indices.get(); }
        const IndexArray* getSecondaryColorIndices() const { return _secondaryColorData.indices.get(); }

        void setSecondaryColorData(const ArrayData& arrayData) { _secondaryColorData = arrayData; }
        ArrayData& getSecondaryColorData() { return _secondaryColorData; }
        const ArrayData& getSecondaryColorData() const { return _secondaryColorData; }


        void setFogCoordBinding(AttributeBinding ab) { _fogCoordData.binding = ab; computeFastPathsUsed();}
        AttributeBinding getFogCoordBinding() const { return _fogCoordData.binding; }

        void setFogCoordArray(Array* array) { _fogCoordData.array = array; if (!_fogCoordData.array.valid()) _fogCoordData.binding=BIND_OFF; dirtyDisplayList(); }
        Array* getFogCoordArray() { return _fogCoordData.array.get(); }
        const Array* getFogCoordArray() const { return _fogCoordData.array.get(); }

        void setFogCoordIndices(IndexArray* array) { _fogCoordData.indices = array; dirtyDisplayList(); }
        IndexArray* getFogCoordIndices() { return _fogCoordData.indices.get(); }
        const IndexArray* getFogCoordIndices() const { return _fogCoordData.indices.get(); }

        void setFogCoordData(const ArrayData& arrayData) { _fogCoordData = arrayData; }
        ArrayData& setFogCoordData() { return _fogCoordData; }
        const ArrayData& setFogCoordData() const { return _fogCoordData; }
        
        

        void setTexCoordArray(unsigned int unit,Array*);
        Array* getTexCoordArray(unsigned int unit);
        const Array* getTexCoordArray(unsigned int unit) const;
        
        void setTexCoordIndices(unsigned int unit,IndexArray*);
        IndexArray* getTexCoordIndices(unsigned int unit);
        const IndexArray* getTexCoordIndices(unsigned int unit) const;

        void setTexCoordData(unsigned int index,const ArrayData& arrayData);
        ArrayData& getTexCoordData(unsigned int index);
        const ArrayData& getTexCoordData(unsigned int index) const;

        unsigned int getNumTexCoordArrays() const {  return _texCoordList.size(); }
        ArrayList& getTexCoordArrayList() { return _texCoordList; }
        const ArrayList& getTexCoordArrayList() const { return _texCoordList; }



        void setVertexAttribArray(unsigned int index,Array* array);
        Array *getVertexAttribArray(unsigned int index);
        const Array *getVertexAttribArray(unsigned int index) const;

        void setVertexAttribIndices(unsigned int index,IndexArray* array);
        IndexArray* getVertexAttribIndices(unsigned int index);
        const IndexArray* getVertexAttribIndices(unsigned int index) const;

        void setVertexAttribBinding(unsigned int index,AttributeBinding ab);
        AttributeBinding getVertexAttribBinding(unsigned int index) const;

        void setVertexAttribNormalize(unsigned int index,GLboolean norm);
        GLboolean getVertexAttribNormalize(unsigned int index) const;

        void setVertexAttribData(unsigned int index,const ArrayData& arrayData);
        ArrayData& getVertexAttribData(unsigned int index);
        const ArrayData& getVertexAttribData(unsigned int index) const;

        unsigned int getNumVertexAttribArrays() const { return _vertexAttribList.size(); }
        ArrayList& getVertexAttribArrayList() { return _vertexAttribList; }
        const ArrayList& getVertexAttribArrayList() const { return _vertexAttribList; }



        typedef std::vector< ref_ptr<PrimitiveSet> > PrimitiveSetList;

        void setPrimitiveSetList(const PrimitiveSetList& primitives) { _primitives = primitives; dirtyDisplayList(); dirtyBound(); }
        PrimitiveSetList& getPrimitiveSetList() { return _primitives; }
        const PrimitiveSetList& getPrimitiveSetList() const { return _primitives; }

        unsigned int getNumPrimitiveSets() const { return _primitives.size(); }
        PrimitiveSet* getPrimitiveSet(unsigned int pos) { return _primitives[pos].get(); }
        const PrimitiveSet* getPrimitiveSet(unsigned int pos) const { return _primitives[pos].get(); }
        
        /** Add a primitive set to the geometry.*/
        bool addPrimitiveSet(PrimitiveSet* primitiveset);
        
        /** Set a primitive set to the specified position in geometry's primitive set list.*/
        bool setPrimitiveSet(unsigned int i,PrimitiveSet* primitiveset);
        
        /** Insert a primitive set to the specified position in geometry's primitive set list.*/
        bool insertPrimitiveSet(unsigned int i,PrimitiveSet* primitiveset);
        
        /** Remove primitive set(s) from the specified position in geometry's primitive set list.*/
        bool removePrimitiveSet(unsigned int i,unsigned int numElementsToRemove=1);

        /** Get the index number of a primitive set, return a value between
          * 0 and getNumPrimitiveSet()-1 if found, if not found then return getNumPrimitiveSet().
          * When checking for a valid find value use if ((value=geoemtry->getPrimitiveSetIndex(primitive))!=geometry.getNumPrimitiveSet()) as*/
        unsigned int getPrimitiveSetIndex(const PrimitiveSet* primitiveset) const;


        /** Set whether fast paths should be used when supported.*/
        void setFastPathHint(bool on) {  _fastPathHint = on; }

        /** Get whether fast paths should be used when supported.*/
        bool getFastPathHint() const { return _fastPathHint; }

        /** return true if OpenGL fast paths will be used with drawing this Geometry.
          * Fast paths use vertex arrays, and glDrawArrays/glDrawElements.  Slow paths
          * use glBegin()/glVertex.../glEnd(). Use of per primitive bindings or per vertex indexed
          * arrays will drop the rendering path off the fast path.*/
        inline bool areFastPathsUsed() const { return _fastPath && _fastPathHint; }

        bool computeFastPathsUsed();

        bool verifyBindings() const;

        void computeCorrectBindingsAndArraySizes();


        bool suitableForOptimization() const;

        void copyToAndOptimize(Geometry& target);

        void computeInternalOptimizedGeometry();

        void removeInternalOptimizedGeometry() { _internalOptimizedGeometry = 0; }

        void setInternalOptimizedGeometry(osg::Geometry* geometry) { _internalOptimizedGeometry = geometry; }

        osg::Geometry* getInternalOptimizedGeometry() { return _internalOptimizedGeometry.get(); }

        const osg::Geometry* getInternalOptimizedGeometry() const { return _internalOptimizedGeometry.get(); }


        /** draw Geometry directly ignoring an OpenGL display list which could be attached.
          * This is the internal draw method which does the drawing itself,
          * and is the method to override when deriving from Geometry for user-drawn objects.
          */
        virtual void drawImplementation(State& state) const;

        /** return true, osg::Geometry does support accept(AttributeFunctor&).*/
        virtual bool supports(AttributeFunctor&) const { return true; }

        /** accept an AttributeFunctor and call its methods to tell it about the interal attributes that this Drawable has.*/
        virtual void accept(AttributeFunctor& af);

        /** return true, osg::Geometry does support accept(ConstAttributeFunctor&).*/
        virtual bool supports(ConstAttributeFunctor&) const { return true; }

        /** accept an ConstAttributeFunctor and call its methods to tell it about the interal attributes that this Drawable has.*/
        virtual void accept(ConstAttributeFunctor& af) const;

        /** return true, osg::Geometry does support accept(PrimitiveFunctor&) .*/
        virtual bool supports(PrimitiveFunctor&) const { return true; }

        /** accept a PrimitiveFunctor and call its methods to tell it about the interal primitives that this Drawable has.*/
        virtual void accept(PrimitiveFunctor& pf) const;



    protected:

        Geometry& operator = (const Geometry&) { return *this;}

        virtual ~Geometry();
       
        bool verifyBindings(const ArrayData& arrayData) const;
        bool verifyBindings(const Vec3ArrayData& arrayData) const;
        
        void computeCorrectBindingsAndArraySizes(ArrayData& arrayData,const char* arrayName);
        void computeCorrectBindingsAndArraySizes(Vec3ArrayData& arrayData,const char* arrayName);
        

        PrimitiveSetList                _primitives;

        ArrayData                       _vertexData;

        Vec3ArrayData                   _normalData;

        ArrayData                       _colorData;

        ArrayData                       _secondaryColorData;

        ArrayData                       _fogCoordData;
        
        ArrayList                       _texCoordList;
        
        ArrayList                       _vertexAttribList;


        mutable bool                    _fastPath;

        bool                            _fastPathHint;

        ref_ptr<Geometry>               _internalOptimizedGeometry;
};

/** Convenience function to be used for creating quad geometry with texture coords.
  * Tex coords go from bottom left (0,0) to top right (1,1).*/
extern SG_EXPORT Geometry* createTexturedQuadGeometry(const Vec3& corner,const Vec3& widthVec,const Vec3& heightVec);


}

#endif
