/* osgEarth
* Copyright 2008-2014 Pelican Mapping
* MIT License
*/
#pragma once

#include "Common"
#include "TileRenderModel"
#include "GeometryPool"
#include <osg/Geometry>
#include <osg/Matrixf>
#include <osgEarth/TileKey>
#include <osgEarth/TextureArena>

using namespace osgEarth;

namespace osgEarth { namespace REX
{
    class EngineContext;

    /**
     * Callback that allows the layers in a Map to alter the
     * tile's perceived bounding box. This is here to support 
     * layers that may perform GPU vertex modification.
     */
    struct ModifyBoundingBoxCallback : public osg::Referenced
    {
        ModifyBoundingBoxCallback(EngineContext* context);
        void operator()(const TileKey& key, osg::BoundingBox& bbox);
        EngineContext* _context;
    };

    /**
     * TileDrawable is an osg::Drawable that represents an individual terrain tile
     * for the purposes of scene graph operations (like intersections, bounds
     * computation, statistics, etc.)
     * 
     * NOTE: TileDrawable does not actually render anything!
     * It is merely a "proxy" to support intersections, etc.
     *
     * Instead, it exposes various osg::Drawable Functors for traversing
     * the terrain's geometry. It also hold a pointer to the tile's elevation
     * raster so it can properly reflect the elevation data in the texture.
     */
    class TileDrawable : public osg::Drawable
    {
    public:
        // underlying geometry, possibly shared between this tile and other.
        osg::ref_ptr<SharedGeometry> _geom;

        int _tileSize;
        const TileKey _key;

        // cached 3D mesh of the terrain tile (derived from the elevation raster)
        std::vector<osg::Vec3> _mesh;
        osg::BoundingBox _bboxOffsets;
        ModifyBoundingBoxCallback* _bboxCB;
        mutable float _bboxRadius, _bboxHalfWidth, _bboxHalfHeight;

    private:

        Texture::Ptr _elevationRaster;
        osg::Matrixf _elevationScaleBias;

    public:
        
        //! construct a new TileDrawable that maintains the in-memory mesh
        //! for intersection testing
        TileDrawable(
            const TileKey& key,
            SharedGeometry* geometry,
            int tileSize);

        //! destructor
        virtual ~TileDrawable();

    public:

        // Sets the elevation raster for this tile
        void setElevationRaster(Texture::Ptr image, const osg::Matrixf& scaleBias);

        Texture::Ptr getElevationRaster() const {
            return _elevationRaster;
        }

        const osg::Matrixf& getElevationMatrix() const {
            return _elevationScaleBias;
        }

        // Set the render model so we can properly calculate bounding boxes
        void setModifyBBoxCallback(ModifyBoundingBoxCallback* bboxCB) { _bboxCB = bboxCB; }

    public: // osg::Drawable overrides

        // These methods defer functors (like stats collection) to the underlying
        // (possibly shared) geometry instance.
        bool supports(const osg::Drawable::AttributeFunctor& f) const override { return true; }
        void accept(osg::Drawable::AttributeFunctor& f) override { if ( _geom.valid() ) _geom->accept(f); }

        bool supports(const osg::Drawable::ConstAttributeFunctor& f) const override { return true; }
        void accept(osg::Drawable::ConstAttributeFunctor& f) const override { if ( _geom.valid() ) _geom->accept(f); }

        bool supports(const osg::PrimitiveFunctor& f) const override { return true; }
        void accept(osg::PrimitiveFunctor& f) const override;
        
        /** indexed functor is NOT supported since we need to apply elevation dynamically */
        bool supports(const osg::PrimitiveIndexFunctor& f) const override { return false; }

        osg::BoundingSphere computeBound() const override;
        osg::BoundingBox computeBoundingBox() const override;

        void resizeGLObjectBuffers(unsigned maxsize) override;
        void releaseGLObjects(osg::State* state) const override;
    };

} } // namespace osgEarth::REX

