/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/

#ifndef CRYINCLUDE_EDITOR_UTILS_REFLECTEDVAR_H
#define CRYINCLUDE_EDITOR_UTILS_REFLECTEDVAR_H
#pragma once

#include <AzCore/Serialization/SerializeContext.h>
#include <algorithm>
#include <limits>
#include "Util/VariablePropertyType.h"
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Vector4.h>


//Base class for generic reflected variables
class CReflectedVar
{
public:
    AZ_RTTI(CReflectedVar, "{9CF461B5-4093-4F7E-9A28-75531F0D046C}")

    CReflectedVar() = default;
    CReflectedVar(const AZStd::string& name)
        : m_varName(name){}
    virtual ~CReflectedVar(){}

    AZStd::string m_varName;
    AZStd::string m_description;
};


// Reflected container of reflected values.  Also holds ePropertyTable data
class CPropertyContainer
    : public CReflectedVar
{
public:
    AZ_RTTI(CPropertyContainer, "{99500790-241A-4274-BAD8-C4510E869FC6}", CReflectedVar)
    CPropertyContainer(const AZStd::string& name)
        : CReflectedVar(name) {}
    CPropertyContainer() = default;

    AZStd::string varName() const { return m_varName; }
    AZStd::string description() const { return m_description; }

    void AddProperty(CReflectedVar* property);

    void Clear();

    //If we're an unnamed container, just show our children in flat list.  Otherwise show the container name with children underneath
    AZ::u32 GetVisibility() const
    {
        return m_varName.empty() ? AZ_CRC("PropertyVisibility_ShowChildrenOnly", 0xef428f20) : AZ_CRC("PropertyVisibility_Show", 0xa43c82dd);
    }

    void SetAutoExpand(bool autoExpand) { m_autoExpand = autoExpand; }
    bool AutoExpand() const { return m_autoExpand; }

    AZStd::vector<CReflectedVar*> GetProperties() const { return m_properties; }

    void SetValueText(const AZStd::string& valueText) { m_valueText = valueText; }

    friend class ReflectedVarInit;

private:
    AZStd::vector<CReflectedVar*> m_properties;
    bool m_autoExpand = false;
    AZStd::string m_valueText;
};


template<class T>
class CReflectedVarAny
    : public CReflectedVar
{
public:
    AZ_RTTI((CReflectedVarAny<T>, "{EE8293C3-9B1E-470B-9922-2CBB8DA13D78}", T), CReflectedVar)

    CReflectedVarAny(const AZStd::string& name, const T& val = T())
        : CReflectedVar(name)
        , m_value(val) {}
    CReflectedVarAny() = default;

    AZStd::string varName() const { return m_varName; }
    AZStd::string description() const { return m_description; }

    static void reflect(AZ::SerializeContext* serializeContext);

    T m_value;
};

// Class to hold values that have min/max
// T = data type held in this variable
// R = data type of the range
template<class T, class R>
class CReflectedVarRanged
    : public CReflectedVar
{
public:
    AZ_RTTI((CReflectedVarRanged, "{6AB4EC29-E17B-4B3B-A153-BFDAA48B8CF8}", T, R), CReflectedVar)

    CReflectedVarRanged(const AZStd::string& name, const T& val = T())
        : CReflectedVar(name)
        , m_value(val)
        , m_minVal(std::numeric_limits<R>::lowest())
        , m_maxVal(std::numeric_limits<R>::max())
        , m_stepSize(1)
        , m_softMinVal(std::numeric_limits<R>::lowest())
        , m_softMaxVal(std::numeric_limits<R>::max())
    {}
    CReflectedVarRanged()
        : CReflectedVarRanged(AZStd::string(), T()){}

    AZStd::string varName() const { return m_varName; }
    AZStd::string description() const { return m_description; }

    R minValue() const { return m_minVal; }
    R maxValue() const { return m_maxVal; }
    R stepSize() const { return m_stepSize; }
    R softMinVal() const { return m_softMinVal; }
    R softMaxVal() const { return m_softMaxVal; }

    static void reflect(AZ::SerializeContext* serializeContext);

    T m_value;
    R m_minVal;
    R m_maxVal;
    R m_stepSize;
    R m_softMinVal;
    R m_softMaxVal;
};

//name some commonly-used variable types
template <class T>
using  CReflectedVarNumeric = CReflectedVarRanged<T, T>;

//ePropertyFloat
using CReflectedVarFloat = CReflectedVarNumeric<float>;

//ePropertyInt
using CReflectedVarInt = CReflectedVarNumeric<int>;

//ePropertyString
using CReflectedVarString = CReflectedVarAny<AZStd::string>;

//ePropertyBool
using CReflectedVarBool = CReflectedVarAny<bool>;

//ePropertyVector2
using CReflectedVarVector2 = CReflectedVarRanged<AZ::Vector2, float>;

//ePropertyVector
using CReflectedVarVector3 = CReflectedVarRanged<AZ::Vector3, float>;

//ePropertyVector4
using CReflectedVarVector4 = CReflectedVarRanged<AZ::Vector4, float>;

// Class for holding enumerated values, ePropertySelection
// Keeps a key-value pair values (int, string, float, etc) and names corresponding to each value
// The names are displayed to user when editing, the values are used by underlying code.

template<class T>
class CReflectedVarEnum
    : public CReflectedVar
{
public:
    AZ_RTTI((CReflectedVarEnum<T>, "{40AE7D74-7E3A-41A9-8F71-2BBC3067118B}", T), CReflectedVar)

    CReflectedVarEnum(const AZStd::string& name)
        : CReflectedVar(name) {}
    CReflectedVarEnum() = default;

    void setEnums(const AZStd::vector<AZStd::pair<T, AZStd::string> >& enums)
    {
        m_enums = enums;
        if (m_enums.size() > 0)
        {
            m_value = m_enums.at(0).first;
            m_selectedEnumName = m_enums.at(0).second;
        }
        else
        {
            m_value = T();
            m_selectedEnumName.clear();
        }
    }

    void addEnum(const T& value, const AZStd::string& name)
    {
        m_enums.push_back(AZStd::pair<T, AZStd::string>(value, name));
        if (m_enums.size() == 1)
        {
            m_selectedEnumName = name;
            m_value = value;
        }
    }

    void setEnumValue(const T& value)
    {
        auto it = std::find_if(m_enums.cbegin(), m_enums.cend(), [value](const AZStd::pair<T, AZStd::string>& item) -> bool { return item.first == value; });
        if (it != m_enums.end())
        {
            m_value = it->first;
            m_selectedEnumName = it->second;
        }
    }

    void setEnumByName(const AZStd::string& name)
    {
        auto it = std::find_if(m_enums.cbegin(), m_enums.cend(), [name](const AZStd::pair<T, AZStd::string>& item) -> bool { return item.second == name; });
        if (it != m_enums.end())
        {
            m_value = it->first;
            m_selectedEnumName = it->second;
        }
    }

    void OnEnumChanged()
    {
        setEnumByName(m_selectedEnumName);
    }

    AZStd::vector < AZStd::string> GetEnums() const
    {
        AZStd::vector < AZStd::string> returnVal;
        for (const auto& i : m_enums)
        {
            returnVal.push_back(i.second);
        }

        return returnVal;
    }


    AZStd::string varName() const { return m_varName; }
    AZStd::string description() const { return m_description; }

    static void reflect(AZ::SerializeContext* serializeContext);

    T m_value;
    AZStd::string m_selectedEnumName;
    AZStd::vector<AZStd::pair<T, AZStd::string> > m_enums;
};

//Class to hold ePropertyColor (IVariable::DT_COLOR)
class CReflectedVarColor
    : public CReflectedVar
{
public:
    AZ_RTTI(CReflectedVarColor, "{CC69E773-B4FA-4B6D-8A46-0B580097B6D2}", CReflectedVar)

    CReflectedVarColor(const AZStd::string& name, AZ::Vector3 color = AZ::Vector3())
        : CReflectedVar(name)
        , m_color(color) {}
    CReflectedVarColor() {}

    AZStd::string varName() const { return m_varName; }
    AZStd::string description() const { return m_description; }

    AZ::Vector3 m_color;
};

//Class to hold ePropertyAnimation (IVariable::DT_ANIMATION )
class CReflectedVarAnimation
    : public CReflectedVar
{
public:
    AZ_RTTI(CReflectedVarAnimation, "{635D982E-23EC-463F-8F33-4FC2C19D5673}", CReflectedVar)

    CReflectedVarAnimation(const AZStd::string& name)
        : CReflectedVar(name)
        , m_entityID(0)
    {}
    CReflectedVarAnimation()
        : m_entityID(0){}

    AZStd::string varName() const { return m_varName; }
    AZStd::string description() const { return m_description; }

    AZStd::string m_animation;
    AZ::EntityId m_entityID;
};

//Class to hold:
// ePropertyTexture          (IVariable::DT_TEXTURE)
// ePropertyMaterial         (IVariable::DT_MATERIAL)
// ePropertyModel            (IVariable::DT_OBJECT)
// ePropertyGeomCache        (IVariable::DT_GEOM_CACHE)
// ePropertyAudioTrigger        (IVariable::DT_AUDIO_TRIGGER)
// ePropertyAudioSwitch         (IVariable::DT_AUDIO_SWITCH )
// ePropertyAudioSwitchState    (IVariable::DT_AUDIO_SWITCH_STATE)
// ePropertyAudioRTPC           (IVariable::DT_AUDIO_RTPC)
// ePropertyAudioEnvironment    (IVariable::DT_AUDIO_ENVIRONMENT)
// ePropertyAudioPreloadRequest (IVariable::DT_AUDIO_PRELOAD_REQUEST)

class CReflectedVarResource
    : public CReflectedVar
{
public:
    AZ_RTTI(CReflectedVarResource, "{162864C2-0C3E-4B6A-84D3-BBAD975B4FD2}", CReflectedVar)

    CReflectedVarResource(const AZStd::string& name)
        : CReflectedVar(name)
        , m_propertyType(ePropertyInvalid)
    {}
    CReflectedVarResource()
        : m_propertyType(ePropertyInvalid){}

    AZStd::string varName() const { return m_varName; }
    AZStd::string description() const { return m_description; }

    AZStd::string m_path;
    PropertyType m_propertyType;
};

//Class to hold ePropertyUser (IVariable::DT_USERITEMCB)
class CReflectedVarUser 
    : public CReflectedVar
{
public:
    AZ_RTTI(CReflectedVarUser, "{A901DA91-3893-4848-9AE8-62C0ED074970}", CReflectedVar)

        CReflectedVarUser(const AZStd::string &name)
        : CReflectedVar(name)
        , m_enableEdit(false)
        , m_useTree(false)
    {}
    CReflectedVarUser() : m_enableEdit(false), m_useTree(false) {}

    AZStd::string varName() const { return m_varName; }

    AZStd::string m_value;

    bool m_enableEdit;
    bool m_useTree;
    AZStd::string m_dialogTitle;
    AZStd::string m_treeSeparator;
    AZStd::vector<AZStd::string> m_itemNames;
    AZStd::vector<AZStd::string> m_itemDescriptions;
};

//Class to hold ePropertyAnimation (IVariable::DT_ANIMATION )
class CReflectedVarSpline
    : public CReflectedVar
{
public:
    AZ_RTTI(CReflectedVarSpline, "{9A928683-7C84-48BF-8A2E-F7BEC423EE4E}", CReflectedVar)

        CReflectedVarSpline(PropertyType propertyType, const AZStd::string &name)
        : CReflectedVar(name)
        , m_spline(0)
        , m_propertyType(propertyType)
    {}

    CReflectedVarSpline()
        : m_spline(0)
        , m_propertyType(ePropertyInvalid) 
    {}

    AZStd::string varName() const { return m_varName; }
    AZ::u32 handler();

    uint64_t m_spline;
    PropertyType m_propertyType;
};

//Class to wrap all the many properties that can be represented by a string and edited via a popup
class CReflectedVarGenericProperty
    : public CReflectedVar
{
public:
    AZ_RTTI(CReflectedVarGenericProperty, "{C4A34C95-3D71-40CE-86D2-DDE314B33CC5}", CReflectedVar)

    CReflectedVarGenericProperty(PropertyType pType, const AZStd::string& name = AZStd::string(), const AZStd::string& val = AZStd::string())
        : CReflectedVar(name)
        , m_propertyType(pType)
        , m_value(val) {}
    CReflectedVarGenericProperty()
        : CReflectedVar()
        , m_propertyType(ePropertyInvalid){}

    AZStd::string varName() const { return m_varName; }
    AZStd::string description() const { return m_description; }
    PropertyType propertyType() const { return m_propertyType; }

    AZ::u32 handler();

    static void reflect(AZ::SerializeContext* serializeContext);

    PropertyType m_propertyType;
    AZStd::string m_value;
};


class ReflectedVarInit
{
public:
    static void setupReflection(AZ::SerializeContext* serializeContext);
private:
    static bool s_reflectionDone;
};

//Class to hold ePropertyMotion (IVariable::DT_MOTION )
class CReflectedVarMotion
    : public CReflectedVar
{
public:
    AZ_RTTI(CReflectedVarMotion, "{66397EFB-620A-40B8-8C66-D6AECF690DF5}", CReflectedVar)

    CReflectedVarMotion(const AZStd::string& name)
        : CReflectedVar(name)
        , m_assetId(0) {}

    CReflectedVarMotion()
        : m_assetId(0) {}

    AZStd::string varName() const { return m_varName; }
    AZStd::string description() const { return m_description; }

    AZStd::string m_motion;
    AZ::Data::AssetId m_assetId;
};

// Class to hold ePropertyAsset (IVariable::DT_ASSET)
class CReflectedVarAsset
    : public CReflectedVar
{
public:
    AZ_RTTI(CReflectedVarAsset, "{38F49605-F805-4756-BC0A-DCB5612ED122}", CReflectedVar)

        CReflectedVarAsset(const AZStd::string& name)
        : CReflectedVar(name)
        , m_assetId()
        , m_assetTypeName("")
    {}

    CReflectedVarAsset()
        : m_assetId()
        , m_assetTypeName("")
    {}

    AZStd::string varName() const { return m_varName; }
    AZStd::string description() const { return m_description; }

    AZ::Data::AssetId m_assetId;
    AZStd::string m_assetTypeName;
};

#endif // CRYINCLUDE_EDITOR_UTILS_REFLECTEDVAR_H