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

#include <osgEarthImGui/ImGuiPanel>
#include <osgEarth/Ephemeris>
#include <osgEarth/Sky>
#include <osgEarth/ShaderLoader>
#include <osgEarth/Shadowing>
#include <osgEarth/VirtualProgram>
#include <osgEarth/WindLayer>

namespace
{
    const char* render_wind = R"(
#version 450
#pragma vp_function oe_ui_render_wind_vert, vertex_view
out vec3 viewpos3_wind;
void oe_ui_render_wind_vert(inout vec4 vertex) {
    viewpos3_wind = vertex.xyz;
}
[break]
#version 450
#pragma vp_function oeui_render_wind_texture, fragment_output
#pragma import_defines(OE_WIND_TEX)
#pragma import_defines(OE_WIND_TEX_MATRIX)
in vec3 viewpos3_wind;
out vec4 frag_out;
#ifdef OE_WIND_TEX
uniform sampler3D OE_WIND_TEX;
uniform mat4 OE_WIND_TEX_MATRIX;
uniform vec3 oe_Camera;
uniform float osg_FrameTime;

#pragma import_defines(OE_TWEAKABLE)
#ifdef OE_TWEAKABLE
#define tweakable uniform
#else
#define tweakable const
#endif
tweakable float oe_wind_power = 1.0;

#define MAX_WIND_SPEED 50.0
#endif

void oeui_render_wind_texture(inout vec4 color)
{
    frag_out = color;

  #ifdef OE_WIND_TEX
    vec4 texel = textureProj(OE_WIND_TEX, (OE_WIND_TEX_MATRIX*vec4(viewpos3_wind,1)));
    float speed = texel.a * oe_wind_power;

    vec3 wind_view = 2.0*texel.xyz - 1.0;
    vec3 wind_clip = mat3(gl_ProjectionMatrix) * wind_view;
    vec2 wind_screen = wind_clip.xy * oe_Camera.xy;

    vec2 coord = (gl_FragCoord.xy - 0.5);
    vec2 rv = normalize(wind_screen);
    vec2 coordProj = mat2(rv.x, -rv.y, rv.y, rv.x) * coord;

    const float oe_wind_animation_speed = 2.8 * MAX_WIND_SPEED * speed; // 32.0
    int cx = int(coordProj.x - int(osg_FrameTime*oe_wind_animation_speed));
    int ci = cx % 32;
    const int stipple = 0x00000001;
    int pattern32 = 0xffffffff & (stipple & (1 << ci));
    if (pattern32 != 0)
        frag_out = vec4(0.5,0.5,1,0.5);
  #endif
}
)";
}

namespace osgEarth
{
    using namespace osgEarth::Util;

    class EnvironmentGUI : public ImGuiPanel
    {
    private:
        osg::observer_ptr<MapNode> _mapNode;
        osg::observer_ptr<SkyNode> _skyNode;
        osg::observer_ptr<ShadowCaster> _shadowCaster;
        osg::observer_ptr<WindLayer> _windLayer;
        bool _showDetails = false;
        float _hour;
        int _day, _month, _year;
        float _exposure = 3.5f;
        float _contrast = 1.0f;
        float _ambient = 0.033f;
        float _max_ambient_intensity = 0.75;
        bool _first = true;
        bool _shadows = false;
        float _haze_cutoff = 0.0f, _haze_strength = 16.0f;
        float _shadow_darkness = 0.5f, _shadow_blur = 0.001f;
        float _wind_power = 1.0f;

    public:
        EnvironmentGUI() : ImGuiPanel("Sky")
        {
            DateTime now;
            _hour = now.hours(), _day = now.day(), _month = now.month(), _year = now.year();
        }

        void load(const Config& conf) override
        {
            conf.get("ShowDetails", _showDetails);
            conf.get("Exposure", _exposure);
            conf.get("Contrast", _contrast);
            conf.get("Ambient", _ambient);
            conf.get("HazeCutoff", _haze_cutoff);
            conf.get("HazeStrength", _haze_strength);
            conf.get("WindPower", _wind_power);
        }
        void save(Config& conf) override
        {
            conf.set("ShowDetails", _showDetails);
            conf.set("Exposure", _exposure);
            conf.set("Contrast", _contrast);
            conf.set("Ambient", _ambient);
            conf.set("HazeCutoff", _haze_cutoff);
            conf.set("HazeStrength", _haze_strength);
            conf.set("WindPower", _wind_power);
        }

        void draw(osg::RenderInfo& ri) override
        {
            if (!isVisible() || !findNodeOrHide(_mapNode, ri))
                return;

            if (ImGui::Begin(name(), visible()))
            {
                if (!findNode(_skyNode, ri))
                {
                    ImGui::Text("No Sky installed.");
                    if (ImGui::Button("Install"))
                    {
                        auto sky = SkyNode::create();
                        auto parent = _mapNode->getParent(0);
                        sky->addChild(_mapNode.get());
                        parent->addChild(sky);
                        parent->removeChild(_mapNode.get());
                        sky->attach(view(ri));
                    }
                    ImGui::End();
                    return;
                }

                if (_first)
                {
                    findNode(_shadowCaster, ri);
                    if (_shadowCaster.valid())
                        _shadows = _shadowCaster->getEnabled();

                    findLayer(_windLayer, ri);

                    _skyNode->setDateTime(DateTime(_year, _month, _day, _hour));

                    // so we can visualize tiume-series layers.
                    _skyNode->setSimulationTimeTracksDateTime(true);
                }

                bool lighting = _skyNode->getLighting();
                ImGui::Checkbox("Lighting", &lighting);
                _skyNode->setLighting(lighting);

                if (_shadowCaster.valid()) {
                    ImGui::SameLine();
                    ImGui::Checkbox("Shadows", &_shadows);
                    _shadowCaster->setEnabled(_shadows);
                }

                ImGui::SameLine();
                if (ImGui::Checkbox("Details", &_showDetails)) dirtySettings();

                ImGui::SameLine();
                if (ImGui::Button("Now")) {
                    _skyNode->setDateTime(DateTime());
                    dirtySettings();
                }

                ImGui::Separator();
                if (ImGuiLTable::Begin("Environment"))
                {
                    ImGuiLTable::Section("Date & Time:");

                    auto mark = _skyNode->getDateTime();
                    auto day = mark.day();
                    auto month = mark.month();
                    auto year = mark.year();
                    auto hour = mark.hours();

                    if (ImGuiLTable::SliderDouble("Hour (UTC)", &hour, 0.0f, 24.0f))
                        _hour = hour, dirtySettings();

                    if (_showDetails)
                    {
                        if (ImGuiLTable::SliderInt("Day", &day, 1, 31))
                            _day = day, dirtySettings();
                        if (ImGuiLTable::SliderInt("Month", &month, 1, 12))
                            _month = month, dirtySettings();
                        if (ImGuiLTable::SliderInt("Year", &year, 1970, 2061))
                            _year = year, dirtySettings();
                    }
                    _skyNode->setDateTime(DateTime(year, month, day, hour));

                    if (lighting)
                    {
                        if (ImGuiLTable::SliderFloat("Exposure", &_exposure, 1.0f, 10.0f)) dirtySettings();
                        _skyNode->getOrCreateStateSet()->getOrCreateUniform("oe_sky_exposure", osg::Uniform::FLOAT)->set(_exposure);

                        if (ImGuiLTable::SliderFloat("Ambient min", &_ambient, 0.0f, 1.0f)) dirtySettings();
                        _skyNode->getSunLight()->setAmbient(osg::Vec4(_ambient, _ambient, _ambient, 1.0f));

                        if (ImGuiLTable::SliderFloat("Ambient max", &_max_ambient_intensity, 0.0f, 1.0f)) dirtySettings();
                        _skyNode->getOrCreateStateSet()->getOrCreateUniform("oe_sky_maxAmbientIntensity", osg::Uniform::FLOAT)->set(_max_ambient_intensity);

                        auto diffuse_color = _skyNode->getSunLight()->getDiffuse();
                        if (ImGuiLTable::ColorEdit3("Diffuse color", &diffuse_color[0], ImGuiColorEditFlags_Float)) {
                            _skyNode->getSunLight()->setDiffuse(diffuse_color);
                            dirtySettings();
                        }

                        static float normal_boost = 1.0f;
                        if (ImGuiLTable::SliderFloat("Normal boost", &normal_boost, 1.0f, 5.0f)) {
                            _skyNode->getOrCreateStateSet()->getOrCreateUniform("oe_normal_boost", osg::Uniform::FLOAT)->set(normal_boost);
                            dirtySettings();
                        }
                    }
                    else
                    {
                        _shadows = false;
                    }

                    if (_windLayer.valid())
                    {
                        ImGui::Separator();
                        ImGuiLTable::Section("Wind");

                        if (ImGuiLTable::SliderFloat("Speed mult", &_wind_power, 0.0f, 9.0f, "%.1f", 0) || _first)
                        {
                            stateset(ri)->addUniform(new osg::Uniform("oe_wind_power", _wind_power),
                                osg::StateAttribute::OVERRIDE | 0x01);
                            dirtySettings();
                        }

                        static bool show_wind = false;
                        if (ImGuiLTable::Checkbox("Debug view", &show_wind))
                        {
                            if (show_wind)
                                ShaderLoader::load(VirtualProgram::getOrCreate(stateset(ri)), render_wind);
                            else
                                ShaderLoader::unload(VirtualProgram::getOrCreate(stateset(ri)), render_wind);
                        }
                    }

                    if (_showDetails)
                    {
                        ImGui::Separator();
                        ImGuiLTable::Section("Details");

                        if (_shadows)
                        {
                            if (ImGuiLTable::SliderFloat("Shadow darkness", &_shadow_darkness, 0.0f, 1.0f))
                                stateset(ri)->addUniform(new osg::Uniform("oe_shadow_color", _shadow_darkness), 0x7);

                            if (ImGuiLTable::SliderFloat("Shadow blur", &_shadow_blur, 0.0f, 0.002f))
                                stateset(ri)->addUniform(new osg::Uniform("oe_shadow_blur", _shadow_blur), 0x07);
                        }

                        if (ImGuiLTable::SliderFloat("Haze cutoff", &_haze_cutoff, 0.0f, 0.2f))
                            dirtySettings();
                        _skyNode->getOrCreateStateSet()->getOrCreateUniform("atmos_haze_cutoff", osg::Uniform::FLOAT)->set(_haze_cutoff);

                        if (ImGuiLTable::SliderFloat("Haze strength", &_haze_strength, 0.0f, 24.0f))
                            dirtySettings();
                        _skyNode->getOrCreateStateSet()->getOrCreateUniform("atmos_haze_strength", osg::Uniform::FLOAT)->set(_haze_strength);

                        bool atmos_visible = _skyNode->getAtmosphereVisible();
                        ImGuiLTable::Checkbox("Atmosphere", &atmos_visible);
                        _skyNode->setAtmosphereVisible(atmos_visible);

                        bool sun_visible = _skyNode->getSunVisible();
                        ImGuiLTable::Checkbox("Sun", &sun_visible);
                        _skyNode->setSunVisible(sun_visible);

                        bool moon_visible = _skyNode->getMoonVisible();
                        ImGuiLTable::Checkbox("Moon", &moon_visible);
                        _skyNode->setMoonVisible(moon_visible);

                        bool stars_visible = _skyNode->getStarsVisible();
                        ImGuiLTable::Checkbox("Stars", &stars_visible);
                        _skyNode->setStarsVisible(stars_visible);


                        ImGui::Separator();

                        DateTime dt = _skyNode->getDateTime();

                        CelestialBody sun = _skyNode->getEphemeris()->getSunPosition(dt);
                        ImGuiLTable::Text("Sun:", "RA (%.2f) Decl (%.2f)",
                            sun.rightAscension.as(Units::DEGREES),
                            sun.declination.as(Units::DEGREES));

                        CelestialBody moon = _skyNode->getEphemeris()->getMoonPosition(dt);
                        ImGuiLTable::Text("Moon:", "RA (%.2f) Decl (%.2f)",
                            moon.rightAscension.as(Units::DEGREES),
                            moon.declination.as(Units::DEGREES));
                    }

                    ImGuiLTable::End();
                }

                _first = false;
            }
            ImGui::End();
        }
    };
}
