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

#include "EngineContext"
#include "TerrainRenderData"
#include "SelectionInfo"
#include <osgEarth/Containers>
#include <osgEarth/Horizon>

#include <osg/NodeVisitor>
#include <osgUtil/CullVisitor>


using namespace osgEarth;

namespace osgEarth { namespace REX
{
    struct LayerExtent
    {
        LayerExtent() : _revision(-1) { }
        osg::observer_ptr<const Layer> _layer;
        Revision _revision;
        GeoExtent _extent;
    };
    using LayerExtentMap = std::unordered_map<UID, LayerExtent>;

    /**
     * Node visitor responsible for assembling a TerrainRenderData that 
     * contains all the information necessary to render the terrain.
     */
    class TerrainCuller : public osg::NodeVisitor, public osg::CullStack
    {
    public:
        osgUtil::CullVisitor* _cv;
        EngineContext* _context;
        osg::Camera* _camera;
        TileNode* _currentTileNode;
        DrawTileCommand* _firstDrawCommandForTile;
        unsigned _orphanedPassesDetected;
        LayerExtentMap* _layerExtents;
        bool _isSpy;
        std::vector<PatchLayer*> _patchLayers;
        double _lastTimeVisited;
        bool _acceptSurfaceNodes;
        TerrainRenderData _terrain;
        osg::ref_ptr<Horizon> _horizon;
        float _sse = 0.0f;

    public:
        //! Construct the culler
        TerrainCuller();

        //! Reset for the next frame
        void reset(
            osgUtil::CullVisitor* parentCullVisitor,
            TerrainRenderData::PersistentData& pd,
            EngineContext* context,
            LayerExtentMap& layerExtents);

        /** The active camera */
        osg::Camera* getCamera() { return _camera; }

        /** Access to terrain engine resources */
        EngineContext* getEngineContext() { return _context; }

        /** The CullVIsitor that parents this culler. */
        osgUtil::CullVisitor& getParent() { return *_cv; }

        bool isCulledToBBox(osg::Transform* node, const osg::BoundingBox& box);

        void removeLayer(const Layer* layer);

        void setScreenSpaceError(float value) {
            _sse = value;
        }

        float getScreenSpaceError() const {
            return _sse;
        }

    public: // osg::NodeVisitor
        void apply(osg::Node& node);
        void apply(TileNode& node);
        void apply(SurfaceNode& node);
        
        float getDistanceToViewPoint(const osg::Vec3& pos, bool withLODScale) const;

    private:

        DrawTileCommand* addDrawCommand(
            UID sourceUID, 
            const TileRenderModel* model, 
            const RenderingPass* pass, 
            TileNode* node);

    };

} } // namespace 

