/**
* @file llsettingsbase.h
* @author optional
* @brief A base class for asset based settings groups.
*
* $LicenseInfo:2011&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2017, 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$
*/

#ifndef LL_SETTINGS_BASE_H
#define LL_SETTINGS_BASE_H

#include <string>
#include <map>
#include <vector>
#include <boost/signals2.hpp>

#include "boost/unordered/unordered_flat_map.hpp"
#include "boost/unordered/unordered_flat_set.hpp"

#include "llsd.h"
#include "llsdutil.h"
#include "v2math.h"
#include "v3math.h"
#include "v4math.h"
#include "llquaternion.h"
#include "v4color.h"
#include "v3color.h"
#include "llunits.h"

#include "llinventorysettings.h"

#define PTR_NAMESPACE     std
#define SETTINGS_OVERRIDE override

class LLSettingsBase : 
    public PTR_NAMESPACE::enable_shared_from_this<LLSettingsBase>,
    private boost::noncopyable
{
    friend class LLEnvironment;
    friend class LLSettingsDay;

    friend std::ostream &operator <<(std::ostream& os, LLSettingsBase &settings);

protected:
    LOG_CLASS(LLSettingsBase);
public:
    typedef F64Seconds Seconds;
    typedef F64        BlendFactor;
    typedef F32        TrackPosition; // 32-bit as these are stored in LLSD as such
    static const TrackPosition INVALID_TRACKPOS;
    static const std::string DEFAULT_SETTINGS_NAME;

    static const std::string SETTING_ID;
    static const std::string SETTING_NAME;
    static const std::string SETTING_HASH;
    static const std::string SETTING_TYPE;
    static const std::string SETTING_ASSETID;
    static const std::string SETTING_FLAGS;

    static const U32 FLAG_NOCOPY;
    static const U32 FLAG_NOMOD;
    static const U32 FLAG_NOTRANS;
    static const U32 FLAG_NOSAVE;

    class DefaultParam
    {
    public:
        DefaultParam(S32 key, const LLSD& value) : mShaderKey(key), mDefaultValue(value) {}
        DefaultParam() : mShaderKey(-1) {}
        S32 getShaderKey() const { return mShaderKey; }
        const LLSD getDefaultValue() const { return mDefaultValue; }

    private:
        S32 mShaderKey;
        LLSD mDefaultValue;
    };
    // Contains settings' names (map key), related shader id-key and default
    // value for revert in case we need to reset shader (no need to search each time)
    typedef boost::unordered_flat_map<std::string, DefaultParam>  parammapping_t;

    typedef PTR_NAMESPACE::shared_ptr<LLSettingsBase> ptr_t;

    virtual ~LLSettingsBase() = default;

    //---------------------------------------------------------------------
    virtual std::string getSettingsType() const = 0;

    virtual LLSettingsType::type_e getSettingsTypeValue() const = 0;

    //---------------------------------------------------------------------
    // Settings status 
    inline bool hasSetting(const std::string &param) const { return mSettings.has(param); }
    virtual bool isDirty() const { return mDirty; }
    virtual bool isVeryDirty() const { return mReplaced; }
    inline void setDirtyFlag(bool dirty) { mDirty = dirty; clearAssetId(); }

    size_t getHash() const; // Hash will not include Name, ID or a previously stored Hash

    inline LLUUID getId() const
    {
        return getValue(SETTING_ID).asUUID();
    }

    inline std::string getName() const
    {
        return getValue(SETTING_NAME).asString();
    }

    inline void setName(std::string val)
    {
        setValue(SETTING_NAME, val);
    }

    inline LLUUID getAssetId() const
    {
        if (mSettings.has(SETTING_ASSETID))
            return mSettings[SETTING_ASSETID].asUUID();
        return LLUUID();
    }

    inline U32 getFlags() const
    {
        if (mSettings.has(SETTING_FLAGS))
            return static_cast<U32>(mSettings[SETTING_FLAGS].asInteger());
        return 0;
    }

    inline void setFlags(U32 value)
    {
        setLLSD(SETTING_FLAGS, LLSD::Integer(value));
    }

    inline bool getFlag(U32 flag) const
    {
        if (mSettings.has(SETTING_FLAGS))
            return ((U32)mSettings[SETTING_FLAGS].asInteger() & flag) == flag;
        return false;
    }

    inline void setFlag(U32 flag)
    {
        U32 flags((mSettings.has(SETTING_FLAGS)) ? (U32)mSettings[SETTING_FLAGS].asInteger() : 0);

        flags |= flag;

        if (flags)
            mSettings[SETTING_FLAGS] = LLSD::Integer(flags);
        else
            mSettings.erase(SETTING_FLAGS);
    }

    inline void clearFlag(U32 flag)
    {
        U32 flags((mSettings.has(SETTING_FLAGS)) ? (U32)mSettings[SETTING_FLAGS].asInteger() : 0);

        flags &= ~flag;

        if (flags)
            mSettings[SETTING_FLAGS] = LLSD::Integer(flags);
        else
            mSettings.erase(SETTING_FLAGS);
    }

    virtual void replaceSettings(LLSD settings)
    {
        mBlendedFactor = 0.0;
        setDirtyFlag(true);
        mReplaced = true;
        mSettings = settings;
    }

    virtual LLSD getSettings() const;

    //---------------------------------------------------------------------
    // 
    inline void setLLSD(const std::string &name, const LLSD &value)
    {
        mSettings[name] = value;
        mDirty = true;
        if (name != SETTING_ASSETID)
            clearAssetId();
    }

    inline void setValue(const std::string &name, const LLSD &value)
    {
        setLLSD(name, value);
    }

    inline LLSD getValue(const std::string &name, const LLSD &deflt = LLSD()) const
    {
        if (!mSettings.has(name))
            return deflt;
        return mSettings[name];
    }

    inline void setValue(const std::string &name, F32 v)
    {
        setLLSD(name, LLSD::Real(v));
    }

    inline void setValue(const std::string &name, const LLVector2 &value)
    {
        setValue(name, value.getValue());
    }

    inline void setValue(const std::string &name, const LLVector3 &value)
    {
        setValue(name, value.getValue());
    }

    inline void setValue(const std::string &name, const LLVector4 &value)
    {
        setValue(name, value.getValue());
    }

    inline void setValue(const std::string &name, const LLQuaternion &value)
    {
        setValue(name, value.getValue());
    }

    inline void setValue(const std::string &name, const LLColor3 &value)
    {
        setValue(name, value.getValue());
    }

    inline void setValue(const std::string &name, const LLColor4 &value)
    {
        setValue(name, value.getValue());
    }

    inline BlendFactor getBlendFactor() const
    {
        return mBlendedFactor;
    }

    // Note this method is marked const but may modify the settings object.
    // (note the internal const cast).  This is so that it may be called without
    // special consideration from getters.
    inline void     update() const
    {
        if ((!mDirty) && (!mReplaced))
            return;
        (const_cast<LLSettingsBase *>(this))->updateSettings();
    }

    virtual void    blend(const ptr_t &end, BlendFactor blendf) = 0;

    virtual bool    validate();

    virtual ptr_t   buildDerivedClone() const = 0;

    class Validator
    {
    public:
        static const U32 VALIDATION_PARTIAL;
        
        typedef boost::function<bool(LLSD &, U32)> verify_pr;

        Validator(std::string name, bool required, LLSD::Type type, verify_pr verify = verify_pr(), LLSD defval = LLSD())  :
            mName(name),
            mRequired(required),
            mType(type),
            mVerify(verify),
            mDefault(defval)
        {   }

        std::string getName() const { return mName; }
        bool        isRequired() const { return mRequired; }
        LLSD::Type  getType() const { return mType; }

        bool        verify(LLSD &data, U32 flags) const;

        // Some basic verifications
        static bool verifyColor(LLSD &value, U32 flags);
        static bool verifyVector(LLSD &value, U32 flags, S32 length);
        static bool verifyVectorMinMax(LLSD &value, U32 flags, LLSD minvals, LLSD maxvals);
        static bool verifyVectorNormalized(LLSD &value, U32 flags, S32 length);
        static bool verifyQuaternion(LLSD &value, U32 flags);
        static bool verifyQuaternionNormal(LLSD &value, U32 flags);
        static bool verifyFloatRange(LLSD &value, U32 flags, LLSD range);
        static bool verifyIntegerRange(LLSD &value, U32 flags, LLSD range);
        static bool verifyStringLength(LLSD &value, U32 flags, S32 length);

    private:
        std::string mName;
        bool        mRequired;
        LLSD::Type  mType;
        verify_pr   mVerify;
        LLSD        mDefault;
    };
    typedef std::vector<Validator> validation_list_t;

    static LLSD settingValidation(LLSD &settings, const validation_list_t &validations, bool partial = false);

    inline void setAssetId(LLUUID value)
    {   // note that this skips setLLSD
        mSettings[SETTING_ASSETID] = value;
    }

    inline void clearAssetId()
    {
        if (mSettings.has(SETTING_ASSETID))
            mSettings.erase(SETTING_ASSETID);
    }

    // Calculate any custom settings that may need to be cached.
    virtual void updateSettings() { mDirty = false; mReplaced = false; }
protected:

    LLSettingsBase();
    LLSettingsBase(const LLSD setting);

    typedef boost::unordered_flat_set<std::string>   stringset_t;
    
    // combining settings objects. Customize for specific setting types
    virtual void lerpSettings(const LLSettingsBase &other, BlendFactor mix);

    // combining settings maps where it can based on mix rate
    // @settings initial value (mix==0)
    // @other target value (mix==1)
    // @defaults list of default values for legacy fields and (re)setting shaders
    // @mix from 0 to 1, ratio or rate of transition from initial 'settings' to 'other'
    // return interpolated and combined LLSD map
    LLSD    interpolateSDMap(const LLSD &settings, const LLSD &other, const parammapping_t& defaults, BlendFactor mix) const;
    LLSD    interpolateSDValue(const std::string& name, const LLSD &value, const LLSD &other, const parammapping_t& defaults, BlendFactor mix, const stringset_t& slerps) const;

    /// when lerping between settings, some may require special handling.  
    /// Get a list of these key to be skipped by the default settings lerp.
    /// (handling should be performed in the override of lerpSettings.
    virtual const stringset_t& getSkipInterpolateKeys() const; 

    // A list of settings that represent quaternions and should be slerped 
    // rather than lerped.
    virtual const stringset_t& getSlerpKeys() const;

    virtual const validation_list_t& getValidationList() const = 0;

    // Apply any settings that need special handling. 
    virtual void applySpecial(void *, bool force = false) { };

    virtual const parammapping_t& getParameterMap() const;

    LLSD        mSettings;

    LLSD        cloneSettings() const;

    inline void setBlendFactor(BlendFactor blendfactor) 
    {
        mBlendedFactor = blendfactor;
    }

    void replaceWith(LLSettingsBase::ptr_t other)
    {
        replaceSettings(other->cloneSettings());
        setBlendFactor(other->getBlendFactor());
    }

private:
    bool        mDirty;
    bool        mReplaced; // super dirty!

    LLSD        combineSDMaps(const LLSD &first, const LLSD &other) const;

    BlendFactor mBlendedFactor;
};


class LLSettingsBlender : public PTR_NAMESPACE::enable_shared_from_this<LLSettingsBlender>
{
    LOG_CLASS(LLSettingsBlender);
public:
    typedef PTR_NAMESPACE::shared_ptr<LLSettingsBlender>      ptr_t;
    typedef boost::signals2::signal<void(const ptr_t )> finish_signal_t;
    typedef boost::signals2::connection     connection_t;

    LLSettingsBlender(const LLSettingsBase::ptr_t &target,
            const LLSettingsBase::ptr_t &initsetting, const LLSettingsBase::ptr_t &endsetting) :
        mOnFinished(),
        mTarget(target),
        mInitial(initsetting),
        mFinal(endsetting)
    {
        if (mInitial && mTarget)
            mTarget->replaceSettings(mInitial->getSettings());

        if (!mFinal)
            mFinal = mInitial;
    }

    virtual ~LLSettingsBlender() = default;

    virtual void reset( LLSettingsBase::ptr_t &initsetting, const LLSettingsBase::ptr_t &endsetting, const LLSettingsBase::TrackPosition&)
    {
        // note: the 'span' reset parameter is unused by the base class.
        if (!mInitial)
            LL_WARNS("BLENDER") << "Reseting blender with empty initial setting. Expect badness in the future." << LL_ENDL;

        mInitial = initsetting;
        mFinal = endsetting;

        if (!mFinal)
            mFinal = mInitial;

        if (mTarget)
            mTarget->replaceSettings(mInitial->getSettings());
    }

    LLSettingsBase::ptr_t getTarget() const
    {
        return mTarget;
    }

    LLSettingsBase::ptr_t getInitial() const
    {
        return mInitial;
    }

    LLSettingsBase::ptr_t getFinal() const
    {
        return mFinal;
    }

    connection_t            setOnFinished(const finish_signal_t::slot_type &onfinished)
    {
        return mOnFinished.connect(onfinished);
    }

    virtual void            update(const LLSettingsBase::BlendFactor& blendf);
    virtual bool            applyTimeDelta(const LLSettingsBase::Seconds& timedelta)
    {
        llassert(false);
        // your derived class needs to implement an override of this func
        return false;
    }

    virtual F64             setBlendFactor(const LLSettingsBase::BlendFactor& position);

    virtual void            switchTrack(S32 trackno, const LLSettingsBase::TrackPosition& position) { /*NoOp*/ }

protected:
    void                    triggerComplete();

    finish_signal_t         mOnFinished;

    LLSettingsBase::ptr_t   mTarget;
    LLSettingsBase::ptr_t   mInitial;
    LLSettingsBase::ptr_t   mFinal;
};

class LLSettingsBlenderTimeDelta : public LLSettingsBlender
{
    LOG_CLASS(LLSettingsBlenderTimeDelta);
public:
    static const LLSettingsBase::BlendFactor MIN_BLEND_DELTA;

    LLSettingsBlenderTimeDelta(const LLSettingsBase::ptr_t &target,
        const LLSettingsBase::ptr_t &initsetting, const LLSettingsBase::ptr_t &endsetting, const LLSettingsBase::Seconds& blend_span) :
        LLSettingsBlender(target, initsetting, endsetting),
        mBlendSpan(blend_span),
        mLastUpdate(0.0f),
        mTimeSpent(0.0f),
        mBlendFMinDelta(MIN_BLEND_DELTA),
        mLastBlendF(-1.0f)
    {
        mTimeStart = LLSettingsBase::Seconds(LLDate::now().secondsSinceEpoch());
        mLastUpdate = mTimeStart;
    }

    virtual ~LLSettingsBlenderTimeDelta() = default;

    virtual void reset(LLSettingsBase::ptr_t &initsetting, const LLSettingsBase::ptr_t &endsetting, const LLSettingsBase::TrackPosition& blend_span) SETTINGS_OVERRIDE
    {
        LLSettingsBlender::reset(initsetting, endsetting, blend_span);

        mBlendSpan  = blend_span;
        mTimeStart  = LLSettingsBase::Seconds(LLDate::now().secondsSinceEpoch());
        mLastUpdate = mTimeStart;
        mTimeSpent  = LLSettingsBase::Seconds(0.0f);
        mLastBlendF = LLSettingsBase::BlendFactor(-1.0f);
    }

    virtual bool applyTimeDelta(const LLSettingsBase::Seconds& timedelta) SETTINGS_OVERRIDE;

    inline void setTimeSpent(LLSettingsBase::Seconds time) { mTimeSpent = time; }
protected:
    LLSettingsBase::BlendFactor calculateBlend(const LLSettingsBase::TrackPosition& spanpos, const LLSettingsBase::TrackPosition& spanlen) const;

    LLSettingsBase::TrackPosition mBlendSpan;
    LLSettingsBase::Seconds mLastUpdate;
    LLSettingsBase::Seconds mTimeSpent;
    LLSettingsBase::Seconds mTimeStart;
    LLSettingsBase::BlendFactor mBlendFMinDelta;
    LLSettingsBase::BlendFactor mLastBlendF;
};


#endif
