/* osgEarth
 * Copyright 2025 Pelican Mapping
 * MIT License
 */
#ifndef OSGEARTH_EXT_TERRAIN_SHADER_OPTIONS
#define OSGEARTH_EXT_TERRAIN_SHADER_OPTIONS 1

#include <osgEarth/Config>
#include <osgEarth/URI>
#include <vector>

namespace osgEarth { namespace TerrainShader
{
    using namespace osgEarth;

    /**
     * Options for applying an inline shader to the terrain.
     */
    class TerrainShaderOptions : public ConfigOptions // NO EXPORT; header only
    {
    public:
        struct Code
        {
            std::string   _source;
            optional<URI> _uri;
        };

        struct Sampler
        {
            std::string      _name;
            std::vector<URI> _uris;
        };

        struct Uniform
        {
            std::string     _name;
            optional<float> _value;
        };

        std::vector<Code>& code() { return _code; }
        const std::vector<Code>& code() const { return _code; }

        std::vector<Sampler>& samplers() { return _samplers; }
        const std::vector<Sampler>& samplers() const { return _samplers; }

        std::vector<Uniform>& uniforms() { return _uniforms; }
        const std::vector<Uniform>& uniforms() const { return _uniforms; }

    public:
        TerrainShaderOptions( const ConfigOptions& opt =ConfigOptions() ) : ConfigOptions( opt )
        {
            fromConfig( _conf );
        }

        virtual ~TerrainShaderOptions() { }

    public:
        virtual Config getConfig() const
        {
            Config conf = ConfigOptions::getConfig();

            conf.remove("code");
            for(unsigned i=0; i<_code.size(); ++i) {
                Config c("code", _code[i]._source);
                c.set("url", _code[i]._uri);
                conf.add( c );
            }

            conf.remove("sampler");
            for(unsigned i=0; i<_samplers.size(); ++i) {
                Config c("sampler");
                c.add("name", _samplers[i]._name);
                if ( _samplers[i]._uris.size() > 1 ) {
                    Config urlarray("array");
                    c.add( urlarray );
                    for( std::vector<URI>::const_iterator j = _samplers[i]._uris.begin(); j != _samplers[i]._uris.end(); ++j) {
                        urlarray.add( j->getConfig() );
                    }
                }
                else if ( _samplers[i]._uris.size() == 1 ) {
                    c.add( _samplers[i]._uris.back().getConfig() );
                }
                conf.add( c );
            }

            conf.remove("uniform");
            for(unsigned i=0; i<_uniforms.size(); ++i) {
                Config c("uniform");
                c.set("name", _uniforms[i]._name);
                c.set("value", _uniforms[i]._value);
                conf.add( c );
            }
            
            return conf;
        }

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

    private:
        void fromConfig( const Config& conf ) {

            _code.clear();
            _samplers.clear();
            _uniforms.clear();

            ConfigSet s = conf.children("code");
            for(ConfigSet::const_iterator i = s.begin(); i != s.end(); ++i) {
                _code.push_back(Code());
                _code.back()._source = i->value();
                i->get("url", _code.back()._uri);
            }

            s = conf.children("sampler");
            for(ConfigSet::const_iterator i = s.begin(); i != s.end(); ++i) {
                _samplers.push_back(Sampler());
                _samplers.back()._name = i->value("name");
                const Config* urlarray = i->find("array");
                if ( urlarray ) {
                    ConfigSet uris = urlarray->children("url");
                    for(ConfigSet::const_iterator j = uris.begin(); j != uris.end(); ++j) {
                        URI uri( j->value(), URIContext(conf.referrer()) );
                        _samplers.back()._uris.push_back( uri );
                    }
                }
                else {
                   optional<URI> uri;
                   i->get("url", uri);
                   if ( uri.isSet() )
                       _samplers.back()._uris.push_back( uri.get() );
                }
            }

            s = conf.children("uniform");
            for(ConfigSet::const_iterator i = s.begin(); i != s.end(); ++i) {
                _uniforms.push_back(Uniform());
                _uniforms.back()._name = i->value("name");
                i->get("value", _uniforms.back()._value);
            }
        }

        std::vector<Code>     _code;
        std::vector<Sampler>  _samplers;
        std::vector<Uniform>  _uniforms;
    };

} } // namespace osgEarth::TerrainShader

#endif // OSGEARTH_EXT_TERRAIN_SHADER_OPTIONS

