/* -*-c++-*- */
/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
* Copyright 2008-2014 Pelican Mapping
* http://osgearth.org
*
* osgEarth is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
*/
#ifndef OSGEARTH_AERODROME_AERODROMEFACTORY
#define OSGEARTH_AERODROME_AERODROMEFACTORY 1

#include <osg/Group>

#include "Common"
#include "AerodromeNode"
#include "AerodromeCatalog"
#include "AerodromeFeatureOptions"
#include "AerodromeRenderer"
#include "StopwayNode"
#include <osgEarth/Map>
#include <osgEarth/SceneGraphCallback>
#include <unordered_set>


namespace osgEarth { namespace Aerodrome
{
    using namespace osgEarth;

    /**
     * Aerodrome
     */
    class OSGEARTHAERODROME_EXPORT AerodromeFactory : public osg::Group
    {
    public:
        //! Construct a factory
        AerodromeFactory(
            const Map* map,
            AerodromeCatalog* catalog,
            SceneGraphCallbacks* callbacks);

        //! Destructor
        virtual ~AerodromeFactory();

        //! Initialize the factory (after setting any options below)
        void init(const osgDB::Options* options);

        //! Page-in range. Call before init().
        void setRange(const Distance& value) { _lodRange = value; }
        const Distance& getRange() const { return _lodRange; }

        //! Whether to include airport terminals in the geometry (default = true)
        void setBuildTerminals(bool value) { _buildTerminals = value; }
        bool getBuildTerminals() const { return _buildTerminals; }

        //! Whether to clamp elements to the terrain instead of using the flattened elevation
        void setClampToTerrain(bool value) { _clampToTerrain = value; }
        bool getClampToTerrain() const { return _clampToTerrain; }

        //! Collection of ICAOs to exclude
        //! Call before init();
        std::unordered_set<std::string>& icaoExclusions() { return _icaoExclusions; };
        const std::unordered_set<std::string>& icaoExclusions() const { return _icaoExclusions; };


        //! Map to reference for geometry generation
        const Map* getMap() const { return _map.get(); }

        //! creates an AerodromeNode from AerodromeCatalog
        static AerodromeNode* createAerodrome(AerodromeCatalog* catalog, const std::string& icao, bool buildTerminals, const osgDB::Options* options=0L);

        //! gets the aerodrome scene graph assocaited with the provided ICAO (airport code)
        AerodromeNode* getAerodromeNode(const std::string& icao) const;

        //! Set a creator object for aerodrome renderer implementations.
        using RendererFactory = std::function<AerodromeRenderer*()>;
        static void setRendererFactory(RendererFactory);
        static RendererFactory getRendererFactory() { return s_renderer_factory; }

        //! Callbacks for adds and removes
        SceneGraphCallbacks* getSceneGraphCallbacks() const { return _sceneGraphCallbacks.get(); }

    protected:

        // creates PagedLODs for delayed aerodrome creation from an AerodromeCatalog
        void seedAerodromes(AerodromeCatalog* catalog, const osgDB::Options* options=0L);

        template <typename T, typename Y, typename P> static void createFeatureNodes(P featureOpts, AerodromeNode* aerodrome, const osgDB::Options* options, void (*processor)(T* node, AerodromeNode* aerodrome)=0L);
        template <typename T, typename Y, typename P> static void createMergedFeatureNodes(P featureOpts, AerodromeNode* aerodrome, const osgDB::Options* options, void (*processor)(T* node, AerodromeNode* aerodrome)=0L);
        static void createBoundaryNodes(BoundaryFeatureOptions boundaryOpts, AerodromeNode* aerodrome, const osgDB::Options* options);

        static void processStopwayNode(StopwayNode* stopway, AerodromeNode* aerodrome);


        static RendererFactory s_renderer_factory;

        UID _uid;
        osg::observer_ptr<const Map> _map;
        osg::ref_ptr<AerodromeCatalog> _catalog;
        osg::ref_ptr<const osgDB::Options> _dbOptions;
        osg::ref_ptr<SceneGraphCallbacks> _sceneGraphCallbacks;

        bool _serialLoading = false;
        mutable std::mutex _serialMutex;

        bool _buildTerminals = true;
        std::unordered_set<std::string> _icaoExclusions;
        Distance _lodRange = Distance(15000.0f, Units::METERS);
        bool _clampToTerrain = false;

        std::shared_ptr<std::atomic_int> _residentTiles;
        friend class AerodromeLayer;
    };

} } // namespace osgEarth::Aerodrome

#endif // OSGEARTH_AERODROME_AERODROMEFACTORY
