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

#include <osgEarth/Common>
#include <osgEarth/Ephemeris>
#include <osgEarth/DateTime>
#include <osgEarth/GeoData>
#include <osgEarth/Config>
#include <osgEarth/SpatialReference>
#include <osg/Group>
#include <osg/Uniform>
#include <osg/View>
#include <osg/ArgumentParser>
#include <osgDB/ReaderWriter>

namespace osgDB {
    class Options;
}

namespace osgEarth
{
    /**
     * Base Options structure for loading an environment node from
     * a plugin.
     */
    class SkyOptions : public DriverConfigOptions
    {
    public:
        enum CoordinateSystem 
        {
            COORDSYS_ECEF,
            COORDSYS_ECI
        };

        enum Quality
        {
            QUALITY_UNSET,
            QUALITY_DEFAULT,
            QUALITY_LOW,
            QUALITY_MEDIUM,
            QUALITY_HIGH,
            QUALITY_BEST
        };

        static Quality parseQuality(osg::ArgumentParser& args) {
            return
                args.read("--sky") ? QUALITY_DEFAULT :
                args.read("--sky-low") ? QUALITY_LOW :
                args.read("--sky-medium") ? QUALITY_MEDIUM :
                args.read("--sky-high") ? QUALITY_HIGH :
                args.read("--sky-best") ? QUALITY_BEST :
                QUALITY_UNSET;
        }

        //! Coordinate system for whole-earth maps (default = ECEF)
        OE_OPTION(CoordinateSystem, coordinateSystem);

        /** Time of day - Hours [0..24] component of DateTime */
        OE_OPTION(float, hours);

        OE_OPTION(float, ambient);
        OE_OPTION(Quality, quality);

    public:
        SkyOptions( const ConfigOptions& options =ConfigOptions() ) : DriverConfigOptions(options) {
            coordinateSystem().setDefault(COORDSYS_ECEF);
            quality().setDefault(QUALITY_DEFAULT);
            ambient().setDefault(0.033f);
            hours().setDefault(12.0f);
            fromConfig(_conf);
        }
        virtual ~SkyOptions() { }
        virtual Config getConfig() const {
            Config conf = DriverConfigOptions::getConfig();
            conf.set("coordsys", "ecef", coordinateSystem(), COORDSYS_ECEF);
            conf.set("coordsys", "eci", coordinateSystem(), COORDSYS_ECI);
            conf.set("hours", hours());
            conf.set("ambient", ambient());
            conf.set("quality", "default", quality(), QUALITY_DEFAULT);
            conf.set("quality", "low", quality(), QUALITY_LOW);
            conf.set("quality", "medium", quality(), QUALITY_MEDIUM);
            conf.set("quality", "high", quality(), QUALITY_HIGH);
            conf.set("quality", "best", quality(), QUALITY_BEST);
            return conf;
        }

    protected:
        virtual void mergeConfig( const Config& conf ) {
            ConfigOptions::mergeConfig( conf );
            fromConfig( conf );
        }

    private:
        void fromConfig( const Config& conf ) {
            conf.get("coordsys", "ecef", coordinateSystem(), COORDSYS_ECEF);
            conf.get("coordsys", "eci", coordinateSystem(), COORDSYS_ECI);
            conf.get("hours", hours());
            conf.get("ambient", ambient());
            conf.get("quality", "default", quality(), QUALITY_DEFAULT);
            conf.get("quality", "low", quality(), QUALITY_LOW);
            conf.get("quality", "medium", quality(), QUALITY_MEDIUM);
            conf.get("quality", "high", quality(), QUALITY_HIGH);
            conf.get("quality", "best", quality(), QUALITY_BEST);
        }
    };


    /**
    * Interface for classes that provide sky, lighting, and other
    * environmental effect.
    */
    class OSGEARTH_EXPORT SkyNode : public osg::Group
    {
    public:
        //! Creates a SkyNode with the default implementation.
        static SkyNode* create();

        //! Creates a SkyNode with custom options.
        static SkyNode* create(const SkyOptions&);

        //! Creates a SkyNode from a raw driver name.
        static SkyNode* create(const std::string& driver);

    protected:
        // CTOR (abstract base class)
        SkyNode();

        // CTOR (abstract base class)
        SkyNode(const SkyOptions& options);

        // protected DTOR (heap-only)
        virtual ~SkyNode();

    public:
        /**
         * The ephemeris reference point for projected maps. Not applicable
         * for geocentric maps. Setting this also informs the skynode that is
         * should operate in projected-map mode if possible.
         */
        void setReferencePoint(const GeoPoint& point);
        const GeoPoint& getReferencePoint() const { return *_refpoint; }

        /**
         * Gets/Sets the Ephemeris used to position the sun and the moon
         * based on date/time.
         */
        void setEphemeris(Ephemeris* ephemeris);
        const Ephemeris* getEphemeris() const;

        /**
         * Whether the sky lights its subgraph.
         */
        void setLighting(osg::StateAttribute::OverrideValue value);
        osg::StateAttribute::OverrideValue getLighting() const { return _lightingValue; }

        /**
         * Gets the date/time for which the environment is configured.
         * Pass in an optional View to get the date/time specific to
         * that View.
         */
        void setDateTime(const DateTime& dt);
        DateTime getDateTime() const;

        //! Whether the traversal framestamp simulation time should
        //! be set to the sky's date time. Default is false.
        void setSimulationTimeTracksDateTime(bool value);
        bool getSimulationTimeTracksDateTime() const;

        /** Whether the sun is visible */
        void setSunVisible(bool value);
        bool getSunVisible() const { return _sunVisible; }

        /** Whether the moon is visible */
        void setMoonVisible(bool value);
        bool getMoonVisible() const { return _moonVisible; }

        /** Whether the stars are visible */
        void setStarsVisible(bool value);
        bool getStarsVisible() const { return _starsVisible; }

        /** Whether the atmosphere is visible */
        void setAtmosphereVisible(bool value);
        bool getAtmosphereVisible() const { return _atmosphereVisible; }

        /** Access the osg::Light representing the sun */
        virtual osg::Light* getSunLight() const = 0;

    public:

        /** Attaches this sky node to a view (placing a sky light). Optional */
        virtual void attach(osg::View* view, int lightNum) { }
        void attach(osg::View* view) { attach(view, 0); }

    public: // osg::Node

        void traverse(osg::NodeVisitor& nv) override;

    protected:

        // impl class can override these events.
        virtual void onSetEphemeris() { }
        virtual void onSetDateTime() { }
        virtual void onSetReferencePoint() { }
        virtual void onSetMoonVisible() { }
        virtual void onSetStarsVisible() { }
        virtual void onSetSunVisible() { }
        virtual void onSetAtmosphereVisible() { }

    private:

        osg::ref_ptr<Ephemeris> _ephemeris;
        bool _sunVisible = true;
        bool _moonVisible = true;
        bool _starsVisible = true;
        bool _atmosphereVisible = true;
        osg::Vec4f _minimumAmbient = { 0,0,0,1 };
        optional<GeoPoint> _refpoint;
        bool _simTimeTracksDateTime = false;

        osg::StateAttribute::OverrideValue _lightingValue;
        osg::ref_ptr<osg::Uniform> _lightingUniform;

        void baseInit(const SkyOptions&);

        std::atomic_bool _callbackInstalled = { false };
    };

    namespace Util 
    {
        /**
         * Base class for an sky driver plugin implementation.
         */
        class OSGEARTH_EXPORT SkyDriver : public osgDB::ReaderWriter
        {
        protected:
            const SkyOptions& getSkyOptions(const osgDB::Options* opt) const;
        };

        /**
         * Factory interface that sky extensions need to implement. Someday we will
         * convert the sky extensions into proper NodeKits, at which point this method
         * will probably be no longer necessary since you can just link with the library
         * and create the SkyNode normally.
         */
        class /*header-only*/ SkyNodeFactory
        {
        public:
            virtual SkyNode* createSkyNode() =0;
        };
    }
}
