/**
 * @file llenvmanager.cpp
 * @brief Implementation of classes managing WindLight and water settings.
 *
 * $LicenseInfo:firstyear=2009&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2011, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "llviewerprecompiledheaders.h"

#include "llenvironment.h"

#include <algorithm>

#include "llagent.h"
#include "llviewercontrol.h" // for gSavedSettings
#include "llviewerregion.h"
#include "llwlhandlers.h"
#include "lltrans.h"
#include "lltrace.h"
#include "llfasttimer.h"
#include "llviewercamera.h"
#include "pipeline.h"
#include "llsky.h"

#include "llviewershadermgr.h"

#include "llparcel.h"
#include "llviewerparcelmgr.h"

#include "llsdserialize.h"
#include "lldiriterator.h"

#include "llsettingsvo.h"
#include "llnotificationsutil.h"

#include "llregioninfomodel.h"

#include <boost/make_shared.hpp>

#include "llatmosphere.h"
#include "llagent.h"
#include "roles_constants.h"
#include "llestateinfomodel.h"

#include "lldispatcher.h"
#include "llviewergenericmessage.h"
#include "llexperiencelog.h"

//=========================================================================
namespace
{
    const std::string KEY_ENVIRONMENT("environment");
    const std::string KEY_DAYASSET("day_asset");
    const std::string KEY_DAYCYCLE("day_cycle");
    const std::string KEY_DAYHASH("day_hash");
    const std::string KEY_DAYLENGTH("day_length");
    const std::string KEY_DAYNAME("day_name");
    const std::string KEY_DAYNAMES("day_names");
    const std::string KEY_DAYOFFSET("day_offset");
    const std::string KEY_ENVVERSION("env_version");
    const std::string KEY_ISDEFAULT("is_default");
    const std::string KEY_PARCELID("parcel_id");
    const std::string KEY_REGIONID("region_id");
    const std::string KEY_TRACKALTS("track_altitudes");
    const std::string KEY_FLAGS("flags");

    const std::string MESSAGE_PUSHENVIRONMENT("PushExpEnvironment");

    const std::string ACTION_CLEARENVIRONMENT("ClearEnvironment");
    const std::string ACTION_PUSHFULLENVIRONMENT("PushFullEnvironment");
    const std::string ACTION_PUSHPARTIALENVIRONMENT("PushPartialEnvironment");

    const std::string KEY_ASSETID("asset_id");
    const std::string KEY_TRANSITIONTIME("transition_time");
    const std::string KEY_ACTION("action");
    const std::string KEY_ACTIONDATA("action_data");
    const std::string KEY_EXPERIENCEID("public_id");
    const std::string KEY_OBJECTNAME("ObjectName");     // some of these do not conform to the '_' format.
    const std::string KEY_PARCELNAME("ParcelName");     // But changing these would also alter the Experience Log requirements.
    const std::string KEY_COUNT("Count");

    const std::string LISTENER_NAME("LLEnvironmentSingleton");
    const std::string PUMP_EXPERIENCE("experience_permission");

    const std::string LOCAL_ENV_STORAGE_FILE("local_environment_data.bin");

    //---------------------------------------------------------------------
    LLTrace::BlockTimerStatHandle   FTM_ENVIRONMENT_UPDATE("Update Environment Tick");

    LLSettingsBase::Seconds         DEFAULT_UPDATE_THRESHOLD(10.0);
    const LLSettingsBase::Seconds   MINIMUM_SPANLENGTH(0.01f);

    //---------------------------------------------------------------------
    inline LLSettingsBase::TrackPosition get_wrapping_distance(LLSettingsBase::TrackPosition begin, LLSettingsBase::TrackPosition end)
    {
        if (begin < end)
        {
            return end - begin;
        }
        else if (begin > end)
        {
            return LLSettingsBase::TrackPosition(1.0) - (begin - end);
        }

        return 1.0f;
    }

    LLSettingsDay::CycleTrack_t::iterator get_wrapping_atafter(LLSettingsDay::CycleTrack_t &collection, const LLSettingsBase::TrackPosition& key)
    {
        if (collection.empty())
            return collection.end();

        LLSettingsDay::CycleTrack_t::iterator it = collection.upper_bound(key);

        if (it == collection.end())
        {   // wrap around
            it = collection.begin();
        }

        return it;
    }

    LLSettingsDay::CycleTrack_t::iterator get_wrapping_atbefore(LLSettingsDay::CycleTrack_t &collection, const LLSettingsBase::TrackPosition& key)
    {
        if (collection.empty())
            return collection.end();

        LLSettingsDay::CycleTrack_t::iterator it = collection.lower_bound(key);

        if (it == collection.end())
        {   // all keyframes are lower, take the last one.
            --it; // we know the range is not empty
        }
        else if ((*it).first > key)
        {   // the keyframe we are interested in is smaller than the found.
            if (it == collection.begin())
                it = collection.end();
            --it;
        }

        return it;
    }

    LLSettingsDay::TrackBound_t get_bounding_entries(LLSettingsDay::CycleTrack_t &track, const LLSettingsBase::TrackPosition& keyframe)
    {
        return LLSettingsDay::TrackBound_t(get_wrapping_atbefore(track, keyframe), get_wrapping_atafter(track, keyframe));
    }

    // Find normalized track position of given time along full length of cycle
    inline LLSettingsBase::TrackPosition convert_time_to_position(const LLSettingsBase::Seconds& time, const LLSettingsBase::Seconds& len)
    {
        // early out to avoid divide by zero.  if len is zero then jump to end position
        if (len == 0.f) return 1.f;

        LLSettingsBase::TrackPosition position = LLSettingsBase::TrackPosition(fmod((F64)time, (F64)len) / (F64)len);
        return llclamp(position, 0.0f, 1.0f);
    }

    inline LLSettingsBase::BlendFactor convert_time_to_blend_factor(const LLSettingsBase::Seconds& time, const LLSettingsBase::Seconds& len, LLSettingsDay::CycleTrack_t &track)
    {
        LLSettingsBase::TrackPosition position = convert_time_to_position(time, len);
        LLSettingsDay::TrackBound_t bounds(get_bounding_entries(track, position));

        LLSettingsBase::TrackPosition spanlength(get_wrapping_distance((*bounds.first).first, (*bounds.second).first));
        if (position < (*bounds.first).first)
            position += 1.0;

        LLSettingsBase::TrackPosition start = position - (*bounds.first).first;

        return static_cast<LLSettingsBase::BlendFactor>(start / spanlength);
    }

    //---------------------------------------------------------------------
    class LLTrackBlenderLoopingTime : public LLSettingsBlenderTimeDelta
    {
    public:
        LLTrackBlenderLoopingTime(const LLSettingsBase::ptr_t &target, const LLSettingsDay::ptr_t &day, S32 trackno,
            LLSettingsBase::Seconds cyclelength, LLSettingsBase::Seconds cycleoffset, LLSettingsBase::Seconds updateThreshold) :
            LLSettingsBlenderTimeDelta(target, LLSettingsBase::ptr_t(), LLSettingsBase::ptr_t(), LLSettingsBase::Seconds(1.0)),
            mDay(day),
            mTrackNo(0),
            mCycleLength(cyclelength),
            mCycleOffset(cycleoffset)
        {
            // must happen prior to getBoundingEntries call...
            mTrackNo = selectTrackNumber(trackno);

            LLSettingsBase::Seconds now(getAdjustedNow());
            LLSettingsDay::TrackBound_t initial = getBoundingEntries(now);

            mInitial = (*initial.first).second;
            mFinal = (*initial.second).second;
            mBlendSpan = getSpanTime(initial);

            initializeTarget(now);
            setOnFinished([this](const LLSettingsBlender::ptr_t &){ onFinishedSpan(); });
        }

        void switchTrack(S32 trackno, const LLSettingsBase::TrackPosition&) override
        {
            S32 use_trackno = selectTrackNumber(trackno);

            if (use_trackno == mTrackNo)
            {   // results in no change
                return;
            }

            LLSettingsBase::ptr_t pstartsetting = mTarget->buildDerivedClone();
            mTrackNo = use_trackno;

            LLSettingsBase::Seconds now = getAdjustedNow() + LLEnvironment::TRANSITION_ALTITUDE;
            LLSettingsDay::TrackBound_t bounds = getBoundingEntries(now);

            LLSettingsBase::ptr_t pendsetting = (*bounds.first).second->buildDerivedClone();
            LLSettingsBase::TrackPosition targetpos = convert_time_to_position(now, mCycleLength) - (*bounds.first).first;
            LLSettingsBase::TrackPosition targetspan = get_wrapping_distance((*bounds.first).first, (*bounds.second).first);

            LLSettingsBase::BlendFactor blendf = calculateBlend(targetpos, targetspan);
            pendsetting->blend((*bounds.second).second, blendf);

            reset(pstartsetting, pendsetting, LLEnvironment::TRANSITION_ALTITUDE);
        }

    protected:
        S32 selectTrackNumber(S32 trackno)
        {
            if (trackno == 0)
            {   // We are dealing with the water track.  There is only ever one.
                return trackno;
            }

            for (S32 test = trackno; test != 0; --test)
            {   // Find the track below the requested one with data.
                LLSettingsDay::CycleTrack_t &track = mDay->getCycleTrack(test);

                if (!track.empty())
                    return test;
            }

            return 1;
        }

        LLSettingsDay::TrackBound_t getBoundingEntries(LLSettingsBase::Seconds time)
        {
            LLSettingsDay::CycleTrack_t &wtrack = mDay->getCycleTrack(mTrackNo);
            LLSettingsBase::TrackPosition position = convert_time_to_position(time, mCycleLength);
            LLSettingsDay::TrackBound_t bounds = get_bounding_entries(wtrack, position);
            return bounds;
        }

        void initializeTarget(LLSettingsBase::Seconds time)
        {
            LLSettingsBase::BlendFactor blendf(convert_time_to_blend_factor(time, mCycleLength, mDay->getCycleTrack(mTrackNo)));

            blendf = llclamp(blendf, 0.0, 0.999);
            setTimeSpent(LLSettingsBase::Seconds(blendf * mBlendSpan));

            setBlendFactor(blendf);
        }

        LLSettingsBase::Seconds getAdjustedNow() const
        {
            LLSettingsBase::Seconds now(LLDate::now().secondsSinceEpoch());

            return (now + mCycleOffset);
        }

        LLSettingsBase::Seconds getSpanTime(const LLSettingsDay::TrackBound_t &bounds) const
        {
            LLSettingsBase::Seconds span = mCycleLength * get_wrapping_distance((*bounds.first).first, (*bounds.second).first);
            if (span < MINIMUM_SPANLENGTH) // for very short spans set a minimum length.
                span = MINIMUM_SPANLENGTH;
            return span;
        }

    private:
        LLSettingsDay::ptr_t        mDay;
        S32                         mTrackNo;
        LLSettingsBase::Seconds     mCycleLength;
        LLSettingsBase::Seconds     mCycleOffset;

        void onFinishedSpan()
        {
            LLSettingsBase::Seconds adjusted_now = getAdjustedNow();
            LLSettingsDay::TrackBound_t next = getBoundingEntries(adjusted_now);
            LLSettingsBase::Seconds nextspan = getSpanTime(next);

            reset((*next.first).second, (*next.second).second, nextspan);

            // Recalculate (reinitialize) position. Because:
            // - 'delta' from applyTimeDelta accumulates errors (probably should be fixed/changed to absolute time)
            // - freezes and lag can result in reset being called too late, so we need to add missed time
            // - occasional time corrections can happen
            // - some transition switches can happen outside applyTimeDelta thus causing 'desync' from 'delta' (can be fixed by getting rid of delta)
            initializeTarget(adjusted_now);
        }
    };

    class LLEnvironmentPushDispatchHandler : public LLDispatchHandler
    {
    public:
        virtual bool operator()(const LLDispatcher *, const std::string& key, const LLUUID& invoice, const sparam_t& strings) override
        {
            LLSD message;
            sparam_t::const_iterator it = strings.begin();

            if (it != strings.end())
            {
                const std::string& llsdRaw = *it++;
                std::istringstream llsdData(llsdRaw);
                if (!LLSDSerialize::deserialize(message, llsdData, llsdRaw.length()))
                {
                    LL_WARNS() << "LLEnvironmentPushDispatchHandler: Attempted to read parameter data into LLSD but failed:" << llsdRaw << LL_ENDL;
                }
            }

            message[KEY_EXPERIENCEID] = invoice;
            // Object Name
            if (it != strings.end())
            {
                message[KEY_OBJECTNAME] = *it++;
            }

            // parcel Name
            if (it != strings.end())
            {
                message[KEY_PARCELNAME] = *it++;
            }
            message[KEY_COUNT] = 1;

            LLEnvironment::instance().handleEnvironmentPush(message);
            return true;
        }
    };

    LLEnvironmentPushDispatchHandler environment_push_dispatch_handler;

    template<class SETTINGT>
    class LLSettingsInjected : public SETTINGT
    {
    public:
        typedef std::shared_ptr<LLSettingsInjected<SETTINGT> >  ptr_t;

        LLSettingsInjected(typename SETTINGT::ptr_t source) :
            SETTINGT(),
            mSource(source),
            mLastSourceHash(0),
            mLastHash(0)
        {}

        virtual ~LLSettingsInjected() {};

        typename SETTINGT::ptr_t getSource() const                    { return this->mSource; }
        void setSource(const typename SETTINGT::ptr_t &source)
        {
            if (source.get() == this) // do not set a source to itself.
                return;
            this->mSource = source;
            this->setDirtyFlag(true);
            this->mLastSourceHash = 0;
        }

        virtual bool isDirty() const override { return SETTINGT::isDirty() || (this->mSource->isDirty()); }
        virtual bool isVeryDirty() const override { return SETTINGT::isVeryDirty() || (this->mSource->isVeryDirty()); }

        void injectSetting(const std::string keyname, LLSD value, LLUUID experience_id, F32Seconds transition)
        {
            if (transition > 0.1)
            {
                typename Injection::ptr_t injection = std::make_shared<Injection>(transition, keyname, value, true, experience_id);

                mInjections.push_back(injection);
                std::stable_sort(mInjections.begin(), mInjections.end(), [](const typename Injection::ptr_t &a, const typename Injection::ptr_t &b) { return a->mTimeRemaining < b->mTimeRemaining; });
            }
            else
            {
                mOverrideValues[keyname] = value;
                mOverrideExps[keyname] = experience_id;
                this->setDirtyFlag(true);
            }
        }

        void removeInjection(const std::string keyname, LLUUID experience, LLSettingsBase::Seconds transition)
        {
            injections_t injections_buf;
            for (auto it = mInjections.begin(); it != mInjections.end(); it++)
            {
                if ((keyname.empty() || ((*it)->mKeyName == keyname)) &&
                    (experience.isNull() || (experience == (*it)->mExperience)))
                {
                    if (transition != LLEnvironment::TRANSITION_INSTANT)
                    {
                        typename Injection::ptr_t injection = std::make_shared<Injection>(transition, keyname, (*it)->mLastValue, false, LLUUID::null);
                        injections_buf.push_front(injection);
                    }
                }
                else
                {
                    injections_buf.push_front(*it);
                }
            }
            mInjections.clear();
            mInjections = injections_buf;

            for (auto itexp = mOverrideExps.begin(); itexp != mOverrideExps.end();)
            {
                if (experience.isNull() || ((*itexp).second == experience))
                {
                    if (transition != LLEnvironment::TRANSITION_INSTANT)
                    {
                        typename Injection::ptr_t injection = std::make_shared<Injection>(transition, (*itexp).first, mOverrideValues[(*itexp).first], false, LLUUID::null);
                        mInjections.push_front(injection);
                    }
                    mOverrideValues.erase((*itexp).first);
                    mOverrideExps.erase(itexp++);
                }
                else
                    ++itexp;
            }
            std::stable_sort(mInjections.begin(), mInjections.end(), [](const typename Injection::ptr_t &a, const typename Injection::ptr_t &b) { return a->mTimeRemaining < b->mTimeRemaining; });
        }

        void removeInjections(LLUUID experience_id, LLSettingsBase::Seconds transition)
        {
            removeInjection(std::string(), experience_id, transition);
        }

        void injectExperienceValues(LLSD values, LLUUID experience_id, typename LLSettingsBase::Seconds transition)
        {
            for (auto it = values.beginMap(); it != values.endMap(); ++it)
            {
                injectSetting((*it).first, (*it).second, experience_id, transition);
            }
            this->setDirtyFlag(true);
        }

        void applyInjections(LLSettingsBase::Seconds delta)
        {
            this->mSettings = this->mSource->getSettings();

            for (auto ito = mOverrideValues.beginMap(); ito != mOverrideValues.endMap(); ++ito)
            {
                this->mSettings[(*ito).first] = (*ito).second;
            }

            const LLSettingsBase::stringset_t &slerps = this->getSlerpKeys();
            const LLSettingsBase::stringset_t &skips = this->getSkipInterpolateKeys();
            const LLSettingsBase::stringset_t &specials = this->getSpecialKeys();

            typename injections_t::iterator it;
            for (it = mInjections.begin(); it != mInjections.end(); ++it)
            {
                std::string key_name = (*it)->mKeyName;

                LLSD value = this->mSettings[key_name];
                LLSD target = (*it)->mValue;

                if ((*it)->mFirstTime)
                    (*it)->mFirstTime = false;
                else
                    (*it)->mTimeRemaining -= delta;

                typename LLSettingsBase::BlendFactor mix = 1.0f - ((*it)->mTimeRemaining.value() / (*it)->mTransition.value());

                if (mix >= 1.0)
                {
                    if ((*it)->mBlendIn)
                    {
                        mOverrideValues[key_name] = target;
                        mOverrideExps[key_name] = (*it)->mExperience;
                        this->mSettings[key_name] = target;
                    }
                    else
                    {
                        this->mSettings.erase(key_name);
                    }
                }
                else if (specials.find(key_name) != specials.end())
                {
                    updateSpecial(*it, mix);
                }
                else if (skips.find(key_name) == skips.end())
                {
                    if (!(*it)->mBlendIn)
                        mix = 1.0 - mix;
                    (*it)->mLastValue = this->interpolateSDValue(key_name, value, target, this->getParameterMap(), mix, slerps);
                    this->mSettings[key_name] = (*it)->mLastValue;
                }
            }

            size_t hash = this->getHash();

            if (hash != mLastHash)
            {
                this->setDirtyFlag(true);
                mLastHash = hash;
            }

            it = mInjections.begin();
            it = std::find_if(mInjections.begin(), mInjections.end(), [](const typename Injection::ptr_t &a) { return a->mTimeRemaining > 0.0f; });

            if (it != mInjections.begin())
            {
                mInjections.erase(mInjections.begin(), mInjections.end());
            }

        }

        bool hasInjections() const
        {
            return (!mInjections.empty() || (mOverrideValues.size() > 0));
        }

    protected:
        struct Injection
        {
            Injection(typename LLSettingsBase::Seconds transition, const std::string &keyname, LLSD value, bool blendin, LLUUID experince, S32 index = -1) :
                mTransition(transition),
                mTimeRemaining(transition),
                mKeyName(keyname),
                mValue(value),
                mExperience(experince),
                mIndex(index),
                mBlendIn(blendin),
                mFirstTime(true)
            {}

            typename LLSettingsBase::Seconds    mTransition;
            typename LLSettingsBase::Seconds    mTimeRemaining;
            std::string                         mKeyName;
            LLSD                                mValue;
            LLSD                                mLastValue;
            LLUUID                              mExperience;
            S32                                 mIndex;
            bool                                mBlendIn;
            bool                                mFirstTime;

            typedef std::shared_ptr<Injection>  ptr_t;
        };


        virtual void updateSettings() override
        {
            static LLFrameTimer timer;

            if (!this->mSource)
                return;

            // clears the dirty flag on this object.  Need to prevent triggering
            // more calls into this updateSettings
            LLSettingsBase::updateSettings();

            resetSpecial();

            if (this->mSource->isDirty())
            {
                this->mSource->updateSettings();
            }

            typename LLSettingsBase::Seconds delta(timer.getElapsedTimeAndResetF32());


            SETTINGT::updateSettings();

            if (!mInjections.empty())
                this->setDirtyFlag(true);
        }

        LLSettingsBase::stringset_t getSpecialKeys() const;
        void                        resetSpecial();
        void                        updateSpecial(const typename Injection::ptr_t &injection, typename LLSettingsBase::BlendFactor mix);

    private:
        typedef std::map<std::string, LLUUID>   key_to_expid_t;
        typedef std::deque<typename Injection::ptr_t>    injections_t;

        size_t                      mLastSourceHash;
        size_t                      mLastHash;
        typename SETTINGT::ptr_t    mSource;
        injections_t                mInjections;
        LLSD                        mOverrideValues;
        key_to_expid_t              mOverrideExps;
    };

    template<>
    LLSettingsBase::stringset_t LLSettingsInjected<LLSettingsVOSky>::getSpecialKeys() const
    {
        static LLSettingsBase::stringset_t specialSet;

        if (specialSet.empty())
        {
            specialSet.insert(SETTING_BLOOM_TEXTUREID);
            specialSet.insert(SETTING_RAINBOW_TEXTUREID);
            specialSet.insert(SETTING_HALO_TEXTUREID);
            specialSet.insert(SETTING_CLOUD_TEXTUREID);
            specialSet.insert(SETTING_MOON_TEXTUREID);
            specialSet.insert(SETTING_SUN_TEXTUREID);
            specialSet.insert(SETTING_CLOUD_SHADOW); // due to being part of skips
        }
        return specialSet;
    }

    template<>
    LLSettingsBase::stringset_t LLSettingsInjected<LLSettingsVOWater>::getSpecialKeys() const
    {
        static stringset_t specialSet;

        if (specialSet.empty())
        {
            specialSet.insert(SETTING_TRANSPARENT_TEXTURE);
            specialSet.insert(SETTING_NORMAL_MAP);
        }
        return specialSet;
    }

    template<>
    void LLSettingsInjected<LLSettingsVOSky>::resetSpecial()
    {
        mNextSunTextureId.setNull();
        mNextMoonTextureId.setNull();
        mNextCloudTextureId.setNull();
        mNextBloomTextureId.setNull();
        mNextRainbowTextureId.setNull();
        mNextHaloTextureId.setNull();
        setBlendFactor(0.0f);
    }

    template<>
    void LLSettingsInjected<LLSettingsVOWater>::resetSpecial()
    {
        mNextNormalMapID.setNull();
        mNextTransparentTextureID.setNull();
        setBlendFactor(0.0f);
    }

    template<>
    void LLSettingsInjected<LLSettingsVOSky>::updateSpecial(const typename LLSettingsInjected<LLSettingsVOSky>::Injection::ptr_t &injection, typename LLSettingsBase::BlendFactor mix)
    {
        bool is_texture = true;
        if (injection->mKeyName == SETTING_SUN_TEXTUREID)
        {
            mNextSunTextureId = injection->mValue.asUUID();
        }
        else if (injection->mKeyName == SETTING_MOON_TEXTUREID)
        {
            mNextMoonTextureId = injection->mValue.asUUID();
        }
        else if (injection->mKeyName == SETTING_CLOUD_TEXTUREID)
        {
            mNextCloudTextureId = injection->mValue.asUUID();
        }
        else if (injection->mKeyName == SETTING_BLOOM_TEXTUREID)
        {
            mNextBloomTextureId = injection->mValue.asUUID();
        }
        else if (injection->mKeyName == SETTING_RAINBOW_TEXTUREID)
        {
            mNextRainbowTextureId = injection->mValue.asUUID();
        }
        else if (injection->mKeyName == SETTING_HALO_TEXTUREID)
        {
            mNextHaloTextureId = injection->mValue.asUUID();
        }
        else if (injection->mKeyName == LLSettingsSky::SETTING_CLOUD_SHADOW)
        {
            // Special case due to being texture dependent and part of skips
            is_texture = false;
            if (!injection->mBlendIn)
                mix = 1.0 - mix;
            stringset_t dummy;
            F64 value = this->mSettings[injection->mKeyName].asReal();
            if (this->getCloudNoiseTextureId().isNull())
            {
                value = 0; // there was no texture so start from zero coverage
            }
            // Ideally we need to check for texture in injection, but
            // in this case user is setting value explicitly, potentially
            // with different transitions, don't ignore it
            F64 result = lerp(value, injection->mValue.asReal(), mix);
            injection->mLastValue = LLSD::Real(result);
            this->mSettings[injection->mKeyName] = injection->mLastValue;
        }

        // Unfortunately I don't have a per texture blend factor.  We'll just pick the one that is furthest along.
        if (is_texture && getBlendFactor() < mix)
        {
            setBlendFactor(mix);
        }
    }

    template<>
    void LLSettingsInjected<LLSettingsVOWater>::updateSpecial(const typename LLSettingsInjected<LLSettingsVOWater>::Injection::ptr_t &injection, typename LLSettingsBase::BlendFactor mix)
    {
        if (injection->mKeyName == SETTING_NORMAL_MAP)
        {
            mNextNormalMapID = injection->mValue.asUUID();
        }
        else if (injection->mKeyName == SETTING_TRANSPARENT_TEXTURE)
        {
            mNextTransparentTextureID = injection->mValue.asUUID();
        }

        // Unfortunately I don't have a per texture blend factor.  We'll just pick the one that is furthest along.
        if (getBlendFactor() < mix)
        {
            setBlendFactor(mix);
        }
    }

    typedef LLSettingsInjected<LLSettingsVOSky>   LLSettingsInjectedSky;
    typedef LLSettingsInjected<LLSettingsVOWater> LLSettingsInjectedWater;

    //=====================================================================
    class DayInjection : public LLEnvironment::DayInstance
    {
        friend class InjectedTransition;

    public:
        typedef std::shared_ptr<DayInjection> ptr_t;
        typedef std::weak_ptr<DayInjection> wptr_t;

                                            DayInjection(LLEnvironment::EnvSelection_t env);
        virtual                             ~DayInjection();

        virtual bool                        applyTimeDelta(const LLSettingsBase::Seconds& delta) override;

        void                                setInjectedDay(const LLSettingsDay::ptr_t &pday, LLUUID experience_id, LLSettingsBase::Seconds transition);
        void                                setInjectedSky(const LLSettingsSky::ptr_t &psky, LLUUID experience_id, LLSettingsBase::Seconds transition);
        void                                setInjectedWater(const LLSettingsWater::ptr_t &pwater, LLUUID experience_id, LLSettingsBase::Seconds transition);

        void                                injectSkySettings(LLSD settings, LLUUID experience_id, LLSettingsBase::Seconds transition);
        void                                injectWaterSettings(LLSD settings, LLUUID experience_id, LLSettingsBase::Seconds transition);

        void                                clearInjections(LLUUID experience_id, LLSettingsBase::Seconds transition_time);

        virtual void                        animate() override;

        LLEnvironment::DayInstance::ptr_t   getBaseDayInstance() const  { return mBaseDayInstance; }
        void                                setBaseDayInstance(const LLEnvironment::DayInstance::ptr_t &baseday);

        S32                                 countExperiencesActive() const { return static_cast<S32>(mActiveExperiences.size()); }

        bool                                isOverriddenSky() const { return !mSkyExperience.isNull(); }
        bool                                isOverriddenWater() const { return !mWaterExperience.isNull(); }

        bool                                hasInjections() const;

        void                                testExperiencesOnParcel(S32 parcel_id);
    private:
        static void                         testExperiencesOnParcelCoro(wptr_t that, S32 parcel_id);


        void                                animateSkyChange(LLSettingsSky::ptr_t psky, LLSettingsBase::Seconds transition);
        void                                animateWaterChange(LLSettingsWater::ptr_t pwater, LLSettingsBase::Seconds transition);

        void                                onEnvironmentChanged(LLEnvironment::EnvSelection_t env);
        void                                onParcelChange();

        void                                checkExperience();


        LLEnvironment::DayInstance::ptr_t   mBaseDayInstance;

        LLSettingsInjectedSky::ptr_t        mInjectedSky;
        LLSettingsInjectedWater::ptr_t      mInjectedWater;
        std::set<LLUUID>                    mActiveExperiences;
        LLUUID                              mDayExperience;
        LLUUID                              mSkyExperience;
        LLUUID                              mWaterExperience;
        LLEnvironment::connection_t         mEnvChangeConnection;
        boost::signals2::connection         mParcelChangeConnection;
    };

    class InjectedTransition : public LLEnvironment::DayTransition
    {
    public:
                                    InjectedTransition(const DayInjection::ptr_t &injection, const LLSettingsSky::ptr_t &skystart, const LLSettingsWater::ptr_t &waterstart, DayInstance::ptr_t &end, LLSettingsDay::Seconds time):
                                        LLEnvironment::DayTransition(skystart, waterstart, end, time),
                                        mInjection(injection)
                                    { }
        virtual                     ~InjectedTransition() { };

        virtual void                animate() override;

    protected:
        DayInjection::ptr_t         mInjection;
    };

}

//=========================================================================
const F64Seconds LLEnvironment::TRANSITION_INSTANT(0.0f);
const F64Seconds LLEnvironment::TRANSITION_FAST(1.0f);
const F64Seconds LLEnvironment::TRANSITION_DEFAULT(5.0f);
const F64Seconds LLEnvironment::TRANSITION_SLOW(10.0f);
const F64Seconds LLEnvironment::TRANSITION_ALTITUDE(5.0f);

const LLUUID LLEnvironment::KNOWN_SKY_SUNRISE("01e41537-ff51-2f1f-8ef7-17e4df760bfb");
const LLUUID LLEnvironment::KNOWN_SKY_MIDDAY("c46226b4-0e43-5a56-9708-d27ca1df3292");
const LLUUID LLEnvironment::KNOWN_SKY_LEGACY_MIDDAY("cef49723-0292-af49-9b14-9598a616b8a3");
const LLUUID LLEnvironment::KNOWN_SKY_SUNSET("084e26cd-a900-28e8-08d0-64a9de5c15e2");
const LLUUID LLEnvironment::KNOWN_SKY_MIDNIGHT("8a01b97a-cb20-c1ea-ac63-f7ea84ad0090");

const S32 LLEnvironment::NO_TRACK(-1);
const S32 LLEnvironment::NO_VERSION(-3); // For viewer sided change, like ENV_LOCAL. -3 since -1 and -2 are taken by parcel initial server/viewer version
const S32 LLEnvironment::VERSION_CLEANUP(-4); // for cleanups

const F32 LLEnvironment::SUN_DELTA_YAW(F_PI);   // 180deg


const U32 LLEnvironment::DayInstance::NO_ANIMATE_SKY(0x01);
const U32 LLEnvironment::DayInstance::NO_ANIMATE_WATER(0x02);

std::string env_selection_to_string(LLEnvironment::EnvSelection_t sel)
{
#define RTNENUM(E) case LLEnvironment::E: return #E
    switch (sel){
        RTNENUM(ENV_EDIT);
        RTNENUM(ENV_LOCAL);
        RTNENUM(ENV_PUSH);
        RTNENUM(ENV_PARCEL);
        RTNENUM(ENV_REGION);
        RTNENUM(ENV_DEFAULT);
        RTNENUM(ENV_END);
        RTNENUM(ENV_CURRENT);
        RTNENUM(ENV_NONE);
    default:
        return llformat("Unknown(%d)", sel);
    }
#undef RTNENUM
}

//-------------------------------------------------------------------------
LLEnvironment::LLEnvironment():
    mCloudScrollDelta(),
    mCloudScrollPaused(false),
    mSelectedSky(),
    mSelectedWater(),
    mSelectedDay(),
    mSelectedEnvironment(LLEnvironment::ENV_LOCAL),
    mCurrentTrack(1),
    mEditorCounter(0),
    mShowSunBeacon(false),
    mShowMoonBeacon(false)
{
}

void LLEnvironment::initSingleton()
{
    LLSettingsSky::ptr_t p_default_sky = LLSettingsVOSky::buildDefaultSky();
    LLSettingsWater::ptr_t p_default_water = LLSettingsVOWater::buildDefaultWater();

    mCurrentEnvironment = std::make_shared<DayInstance>(ENV_DEFAULT);
    mCurrentEnvironment->setSky(p_default_sky);
    mCurrentEnvironment->setWater(p_default_water);

    mEnvironments[ENV_DEFAULT] = mCurrentEnvironment;

    requestRegion();

    if (!mParcelCallbackConnection.connected())
    {
        mParcelCallbackConnection = gAgent.addParcelChangedCallback([this]() { onParcelChange(); });

        //TODO: This frequently results in one more request than we need.  It isn't breaking, but should be nicer.
        // We need to know new env version to fix this, without it we can only do full re-request
        // Happens: on updates, on opening LLFloaterRegionInfo, on region crossing if info floater is open
        mRegionUpdateCallbackConnection = LLRegionInfoModel::instance().setUpdateCallback([this]() { requestRegion(); });
        mRegionChangeCallbackConnection = gAgent.addRegionChangedCallback([this]() { onRegionChange(); });

        mPositionCallbackConnection = gAgent.whenPositionChanged([this](const LLVector3 &localpos, const LLVector3d &) { onAgentPositionHasChanged(localpos); });
    }

    if (!gGenericDispatcher.isHandlerPresent(MESSAGE_PUSHENVIRONMENT))
    {
        gGenericDispatcher.addHandler(MESSAGE_PUSHENVIRONMENT, &environment_push_dispatch_handler);
    }

    gSavedSettings.getControl("RenderSkyAutoAdjustProbeAmbiance")->getSignal()->connect(
        [](LLControlVariable*, const LLSD& new_val, const LLSD& old_val)
        {
            LLSettingsSky::sAutoAdjustProbeAmbiance = new_val.asReal();
        }
    );
    LLSettingsSky::sAutoAdjustProbeAmbiance = gSavedSettings.getF32("RenderSkyAutoAdjustProbeAmbiance");

    LLEventPumps::instance().obtain(PUMP_EXPERIENCE).stopListening(LISTENER_NAME);
    LLEventPumps::instance().obtain(PUMP_EXPERIENCE).listen(LISTENER_NAME, [this](LLSD message) { listenExperiencePump(message); return false; });
}

void LLEnvironment::cleanupSingleton()
{
    if (mParcelCallbackConnection.connected())
    {
        mParcelCallbackConnection.disconnect();
        mRegionUpdateCallbackConnection.disconnect();
        mRegionChangeCallbackConnection.disconnect();
        mPositionCallbackConnection.disconnect();
    }
    LLEventPumps::instance().obtain(PUMP_EXPERIENCE).stopListening(LISTENER_NAME);
}

LLEnvironment::~LLEnvironment()
{
    cleanupSingleton();
}

bool LLEnvironment::canEdit() const
{
    return true;
}

LLSettingsSky::ptr_t LLEnvironment::getCurrentSky() const
{
    LLSettingsSky::ptr_t psky = mCurrentEnvironment->getSky();

    if (!psky && mCurrentEnvironment->getEnvironmentSelection() >= ENV_EDIT)
    {
        for (int idx = 0; idx < ENV_END; ++idx)
        {
            if (mEnvironments[idx]->getSky())
            {
                psky = mEnvironments[idx]->getSky();
                break;
            }
        }
    }
    return psky;
}

LLSettingsWater::ptr_t LLEnvironment::getCurrentWater() const
{
    LLSettingsWater::ptr_t pwater = mCurrentEnvironment->getWater();

    if (!pwater && mCurrentEnvironment->getEnvironmentSelection() >= ENV_EDIT)
    {
        for (int idx = 0; idx < ENV_END; ++idx)
        {
            if (mEnvironments[idx]->getWater())
            {
                pwater = mEnvironments[idx]->getWater();
                break;
            }
        }
    }
    return pwater;
}

void LayerConfigToDensityLayer(const LLSD& layerConfig, DensityLayer& layerOut)
{
    layerOut.constant_term  = layerConfig[LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM].asReal();
    layerOut.exp_scale      = layerConfig[LLSettingsSky::SETTING_DENSITY_PROFILE_EXP_SCALE_FACTOR].asReal();
    layerOut.exp_term       = layerConfig[LLSettingsSky::SETTING_DENSITY_PROFILE_EXP_TERM].asReal();
    layerOut.linear_term    = layerConfig[LLSettingsSky::SETTING_DENSITY_PROFILE_LINEAR_TERM].asReal();
    layerOut.width          = layerConfig[LLSettingsSky::SETTING_DENSITY_PROFILE_WIDTH].asReal();
}

void LLEnvironment::getAtmosphericModelSettings(AtmosphericModelSettings& settingsOut, const LLSettingsSky::ptr_t &psky)
{
    settingsOut.m_skyBottomRadius   = psky->getSkyBottomRadius();
    settingsOut.m_skyTopRadius      = psky->getSkyTopRadius();
    settingsOut.m_sunArcRadians     = psky->getSunArcRadians();
    settingsOut.m_mieAnisotropy     = psky->getMieAnisotropy();

    LLSD rayleigh = psky->getRayleighConfigs();
    settingsOut.m_rayleighProfile.clear();
    for (LLSD::array_iterator itf = rayleigh.beginArray(); itf != rayleigh.endArray(); ++itf)
    {
        DensityLayer layer;
        LLSD& layerConfig = (*itf);
        LayerConfigToDensityLayer(layerConfig, layer);
        settingsOut.m_rayleighProfile.push_back(layer);
    }

    LLSD mie = psky->getMieConfigs();
    settingsOut.m_mieProfile.clear();
    for (LLSD::array_iterator itf = mie.beginArray(); itf != mie.endArray(); ++itf)
    {
        DensityLayer layer;
        LLSD& layerConfig = (*itf);
        LayerConfigToDensityLayer(layerConfig, layer);
        settingsOut.m_mieProfile.push_back(layer);
    }
    settingsOut.m_mieAnisotropy = psky->getMieAnisotropy();

    LLSD absorption = psky->getAbsorptionConfigs();
    settingsOut.m_absorptionProfile.clear();
    for (LLSD::array_iterator itf = absorption.beginArray(); itf != absorption.endArray(); ++itf)
    {
        DensityLayer layer;
        LLSD& layerConfig = (*itf);
        LayerConfigToDensityLayer(layerConfig, layer);
        settingsOut.m_absorptionProfile.push_back(layer);
    }
}

bool LLEnvironment::canAgentUpdateParcelEnvironment() const
{
    LLParcel *parcel(LLViewerParcelMgr::instance().getAgentOrSelectedParcel());

    return canAgentUpdateParcelEnvironment(parcel);
}


bool LLEnvironment::canAgentUpdateParcelEnvironment(LLParcel *parcel) const
{
    if (!parcel)
        return false;

    if (!LLEnvironment::instance().isExtendedEnvironmentEnabled())
        return false;

    if (gAgent.isGodlike())
        return true;

    if (!parcel->getRegionAllowEnvironmentOverride())
        return false;

    return LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_ALLOW_ENVIRONMENT);
}

bool LLEnvironment::canAgentUpdateRegionEnvironment() const
{
    if (gAgent.isGodlike())
        return true;

    return gAgent.canManageEstate();
}

bool LLEnvironment::isExtendedEnvironmentEnabled() const
{
    return !gAgent.getRegionCapability("ExtEnvironment").empty();
}

bool LLEnvironment::isInventoryEnabled() const
{
    return (!gAgent.getRegionCapability("UpdateSettingsAgentInventory").empty() &&
        !gAgent.getRegionCapability("UpdateSettingsTaskInventory").empty());
}

void LLEnvironment::onRegionChange()
{
//     if (gAgent.getRegionCapability("ExperienceQuery").empty())
//     {
//     // for now environmental experiences do not survive region crossings
    clearExperienceEnvironment(LLUUID::null, TRANSITION_DEFAULT);
//     }

    LLViewerRegion* cur_region = gAgent.getRegion();
    if (!cur_region)
    {
        return;
    }
    if (!cur_region->capabilitiesReceived())
    {
        cur_region->setCapabilitiesReceivedCallback([](const LLUUID &region_id, LLViewerRegion* regionp) {  LLEnvironment::instance().requestRegion(); });
        return;
    }
    requestRegion();
}

void LLEnvironment::onParcelChange()
{
    S32 parcel_id(INVALID_PARCEL_ID);
    LLParcel* parcel = LLViewerParcelMgr::instance().getAgentParcel();

    if (parcel)
    {
        parcel_id = parcel->getLocalID();
    }

    requestParcel(parcel_id);
}

//-------------------------------------------------------------------------
F32 LLEnvironment::getCamHeight() const
{
    return (mCurrentEnvironment->getSky()->getDomeOffset() * mCurrentEnvironment->getSky()->getDomeRadius());
}

F32 LLEnvironment::getWaterHeight() const
{
    LLViewerRegion* cur_region = gAgent.getRegion();
    return cur_region ? cur_region->getWaterHeight() : DEFAULT_WATER_HEIGHT;
}

bool LLEnvironment::getIsSunUp() const
{
    if (!mCurrentEnvironment || !mCurrentEnvironment->getSky())
        return false;
    return mCurrentEnvironment->getSky()->getIsSunUp();
}

bool LLEnvironment::getIsMoonUp() const
{
    if (!mCurrentEnvironment || !mCurrentEnvironment->getSky())
        return false;
    return mCurrentEnvironment->getSky()->getIsMoonUp();
}

//-------------------------------------------------------------------------
void LLEnvironment::setSelectedEnvironment(LLEnvironment::EnvSelection_t env, LLSettingsBase::Seconds transition, bool forced)
{
    mSelectedEnvironment = env;
    updateEnvironment(transition, forced);
    LL_DEBUGS("ENVIRONMENT") << "Setting environment " << env_selection_to_string(env) << " with transition: " << transition << LL_ENDL;
}

bool LLEnvironment::hasEnvironment(LLEnvironment::EnvSelection_t env)
{
    if ((env < ENV_EDIT) || (env >= ENV_DEFAULT) || (!mEnvironments[env]))
    {
        return false;
    }

    return true;
}

LLEnvironment::DayInstance::ptr_t LLEnvironment::getEnvironmentInstance(LLEnvironment::EnvSelection_t env, bool create /*= false*/)
{
    DayInstance::ptr_t environment = mEnvironments[env];
    if (create)
    {
        if (environment)
            environment = environment->clone();
        else
        {
            if (env == ENV_PUSH)
                environment = std::make_shared<DayInjection>(env);
            else
                environment = std::make_shared<DayInstance>(env);
        }
        mEnvironments[env] = environment;
    }

    return environment;
}


void LLEnvironment::setEnvironment(LLEnvironment::EnvSelection_t env, const LLSettingsDay::ptr_t &pday, LLSettingsDay::Seconds daylength, LLSettingsDay::Seconds dayoffset, S32 env_version)
{
    if ((env < ENV_EDIT) || (env >= ENV_DEFAULT))
    {
        LL_WARNS("ENVIRONMENT") << "Attempt to change invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL;
        return;
    }

    logEnvironment(env, pday, env_version);

    DayInstance::ptr_t environment = getEnvironmentInstance(env, true);

    environment->clear();
    environment->setDay(pday, daylength, dayoffset);
    environment->setSkyTrack(mCurrentTrack);
    environment->animate();

    if (!mSignalEnvChanged.empty())
        mSignalEnvChanged(env, env_version);
}

void LLEnvironment::setCurrentEnvironmentSelection(LLEnvironment::EnvSelection_t env)
{
    mCurrentEnvironment->setEnvironmentSelection(env);
}

void LLEnvironment::setEnvironment(LLEnvironment::EnvSelection_t env, LLEnvironment::fixedEnvironment_t fixed, S32 env_version)
{
    if ((env < ENV_EDIT) || (env >= ENV_DEFAULT))
    {
        LL_WARNS("ENVIRONMENT") << "Attempt to change invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL;
        return;
    }

    bool reset_probes = false;

    DayInstance::ptr_t environment = getEnvironmentInstance(env, true);

    if (fixed.first)
    {
        logEnvironment(env, fixed.first, env_version);
        reset_probes = environment->setSky(fixed.first);
        environment->setFlags(DayInstance::NO_ANIMATE_SKY);
    }
    else if (!environment->getSky())
    {
        if (mCurrentEnvironment->getEnvironmentSelection() != ENV_NONE)
        {
            // Note: This looks suspicious. Shouldn't we assign whole day if mCurrentEnvironment has whole day?
            // and then add water/sky on top
            // This looks like it will result in sky using single keyframe instead of whole day if day is present
            // when setting static water without static sky
            reset_probes = environment->setSky(mCurrentEnvironment->getSky());
            environment->setFlags(DayInstance::NO_ANIMATE_SKY);
        }
        else
        {
            // Environment is not properly initialized yet, but we should have environment by this point
            DayInstance::ptr_t substitute = getEnvironmentInstance(ENV_PARCEL, true);
            if (!substitute || !substitute->getSky())
            {
                substitute = getEnvironmentInstance(ENV_REGION, true);
            }
            if (!substitute || !substitute->getSky())
            {
                substitute = getEnvironmentInstance(ENV_DEFAULT, true);
            }

            if (substitute && substitute->getSky())
            {
                reset_probes = environment->setSky(substitute->getSky());
                environment->setFlags(DayInstance::NO_ANIMATE_SKY);
            }
            else
            {
                LL_WARNS("ENVIRONMENT") << "Failed to assign substitute water/sky, environment is not properly initialized" << LL_ENDL;
            }
        }
    }

    if (fixed.second)
    {
        logEnvironment(env, fixed.second, env_version);
        environment->setWater(fixed.second);
        environment->setFlags(DayInstance::NO_ANIMATE_WATER);
    }
    else if (!environment->getWater())
    {
        if (mCurrentEnvironment->getEnvironmentSelection() != ENV_NONE)
        {
            // Note: This looks suspicious. Shouldn't we assign whole day if mCurrentEnvironment has whole day?
            // and then add water/sky on top
            // This looks like it will result in water using single keyframe instead of whole day if day is present
            // when setting static sky without static water
            environment->setWater(mCurrentEnvironment->getWater());
            environment->setFlags(DayInstance::NO_ANIMATE_WATER);
        }
        else
        {
            // Environment is not properly initialized yet, but we should have environment by this point
            DayInstance::ptr_t substitute = getEnvironmentInstance(ENV_PARCEL, true);
            if (!substitute || !substitute->getWater())
            {
                substitute = getEnvironmentInstance(ENV_REGION, true);
            }
            if (!substitute || !substitute->getWater())
            {
                substitute = getEnvironmentInstance(ENV_DEFAULT, true);
            }

            if (substitute && substitute->getWater())
            {
                environment->setWater(substitute->getWater());
                environment->setFlags(DayInstance::NO_ANIMATE_WATER);
            }
            else
            {
                LL_WARNS("ENVIRONMENT") << "Failed to assign substitute water/sky, environment is not properly initialized" << LL_ENDL;
            }
        }
    }

    if (reset_probes)
    { // the sky changed in a way that merits a reset of reflection probes
        gPipeline.mReflectionMapManager.reset();
    }

    if (!mSignalEnvChanged.empty())
        mSignalEnvChanged(env, env_version);
}

void LLEnvironment::setEnvironment(LLEnvironment::EnvSelection_t env, const LLSettingsBase::ptr_t &settings, S32 env_version)
{
    DayInstance::ptr_t environment = getEnvironmentInstance(env);

    if (env == ENV_DEFAULT)
    {
        LL_WARNS("ENVIRONMENT") << "Attempt to set default environment. Not allowed." << LL_ENDL;
        return;
    }

    if (!settings)
    {
        clearEnvironment(env);
        return;
    }

    if (settings->getSettingsType() == "daycycle")
    {
        LLSettingsDay::Seconds daylength(LLSettingsDay::DEFAULT_DAYLENGTH);
        LLSettingsDay::Seconds dayoffset(LLSettingsDay::DEFAULT_DAYOFFSET);
        if (environment)
        {
            daylength = environment->getDayLength();
            dayoffset = environment->getDayOffset();
        }
        setEnvironment(env, std::static_pointer_cast<LLSettingsDay>(settings), daylength, dayoffset);
    }
    else if (settings->getSettingsType() == "sky")
    {
        fixedEnvironment_t fixedenv(std::static_pointer_cast<LLSettingsSky>(settings), LLSettingsWater::ptr_t());
        setEnvironment(env, fixedenv);
    }
    else if (settings->getSettingsType() == "water")
    {
        fixedEnvironment_t fixedenv(LLSettingsSky::ptr_t(), std::static_pointer_cast<LLSettingsWater>(settings));
        setEnvironment(env, fixedenv);
    }
}

void LLEnvironment::setEnvironment(EnvSelection_t env, const LLUUID &assetId, S32 env_version)
{
    setEnvironment(env, assetId, TRANSITION_DEFAULT, env_version);
}

void LLEnvironment::setEnvironment(EnvSelection_t env,
                                   const LLUUID &assetId,
                                   LLSettingsBase::Seconds transition,
                                   S32 env_version)
{
    LLSettingsVOBase::getSettingsAsset(assetId,
        [this, env, env_version, transition](LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status, LLExtStat)
        {
            onSetEnvAssetLoaded(env, asset_id, settings, transition, status, env_version);
        });
}

void LLEnvironment::onSetEnvAssetLoaded(EnvSelection_t env,
                                        LLUUID asset_id,
                                        LLSettingsBase::ptr_t settings,
                                        LLSettingsBase::Seconds transition,
                                        S32 status,
                                        S32 env_version)
{
    if (!settings || status)
    {
        LLSD args;
        args["NAME"] = asset_id.asString();
        LLNotificationsUtil::add("FailedToFindSettings", args);
        LL_DEBUGS("ENVIRONMENT") << "Failed to find settings for " << env_selection_to_string(env) << ", asset_id: " << asset_id << LL_ENDL;
        return;
    }
    LL_DEBUGS("ENVIRONMENT") << "Loaded asset: " << asset_id << LL_ENDL;

    setEnvironment(env, settings);
    updateEnvironment(transition);
}

void LLEnvironment::clearEnvironment(LLEnvironment::EnvSelection_t env)
{
    if ((env < ENV_EDIT) || (env >= ENV_DEFAULT))
    {
        LL_WARNS("ENVIRONMENT") << "Attempt to change invalid environment selection." << LL_ENDL;
        return;
    }

    LL_DEBUGS("ENVIRONMENT") << "Cleaning environment " << env_selection_to_string(env) << LL_ENDL;

    mEnvironments[env].reset();

    if (!mSignalEnvChanged.empty())
        mSignalEnvChanged(env, VERSION_CLEANUP);
}

void LLEnvironment::logEnvironment(EnvSelection_t env, const LLSettingsBase::ptr_t &settings, S32 env_version)
{
    LL_DEBUGS("ENVIRONMENT") << "Setting Day environment " << env_selection_to_string(env) << " with version(update type): " << env_version << LL_NEWLINE;
    // code between LL_DEBUGS and LL_ENDL won't execute unless log is enabled
    if (settings)
    {
        LLUUID asset_id = settings->getAssetId();
        if (asset_id.notNull())
        {
            LL_CONT << "Asset id: " << asset_id << LL_NEWLINE;
        }

        LLUUID id = settings->getId(); // Not in use?
        if (id.notNull())
        {
            LL_CONT << "Settings id: " << id << LL_NEWLINE;
        }

        LL_CONT << "Name: " << settings->getName() << LL_NEWLINE
            << "Type: " << settings->getSettingsType() << LL_NEWLINE
            << "Flags: " << settings->getFlags(); // Not in use?
    }
    else
    {
        LL_CONT << "Empty settings!";
    }
    LL_CONT << LL_ENDL;
}

LLSettingsDay::ptr_t LLEnvironment::getEnvironmentDay(LLEnvironment::EnvSelection_t env)
{
    if ((env < ENV_EDIT) || (env > ENV_DEFAULT))
    {
        LL_WARNS("ENVIRONMENT") << "Attempt to retrieve invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL;
        return LLSettingsDay::ptr_t();
    }

    DayInstance::ptr_t environment = getEnvironmentInstance(env);

    if (environment)
        return environment->getDayCycle();

    return LLSettingsDay::ptr_t();
}

LLSettingsDay::Seconds LLEnvironment::getEnvironmentDayLength(EnvSelection_t env)
{
    if ((env < ENV_EDIT) || (env > ENV_DEFAULT))
    {
        LL_WARNS("ENVIRONMENT") << "Attempt to retrieve invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL;
        return LLSettingsDay::Seconds(0);
    }

    DayInstance::ptr_t environment = getEnvironmentInstance(env);

    if (environment)
        return environment->getDayLength();

    return LLSettingsDay::Seconds(0);
}

LLSettingsDay::Seconds LLEnvironment::getEnvironmentDayOffset(EnvSelection_t env)
{
    if ((env < ENV_EDIT) || (env > ENV_DEFAULT))
    {
        LL_WARNS("ENVIRONMENT") << "Attempt to retrieve invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL;
        return LLSettingsDay::Seconds(0);
    }

    DayInstance::ptr_t environment = getEnvironmentInstance(env);
    if (environment)
        return environment->getDayOffset();

    return LLSettingsDay::Seconds(0);
}


LLEnvironment::fixedEnvironment_t LLEnvironment::getEnvironmentFixed(LLEnvironment::EnvSelection_t env, bool resolve)
{
    if ((env == ENV_CURRENT) || resolve)
    {
        fixedEnvironment_t fixed;
        for (S32 idx = ((resolve) ? env : mSelectedEnvironment); idx < ENV_END; ++idx)
        {
            if (fixed.first && fixed.second)
                break;

            if (idx == ENV_EDIT)
                continue;   // skip the edit environment.

            DayInstance::ptr_t environment = getEnvironmentInstance(static_cast<EnvSelection_t>(idx));
            if (environment)
            {
                if (!fixed.first)
                    fixed.first = environment->getSky();
                if (!fixed.second)
                    fixed.second = environment->getWater();
            }
        }

        if (!fixed.first || !fixed.second)
            LL_WARNS("ENVIRONMENT") << "Can not construct complete fixed environment.  Missing Sky and/or Water." << LL_ENDL;

        return fixed;
    }

    if ((env < ENV_EDIT) || (env > ENV_DEFAULT))
    {
        LL_WARNS("ENVIRONMENT") << "Attempt to retrieve invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL;
        return fixedEnvironment_t();
    }

    DayInstance::ptr_t environment = getEnvironmentInstance(env);

    if (environment)
        return fixedEnvironment_t(environment->getSky(), environment->getWater());

    return fixedEnvironment_t();
}

LLEnvironment::DayInstance::ptr_t LLEnvironment::getSelectedEnvironmentInstance()
{
    for (S32 idx = mSelectedEnvironment; idx < ENV_DEFAULT; ++idx)
    {
        if (mEnvironments[idx])
            return mEnvironments[idx];
    }

    return mEnvironments[ENV_DEFAULT];
}

LLEnvironment::DayInstance::ptr_t LLEnvironment::getSharedEnvironmentInstance()
{
    for (S32 idx = ENV_PARCEL; idx < ENV_DEFAULT; ++idx)
    {
        if (mEnvironments[idx])
            return mEnvironments[idx];
    }

    return mEnvironments[ENV_DEFAULT];
}

void LLEnvironment::updateEnvironment(LLSettingsBase::Seconds transition, bool forced)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT;
    DayInstance::ptr_t pinstance = getSelectedEnvironmentInstance();

    if ((mCurrentEnvironment != pinstance) || forced)
    {
        if (transition != TRANSITION_INSTANT)
        {
            DayInstance::ptr_t trans = std::make_shared<DayTransition>(
                mCurrentEnvironment->getSky(), mCurrentEnvironment->getWater(), pinstance, transition);

            trans->animate();

            mCurrentEnvironment = trans;
        }
        else
        {
            mCurrentEnvironment = pinstance;
        }

        updateSettingsUniforms();
    }
}

LLVector4 LLEnvironment::toCFR(const LLVector3 vec) const
{
    LLVector4 vec_cfr(vec.mV[1], vec.mV[0], vec.mV[2], 0.0f);
    return vec_cfr;
}

LLVector4 LLEnvironment::toLightNorm(const LLVector3 vec) const
{
    LLVector4 vec_ogl(vec.mV[1], vec.mV[2], vec.mV[0], 0.0f);
    return vec_ogl;
}

LLVector3 LLEnvironment::getLightDirection() const
{
    LLSettingsSky::ptr_t psky = mCurrentEnvironment->getSky();
    if (!psky)
    {
        return LLVector3(0, 0, 1);
    }
    return psky->getLightDirection();
}

LLVector3 LLEnvironment::getSunDirection() const
{
    LLSettingsSky::ptr_t psky = mCurrentEnvironment->getSky();
    if (!psky)
    {
        return LLVector3(0, 0, 1);
    }
    return psky->getSunDirection();
}

LLVector3 LLEnvironment::getMoonDirection() const
{
    LLSettingsSky::ptr_t psky = mCurrentEnvironment->getSky();
    if (!psky)
    {
        return LLVector3(0, 0, -1);
    }
    return psky->getMoonDirection();
}

LLVector4 LLEnvironment::getLightDirectionCFR() const
{
    LLVector3 light_direction = getLightDirection();
    LLVector4 light_direction_cfr = toCFR(light_direction);
    return light_direction_cfr;
}

LLVector4 LLEnvironment::getSunDirectionCFR() const
{
    LLVector3 light_direction = getSunDirection();
    LLVector4 light_direction_cfr = toCFR(light_direction);
    return light_direction_cfr;
}

LLVector4 LLEnvironment::getMoonDirectionCFR() const
{
    LLVector3 light_direction = getMoonDirection();
    LLVector4 light_direction_cfr = toCFR(light_direction);
    return light_direction_cfr;
}

LLVector4 LLEnvironment::getClampedLightNorm() const
{
    LLVector3 light_direction = getLightDirection();
    if (light_direction.mV[2] < -0.1f)
    {
        light_direction.mV[2] = -0.1f;
    }
    return toLightNorm(light_direction);
}

LLVector4 LLEnvironment::getClampedSunNorm() const
{
    LLVector3 light_direction = getSunDirection();
    if (light_direction.mV[2] < -0.1f)
    {
        light_direction.mV[2] = -0.1f;
    }
    return toLightNorm(light_direction);
}

LLVector4 LLEnvironment::getClampedMoonNorm() const
{
    LLVector3 light_direction = getMoonDirection();
    if (light_direction.mV[2] < -0.1f)
    {
        light_direction.mV[2] = -0.1f;
    }
    return toLightNorm(light_direction);
}

LLVector4 LLEnvironment::getRotatedLightNorm() const
{
    LLVector3 light_direction = getLightDirection();
    light_direction *= LLQuaternion(-mLastCamYaw, LLVector3(0.f, 1.f, 0.f));
    return toLightNorm(light_direction);
}

extern bool gCubeSnapshot;

//-------------------------------------------------------------------------
void LLEnvironment::update(const LLViewerCamera * cam)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; //LL_RECORD_BLOCK_TIME(FTM_ENVIRONMENT_UPDATE);
    //F32Seconds now(LLDate::now().secondsSinceEpoch());
    if (!gCubeSnapshot)
    {
        static LLFrameTimer timer;

        F32Seconds delta(timer.getElapsedTimeAndResetF32());

        {
            DayInstance::ptr_t keeper = mCurrentEnvironment;
            // make sure the current environment does not go away until applyTimeDelta is done.
            mCurrentEnvironment->applyTimeDelta(delta);

        }
        // update clouds, sun, and general
        updateCloudScroll();

        // cache this for use in rotating the rotated light vec for shader param updates later...
        mLastCamYaw = cam->getYaw() + SUN_DELTA_YAW;
    }

    updateSettingsUniforms();

    // *TODO: potential optimization - this block may only need to be
    // executed some of the time.  For example for water shaders only.
    {
        LLViewerShaderMgr::shader_iter shaders_iter, end_shaders;
        end_shaders = LLViewerShaderMgr::instance()->endShaders();
        for (shaders_iter = LLViewerShaderMgr::instance()->beginShaders(); shaders_iter != end_shaders; ++shaders_iter)
        {
            if ((shaders_iter->mProgramObject != 0)
                && (gPipeline.canUseWindLightShaders()
                || shaders_iter->mShaderGroup == LLGLSLShader::SG_WATER))
            {
                shaders_iter->mUniformsDirty = true;
            }
        }
    }
}

void LLEnvironment::updateCloudScroll()
{
    // This is a function of the environment rather than the sky, since it should
    // persist through sky transitions.
    static LLTimer s_cloud_timer;

    F64 delta_t = s_cloud_timer.getElapsedTimeAndResetF64();

    if (mCurrentEnvironment->getSky() && !mCloudScrollPaused)
    {
        LLVector2 rate = mCurrentEnvironment->getSky()->getCloudScrollRate();
        if (rate.isExactlyZero())
        {
            mCloudScrollDelta.setZero();
        }
        else
        {
            LLVector2 cloud_delta = static_cast<F32>(delta_t) * (mCurrentEnvironment->getSky()->getCloudScrollRate()) / 100.0;
            mCloudScrollDelta += cloud_delta;
        }
    }

}

// static
void LLEnvironment::updateGLVariablesForSettings(LLShaderUniforms* uniforms, const LLSettingsBase::ptr_t &psetting)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_SHADER;

    for (int i = 0; i < LLGLSLShader::SG_COUNT; ++i)
    {
        uniforms[i].clear();
    }

    LLShaderUniforms* shader = &uniforms[LLGLSLShader::SG_ANY];
    //_WARNS("RIDER") << "----------------------------------------------------------------" << LL_ENDL;
    LLSettingsBase::parammapping_t params = psetting->getParameterMap();
    for (auto &it: params)
    {
        LLSD value;
        // legacy first since it contains ambient color and we prioritize value from legacy, see getAmbientColor()
        if (psetting->mSettings.has(LLSettingsSky::SETTING_LEGACY_HAZE) && psetting->mSettings[LLSettingsSky::SETTING_LEGACY_HAZE].has(it.first))
        {
            value = psetting->mSettings[LLSettingsSky::SETTING_LEGACY_HAZE][it.first];
        }
        else if (psetting->mSettings.has(it.first))
        {
            value = psetting->mSettings[it.first];
        }
        else
        {
            // We need to reset shaders, use defaults
            value = it.second.getDefaultValue();
        }

        LLSD::Type setting_type = value.type();
        stop_glerror();
        switch (setting_type)
        {
        case LLSD::TypeInteger:
            shader->uniform1i(it.second.getShaderKey(), value.asInteger());
            //_WARNS("RIDER") << "pushing '" << (*it).first << "' as " << value << LL_ENDL;
            break;
        case LLSD::TypeReal:
            shader->uniform1f(it.second.getShaderKey(), value.asReal());
            //_WARNS("RIDER") << "pushing '" << (*it).first << "' as " << value << LL_ENDL;
            break;

        case LLSD::TypeBoolean:
            shader->uniform1i(it.second.getShaderKey(), value.asBoolean() ? 1 : 0);
            //_WARNS("RIDER") << "pushing '" << (*it).first << "' as " << value << LL_ENDL;
            break;

        case LLSD::TypeArray:
        {
            LLVector4 vect4(value);

            if (gCubeSnapshot && !gPipeline.mReflectionMapManager.isRadiancePass())
            { // maximize and remove tinting if this is an irradiance map render pass and the parameter feeds into the sky background color
                auto max_vec = [](LLVector4 col)
                {
                    LLColor3 color(col);
                    F32 h, s, l;
                    color.calcHSL(&h, &s, &l);

                    col.mV[0] = col.mV[1] = col.mV[2] = l;
                    return col;
                };

                switch (it.second.getShaderKey())
                {
                case LLShaderMgr::BLUE_HORIZON:
                case LLShaderMgr::BLUE_DENSITY:
                    vect4 = max_vec(vect4);
                        break;
                }
            }

            //_WARNS("RIDER") << "pushing '" << (*it).first << "' as " << vect4 << LL_ENDL;
            shader->uniform3fv(it.second.getShaderKey(), LLVector3(vect4.mV) );
            break;
        }

        //  case LLSD::TypeMap:
        //  case LLSD::TypeString:
        //  case LLSD::TypeUUID:
        //  case LLSD::TypeURI:
        //  case LLSD::TypeBinary:
        //  case LLSD::TypeDate:
        default:
            break;
        }
    }
    //_WARNS("RIDER") << "----------------------------------------------------------------" << LL_ENDL;

    psetting->applySpecial(uniforms);
}

void LLEnvironment::updateShaderUniforms(LLGLSLShader* shader)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_SHADER;

    // apply uniforms that should be applied to all shaders
    mSkyUniforms[LLGLSLShader::SG_ANY].apply(shader);
    mWaterUniforms[LLGLSLShader::SG_ANY].apply(shader);

    // apply uniforms specific to the given shader's shader group
    auto group = shader->mShaderGroup;
    mSkyUniforms[group].apply(shader);
    mWaterUniforms[group].apply(shader);
}

void LLEnvironment::updateSettingsUniforms()
{
    if (mCurrentEnvironment->getWater())
    {
        updateGLVariablesForSettings(mWaterUniforms, mCurrentEnvironment->getWater());
    }
    else
    {
        LL_WARNS("ENVIRONMENT") << "Failed to update GL variable for water settings, environment is not properly set" << LL_ENDL;
    }
    if (mCurrentEnvironment->getSky())
    {
        updateGLVariablesForSettings(mSkyUniforms, mCurrentEnvironment->getSky());
    }
    else
    {
        LL_WARNS("ENVIRONMENT") << "Failed to update GL variable for sky settings, environment is not properly set" << LL_ENDL;
    }
}

void LLEnvironment::recordEnvironment(S32 parcel_id, LLEnvironment::EnvironmentInfo::ptr_t envinfo, LLSettingsBase::Seconds transition)
{
    if (!gAgent.getRegion())
    {
        return;
    }
    // mRegionId id can be null, no specification as to why and if it's valid so check valid ids only
    if (gAgent.getRegion()->getRegionID() != envinfo->mRegionId && envinfo->mRegionId.notNull())
    {
        LL_INFOS("ENVIRONMENT") << "Requested environmend region id: " << envinfo->mRegionId << " agent is on: " << gAgent.getRegion()->getRegionID() << LL_ENDL;
        return;
    }

    if (envinfo->mParcelId == INVALID_PARCEL_ID)
    {
        // the returned info applies to an entire region.
        if (!envinfo->mDayCycle)
        {
            clearEnvironment(ENV_PARCEL);
            setEnvironment(ENV_REGION, LLSettingsDay::GetDefaultAssetId(), TRANSITION_DEFAULT, envinfo->mEnvVersion);
            updateEnvironment();
        }
        else if (envinfo->mDayCycle->isTrackEmpty(LLSettingsDay::TRACK_WATER)
                 || envinfo->mDayCycle->isTrackEmpty(LLSettingsDay::TRACK_GROUND_LEVEL))
        {
            LL_WARNS("ENVIRONMENT") << "Invalid day cycle for region" << LL_ENDL;
            clearEnvironment(ENV_PARCEL);
            setEnvironment(ENV_REGION, LLSettingsDay::GetDefaultAssetId(), TRANSITION_DEFAULT, envinfo->mEnvVersion);
            updateEnvironment();
        }
        else
        {
            mTrackAltitudes = envinfo->mAltitudes;
            // update track selection based on new altitudes
            mCurrentTrack = calculateSkyTrackForAltitude(gAgent.getPositionAgent().mV[VZ]);

            setEnvironment(ENV_REGION, envinfo->mDayCycle, envinfo->mDayLength, envinfo->mDayOffset, envinfo->mEnvVersion);
        }

        LL_DEBUGS("ENVIRONMENT") << "Altitudes set to {" << mTrackAltitudes[0] << ", "<< mTrackAltitudes[1] << ", " << mTrackAltitudes[2] << ", " << mTrackAltitudes[3] << LL_ENDL;
    }
    else
    {
        LLParcel *parcel = LLViewerParcelMgr::instance().getAgentParcel();
        LL_DEBUGS("ENVIRONMENT") << "Have parcel environment #" << envinfo->mParcelId << LL_ENDL;
        if (parcel && (parcel->getLocalID() != parcel_id))
        {
            LL_DEBUGS("ENVIRONMENT") << "Requested parcel #" << parcel_id << " agent is on " << parcel->getLocalID() << LL_ENDL;
            return;
        }

        if (!envinfo->mDayCycle)
        {
            LL_DEBUGS("ENVIRONMENT") << "Clearing environment on parcel #" << parcel_id << LL_ENDL;
            clearEnvironment(ENV_PARCEL);
        }
        else if (envinfo->mDayCycle->isTrackEmpty(LLSettingsDay::TRACK_WATER)
                 || envinfo->mDayCycle->isTrackEmpty(LLSettingsDay::TRACK_GROUND_LEVEL))
        {
            LL_WARNS("ENVIRONMENT") << "Invalid day cycle for parcel #" << parcel_id << LL_ENDL;
            clearEnvironment(ENV_PARCEL);
        }
        else
        {
            setEnvironment(ENV_PARCEL, envinfo->mDayCycle, envinfo->mDayLength, envinfo->mDayOffset, envinfo->mEnvVersion);
        }
    }

    updateEnvironment(transition);
}

void LLEnvironment::adjustRegionOffset(F32 adjust)
{
    if (isExtendedEnvironmentEnabled())
    {
        LL_WARNS("ENVIRONMENT") << "Attempt to adjust region offset on EEP region.  Legacy regions only." << LL_ENDL;
    }

    if (mEnvironments[ENV_REGION])
    {
        F32 day_length = mEnvironments[ENV_REGION]->getDayLength();
        F32 day_offset = mEnvironments[ENV_REGION]->getDayOffset();

        F32 day_adjustment = adjust * day_length;

        day_offset += day_adjustment;
        if (day_offset < 0.0f)
            day_offset = day_length + day_offset;
        mEnvironments[ENV_REGION]->setDayOffset(LLSettingsBase::Seconds(day_offset));
    }
}

//=========================================================================
void LLEnvironment::requestRegion(environment_apply_fn cb)
{
    requestParcel(INVALID_PARCEL_ID, cb);
}

void LLEnvironment::updateRegion(const LLSettingsDay::ptr_t &pday, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb)
{
    updateParcel(INVALID_PARCEL_ID, pday, day_length, day_offset, altitudes, cb);
}

void LLEnvironment::updateRegion(const LLUUID &asset_id, std::string display_name, S32 track_num, S32 day_length, S32 day_offset, U32 flags, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb)
{
    if (!isExtendedEnvironmentEnabled())
    {
        LL_WARNS("ENVIRONMENT") << "attempt to apply asset id to region not supporting it." << LL_ENDL;
        LLNotificationsUtil::add("NoEnvironmentSettings");
        return;
    }

    updateParcel(INVALID_PARCEL_ID, asset_id, display_name, track_num, day_length, day_offset, flags, altitudes, cb);
}

void LLEnvironment::updateRegion(const LLSettingsSky::ptr_t &psky, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb)
{
    updateParcel(INVALID_PARCEL_ID, psky, day_length, day_offset, altitudes, cb);
}

void LLEnvironment::updateRegion(const LLSettingsWater::ptr_t &pwater, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb)
{
    updateParcel(INVALID_PARCEL_ID, pwater, day_length, day_offset, altitudes, cb);
}


void LLEnvironment::resetRegion(environment_apply_fn cb)
{
    resetParcel(INVALID_PARCEL_ID, cb);
}

void LLEnvironment::requestParcel(S32 parcel_id, environment_apply_fn cb)
{
    if (!isExtendedEnvironmentEnabled())
    {   /*TODO: When EEP is live on the entire grid, this can go away. */
        if (parcel_id == INVALID_PARCEL_ID)
        {
            if (!cb)
            {
                LLSettingsBase::Seconds transition = LLViewerParcelMgr::getInstance()->getTeleportInProgress() ? TRANSITION_FAST : TRANSITION_DEFAULT;
                cb = [this, transition](S32 pid, EnvironmentInfo::ptr_t envinfo)
                {
                    clearEnvironment(ENV_PARCEL);
                    recordEnvironment(pid, envinfo, transition);
                };
            }

            LLEnvironmentRequest::initiate(cb);
        }
        else if (cb)
            cb(parcel_id, EnvironmentInfo::ptr_t());
        return;
    }

    if (!cb)
    {
        LLSettingsBase::Seconds transition = LLViewerParcelMgr::getInstance()->getTeleportInProgress() ? TRANSITION_FAST : TRANSITION_DEFAULT;
        cb = [this, transition](S32 pid, EnvironmentInfo::ptr_t envinfo) { recordEnvironment(pid, envinfo, transition); };
    }

    std::string coroname =
        LLCoros::instance().launch("LLEnvironment::coroRequestEnvironment",
        [this, parcel_id, cb]() { coroRequestEnvironment(parcel_id, cb); });
}

void LLEnvironment::updateParcel(S32 parcel_id, const LLUUID &asset_id, std::string display_name, S32 track_num, S32 day_length, S32 day_offset, U32 flags, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb)
{
    UpdateInfo::ptr_t updates(std::make_shared<UpdateInfo>(asset_id, display_name, day_length, day_offset, altitudes, flags));
    std::string coroname =
        LLCoros::instance().launch("LLEnvironment::coroUpdateEnvironment",
        [this, parcel_id, track_num, updates, cb]() { coroUpdateEnvironment(parcel_id, track_num, updates, cb); });
}

void LLEnvironment::onUpdateParcelAssetLoaded(LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status, S32 parcel_id, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes)
{
    if (status)
    {
        LL_WARNS("ENVIRONMENT") << "Unable to get settings asset with id " << asset_id << "!" << LL_ENDL;
        LLNotificationsUtil::add("FailedToLoadSettingsApply");
        return;
    }

    LLSettingsDay::ptr_t pday;

    if (settings->getSettingsType() == "daycycle")
        pday = std::static_pointer_cast<LLSettingsDay>(settings);
    else
    {
        pday = createDayCycleFromEnvironment( (parcel_id == INVALID_PARCEL_ID) ? ENV_REGION : ENV_PARCEL, settings);
    }

    if (!pday)
    {
        LL_WARNS("ENVIRONMENT") << "Unable to construct day around " << asset_id << "!" << LL_ENDL;
        LLNotificationsUtil::add("FailedToBuildSettingsDay");
        return;
    }

    updateParcel(parcel_id, pday, day_length, day_offset, altitudes);
}

void LLEnvironment::updateParcel(S32 parcel_id, const LLSettingsSky::ptr_t &psky, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb)
{
    LLSettingsDay::ptr_t pday = createDayCycleFromEnvironment((parcel_id == INVALID_PARCEL_ID) ? ENV_REGION : ENV_PARCEL, psky);
    pday->setFlag(psky->getFlags());
    updateParcel(parcel_id, pday, day_length, day_offset, altitudes, cb);
}

void LLEnvironment::updateParcel(S32 parcel_id, const LLSettingsWater::ptr_t &pwater, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb)
{
    LLSettingsDay::ptr_t pday = createDayCycleFromEnvironment((parcel_id == INVALID_PARCEL_ID) ? ENV_REGION : ENV_PARCEL, pwater);
    pday->setFlag(pwater->getFlags());
    updateParcel(parcel_id, pday, day_length, day_offset, altitudes, cb);
}

void LLEnvironment::updateParcel(S32 parcel_id, const LLSettingsDay::ptr_t &pday, S32 track_num, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb)
{
    UpdateInfo::ptr_t updates(std::make_shared<UpdateInfo>(pday, day_length, day_offset, altitudes));

    std::string coroname =
        LLCoros::instance().launch("LLEnvironment::coroUpdateEnvironment",
        [this, parcel_id, track_num, updates, cb]() { coroUpdateEnvironment(parcel_id, track_num, updates, cb); });
}

void LLEnvironment::updateParcel(S32 parcel_id, const LLSettingsDay::ptr_t &pday, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb)
{
    updateParcel(parcel_id, pday, NO_TRACK, day_length, day_offset, altitudes, cb);
}



void LLEnvironment::resetParcel(S32 parcel_id, environment_apply_fn cb)
{
    std::string coroname =
        LLCoros::instance().launch("LLEnvironment::coroResetEnvironment",
        [this, parcel_id, cb]() { coroResetEnvironment(parcel_id, NO_TRACK, cb); });
}

void LLEnvironment::coroRequestEnvironment(S32 parcel_id, LLEnvironment::environment_apply_fn apply)
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ResetEnvironment", httpPolicy));
    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);

    std::string url = gAgent.getRegionCapability("ExtEnvironment");
    if (url.empty())
        return;

    LL_DEBUGS("ENVIRONMENT") << "Requesting for parcel_id=" << parcel_id << LL_ENDL;

    if (parcel_id != INVALID_PARCEL_ID)
    {
        std::stringstream query;

        query << "?parcelid=" << parcel_id;
        url += query.str();
    }

    LLSD result = httpAdapter->getAndSuspend(httpRequest, url);
    // results that come back may contain the new settings

    LLSD httpResults = result["http_result"];
    LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
    if (!status)
    {
        LL_WARNS("ENVIRONMENT") << "Couldn't retrieve environment settings for " << ((parcel_id == INVALID_PARCEL_ID) ? ("region!") : ("parcel!")) << LL_ENDL;
    }
    else if (LLApp::isExiting() || gDisconnected)
    {
        return;
    }
    else
    {
        LLSD environment = result[KEY_ENVIRONMENT];
        if (environment.isDefined() && apply)
        {
            EnvironmentInfo::ptr_t envinfo = LLEnvironment::EnvironmentInfo::extract(environment);
            apply(parcel_id, envinfo);
        }
    }

}

void LLEnvironment::coroUpdateEnvironment(S32 parcel_id, S32 track_no, UpdateInfo::ptr_t updates, environment_apply_fn apply)
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ResetEnvironment", httpPolicy));
    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);

    std::string url = gAgent.getRegionCapability("ExtEnvironment");
    if (url.empty())
        return;

    LLSD body(LLSD::emptyMap());
    body[KEY_ENVIRONMENT] = LLSD::emptyMap();

    if (track_no == NO_TRACK)
    {   // day length and offset are only applicable if we are addressing the entire day cycle.
        if (updates->mDayLength > 0)
            body[KEY_ENVIRONMENT][KEY_DAYLENGTH] = updates->mDayLength;
        if (updates->mDayOffset > 0)
            body[KEY_ENVIRONMENT][KEY_DAYOFFSET] = updates->mDayOffset;

        if ((parcel_id == INVALID_PARCEL_ID) && (updates->mAltitudes.size() == 3))
        {   // only test for altitude changes if we are changing the region.
            body[KEY_ENVIRONMENT][KEY_TRACKALTS] = LLSD::emptyArray();
            for (S32 i = 0; i < 3; ++i)
            {
                body[KEY_ENVIRONMENT][KEY_TRACKALTS][i] = updates->mAltitudes[i];
            }
        }
    }

    if (updates->mDayp)
        body[KEY_ENVIRONMENT][KEY_DAYCYCLE] = updates->mDayp->getSettings();
    else if (!updates->mSettingsAsset.isNull())
    {
        body[KEY_ENVIRONMENT][KEY_DAYASSET] = updates->mSettingsAsset;
        if (!updates->mDayName.empty())
            body[KEY_ENVIRONMENT][KEY_DAYNAME] = updates->mDayName;
    }

    body[KEY_ENVIRONMENT][KEY_FLAGS] = LLSD::Integer(updates->mFlags);
    //_WARNS("ENVIRONMENT") << "Body = " << body << LL_ENDL;

    if ((parcel_id != INVALID_PARCEL_ID) || (track_no != NO_TRACK))
    {
        std::stringstream query;
        query << "?";

        if (parcel_id != INVALID_PARCEL_ID)
        {
            query << "parcelid=" << parcel_id;

            if (track_no != NO_TRACK)
                query << "&";
        }
        if (track_no != NO_TRACK)
        {
            query << "trackno=" << track_no;
        }
        url += query.str();
    }

    LLSD result = httpAdapter->putAndSuspend(httpRequest, url, body);
    // results that come back may contain the new settings

    LLSD notify;

    LLSD httpResults = result["http_result"];
    LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);

    if ((!status) || !result["success"].asBoolean())
    {
        LL_WARNS("ENVIRONMENT") << "Couldn't update Windlight settings for " << ((parcel_id == INVALID_PARCEL_ID) ? ("region!") : ("parcel!")) << LL_ENDL;

        notify = LLSD::emptyMap();
        std::string reason = result["message"].asString();
        if (reason.empty())
        {
            notify["FAIL_REASON"] = status.toString();
        }
        else
        {
            notify["FAIL_REASON"] = reason;
        }
    }
    else if (LLApp::isExiting())
    {
        return;
    }
    else
    {
        LLSD environment = result[KEY_ENVIRONMENT];
        if (environment.isDefined() && apply)
        {
            EnvironmentInfo::ptr_t envinfo = LLEnvironment::EnvironmentInfo::extract(environment);
            apply(parcel_id, envinfo);
        }
    }

    if (!notify.isUndefined())
    {
        LLNotificationsUtil::add("WLRegionApplyFail", notify);
        //LLEnvManagerNew::instance().onRegionSettingsApplyResponse(false);
    }
}

void LLEnvironment::coroResetEnvironment(S32 parcel_id, S32 track_no, environment_apply_fn apply)
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ResetEnvironment", httpPolicy));
    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);

    std::string url = gAgent.getRegionCapability("ExtEnvironment");
    if (url.empty())
        return;

    if ((parcel_id != INVALID_PARCEL_ID) || (track_no != NO_TRACK))
    {
        std::stringstream query;
        query << "?";

        if (parcel_id != INVALID_PARCEL_ID)
        {
            query << "parcelid=" << parcel_id;

            if (track_no != NO_TRACK)
                query << "&";
        }
        if (track_no != NO_TRACK)
        {
            query << "trackno=" << track_no;
        }
        url += query.str();
    }

    LLSD result = httpAdapter->deleteAndSuspend(httpRequest, url);
    // results that come back may contain the new settings

    LLSD notify;

    LLSD httpResults = result["http_result"];
    LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);

    if ((!status) || !result["success"].asBoolean())
    {
        LL_WARNS("ENVIRONMENT") << "Couldn't reset Windlight settings in " << ((parcel_id == INVALID_PARCEL_ID) ? ("region!") : ("parcel!")) << LL_ENDL;

        notify = LLSD::emptyMap();
        std::string reason = result["message"].asString();
        if (reason.empty())
        {
            notify["FAIL_REASON"] = status.toString();
        }
        else
        {
            notify["FAIL_REASON"] = reason;
        }
    }
    else if (LLApp::isExiting())
    {
        return;
    }
    else
    {
       LLSD environment = result[KEY_ENVIRONMENT];
        if (environment.isDefined() && apply)
        {
            EnvironmentInfo::ptr_t envinfo = LLEnvironment::EnvironmentInfo::extract(environment);
            apply(parcel_id, envinfo);
        }
    }

    if (!notify.isUndefined())
    {
        LLNotificationsUtil::add("WLRegionApplyFail", notify);
        //LLEnvManagerNew::instance().onRegionSettingsApplyResponse(false);
    }

}


//=========================================================================

LLEnvironment::EnvironmentInfo::EnvironmentInfo():
    mParcelId(INVALID_PARCEL_ID),
    mRegionId(),
    mDayLength(0),
    mDayOffset(0),
    mDayHash(0),
    mDayCycle(),
    mAltitudes({ { 0.0, 0.0, 0.0, 0.0 } }),
    mIsDefault(false),
    mIsLegacy(false),
    mDayCycleName(),
    mNameList(),
    mEnvVersion(INVALID_PARCEL_ENVIRONMENT_VERSION)
{
}

LLEnvironment::EnvironmentInfo::ptr_t LLEnvironment::EnvironmentInfo::extract(LLSD environment)
{
    ptr_t pinfo = std::make_shared<EnvironmentInfo>();

    pinfo->mIsDefault = environment.has(KEY_ISDEFAULT) ? environment[KEY_ISDEFAULT].asBoolean() : true;
    pinfo->mParcelId = environment.has(KEY_PARCELID) ? environment[KEY_PARCELID].asInteger() : INVALID_PARCEL_ID;
    pinfo->mRegionId = environment.has(KEY_REGIONID) ? environment[KEY_REGIONID].asUUID() : LLUUID::null;
    pinfo->mIsLegacy = false;

    if (environment.has(KEY_TRACKALTS))
    {
        for (int idx = 0; idx < 3; idx++)
        {
            pinfo->mAltitudes[idx+1] = environment[KEY_TRACKALTS][idx].asReal();
        }
        pinfo->mAltitudes[0] = 0;
    }

    if (environment.has(KEY_DAYCYCLE))
    {
        pinfo->mDayCycle = LLSettingsVODay::buildFromEnvironmentMessage(environment[KEY_DAYCYCLE]);
        pinfo->mDayLength = LLSettingsDay::Seconds(environment.has(KEY_DAYLENGTH) ? environment[KEY_DAYLENGTH].asInteger() : -1);
        pinfo->mDayOffset = LLSettingsDay::Seconds(environment.has(KEY_DAYOFFSET) ? environment[KEY_DAYOFFSET].asInteger() : -1);
        pinfo->mDayHash = environment.has(KEY_DAYHASH) ? environment[KEY_DAYHASH].asInteger() : 0;
    }
    else
    {
        pinfo->mDayLength = LLEnvironment::instance().getEnvironmentDayLength(ENV_REGION);
        pinfo->mDayOffset = LLEnvironment::instance().getEnvironmentDayOffset(ENV_REGION);
    }

    if (environment.has(KEY_DAYASSET))
    {
        pinfo->mAssetId = environment[KEY_DAYASSET].asUUID();
    }

    if (environment.has(KEY_DAYNAMES))
    {
        LLSD daynames = environment[KEY_DAYNAMES];
        if (daynames.isArray())
        {
            pinfo->mDayCycleName.clear();
            for (S32 index = 0; index < pinfo->mNameList.size(); ++index)
            {
                pinfo->mNameList[index] = daynames[index].asString();
            }
        }
        else if (daynames.isString())
        {
            for (std::string &name: pinfo->mNameList)
            {
                name.clear();
            }

            pinfo->mDayCycleName = daynames.asString();
        }
    }
    else if (pinfo->mDayCycle)
    {
        pinfo->mDayCycleName = pinfo->mDayCycle->getName();
    }


    if (environment.has(KEY_ENVVERSION))
    {
        LLSD version = environment[KEY_ENVVERSION];
        pinfo->mEnvVersion = version.asInteger();
    }
    else
    {
        // can be used for region, but versions should be same
        pinfo->mEnvVersion = pinfo->mIsDefault ? UNSET_PARCEL_ENVIRONMENT_VERSION : INVALID_PARCEL_ENVIRONMENT_VERSION;
    }

    return pinfo;
}


LLEnvironment::EnvironmentInfo::ptr_t LLEnvironment::EnvironmentInfo::extractLegacy(LLSD legacy)
{
    if (!legacy.isArray() || !legacy[0].has("regionID"))
    {
        LL_WARNS("ENVIRONMENT") << "Invalid legacy settings for environment: " << legacy << LL_ENDL;
        return ptr_t();
    }

    ptr_t pinfo = std::make_shared<EnvironmentInfo>();

    pinfo->mIsDefault = false;
    pinfo->mParcelId = INVALID_PARCEL_ID;
    pinfo->mRegionId = legacy[0]["regionID"].asUUID();
    pinfo->mIsLegacy = true;

    pinfo->mDayLength = LLSettingsDay::DEFAULT_DAYLENGTH;
    pinfo->mDayOffset = LLSettingsDay::DEFAULT_DAYOFFSET;
    pinfo->mDayCycle = LLSettingsVODay::buildFromLegacyMessage(pinfo->mRegionId, legacy[1], legacy[2], legacy[3]);
    if (pinfo->mDayCycle)
        pinfo->mDayHash = pinfo->mDayCycle->getHash();

    pinfo->mAltitudes[0] = 0;
    pinfo->mAltitudes[2] = 10001;
    pinfo->mAltitudes[3] = 10002;
    pinfo->mAltitudes[4] = 10003;

    return pinfo;
}

//=========================================================================
LLSettingsWater::ptr_t LLEnvironment::createWaterFromLegacyPreset(const std::string filename, LLSD &messages)
{
    std::string name(gDirUtilp->getBaseFileName(filename, true));
    std::string path(gDirUtilp->getDirName(filename));

    LLSettingsWater::ptr_t water = LLSettingsVOWater::buildFromLegacyPresetFile(name, path, messages);

    if (!water)
    {
        messages["NAME"] = name;
        messages["FILE"] = filename;
    }
    return water;
}

LLSettingsSky::ptr_t LLEnvironment::createSkyFromLegacyPreset(const std::string filename, LLSD &messages)
{
    std::string name(gDirUtilp->getBaseFileName(filename, true));
    std::string path(gDirUtilp->getDirName(filename));

    LLSettingsSky::ptr_t sky = LLSettingsVOSky::buildFromLegacyPresetFile(name, path, messages);
    if (!sky)
    {
        messages["NAME"] = name;
        messages["FILE"] = filename;
    }
    return sky;
}

LLSettingsDay::ptr_t LLEnvironment::createDayCycleFromLegacyPreset(const std::string filename, LLSD &messages)
{
    std::string name(gDirUtilp->getBaseFileName(filename, true));
    std::string path(gDirUtilp->getDirName(filename));

    LLSettingsDay::ptr_t day = LLSettingsVODay::buildFromLegacyPresetFile(name, path, messages);
    if (!day)
    {
        messages["NAME"] = name;
        messages["FILE"] = filename;
    }
    return day;
}

LLSettingsDay::ptr_t LLEnvironment::createDayCycleFromEnvironment(EnvSelection_t env, LLSettingsBase::ptr_t settings)
{
    std::string type(settings->getSettingsType());

    if (type == "daycycle")
        return std::static_pointer_cast<LLSettingsDay>(settings);

    if ((env != ENV_PARCEL) && (env != ENV_REGION))
    {
        LL_WARNS("ENVIRONMENT") << "May only create from parcel or region environment." << LL_ENDL;
        return LLSettingsDay::ptr_t();
    }

    LLSettingsDay::ptr_t day = this->getEnvironmentDay(env);
    if (!day && (env == ENV_PARCEL))
    {
        day = this->getEnvironmentDay(ENV_REGION);
    }

    if (!day)
    {
        LL_WARNS("ENVIRONMENT") << "Could not retrieve existing day settings." << LL_ENDL;
        return LLSettingsDay::ptr_t();
    }

    day = day->buildClone();

    if (type == "sky")
    {
        for (U32 idx = 1; idx < LLSettingsDay::TRACK_MAX; ++idx)
            day->clearCycleTrack(idx);
        day->setSettingsAtKeyframe(settings, 0.0f, 1);
    }
    else if (type == "water")
    {
        day->clearCycleTrack(LLSettingsDay::TRACK_WATER);
        day->setSettingsAtKeyframe(settings, 0.0f, LLSettingsDay::TRACK_WATER);
    }

    return day;
}

void LLEnvironment::onAgentPositionHasChanged(const LLVector3 &localpos)
{
    S32 trackno = calculateSkyTrackForAltitude(localpos.mV[VZ]);
    if (trackno == mCurrentTrack)
        return;

    mCurrentTrack = trackno;

    LLViewerRegion* cur_region = gAgent.getRegion();
    if (!cur_region || !cur_region->capabilitiesReceived())
    {
        // Environment not ready, environment will be updated later, don't cause 'blend' yet.
        // But keep mCurrentTrack updated in case we won't get new altitudes for some reason
        return;
    }

    for (S32 env = ENV_LOCAL; env < ENV_DEFAULT; ++env)
    {
        if (mEnvironments[env])
            mEnvironments[env]->setSkyTrack(mCurrentTrack);
    }
}

S32 LLEnvironment::calculateSkyTrackForAltitude(F64 altitude)
{
    auto it = std::find_if_not(mTrackAltitudes.begin(), mTrackAltitudes.end(), [altitude](F32 test) { return altitude > test; });

    if (it == mTrackAltitudes.begin())
        return 1;
    else if (it == mTrackAltitudes.end())
        return 4;

    return std::min(static_cast<S32>(std::distance(mTrackAltitudes.begin(), it)), 4);
}

//-------------------------------------------------------------------------
void LLEnvironment::handleEnvironmentPush(LLSD &message)
{
    // Log the experience message
    LLExperienceLog::instance().handleExperienceMessage(message);

    std::string action = message[KEY_ACTION].asString();
    LLUUID experience_id = message[KEY_EXPERIENCEID].asUUID();
    LLSD action_data = message[KEY_ACTIONDATA];
    F32 transition_time = action_data[KEY_TRANSITIONTIME].asReal();

    //TODO: Check here that the viewer thinks the experience is still valid.


    if (action == ACTION_CLEARENVIRONMENT)
    {
        handleEnvironmentPushClear(experience_id, action_data, transition_time);
    }
    else if (action == ACTION_PUSHFULLENVIRONMENT)
    {
        handleEnvironmentPushFull(experience_id, action_data, transition_time);
    }
    else if (action == ACTION_PUSHPARTIALENVIRONMENT)
    {
        handleEnvironmentPushPartial(experience_id, action_data, transition_time);
    }
    else
    {
        LL_WARNS("ENVIRONMENT", "GENERICMESSAGES") << "Unknown environment push action '" << action << "'" << LL_ENDL;
    }
}

void LLEnvironment::handleEnvironmentPushClear(LLUUID experience_id, LLSD &message, F32 transition)
{
    clearExperienceEnvironment(experience_id, LLSettingsBase::Seconds(transition));
}

void LLEnvironment::handleEnvironmentPushFull(LLUUID experience_id, LLSD &message, F32 transition)
{
    LLUUID asset_id(message[KEY_ASSETID].asUUID());

    setExperienceEnvironment(experience_id, asset_id, LLSettingsBase::Seconds(transition));
}

void LLEnvironment::handleEnvironmentPushPartial(LLUUID experience_id, LLSD &message, F32 transition)
{
    LLSD settings(message["settings"]);

    if (settings.isUndefined())
        return;

    setExperienceEnvironment(experience_id, settings, LLSettingsBase::Seconds(transition));
}

void LLEnvironment::clearExperienceEnvironment(LLUUID experience_id, LLSettingsBase::Seconds transition_time)
{
    DayInjection::ptr_t injection = std::dynamic_pointer_cast<DayInjection>(getEnvironmentInstance(ENV_PUSH));
    if (injection)
    {
        injection->clearInjections(experience_id, transition_time);
    }

}

void LLEnvironment::setSharedEnvironment()
{
    clearEnvironment(LLEnvironment::ENV_LOCAL);
    setSelectedEnvironment(LLEnvironment::ENV_LOCAL);
    updateEnvironment();
}

void LLEnvironment::setExperienceEnvironment(LLUUID experience_id, LLUUID asset_id, F32 transition_time)
{
    LLSettingsVOBase::getSettingsAsset(asset_id,
        [this, experience_id, transition_time](LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status, LLExtStat)
    {
        onSetExperienceEnvAssetLoaded(experience_id, settings, transition_time, status);
    });


}

void LLEnvironment::onSetExperienceEnvAssetLoaded(LLUUID experience_id, LLSettingsBase::ptr_t settings, F32 transition_time, S32 status)
{
    DayInjection::ptr_t environment = std::dynamic_pointer_cast<DayInjection>(getEnvironmentInstance(ENV_PUSH));
    bool updateenvironment(false);

    if (!settings || status)
    {
        LLSD args;
        args["NAME"] = experience_id.asString();
        LLNotificationsUtil::add("FailedToFindSettings", args);
        return;
    }

    if (!environment)
    {
        environment = std::dynamic_pointer_cast<DayInjection>(getEnvironmentInstance(ENV_PUSH, true));
        updateenvironment = true;
    }

    if (settings->getSettingsType() == "daycycle")
    {
        environment->setInjectedDay(std::static_pointer_cast<LLSettingsDay>(settings), experience_id, LLSettingsBase::Seconds(transition_time));
    }
    else if (settings->getSettingsType() == "sky")
    {
        environment->setInjectedSky(std::static_pointer_cast<LLSettingsSky>(settings), experience_id, LLSettingsBase::Seconds(transition_time));
    }
    else if (settings->getSettingsType() == "water")
    {
        environment->setInjectedWater(std::static_pointer_cast<LLSettingsWater>(settings), experience_id, LLSettingsBase::Seconds(transition_time));
    }

    if (updateenvironment)
        updateEnvironment(TRANSITION_INSTANT, true);
}


void LLEnvironment::setExperienceEnvironment(LLUUID experience_id, LLSD data, F32 transition_time)
{
    LLSD sky(data["sky"]);
    LLSD water(data["water"]);

    if (sky.isUndefined() && water.isUndefined())
    {
        clearExperienceEnvironment(experience_id, LLSettingsBase::Seconds(transition_time));
        return;
    }

    DayInjection::ptr_t environment = std::dynamic_pointer_cast<DayInjection>(getEnvironmentInstance(ENV_PUSH));
    bool updateenvironment(false);

    if (!environment)
    {
        environment = std::dynamic_pointer_cast<DayInjection>(getEnvironmentInstance(ENV_PUSH, true));
        updateenvironment = true;
    }

    if (!sky.isUndefined())
    {
        environment->injectSkySettings(sky, experience_id, LLSettingsBase::Seconds(transition_time));
    }

    if (!water.isUndefined())
    {
        environment->injectWaterSettings(water, experience_id, LLSettingsBase::Seconds(transition_time));
    }

    if (updateenvironment)
        updateEnvironment(TRANSITION_INSTANT, true);

}

void LLEnvironment::listenExperiencePump(const LLSD &message)
{
    LLUUID experience_id = message["experience"];
    LLSD data = message[experience_id.asString()];
    std::string permission(data["permission"].asString());

    if ((permission == "Forget") || (permission == "Block"))
    {
        clearExperienceEnvironment(experience_id, (permission == "Block") ? TRANSITION_INSTANT : TRANSITION_FAST);
    }
}

//=========================================================================
LLEnvironment::DayInstance::DayInstance(EnvSelection_t env) :
    mDayCycle(),
    mSky(),
    mWater(),
    mDayLength(LLSettingsDay::DEFAULT_DAYLENGTH),
    mDayOffset(LLSettingsDay::DEFAULT_DAYOFFSET),
    mBlenderSky(),
    mBlenderWater(),
    mInitialized(false),
    mSkyTrack(1),
    mEnv(env),
    mAnimateFlags(0)
{ }


LLEnvironment::DayInstance::ptr_t LLEnvironment::DayInstance::clone() const
{
    ptr_t environment = std::make_shared<DayInstance>(mEnv);

    environment->mDayCycle = mDayCycle;
    environment->mSky = mSky;
    environment->mWater = mWater;
    environment->mDayLength = mDayLength;
    environment->mDayOffset = mDayOffset;
    environment->mBlenderSky = mBlenderSky;
    environment->mBlenderWater = mBlenderWater;
    environment->mInitialized = mInitialized;
    environment->mSkyTrack = mSkyTrack;
    environment->mAnimateFlags = mAnimateFlags;

    return environment;
}

bool LLEnvironment::DayInstance::applyTimeDelta(const LLSettingsBase::Seconds& delta)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT;
    ptr_t keeper(shared_from_this());   // makes sure that this does not go away while it is being worked on.

    bool changed(false);
    if (!mInitialized)
        initialize();

    if (mBlenderSky)
        changed |= mBlenderSky->applyTimeDelta(delta);
    if (mBlenderWater)
        changed |= mBlenderWater->applyTimeDelta(delta);
    return changed;
}

void LLEnvironment::DayInstance::setDay(const LLSettingsDay::ptr_t &pday, LLSettingsDay::Seconds daylength, LLSettingsDay::Seconds dayoffset)
{
    mInitialized = false;

    mAnimateFlags = 0;

    mDayCycle = pday;
    mDayLength = daylength;
    mDayOffset = dayoffset;

    mBlenderSky.reset();
    mBlenderWater.reset();

    mSky = LLSettingsVOSky::buildDefaultSky();
    mWater = LLSettingsVOWater::buildDefaultWater();

    animate();
}


bool LLEnvironment::DayInstance::setSky(const LLSettingsSky::ptr_t &psky)
{
    mInitialized = false;

    bool changed = psky == nullptr || mSky == nullptr || mSky->getHash() != psky->getHash();

    bool different_sky = mSky != psky;

    mSky = psky;
    mSky->mReplaced |= different_sky;
    mSky->update();
    mBlenderSky.reset();

    if (gAtmosphere)
    {
        AtmosphericModelSettings settings;
        LLEnvironment::getAtmosphericModelSettings(settings, psky);
        gAtmosphere->configureAtmosphericModel(settings);
    }

    return changed;
}

void LLEnvironment::DayInstance::setWater(const LLSettingsWater::ptr_t &pwater)
{
    mInitialized = false;

    bool different_water = mWater != pwater;
    mWater = pwater;
    mWater->mReplaced |= different_water;
    mWater->update();
    mBlenderWater.reset();
}

void LLEnvironment::DayInstance::initialize()
{
    mInitialized = true;

    if (!mWater)
        mWater = LLSettingsVOWater::buildDefaultWater();
    if (!mSky)
        mSky = LLSettingsVOSky::buildDefaultSky();
}

void LLEnvironment::DayInstance::clear()
{
    mDayCycle.reset();
    mSky.reset();
    mWater.reset();
    mDayLength = LLSettingsDay::DEFAULT_DAYLENGTH;
    mDayOffset = LLSettingsDay::DEFAULT_DAYOFFSET;
    mBlenderSky.reset();
    mBlenderWater.reset();
    mSkyTrack = 1;
}

void LLEnvironment::DayInstance::setSkyTrack(S32 trackno)
{
    mSkyTrack = trackno;
    if (mBlenderSky)
    {
        mBlenderSky->switchTrack(trackno, 0.0);
    }
}

void LLEnvironment::DayInstance::setBlenders(const LLSettingsBlender::ptr_t &skyblend, const LLSettingsBlender::ptr_t &waterblend)
{
    mBlenderSky = skyblend;
    mBlenderWater = waterblend;
}

LLSettingsBase::TrackPosition LLEnvironment::DayInstance::getProgress() const
{
    LLSettingsBase::Seconds now(LLDate::now().secondsSinceEpoch());
    now += mDayOffset;

    if ((mDayLength <= 0) || !mDayCycle)
        return -1.0f;   // no actual day cycle.

    return convert_time_to_position(now, mDayLength);
}

LLSettingsBase::TrackPosition LLEnvironment::DayInstance::secondsToKeyframe(LLSettingsDay::Seconds seconds)
{
    return convert_time_to_position(seconds, mDayLength);
}

void LLEnvironment::DayInstance::animate()
{
    LLSettingsBase::Seconds now(LLDate::now().secondsSinceEpoch());

    now += mDayOffset;

    if (!mDayCycle)
        return;

    if (!(mAnimateFlags & NO_ANIMATE_WATER))
    {
        LLSettingsDay::CycleTrack_t &wtrack = mDayCycle->getCycleTrack(0);

        if (wtrack.empty())
        {
            mWater.reset();
            mBlenderWater.reset();
        }
        else
        {
            mWater = LLSettingsVOWater::buildDefaultWater();
            mBlenderWater = std::make_shared<LLTrackBlenderLoopingTime>(mWater, mDayCycle, 0,
                mDayLength, mDayOffset, DEFAULT_UPDATE_THRESHOLD);
        }
    }

    if (!(mAnimateFlags & NO_ANIMATE_SKY))
    {
        // sky, initialize to track 1
        LLSettingsDay::CycleTrack_t &track = mDayCycle->getCycleTrack(1);

        if (track.empty())
        {
            mSky.reset();
            mBlenderSky.reset();
        }
        else
        {
            mSky = LLSettingsVOSky::buildDefaultSky();
            mBlenderSky = std::make_shared<LLTrackBlenderLoopingTime>(mSky, mDayCycle, 1,
                mDayLength, mDayOffset, DEFAULT_UPDATE_THRESHOLD);
            mBlenderSky->switchTrack(mSkyTrack, 0.0);
        }
    }
}

//-------------------------------------------------------------------------
LLEnvironment::DayTransition::DayTransition(const LLSettingsSky::ptr_t &skystart,
    const LLSettingsWater::ptr_t &waterstart, LLEnvironment::DayInstance::ptr_t &end, LLSettingsDay::Seconds time) :
    DayInstance(ENV_NONE),
    mStartSky(skystart),
    mStartWater(waterstart),
    mNextInstance(end),
    mTransitionTime(time)
{

}

bool LLEnvironment::DayTransition::applyTimeDelta(const LLSettingsBase::Seconds& delta)
{
    bool changed(false);

    changed = mNextInstance->applyTimeDelta(delta);
    changed |= DayInstance::applyTimeDelta(delta);
    return changed;
}

void LLEnvironment::DayTransition::animate()
{
    mNextInstance->animate();

    mWater = mStartWater->buildClone();
    mBlenderWater = std::make_shared<LLSettingsBlenderTimeDelta>(mWater, mStartWater, mNextInstance->getWater(), mTransitionTime);
    mBlenderWater->setOnFinished(
        [this](LLSettingsBlender::ptr_t blender) {
            mBlenderWater.reset();

            if (!mBlenderSky && !mBlenderWater)
                LLEnvironment::instance().mCurrentEnvironment = mNextInstance;
            else
                setWater(mNextInstance->getWater());
    });


    // pause probe updates and reset reflection maps on sky change
    gPipeline.mReflectionMapManager.pause();
    gPipeline.mReflectionMapManager.reset();

    mSky = mStartSky->buildClone();
    mBlenderSky = std::make_shared<LLSettingsBlenderTimeDelta>(mSky, mStartSky, mNextInstance->getSky(), mTransitionTime);
    mBlenderSky->setOnFinished(
        [this](LLSettingsBlender::ptr_t blender) {
        mBlenderSky.reset();

        // resume reflection probe updates
        gPipeline.mReflectionMapManager.resume();

        if (!mBlenderSky && !mBlenderWater)
            LLEnvironment::instance().mCurrentEnvironment = mNextInstance;
        else
            setSky(mNextInstance->getSky());
    });
}

void LLEnvironment::saveToSettings()
{
    std::string user_dir = gDirUtilp->getLindenUserDir();
    if (user_dir.empty())
    {
        // not logged in
        return;
    }
    bool has_data = false;

    if (gSavedSettings.getBOOL("EnvironmentPersistAcrossLogin"))
    {
        DayInstance::ptr_t environment = getEnvironmentInstance(ENV_LOCAL);
        if (environment)
        {
            // Environment is 'layered'. No data in ENV_LOCAL means we are using parcel/region
            // Store local environment for next session
            LLSD env_data;

            LLSettingsDay::ptr_t day = environment->getDayCycle();
            if (day)
            {
                const std::string name = day->getName();
                const LLUUID asset_id = day->getAssetId();
                if (asset_id.notNull())
                {
                    // just save the id
                    env_data["day_id"] = asset_id;
                    env_data["day_length"] = LLSD::Integer(environment->getDayLength());
                    env_data["day_offset"] = LLSD::Integer(environment->getDayOffset());
                    has_data = true;
                }
                else if (!name.empty() && name != LLSettingsBase::DEFAULT_SETTINGS_NAME)
                {
                    // This setting was created locally and was not saved
                    // The only option is to save the whole thing
                    env_data["day_llsd"] = day->getSettings();
                    env_data["day_length"] = LLSD::Integer(environment->getDayLength());
                    env_data["day_offset"] = LLSD::Integer(environment->getDayOffset());
                    has_data = true;
                }
            }

            LLSettingsSky::ptr_t sky = environment->getSky();
            if ((environment->getFlags() & DayInstance::NO_ANIMATE_SKY) && sky)
            {
                const std::string name = sky->getName();
                const LLUUID asset_id = sky->getAssetId();
                if (asset_id.notNull())
                {
                    // just save the id
                    env_data["sky_id"] = asset_id;
                    has_data = true;
                }
                else if (!name.empty() && name != LLSettingsBase::DEFAULT_SETTINGS_NAME)
                {
                    // This setting was created locally and was not saved
                    // The only option is to save the whole thing
                    env_data["sky_llsd"] = sky->getSettings();
                    has_data = true;
                }
                has_data = true;
            }

            LLSettingsWater::ptr_t water = environment->getWater();
            if ((environment->getFlags() & DayInstance::NO_ANIMATE_WATER) && water)
            {
                const std::string name = water->getName();
                const LLUUID asset_id = water->getAssetId();
                if (asset_id.notNull())
                {
                    // just save the id
                    env_data["water_id"] = asset_id;
                    has_data = true;
                }
                else if (!name.empty() && name != LLSettingsBase::DEFAULT_SETTINGS_NAME)
                {
                    // This setting was created locally and was not saved
                    // The only option is to save the whole thing
                    env_data["water_llsd"] = water->getSettings();
                    has_data = true;
                }
            }

            std::string user_filepath = user_dir + gDirUtilp->getDirDelimiter() + LOCAL_ENV_STORAGE_FILE;
            llofstream out(user_filepath.c_str(), std::ios_base::out | std::ios_base::binary);
            if (out.good())
            {
                LLSDSerialize::toBinary(env_data, out);
                out.close();
            }
            else
            {
                LL_WARNS("ENVIRONMENT") << "Unable to open " << user_filepath << " for output." << LL_ENDL;
            }
        }
    }

    if (!has_data)
    {
        LLFile::remove(user_dir + gDirUtilp->getDirDelimiter() + LOCAL_ENV_STORAGE_FILE, ENOENT);
    }
}

void LLEnvironment::loadSkyWaterFromSettings(const LLSD &env_data, bool &valid, bool &assets_present)
{
    if (env_data.has("sky_id"))
    {
        // causes asset loaded callback and an update
        setEnvironment(ENV_LOCAL, env_data["sky_id"].asUUID());
        valid = true;
        assets_present = true;
    }
    else if (env_data.has("sky_llsd"))
    {
        LLSettingsSky::ptr_t sky = LLSettingsVOSky::buildSky(env_data["sky_llsd"]);
        setEnvironment(ENV_LOCAL, sky);
        valid = true;
    }

    if (env_data.has("water_id"))
    {
        // causes asset loaded callback and an update
        setEnvironment(ENV_LOCAL, env_data["water_id"].asUUID());
        valid = true;
        assets_present = true;
    }
    else if (env_data.has("water_llsd"))
    {
        LLSettingsWater::ptr_t sky = LLSettingsVOWater::buildWater(env_data["water_llsd"]);
        setEnvironment(ENV_LOCAL, sky);
        valid = true;
    }
}

bool LLEnvironment::loadFromSettings()
{
    if (!gSavedSettings.getBOOL("EnvironmentPersistAcrossLogin"))
    {
        return false;
    }

    std::string user_path = gDirUtilp->getLindenUserDir();
    if (user_path.empty())
    {
        LL_WARNS("ENVIRONMENT") << "Can't load previous environment, Environment was initialized before user logged in" << LL_ENDL;
        return false;
    }
    std::string user_filepath(user_path + gDirUtilp->getDirDelimiter() + LOCAL_ENV_STORAGE_FILE);
    if (!gDirUtilp->fileExists(user_filepath))
    {
        // No previous environment
        return false;
    }

    LLSD env_data;
    llifstream file(user_filepath.c_str(), std::ios_base::in | std::ios_base::binary);
    if (file.is_open())
    {
        LLSDSerialize::fromBinary(env_data, file, LLSDSerialize::SIZE_UNLIMITED);
        if (env_data.isUndefined())
        {
            LL_WARNS("ENVIRONMENT") << "error loading " << user_filepath << LL_ENDL;
            return false;
        }
        else
        {
            LL_INFOS("ENVIRONMENT") << "Loaded previous session environment from: " << user_filepath << LL_ENDL;
        }
        file.close();
    }
    else
    {
        LL_INFOS("ENVIRONMENT") << "Unable to open previous session environment file " << user_filepath << LL_ENDL;
    }

    if (!env_data.isMap() || (env_data.size() == 0))
    {
        LL_DEBUGS("ENVIRONMENT") << "Empty map loaded from: " << user_filepath << LL_ENDL;
        return false;
    }

    bool valid = false;
    bool has_assets = false;

    if (env_data.has("day_id"))
    {
        LLUUID assetId = env_data["day_id"].asUUID();

        LLSettingsVOBase::getSettingsAsset(assetId,
            [this, env_data](LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status, LLExtStat)
        {
            // Day should be always applied first,
            // otherwise it will override sky or water that was set earlier
            // so wait for asset to load before applying sky/water
            onSetEnvAssetLoaded(ENV_LOCAL, asset_id, settings, TRANSITION_DEFAULT, status, NO_VERSION);
            bool valid = false, has_assets = false;
            loadSkyWaterFromSettings(env_data, valid, has_assets);
            if (!has_assets && valid)
            {
                // Settings were loaded from file without having an asset, needs update
                // otherwise update will be done by asset callback
                updateEnvironment(TRANSITION_DEFAULT, true);
            }
        });
        // bail early, everything have to be done at callback
        return true;
    }
    else if (env_data.has("day_llsd"))
    {
        S32 length = env_data["day_length"].asInteger();
        S32 offset = env_data["day_offset"].asInteger();
        LLSettingsDay::ptr_t pday = LLSettingsVODay::buildDay(env_data["day_llsd"]);
        setEnvironment(ENV_LOCAL, pday, LLSettingsDay::Seconds(length), LLSettingsDay::Seconds(offset));
        valid = true;
    }

    loadSkyWaterFromSettings(env_data, valid, has_assets);

    if (valid && !has_assets)
    {
        // Settings were loaded from file without having an asset, needs update
        // otherwise update will be done by asset callback
        updateEnvironment(TRANSITION_DEFAULT, true);
    }
    return valid;
}

void LLEnvironment::saveBeaconsState()
{
    if (mEditorCounter == 0)
    {
        mShowSunBeacon = gSavedSettings.getBOOL("sunbeacon");
        mShowMoonBeacon = gSavedSettings.getBOOL("moonbeacon");
    }
    ++mEditorCounter;
}
void LLEnvironment::revertBeaconsState()
{
    --mEditorCounter;
    if (mEditorCounter == 0)
    {
        gSavedSettings.setBOOL("sunbeacon", mShowSunBeacon && gSavedSettings.getBOOL("sunbeacon"));
        gSavedSettings.setBOOL("moonbeacon", mShowMoonBeacon && gSavedSettings.getBOOL("moonbeacon"));
    }
}

//=========================================================================
LLTrackBlenderLoopingManual::LLTrackBlenderLoopingManual(const LLSettingsBase::ptr_t &target, const LLSettingsDay::ptr_t &day, S32 trackno) :
        LLSettingsBlender(target, LLSettingsBase::ptr_t(), LLSettingsBase::ptr_t()),
        mDay(day),
        mTrackNo(trackno),
        mPosition(0.0)
{
    LLSettingsDay::TrackBound_t initial = getBoundingEntries(mPosition);

    if (initial.first != mEndMarker)
    {   // No frames in track
        mInitial = (*initial.first).second;
        mFinal = (*initial.second).second;

        LLSD initSettings = mInitial->getSettings();
        mTarget->replaceSettings(initSettings);
    }
}

LLSettingsBase::BlendFactor LLTrackBlenderLoopingManual::setPosition(const LLSettingsBase::TrackPosition& position)
{
    mPosition = llclamp(position, 0.0f, 1.0f);

    LLSettingsDay::TrackBound_t bounds = getBoundingEntries(mPosition);

    if (bounds.first == mEndMarker)
    {   // No frames in track.
        return 0.0;
    }

    mInitial = (*bounds.first).second;
    mFinal = (*bounds.second).second;

    F64 spanLength = getSpanLength(bounds);

    F64 spanPos = ((mPosition < (*bounds.first).first) ? (mPosition + 1.0) : mPosition) - (*bounds.first).first;

    if (spanPos > spanLength)
    {
        // we are clamping position to 0-1 and spanLength is 1
        // so don't account for case of spanPos == spanLength
        spanPos = fmod(spanPos, spanLength);
    }

    F64 blendf = spanPos / spanLength;
    return LLSettingsBlender::setBlendFactor(blendf);
}

void LLTrackBlenderLoopingManual::switchTrack(S32 trackno, const LLSettingsBase::TrackPosition& position)
{
    mTrackNo = trackno;

    LLSettingsBase::TrackPosition useposition = (position < 0.0) ? mPosition : position;

    setPosition(useposition);
}

LLSettingsDay::TrackBound_t LLTrackBlenderLoopingManual::getBoundingEntries(F64 position)
{
    LLSettingsDay::CycleTrack_t &wtrack = mDay->getCycleTrack(mTrackNo);

    mEndMarker = wtrack.end();

    LLSettingsDay::TrackBound_t bounds = get_bounding_entries(wtrack, position);
    return bounds;
}

F64 LLTrackBlenderLoopingManual::getSpanLength(const LLSettingsDay::TrackBound_t &bounds) const
{
    return get_wrapping_distance((*bounds.first).first, (*bounds.second).first);
}

//=========================================================================
namespace
{
    DayInjection::DayInjection(LLEnvironment::EnvSelection_t env):
        LLEnvironment::DayInstance(env),
        mBaseDayInstance(),
        mInjectedSky(),
        mInjectedWater(),
        mActiveExperiences(),
        mDayExperience(),
        mSkyExperience(),
        mWaterExperience(),
        mEnvChangeConnection(),
        mParcelChangeConnection()
    {
        mInjectedSky = std::make_shared<LLSettingsInjectedSky>(LLEnvironment::instance().getCurrentSky());
        mInjectedWater = std::make_shared<LLSettingsInjectedWater>(LLEnvironment::instance().getCurrentWater());
        mBaseDayInstance = LLEnvironment::instance().getSharedEnvironmentInstance();
        mSky = mInjectedSky;
        mWater = mInjectedWater;

        mEnvChangeConnection = LLEnvironment::instance().setEnvironmentChanged([this](LLEnvironment::EnvSelection_t env, S32) { onEnvironmentChanged(env); });
        mParcelChangeConnection = gAgent.addParcelChangedCallback([this]() { onParcelChange(); });
    }

    DayInjection::~DayInjection()
    {
        if (mEnvChangeConnection.connected())
            mEnvChangeConnection.disconnect();
        if (mParcelChangeConnection.connected())
            mParcelChangeConnection.disconnect();
    }


    bool DayInjection::applyTimeDelta(const LLSettingsBase::Seconds& delta)
    {
        bool changed(false);

        if (mBaseDayInstance)
            changed |= mBaseDayInstance->applyTimeDelta(delta);
        mInjectedSky->applyInjections(delta);
        mInjectedWater->applyInjections(delta);
        changed |= LLEnvironment::DayInstance::applyTimeDelta(delta);
        if (changed)
        {
            mInjectedSky->setDirtyFlag(true);
            mInjectedWater->setDirtyFlag(true);
        }
        mInjectedSky->update();
        mInjectedWater->update();

        if (!hasInjections())
        {   // There are no injections being managed.  This should really go away.
            LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_PUSH);
            LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT);
        }

        return changed;
    }

    void DayInjection::setBaseDayInstance(const LLEnvironment::DayInstance::ptr_t &baseday)
    {
        mBaseDayInstance = baseday;

        if (mSkyExperience.isNull())
            mInjectedSky->setSource(mBaseDayInstance->getSky());
        if (mWaterExperience.isNull())
            mInjectedWater->setSource(mBaseDayInstance->getWater());
    }


    bool DayInjection::hasInjections() const
    {
        return (!mSkyExperience.isNull() || !mWaterExperience.isNull() || !mDayExperience.isNull() ||
            mBlenderSky || mBlenderWater || mInjectedSky->hasInjections() || mInjectedWater->hasInjections());
    }


    void DayInjection::testExperiencesOnParcel(S32 parcel_id)
    {
        LLCoros::instance().launch("DayInjection::testExperiencesOnParcel",
            [this, parcel_id]() { DayInjection::testExperiencesOnParcelCoro(std::static_pointer_cast<DayInjection>(this->shared_from_this()), parcel_id); });

    }

    void DayInjection::setInjectedDay(const LLSettingsDay::ptr_t &pday, LLUUID experience_id, LLSettingsBase::Seconds transition)
    {
        mSkyExperience = experience_id;
        mWaterExperience = experience_id;
        mDayExperience = experience_id;

        mBaseDayInstance = mBaseDayInstance->clone();
        mBaseDayInstance->setEnvironmentSelection(LLEnvironment::ENV_NONE);
        mBaseDayInstance->setDay(pday, mBaseDayInstance->getDayLength(), mBaseDayInstance->getDayOffset());
        animateSkyChange(mBaseDayInstance->getSky(), transition);
        animateWaterChange(mBaseDayInstance->getWater(), transition);

        mActiveExperiences.insert(experience_id);
    }

    void DayInjection::setInjectedSky(const LLSettingsSky::ptr_t &psky, LLUUID experience_id, LLSettingsBase::Seconds transition)
    {
        mSkyExperience = experience_id;
        mActiveExperiences.insert(experience_id);
        checkExperience();
        animateSkyChange(psky, transition);
    }

    void DayInjection::setInjectedWater(const LLSettingsWater::ptr_t &pwater, LLUUID experience_id, LLSettingsBase::Seconds transition)
    {
        mWaterExperience = experience_id;
        mActiveExperiences.insert(experience_id);
        checkExperience();
        animateWaterChange(pwater, transition);
    }

    void DayInjection::injectSkySettings(LLSD settings, LLUUID experience_id, LLSettingsBase::Seconds transition)
    {
        mInjectedSky->injectExperienceValues(settings, experience_id, transition);
        mActiveExperiences.insert(experience_id);
    }

    void DayInjection::injectWaterSettings(LLSD settings, LLUUID experience_id, LLSettingsBase::Seconds transition)
    {
        mInjectedWater->injectExperienceValues(settings, experience_id, transition);
        mActiveExperiences.insert(experience_id);
    }

    void DayInjection::clearInjections(LLUUID experience_id, LLSettingsBase::Seconds transition_time)
    {
        if ((experience_id.isNull() && !mDayExperience.isNull()) || (experience_id == mDayExperience))
        {
            mDayExperience.setNull();
            if (mSkyExperience == experience_id)
                mSkyExperience.setNull();
            if (mWaterExperience == experience_id)
                mWaterExperience.setNull();

            mBaseDayInstance = LLEnvironment::instance().getSharedEnvironmentInstance();

            if (mSkyExperience.isNull())
                animateSkyChange(mBaseDayInstance->getSky(), transition_time);
            if (mWaterExperience.isNull())
                animateWaterChange(mBaseDayInstance->getWater(), transition_time);
        }

        if ((experience_id.isNull() && !mSkyExperience.isNull()) || (experience_id == mSkyExperience))
        {
            mSkyExperience.setNull();
            animateSkyChange(mBaseDayInstance->getSky(), transition_time);
        }
        if ((experience_id.isNull() && !mWaterExperience.isNull()) || (experience_id == mWaterExperience))
        {
            mWaterExperience.setNull();
            animateWaterChange(mBaseDayInstance->getWater(), transition_time);
        }

        mInjectedSky->removeInjections(experience_id, transition_time);
        mInjectedWater->removeInjections(experience_id, transition_time);

        if (experience_id.isNull())
            mActiveExperiences.clear();
        else
            mActiveExperiences.erase(experience_id);

        if ((transition_time == LLEnvironment::TRANSITION_INSTANT) && (countExperiencesActive() == 0))
        {   // Only do this if instant and there are no other experiences injecting values.
            // (otherwise will be handled after transition)
            LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_PUSH);
            LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT);
        }
    }


    void DayInjection::testExperiencesOnParcelCoro(wptr_t that, S32 parcel_id)
    {
        LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
        LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
            httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("testExperiencesOnParcelCoro", httpPolicy));
        LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
        std::string url = gAgent.getRegionCapability("ExperienceQuery");

        if (url.empty())
        {
            LL_WARNS("ENVIRONMENT") << "No experience query cap." << LL_ENDL;
            return; // no checking in this region.
        }

        {
            ptr_t thatlock(that);
            std::stringstream fullurl;

            if (!thatlock)
                return;

            fullurl << url << "?";
            fullurl << "parcelid=" << parcel_id;

            for (auto it = thatlock->mActiveExperiences.begin(); it != thatlock->mActiveExperiences.end(); ++it)
            {
                if (it != thatlock->mActiveExperiences.begin())
                    fullurl << ",";
                else
                    fullurl << "&experiences=";
                fullurl << (*it).asString();
            }
            url = fullurl.str();
        }

        LLSD result = httpAdapter->getAndSuspend(httpRequest, url);
        LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
        LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);

        if (!status)
        {
            LL_WARNS() << "Unable to retrieve experience status for parcel." << LL_ENDL;
            return;
        }

        {
            LLParcel* parcel = LLViewerParcelMgr::instance().getAgentParcel();
            if (!parcel)
                return;

            if (parcel_id != parcel->getLocalID())
            {
                // Agent no longer on queried parcel.
                return;
            }
        }


        LLSD experiences = result["experiences"];
        {
            ptr_t thatlock(that);
            if (!thatlock)
                return;

            for (LLSD::map_iterator itr = experiences.beginMap(); itr != experiences.endMap(); ++itr)
            {
                if (!((*itr).second.asBoolean()))
                    thatlock->clearInjections(LLUUID((*itr).first), LLEnvironment::TRANSITION_FAST);

            }
        }
    }

    void DayInjection::animateSkyChange(LLSettingsSky::ptr_t psky, LLSettingsBase::Seconds transition)
    {
        if (mInjectedSky.get() == psky.get())
        {   // An attempt to animate to itself... don't do it.
            return;
        }
        if (transition == LLEnvironment::TRANSITION_INSTANT)
        {
            mBlenderSky.reset();
            mInjectedSky->setSource(psky);
        }
        else
        {
            LLSettingsSky::ptr_t start_sky(mInjectedSky->getSource()->buildClone());
            LLSettingsSky::ptr_t target_sky(start_sky->buildClone());
            mInjectedSky->setSource(target_sky);

            // clear reflection probes and pause updates during sky change
            gPipeline.mReflectionMapManager.pause();
            gPipeline.mReflectionMapManager.reset();

            mBlenderSky = std::make_shared<LLSettingsBlenderTimeDelta>(target_sky, start_sky, psky, transition);
            mBlenderSky->setOnFinished(
                [this, psky](LLSettingsBlender::ptr_t blender)
                {
                    mBlenderSky.reset();
                    mInjectedSky->setSource(psky);

                    // resume updating reflection probes when done animating sky
                    gPipeline.mReflectionMapManager.resume();
                    setSky(mInjectedSky);
                    if (!mBlenderWater && (countExperiencesActive() == 0))
                    {
                        LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_PUSH);
                        LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT);
                    }
                });
        }
    }

    void DayInjection::animateWaterChange(LLSettingsWater::ptr_t pwater, LLSettingsBase::Seconds transition)
    {
        if (mInjectedWater.get() == pwater.get())
        {   // An attempt to animate to itself. Bad idea.
            return;
        }
        if (transition == LLEnvironment::TRANSITION_INSTANT)
        {
            mBlenderWater.reset();
            mInjectedWater->setSource(pwater);
        }
        else
        {
            LLSettingsWater::ptr_t start_Water(mInjectedWater->getSource()->buildClone());
            LLSettingsWater::ptr_t scratch_Water(start_Water->buildClone());
            mInjectedWater->setSource(scratch_Water);

            mBlenderWater = std::make_shared<LLSettingsBlenderTimeDelta>(scratch_Water, start_Water, pwater, transition);
            mBlenderWater->setOnFinished(
                [this, pwater](LLSettingsBlender::ptr_t blender)
                {
                    mBlenderWater.reset();
                    mInjectedWater->setSource(pwater);
                    setWater(mInjectedWater);
                    if (!mBlenderSky && (countExperiencesActive() == 0))
                    {
                        LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_PUSH);
                        LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT);
                    }
                });
        }
    }

    void DayInjection::onEnvironmentChanged(LLEnvironment::EnvSelection_t env)
    {
        if (env >= LLEnvironment::ENV_PARCEL)
        {
            LLEnvironment::EnvSelection_t base_env(mBaseDayInstance->getEnvironmentSelection());
            LLEnvironment::DayInstance::ptr_t nextbase = LLEnvironment::instance().getSharedEnvironmentInstance();

            if ((base_env == LLEnvironment::ENV_NONE) || (nextbase == mBaseDayInstance) ||
                    (!mSkyExperience.isNull() && !mWaterExperience.isNull()))
            {   // base instance completely overridden, or not changed no transition will happen
                return;
            }

            LL_WARNS("PUSHENV", "ENVIRONMENT") << "Underlying environment has changed (" << env << ")! Base env is type " << base_env << LL_ENDL;

            LLEnvironment::DayInstance::ptr_t trans = std::make_shared<InjectedTransition>(std::static_pointer_cast<DayInjection>(shared_from_this()),
                mBaseDayInstance->getSky(), mBaseDayInstance->getWater(), nextbase, LLEnvironment::TRANSITION_DEFAULT);

            trans->animate();
            setBaseDayInstance(trans);
        }
    }

    void DayInjection::onParcelChange()
    {
        S32 parcel_id(INVALID_PARCEL_ID);
        LLParcel* parcel = LLViewerParcelMgr::instance().getAgentParcel();

        if (!parcel)
            return;

        parcel_id = parcel->getLocalID();

        testExperiencesOnParcel(parcel_id);
    }

    void DayInjection::checkExperience()
    {
        if ((!mDayExperience.isNull()) && (mSkyExperience != mDayExperience) && (mWaterExperience != mDayExperience))
        {   // There was a day experience but we've replaced it with a water and a sky experience.
            mDayExperience.setNull();
            mBaseDayInstance = LLEnvironment::instance().getSharedEnvironmentInstance();
        }
    }

    void DayInjection::animate()
    {

    }

    void InjectedTransition::animate()
    {
        mNextInstance->animate();

        if (!mInjection->isOverriddenSky())
        {
            mSky = mStartSky->buildClone();
            mBlenderSky = std::make_shared<LLSettingsBlenderTimeDelta>(mSky, mStartSky, mNextInstance->getSky(), mTransitionTime);
            mBlenderSky->setOnFinished(
                [this](LLSettingsBlender::ptr_t blender) {
                mBlenderSky.reset();

                if (!mBlenderSky && !mBlenderSky)
                    mInjection->setBaseDayInstance(mNextInstance);
                else
                    mInjection->mInjectedSky->setSource(mNextInstance->getSky());
            });
        }
        else
        {
            mSky = mInjection->getSky();
            mBlenderSky.reset();
        }

        if (!mInjection->isOverriddenWater())
        {
            mWater = mStartWater->buildClone();
            mBlenderWater = std::make_shared<LLSettingsBlenderTimeDelta>(mWater, mStartWater, mNextInstance->getWater(), mTransitionTime);
            mBlenderWater->setOnFinished(
                [this](LLSettingsBlender::ptr_t blender) {
                mBlenderWater.reset();

                if (!mBlenderSky && !mBlenderWater)
                    mInjection->setBaseDayInstance(mNextInstance);
                else
                    mInjection->mInjectedWater->setSource(mNextInstance->getWater());
            });
        }
        else
        {
            mWater = mInjection->getWater();
            mBlenderWater.reset();
        }

    }

}