/* osgEarth
 * Copyright 2025 Pelican Mapping
 * MIT License
 */
#include <osgEarth/IconSymbol>
#include <osgEarth/IconResource>
#include <osgEarth/Style>
#include <osgEarth/Threading>
#include <osgEarth/Registry>
#include <osgEarth/ImageUtils>
#include <osgDB/Options>

using namespace osgEarth;

OSGEARTH_REGISTER_SIMPLE_SYMBOL(icon, IconSymbol);

IconSymbol::IconSymbol(const IconSymbol& rhs, const osg::CopyOp& copyop) :
    InstanceSymbol(rhs, copyop),
    _alignment(rhs._alignment),
    _heading(rhs._heading),
    _declutter(rhs._declutter),
    _image(rhs._image),
    _occlusionCull(rhs._occlusionCull),
    _occlusionCullAltitude(rhs._occlusionCullAltitude)
{
}

IconSymbol::IconSymbol(const Config& conf) :
    InstanceSymbol(conf)
{
    mergeConfig(conf);
}

Config 
IconSymbol::getConfig() const
{
    Config conf = InstanceSymbol::getConfig();
    conf.key() = "icon";

    conf.set( "alignment", "left_top",      _alignment, ALIGN_LEFT_TOP );
    conf.set( "alignment", "left_center",   _alignment, ALIGN_LEFT_CENTER );
    conf.set( "alignment", "left_bottom",   _alignment, ALIGN_LEFT_BOTTOM );
    conf.set( "alignment", "center_top",    _alignment, ALIGN_CENTER_TOP );
    conf.set( "alignment", "center_center", _alignment, ALIGN_CENTER_CENTER );
    conf.set( "alignment", "center_bottom", _alignment, ALIGN_CENTER_BOTTOM );
    conf.set( "alignment", "right_top",     _alignment, ALIGN_RIGHT_TOP );
    conf.set( "alignment", "right_center",  _alignment, ALIGN_RIGHT_CENTER );
    conf.set( "alignment", "right_bottom",  _alignment, ALIGN_RIGHT_BOTTOM );

    conf.set( "heading",   _heading );
    conf.set( "declutter", _declutter );	                  
	conf.set( "icon-occlusion-cull", _occlusionCull );
    conf.set( "icon-occlusion-cull-altitude", _occlusionCullAltitude );

    //conf.setNonSerializable( "IconSymbol::image", _image.get() );
    return conf;
}

void 
IconSymbol::mergeConfig( const Config& conf )
{
    conf.get( "alignment", "left_top",      _alignment, ALIGN_LEFT_TOP );
    conf.get( "alignment", "left_center",   _alignment, ALIGN_LEFT_CENTER );
    conf.get( "alignment", "left_bottom",   _alignment, ALIGN_LEFT_BOTTOM );
    conf.get( "alignment", "center_top",    _alignment, ALIGN_CENTER_TOP );
    conf.get( "alignment", "center_center", _alignment, ALIGN_CENTER_CENTER );
    conf.get( "alignment", "center_bottom", _alignment, ALIGN_CENTER_BOTTOM );
    conf.get( "alignment", "right_top",     _alignment, ALIGN_RIGHT_TOP );
    conf.get( "alignment", "right_center",  _alignment, ALIGN_RIGHT_CENTER );
    conf.get( "alignment", "right_bottom",  _alignment, ALIGN_RIGHT_BOTTOM );

    conf.get( "heading",   _heading );
    conf.get( "declutter", _declutter );
	conf.get( "icon-occlusion-cull", _occlusionCull );
    conf.get( "icon-occlusion-cull-altitude", _occlusionCullAltitude );

    //_image = conf.getNonSerializable<osg::Image>( "IconSymbol::image" );
}

namespace
{
    static std::mutex s_getImageMutex;
}

osg::Image*
IconSymbol::getImage( unsigned maxSize ) const
{
    if ( !_image.valid() && url().isSet() )
    {
        std::lock_guard<std::mutex> lock(s_getImageMutex);
        if ( !_image.valid() )
        {
            osg::ref_ptr<osgDB::Options> dbOptions = Registry::instance()->cloneOrCreateOptions();
            dbOptions->setObjectCacheHint( osgDB::Options::CACHE_IMAGES );
            _image = url()->literal().getImage(dbOptions.get());
            if ( _image.valid() && (maxSize < (unsigned int)_image->s() || maxSize < (unsigned int)_image->t()) )
            {
                unsigned new_s, new_t;

                if ( _image->s() >= _image->t() ) {
                    new_s = maxSize;
                    float ratio = (float)new_s/(float)_image->s();
                    new_t = (unsigned)((float)_image->t() * ratio);
                }
                else {
                    new_t = maxSize;
                    float ratio = (float)new_t/(float)_image->t();
                    new_s = (unsigned)((float)_image->s() * ratio);
                }
                    
                osg::ref_ptr<osg::Image> result;
                ImageUtils::resizeImage( _image.get(), new_s, new_t, result );
                _image = result.get();
            }
        }
    }
    return _image.get();
}

InstanceResource*
IconSymbol::createResource() const 
{
    return new IconResource();
}


void
IconSymbol::parseSLD(const Config& c, Style& style)
{
    IconSymbol defaults;

    if (match(c.key(), "library")) {
        if (!c.value().empty())
            style.getOrCreate<SkinSymbol>()->library() = Strings::unquote(c.value());
    }
    else
    if ( match(c.key(), "icon") ) {
        style.getOrCreate<IconSymbol>()->url() = Expression<URI>(c.value(), c.referrer());
    }
    else if ( match(c.key(),"icon-library") ) {
        style.getOrCreate<IconSymbol>()->library() = Strings::unquote(c.value());
    }
    else if ( match(c.key(), "icon-placement") ) {
        if      ( match(c.value(), "vertex") )   
            style.getOrCreate<IconSymbol>()->placement() = ModelSymbol::PLACEMENT_VERTEX;
        else if ( match(c.value(), "interval") ) 
            style.getOrCreate<IconSymbol>()->placement() = ModelSymbol::PLACEMENT_INTERVAL;
        else if ( match(c.value(), "random") )   
            style.getOrCreate<IconSymbol>()->placement() = ModelSymbol::PLACEMENT_RANDOM;
        else if ( match(c.value(), "centroid") ) 
            style.getOrCreate<IconSymbol>()->placement() = ModelSymbol::PLACEMENT_CENTROID;
    }
    else if ( match(c.key(), "icon-density") ) {
        style.getOrCreate<IconSymbol>()->density() = as<float>(c.value(), *defaults.density() );
    }
    else if ( match(c.key(), "icon-random-seed") ) {
        style.getOrCreate<IconSymbol>()->randomSeed() = as<unsigned>(c.value(), *defaults.randomSeed());
    }
    else if ( match(c.key(), "icon-scale") ) {
        style.getOrCreate<IconSymbol>()->scale() = c.value();
    }
    else if ( match(c.key(), "icon-align") ) {
        if      ( match(c.value(), "left-top") )      
            style.getOrCreate<IconSymbol>()->alignment() = IconSymbol::ALIGN_LEFT_TOP;
        else if ( match(c.value(), "left-center") )   
            style.getOrCreate<IconSymbol>()->alignment() = IconSymbol::ALIGN_LEFT_CENTER;
        else if ( match(c.value(), "left-bottom") )   
            style.getOrCreate<IconSymbol>()->alignment() = IconSymbol::ALIGN_LEFT_BOTTOM;
        else if ( match(c.value(), "center-top")  )   
            style.getOrCreate<IconSymbol>()->alignment() = IconSymbol::ALIGN_CENTER_TOP;
        else if ( match(c.value(), "center-center") ) 
            style.getOrCreate<IconSymbol>()->alignment() = IconSymbol::ALIGN_CENTER_CENTER;
        else if ( match(c.value(), "center-bottom") ) 
            style.getOrCreate<IconSymbol>()->alignment() = IconSymbol::ALIGN_CENTER_BOTTOM;
        else if ( match(c.value(), "right-top") )     
            style.getOrCreate<IconSymbol>()->alignment() = IconSymbol::ALIGN_RIGHT_TOP;
        else if ( match(c.value(), "right-center") )  
            style.getOrCreate<IconSymbol>()->alignment() = IconSymbol::ALIGN_RIGHT_CENTER;
        else if ( match(c.value(), "right-bottom") )  
            style.getOrCreate<IconSymbol>()->alignment() = IconSymbol::ALIGN_RIGHT_BOTTOM;
    }
    else if ( match(c.key(), "icon-heading") ) {
        style.getOrCreate<IconSymbol>()->heading() = c.value();
    }
    else if ( match(c.key(), "icon-declutter") ) {
        style.getOrCreate<IconSymbol>()->declutter() = as<bool>(c.value(), *defaults.declutter());
    }
    else if ( match(c.key(), "icon-occlusion-cull") ) {
        style.getOrCreate<IconSymbol>()->occlusionCull() = as<bool>(c.value(), *defaults.occlusionCull());
    }
    else if ( match(c.key(), "icon-occlusion-cull-altitude") ) {
        style.getOrCreate<IconSymbol>()->occlusionCullAltitude() = as<float>(c.value(), *defaults.occlusionCullAltitude());
    }
    else if ( match(c.key(), "icon-script") ) {
        style.getOrCreate<IconSymbol>()->script() = c.value();
    }
}
