// -*- mode: c++ -*-
#ifndef __IMP_PROPERTY__90140360
#define __IMP_PROPERTY__90140360

#include <imp/util/Convert>
#include <imp/util/Clamped>
#include <imp/Prelude>
#include <imp/util/Optional>

namespace std {
  inline String to_string(String x)
  { return x; }
}

namespace imp {
  template <class T>
  class TypedProperty;

  class Property {
      String mName;
      String mDescription;
      int mFlags;

  public:
      /*! Don't save the property in config.cfg */
      static constexpr int noconfig  = 0x1;
      /*!  */
      static constexpr int from_param = 0x2;
      /*! Readonly if sv_cheats == 0 */
      static constexpr int cheat   = 0x4;
      /*! Don't display in console */
      static constexpr int hidden  = 0x8;
      /*! Synchronise the value with the server */
      static constexpr int network = 0x16;

      static std::vector<Property *> all();

      /*!
       * \brief Look for a property
       * \param name Property name
       * \return A Property instance or nullptr if not found
       */
      static Optional<Property&> find(StringView name);

      /*!
       * \brief Look for a typed property
       * \tparam T Property type
       * \param name Property name
       * \return A TypeProperty instance or nullptr if not found
       */
      template <class T>
      static Optional<TypedProperty<T>&> find(StringView name)
      {
          return find(name).if_value([](auto p)
                                     {
                                         auto p2 = dynamic_cast<TypedProperty<T> *>(p);
                                         return Optional<TypedProperty<T>&> { p2 ? *p2 : nullopt };
                                     });
      }

      /*!
       * Get a list of all properties that whose name starts with a prefix
       * @param prefix Prefix to search for
       * @return a list of non-hidden properties
       */
      static Vector<Property *> partial(StringView prefix);

      Property(StringView name, StringView description, int flags = 0);

      virtual ~Property();

      StringView name() const
      {
          return mName;
      }

      StringView description() const
      {
          return mDescription;
      }

      void set_flag(int flag)
      { mFlags |= flag; }

      int flags() const
      { return mFlags; }

      bool is_noconfig() const
      { return (mFlags & noconfig) != 0; }

      bool is_from_param() const
      { return (mFlags & from_param) != 0; }

      bool is_cheat() const
      { return (mFlags & cheat) != 0; }

      bool is_hidden() const
      { return (mFlags & hidden) != 0; }

      bool is_network() const
      { return (mFlags & network) != 0; }

      virtual String string() const = 0;

      virtual void set_string(StringView str) = 0;

      virtual String default_string() const = 0;

      virtual void reset_default() = 0;

      void update();
  };

  template <class T>
  class TypedProperty : public Property {
  public:
      using setter_func = void (*)(const TypedProperty &property, T oldValue, T& value);

  protected:
      setter_func mSetter {};

      const T mDefault;

      T mValue;

  public:
      TypedProperty(StringView name, StringView description):
          Property(name, description, 0),
          mDefault(T()),
          mValue(T()) {}

      TypedProperty(StringView name, StringView description, T def, int flags = 0, setter_func setter = nullptr):
          Property(name, description, flags),
          mSetter(setter),
          mDefault(def),
          mValue(def) {}

      const T& operator*() const
      { return mValue; }

      const T* operator->() const
      { return &mValue; }

      const T& value() const
      { return mValue; }

      operator T&()
      { return mValue; }

      operator const T&() const
      { return mValue; }

      template <class U>
      TypedProperty& operator=(U value)
      {
          auto old = mValue;
          mValue = value;
          mSetter ? mSetter(*this, old, mValue) : (void) 0;
          update();
          return *this;
      }

      String string() const override
      {
          return std::to_string(mValue);
      }

      String default_string() const override
      {
          return std::to_string(mDefault);
      }

      void set_string(StringView strValue) override
      {
          *this = from_string<T>(strValue);
      }

      void reset_default() override
      {
          mValue = mDefault;
          update();
      }
  };

  using IntProperty = TypedProperty<int>;
  using FloatProperty = TypedProperty<float>;
  using BoolProperty = TypedProperty<bool>;
  using StringProperty = TypedProperty<String>;
}

#define __IMP_TYPEDPROPERTY_ARITHMETIC_OPERATORS(Op) \
template <class T, class U> constexpr T operator Op(const imp::TypedProperty<T> &l, const U &r) { return *l Op r; } \
template <class T, class U> constexpr T operator Op(const T &l, const imp::TypedProperty<U> &r) { return l Op *r; }

__IMP_TYPEDPROPERTY_ARITHMETIC_OPERATORS(+)
__IMP_TYPEDPROPERTY_ARITHMETIC_OPERATORS(-)
__IMP_TYPEDPROPERTY_ARITHMETIC_OPERATORS(*)
__IMP_TYPEDPROPERTY_ARITHMETIC_OPERATORS(/)
__IMP_TYPEDPROPERTY_ARITHMETIC_OPERATORS(%)

#undef __IMP_TYPEDPROPERTY_ARITHMETIC_OPERATORS

#define __IMP_TYPEDPROPERTY_COMPARISON_OPERATORS(Op) \
template <class T, class U> constexpr bool operator Op(const imp::TypedProperty<T> &l, const imp::TypedProperty<U> &r) { return *l Op *r; } \
template <class T, class U> constexpr bool operator Op(const imp::TypedProperty<T> &l, const U &r) { return *l Op r; } \
template <class T, class U> constexpr bool operator Op(const T &l, const imp::TypedProperty<U> &r) { return l Op *r; }

__IMP_TYPEDPROPERTY_COMPARISON_OPERATORS(==)
__IMP_TYPEDPROPERTY_COMPARISON_OPERATORS(!=)
__IMP_TYPEDPROPERTY_COMPARISON_OPERATORS(<)
__IMP_TYPEDPROPERTY_COMPARISON_OPERATORS(>)
__IMP_TYPEDPROPERTY_COMPARISON_OPERATORS(<=)
__IMP_TYPEDPROPERTY_COMPARISON_OPERATORS(>=)

#undef __IMP_TYPEDPROPERTY_COMPARISON_OPERATORS

#endif //__IMP_PROPERTY__90140360
