/* osgEarth
 * Copyright 2025 Pelican Mapping
 * MIT License
 */
#pragma once

#include <osgEarth/Common>
#include <osgEarth/CachePolicy>
#include <osgEarth/ShaderGenerator>
#include <osgEarth/Threading>
#include <osgEarth/SpatialReference>
#include <osgEarth/DateTime>
#include <osgEarth/Callbacks>
#include <set>
#include <unordered_set>
#include <unordered_map>
#include <functional>

namespace osgText {
    class Font;
}

namespace osgEarth
{
    class Cache;
    class Capabilities;
    class Profile;
    class URIReadCallback;
    class ObjectIndex;
    class StateSetCache;

    namespace Util
    {
        class ShaderFactory;
    }

    /**
     * Application-wide global repository.
     */
    class OSGEARTH_EXPORT Registry
    {
    public:
        //! Access the global Registry singleton.
        static Registry* instance();

        /** Gets a default cache object - based on environment settings - unless setDefaultCache
            was called to override it. */
        Cache* getDefaultCache() const;

        /** Sets a default cache that a Map will use if none other is specified. */
        void setDefaultCache(Cache* cache);

        /** The default cache policy (used when no policy is set elsewhere) */
        const optional<CachePolicy>& defaultCachePolicy() const;
        void setDefaultCachePolicy( const CachePolicy& policy );

        /** The override cache policy (overrides all others if set) */
        const optional<CachePolicy>& overrideCachePolicy() const;
        void setOverrideCachePolicy( const CachePolicy& policy );

        /** The default cache driver. */
        void setDefaultCacheDriverName( const std::string& name );
        const std::string& getDefaultCacheDriverName() const;

        /**
         * Given a CachePolicy, composites in the default and override cache policies
         * as necessary to create an effective CachePolicy. First it will populate
         * any unset properties in "cp" with defaults if they are available. Then it
         * will override any properties in "cp" with overrides that are available.
         */
        bool resolveCachePolicy(optional<CachePolicy>& cp) const;

        /**
         * Whether the given filename is blacklisted
         */
        bool isBlacklisted(const std::string &filename);

        /**
         * Blacklist the given filename
         */
        void blacklist(const std::string &filename);

        /**
         * Gets the number of blacklisted filenames
         */
        unsigned int getNumBlacklistedFilenames();

        /**
         * Clears the blacklist
         */
        void clearBlacklist();

        /**
         * Sets or gets a default system font to use
         */
        void setDefaultFont( osgText::Font* font );
        osgText::Font* getDefaultFont();

        /**
         * The graphics hardware capabilities for this platform.
         */
        bool hasCapabilities() const;
        const Capabilities& getCapabilities() const;
        void setCapabilities( Capabilities* caps );
        static const Capabilities& capabilities() { return instance()->getCapabilities(); }

        /**
         * Gets or sets the default shader factory. You can replace the default
         * shader factory if you want to alter any of osgEarth's baseline shaders
         * (advanced usage).
         */
        Util::ShaderFactory* getShaderFactory() const;
        void setShaderFactory( Util::ShaderFactory* lib );
        static Util::ShaderFactory* shaderFactory() { return instance()->getShaderFactory(); }

        /**
         * The default shader generator.
         * @deprecated REMOVE in 4.0; use ShaderGenerator directly
         */
        ShaderGeneratorProxy getShaderGenerator() const;
        void setShaderGenerator(ShaderGenerator* gen);
        static ShaderGeneratorProxy shaderGenerator() { return instance()->getShaderGenerator(); }

        //! Access the shader generator prototype, which is the base instance that
        //! gets cloned for each new shader generator proxy. Use this to set up options
        //! on the default shader generator. Be careful not to call this at runtime.
        ShaderGenerator* shaderGeneratorPrototype() const { return instance()->_shaderGen.get(); }

        /**
         * Global object index.
         */
        ObjectIndex* getObjectIndex() const;
        static ObjectIndex* objectIndex() { return instance()->getObjectIndex(); }

        /**
         * A default StateSetCache to use by any process that uses one.
         * A StateSetCache assist in stateset sharing across multiple nodes.
         * Note: A registry-wide SSC is only supported in OSG 3.1.4+. See
         * the Registry.cpp comments for details.
         * @deprecated REMOVE in 4.0
         */
        StateSetCache* getStateSetCache() const;
        void setStateSetCache( StateSetCache* cache );
        static StateSetCache* stateSetCache() { return instance()->getStateSetCache(); }

        /**
         * A shared cache for osg::Program objects created by the shader
         * composition subsystem (VirtualProgram).
         */
        ProgramRepo& getProgramRepo();
        static ProgramRepo& programRepo() { return instance()->getProgramRepo(); }

        /**
         * Sets a global read callback for URI objects.
         * @deprecated REMOVE in 4.0
         */
        void setURIReadCallback( URIReadCallback* callback );

        /**
         * Gets the global read callback for URI objects.
         * @deprecated REMOVE in 4.0
         */
        URIReadCallback* getURIReadCallback() const;

        /**
         * Gets the default set of osgDB::Options to use.
         * @deprecated
         */
        const osgDB::Options* getDefaultOptions() const;

        /**
         * Clones an options structure (fixing the archive caching), or creates
         * a new one.
         */
        static osgDB::Options* cloneOrCreateOptions( const osgDB::Options* options =0L );

        /**
         * Registers a Units definition.
         */
        void registerUnits(const UnitsType& prototype);
        UnitsType getUnits(const std::string& name) const;

        /**
         * The name of the default terrain engine driver
         */
        const std::string& getDefaultTerrainEngineDriverName() const { return _terrainEngineDriver; }

        /**
         * If set, all MapNodes will use the terrain driver specified here regardless
         * of the driver in the TerrainOptions/earth file.
         */
        optional<std::string>& overrideTerrainEngineDriverName() { return _overrideTerrainEngineDriverName; }
        const optional<std::string>& overrideTerrainEngineDriverName() const { return _overrideTerrainEngineDriverName; }

        //! Name of the scripting engine driver to use.
        const std::string& getScriptEngineDriverName() const { return _scriptEngineDriver; }
        void setScriptEngineDriverName(const std::string& value) { _scriptEngineDriver = value; }

        /**
         * For debugging - tracks activities in progress.
         */
        void startActivity(const std::string& name);
        void startActivity(const std::string& name, const std::string& text);
        void endActivity(const std::string& name);
        void getActivities(std::set<std::string>& output);

        /**
         * Gets the mime-type corresponding to a given extension.
         */
        std::string getMimeTypeForExtension(const std::string& extension);

        /**
         * Gets the file extension corresponding to a given mime-type.
         */
        std::string getExtensionForMimeType(const std::string& mimeType);

        /**
         * Sets the policy for calling osg::Texture::setUnRefImageDataAfterApply
         * in the osgEarth terrain engine.
         */
        optional<bool>& unRefImageDataAfterApply() { return _unRefImageDataAfterApply; }
        const optional<bool>& unRefImageDataAfterApply() const { return _unRefImageDataAfterApply; }

        /**
         * Adds a texture image unit number that osgEarth should never use.
         */
        void setTextureImageUnitOffLimits(int unit);
        const std::set<int> getOffLimitsTextureImageUnits() const;

        /**
         * Gets the device pixel ratio.
         * This method calls the custom function installed by calling setDevicePixelRatioFunction(),
         * or return 1.0 if no function has been installed.
         */
        float getDevicePixelRatio(osg::GraphicsContext* gc = nullptr) const;

        /**
        * Assigns a function that will return the  device pixel ratio.
        * This value will be used to scale the size of objects specified in pixels.
        * Since graphics front-ends (Qt, FLTK, etc.) will implement this differently we
        * provide a function to call into your particular framework.
        */
        void setDevicePixelRatioFunction(std::function<float(osg::GraphicsContext*)>&&);

        /**
         * Maximum number of vertices to allow in a single osg::Geometry drawable
         * when performing optimization processing. This varies based on hardware
         * capabilities. Default is 65535.
         */
        void setMaxNumberOfVertsPerDrawable(unsigned value);
        unsigned getMaxNumberOfVertsPerDrawable() const;

        /**
         * Maximum dimension (in either axis of images loaded from a URI).
         * Default is UINT_MAX. This will effectively limit the size of 
         * texture data throughout osgEarth.
         */
        void setMaxTextureSize(int value);
        int getMaxTextureSize() const;

        /**
         * Release OpenGL resources associated with anything in the reigstry
         */
        void releaseGLObjects(class osg::State*) const;

        //! Release any resources held by the registry so it can be re-used
        void release();

        //! Register singleton objects with the registry and defer
        //! their destruction to happen with the registry's own.
        template<typename R> R* registerSingleton(R* singleton)
        {
            std::lock_guard<std::mutex> lock(_regMutex);
            _singletons.push_back(singleton);
            return singleton;
        }

        //! Gets (or creates on demand) a named singleton object.
        //! FUNC must have the signature std::function<T*()>
        template<typename T, typename FUNC> T* getOrCreate(const std::string& name, FUNC&& create)
        {
            std::lock_guard<std::mutex> lock(_regMutex);
            auto& s = _namedSingletons[name];
            if (!s.valid()) s = create();
            return dynamic_cast<T*>(s.get());
        }

        //! Sets the registry-wide date/time.
        void setDateTime(const DateTime&);

        //! Gets the registry-wide date/time.
        DateTime getDateTime() const;

        Callback<void(const DateTime&)> onDateTimeChanged;

        virtual ~Registry();

    protected:
        Registry();

        mutable std::mutex _regMutex;

        ProgramRepo _programRepo;

        mutable osg::ref_ptr<Cache>   _defaultCache;
        mutable optional<CachePolicy> _defaultCachePolicy;
        mutable optional<CachePolicy> _overrideCachePolicy;

        mutable bool _overrideCachePolicyInitialized;

        typedef std::unordered_set<std::string> StringSet;
        Threading::Mutexed<StringSet> _blacklist;

        osg::ref_ptr<Util::ShaderFactory> _shaderLib;
        osg::ref_ptr<ShaderGenerator> _shaderGen;

        // system capabilities:
        osg::ref_ptr< Capabilities > _caps;
        mutable std::mutex     _capsMutex;
        void initCapabilities();

        osg::ref_ptr<osgDB::Options> _defaultOptions;

        osg::ref_ptr<URIReadCallback> _uriReadCallback;

        osg::ref_ptr<osgText::Font> _defaultFont;

        std::vector<UnitsType> _unitsVector;

        osg::ref_ptr<StateSetCache> _stateSetCache;

        std::string _terrainEngineDriver;
        optional<std::string> _overrideTerrainEngineDriverName;

        mutable optional<std::string> _cacheDriver;

        typedef std::pair<std::string,std::string> Activity;
        struct ActivityLess {
            bool operator()(const Activity& lhs, const Activity& rhs) const {
                return lhs.first < rhs.first;
            }
        };
        std::set<Activity,ActivityLess> _activities;
        mutable std::mutex _activityMutex;

        optional<bool> _unRefImageDataAfterApply;

        mutable osg::ref_ptr<ObjectIndex> _objectIndex;

        std::set<int> _offLimitsTextureImageUnits;

        unsigned _maxImageDimension;

        typedef std::unordered_map<SpatialReference::Key, osg::ref_ptr<SpatialReference> > SRSCache;
        Threading::Mutexed<SRSCache> _srsCache;

        unsigned _maxVertsPerDrawable;
        std::vector<osg::ref_ptr<osg::Referenced>> _singletons;
        std::unordered_map<std::string, osg::ref_ptr<osg::Referenced>> _namedSingletons;
        
        //! Gets a spatial reference object (internal)
        //! Please use SpatialReference::get() instead of calling this directly
        osg::ref_ptr<SpatialReference> getOrCreateSRS(const SpatialReference::Key& key);
        friend class SpatialReference;

        DateTime _dateTime;

        std::function<float(osg::GraphicsContext*)> _devicePixelRatioFunc = nullptr;

        std::string _scriptEngineDriver;
    };

    extern OSGEARTH_EXPORT std::int64_t g_startupPrivateBytes;
}
