/* osgEarth
* Copyright 2025 Pelican Mapping
* MIT License
*/
#include <osgEarth/ScreenSpaceLayout>
#include <osgEarth/Utils>
#include <osgEarth/VirtualProgram>
#include <osgEarth/Extension>
#include "ScreenSpaceLayoutDeclutter"
#include "ScreenSpaceLayoutCallout"

#include <stdlib.h> // getenv

#define LC "[ScreenSpaceLayout] "

using namespace osgEarth;
using namespace osgEarth::Internal;

//----------------------------------------------------------------------------

bool osgEarth::ScreenSpaceLayout::globallyEnabled = true;


//----------------------------------------------------------------------------

void
ScreenSpaceLayoutOptions::fromConfig(const Config& conf)
{
    conf.get("min_animation_scale", minAnimationScale());
    conf.get("min_animation_alpha", minAnimationAlpha());
    conf.get("in_animation_time", inAnimationTime());
    conf.get("out_animation_time", outAnimationTime());
    conf.get("sort_by_priority", sortByPriority());
    conf.get("sort_by_distance", sortByDistance());
    conf.get("snap_to_pixel", snapToPixel());
    conf.get("max_objects", maxObjects());
    conf.get("render_order", renderOrder());
    conf.get("technique", "labels", technique(), TECHNIQUE_LABELS);
    conf.get("technique", "callouts", technique(), TECHNIQUE_CALLOUTS);
    conf.get("max_leader_length", leaderLineMaxLength());
    conf.get("leader_line_max_length", leaderLineMaxLength());
    conf.get("leader_line_color", leaderLineColor());
    conf.get("leader_line_width", leaderLineWidth());
}

Config
ScreenSpaceLayoutOptions::getConfig() const
{
    Config conf;
    conf.set("min_animation_scale", minAnimationScale());
    conf.set("min_animation_alpha", minAnimationAlpha());
    conf.set("in_animation_time", inAnimationTime());
    conf.set("out_animation_time", outAnimationTime());
    conf.set("sort_by_priority", sortByPriority());
    conf.set("sort_by_distance", sortByDistance());
    conf.set("snap_to_pixel", snapToPixel());
    conf.set("max_objects", maxObjects());
    conf.set("render_order", renderOrder());
    conf.set("technique", "labels", technique(), TECHNIQUE_LABELS);
    conf.set("technique", "callouts", technique(), TECHNIQUE_CALLOUTS);
    conf.set("max_leader_length", leaderLineMaxLength());
    conf.set("leader_line_max_length", leaderLineMaxLength());
    conf.set("leader_line_color", leaderLineColor());
    conf.set("leader_line_width", leaderLineWidth());
    return conf;
}


namespace
{
    /**
    * The actual custom render bin
    * This wants to be in the global scope for the dynamic registration to work,
    * hence the annoyinging long class name
    */
    class osgEarthScreenSpaceLayoutRenderBin : public osgUtil::RenderBin
    {
    public:
        osgEarthScreenSpaceLayoutRenderBin()
        {
            _vpInstalled = false;

            this->setName( OSGEARTH_SCREEN_SPACE_LAYOUT_BIN );
            _context = new ScreenSpaceLayoutContext();
            clearSortingFunctor();

            // needs its own state set for special magic.
            osg::StateSet* stateSet = new osg::StateSet();
            this->setStateSet( stateSet );
        }

        osgEarthScreenSpaceLayoutRenderBin(const osgEarthScreenSpaceLayoutRenderBin& rhs, const osg::CopyOp& copy)
            : osgUtil::RenderBin(rhs, copy),
            _f(rhs._f.get()),
            _context(rhs._context.get())
        {
            // Set up a VP to do fading. Do it here so it doesn't happen until the first time
            // we clone the render bin. This play nicely with static initialization.
            if (!_vpInstalled)
            {
                std::lock_guard<std::mutex> lock(_vpMutex);
                if (!_vpInstalled)
                {
                    VirtualProgram* vp = VirtualProgram::getOrCreate(getStateSet());
                    vp->setName(typeid(*this).name());
                    vp->setFunction( "oe_declutter_apply_fade", s_faderFS, VirtualProgram::LOCATION_FRAGMENT_COLORING, 0.5f );
                    _vpInstalled = true;
                    //OE_INFO << LC << "Decluttering VP installed\n";
                }
            }
        }

        virtual osg::Object* clone(const osg::CopyOp& copyop) const
        {
            return new osgEarthScreenSpaceLayoutRenderBin(*this, copyop);
        }

        void setSortingFunctor( DeclutterSortFunctor* f )
        {
            _f = f;

            if (_context->_options.technique() == ScreenSpaceLayoutOptions::TECHNIQUE_LABELS)
            {
                setSortCallback(new DeclutterImplementation(_context.get(), f));
                setDrawCallback(new DeclutterDraw(_context.get()));
            }
            else
            {
                CalloutImplementation* impl = new CalloutImplementation(_context.get(), f);
                setSortCallback(impl);
                CalloutDraw* draw = new CalloutDraw(_context.get());
                draw->_sortCallback = impl;
                setDrawCallback(draw);
            }
        }

        void clearSortingFunctor()
        {
            setSortingFunctor(NULL);
        }

        void refresh()
        {
            setSortingFunctor(_f.get());
        }

        osg::ref_ptr<DeclutterSortFunctor> _f;
        osg::ref_ptr<ScreenSpaceLayoutContext> _context;
        static std::mutex _vpMutex;
        static bool _vpInstalled;
    };

    std::mutex osgEarthScreenSpaceLayoutRenderBin::_vpMutex;
    bool osgEarthScreenSpaceLayoutRenderBin::_vpInstalled = false;
}

//----------------------------------------------------------------------------

void
ScreenSpaceLayout::activate(osg::StateSet* stateSet) //, int binNum)
{
    if ( stateSet )
    {
        int binNum = getOptions().renderOrder().get();

        // the OVERRIDE prevents subsequent statesets from disabling the layout bin
        stateSet->setRenderBinDetails(
            binNum,
            OSGEARTH_SCREEN_SPACE_LAYOUT_BIN,
            osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS);

        // Force a single shared layout bin per render stage
        stateSet->setNestRenderBins( false );

        // Range opacity is not supported for screen-space rendering
        stateSet->setDefine("OE_DISABLE_RANGE_OPACITY");
    }
}

void
ScreenSpaceLayout::deactivate(osg::StateSet* stateSet)
{
    if (stateSet)
    {
        stateSet->setRenderBinToInherit();
        stateSet->setNestRenderBins(true);
    }
}

void
ScreenSpaceLayout::setDeclutteringEnabled(bool enabled)
{
    globallyEnabled = enabled;
}

void
ScreenSpaceLayout::setSortFunctor( DeclutterSortFunctor* functor )
{
    // pull our prototype
    osgEarthScreenSpaceLayoutRenderBin* bin = dynamic_cast<osgEarthScreenSpaceLayoutRenderBin*>(
        osgUtil::RenderBin::getRenderBinPrototype( OSGEARTH_SCREEN_SPACE_LAYOUT_BIN ) );

    if ( bin )
    {
        bin->setSortingFunctor( functor );
    }
}

void
ScreenSpaceLayout::clearSortFunctor()
{
    // pull our prototype
    osgEarthScreenSpaceLayoutRenderBin* bin = dynamic_cast<osgEarthScreenSpaceLayoutRenderBin*>(
        osgUtil::RenderBin::getRenderBinPrototype( OSGEARTH_SCREEN_SPACE_LAYOUT_BIN ) );

    if ( bin )
    {
        bin->clearSortingFunctor();
    }
}

void
ScreenSpaceLayout::setOptions( const ScreenSpaceLayoutOptions& options )
{
    // pull our prototype
    osgEarthScreenSpaceLayoutRenderBin* bin = dynamic_cast<osgEarthScreenSpaceLayoutRenderBin*>(
        osgUtil::RenderBin::getRenderBinPrototype( OSGEARTH_SCREEN_SPACE_LAYOUT_BIN ) );

    if ( bin )
    {
        // activate priority-sorting through the options.
        if ( options.sortByPriority().isSetTo( true ) &&
            bin->_context->_options.sortByPriority() == false )
        {
            ScreenSpaceLayout::setSortFunctor(new SortByPriorityPreservingGeodeTraversalOrder());
        }

        // communicate the new options on the shared context.
        bin->_context->_options = options;

        bin->refresh();
    }
}

const ScreenSpaceLayoutOptions&
ScreenSpaceLayout::getOptions()
{
    static ScreenSpaceLayoutOptions s_defaultOptions;

    // pull our prototype
    osgEarthScreenSpaceLayoutRenderBin* bin = dynamic_cast<osgEarthScreenSpaceLayoutRenderBin*>(
        osgUtil::RenderBin::getRenderBinPrototype( OSGEARTH_SCREEN_SPACE_LAYOUT_BIN ) );

    if ( bin )
    {
        return bin->_context->_options;
    }
    else
    {
        return s_defaultOptions;
    }
}

//----------------------------------------------------------------------------

namespace
{
    /** the actual registration. */
    extern "C" void osgEarth_declutter(void) {}
    static osgEarthRegisterRenderBinProxy<osgEarthScreenSpaceLayoutRenderBin> s_regbin(OSGEARTH_SCREEN_SPACE_LAYOUT_BIN);
}


//----------------------------------------------------------------------------

// Extension for configuring the decluterring/SSL options from an Earth file.
namespace osgEarth
{
    class ScreenSpaceLayoutExtension : public Extension,
        public ScreenSpaceLayoutOptions
    {
    public:
        META_OE_Extension(osgEarth, ScreenSpaceLayoutExtension, screen_space_layout);

        ScreenSpaceLayoutExtension() { }

        ScreenSpaceLayoutExtension(const ConfigOptions& co) : ScreenSpaceLayoutOptions(co)
        {
            // sets the global default options.
            ScreenSpaceLayout::setOptions(*this);
        }

        const ConfigOptions& getConfigOptions() const { return *this; }
    };

    REGISTER_OSGEARTH_EXTENSION(osgearth_screen_space_layout, ScreenSpaceLayoutExtension);
    REGISTER_OSGEARTH_EXTENSION(osgearth_screenspacelayout,   ScreenSpaceLayoutExtension);
    REGISTER_OSGEARTH_EXTENSION(osgearth_decluttering,        ScreenSpaceLayoutExtension);
}

