/* Copyright (C) 2014 The glibmm Development Team
 *
 * 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; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 */

#include <glibmm/object.h>
#include <glibmm/refptr.h>
#include <glibmm/ustring.h>
#include <glibmm/value.h>

#include <optional>

_DEFS(glibmm,glib)
_PINCLUDE(glibmm/private/object_p.h)

namespace Glib
{

/** Bind two object properties.
 *
 * %Glib::Binding is the representation of a binding between a property on a
 * Glib::ObjectBase instance (or source) and another property on another Glib::ObjectBase
 * instance (or target). Whenever the source property changes, the same
 * value is applied to the target property; for instance, the following binding:
 *
 * @code
 * Glib::Binding::bind_property(object1->property_a(), object2->property_b());
 * @endcode
 *
 * will cause property_b() of @a object2 to be updated
 * every time the value of property_a() of @a object1 changes.
 *
 * It is possible to create a bidirectional binding between two properties
 * of two Glib::ObjectBase instances, so that if either property changes, the
 * other is updated as well, for instance:
 *
 * @code
 * Glib::Binding::bind_property(object1->property_a(), object2->property_b(),
 *   Glib::Binding::Flags::BIDIRECTIONAL);
 * @endcode
 *
 * will keep the two properties in sync.
 *
 * It is also possible to set a custom transformation function (in both
 * directions, in case of a bidirectional binding) to apply a custom
 * transformation from the source value to the target value before
 * applying it; for instance, the following binding:
 *
 * @code
 * bool celsius_to_fahrenheit(const double& celsius, double& fahrenheit);
 * bool fahrenheit_to_celsius(const double& fahrenheit, double& celsius);
 * Glib::Binding::bind_property(adjustment1->property_value(),
 *   adjustment2->property_value(), Glib::Binding::Flags::BIDIRECTIONAL,
 *   sigc::ptr_fun(celsius_to_fahrenheit), sigc::ptr_fun(fahrenheit_to_celsius));
 * @endcode
 *
 * will keep property_value() of the two adjustments in sync; the
 * celsius_to_fahrenheit() function will be called whenever
 * property_value() of @a adjustment1 changes and will transform the current value
 * of the property before applying it to property_value() of @a adjustment2.
 *
 * Vice versa, the fahrenheit_to_celsius() function will be called whenever
 * property_value() of @a adjustment2 changes, and will transform the
 * current value of the property before applying it to property_value()
 * of @a adjustment1.
 *
 * Note that Glib::Binding does not resolve cycles by itself; a cycle like
 *
 * @code
 *   object1->property_A() -> object2->property_B()
 *   object2->property_B() -> object3->property_C()
 *   object3->property_C() -> object1->property_A()
 * @endcode
 *
 * might lead to an infinite loop. The loop, in this particular case,
 * can be avoided if the objects emit the GObject::notify signal only
 * if the value has effectively been changed. A binding is implemented
 * using the GObject::notify signal, so it is susceptible to all the
 * various ways of blocking a signal emission, like Glib::SignalProxyNormal::emission_stop()
 * or g_signal_handler_block().
 *
 * The binding between the two properties is broken whenever either one of the
 * Glib::ObjectBase instances it refers to is deleted or when unbind() is called.
 * If a RefPtr<Binding> remains after the binding has been broken, get_source()
 * and get_target() return a <tt>nullptr</tt>, and dup_source() and dup_target()
 * return an empty RefPtr. It's not necessary to keep a RefPtr<Binding>
 * in order to keep the binding alive.
 *
 * @newin{2,44}
 */
class GLIBMM_API Binding : public Glib::Object
{
  _CLASS_GOBJECT(Binding, GBinding, G_BINDING, Glib::Object, GObject, , , GLIBMM_API)

public:
  _WRAP_ENUM(Flags, GBindingFlags, newin "2,44", decl_prefix GLIBMM_API)

  /** A slot to be called to transform values in a binding created by
   * bind_property_value().
   *
   * For instance:
   * @code
   *   bool on_transform_to(const GValue* from_value, GValue* to_value);
   * @endcode
   *
   * @return <tt>true</tt> if the transformation was successful, and <tt>false</tt> otherwise.
   */
  using SlotTransform = sigc::slot<bool(const GValue*, GValue*)>;

  /** A slot to be called to transform values in a binding created by
   * bind_property().
   *
   * For instance:
   * @code
   *   std::optional<int> on_transform_to(const Glib::ustring& from_string);
   * @endcode
   *
   * @return a value of type <tt>T_to</tt> if the transformation was successful,
   * and an empty optional with no value (i.e. <tt>std::nullopt</tt>) otherwise.
   */
  template <typename T_from, typename T_to>
  using SlotTypedTransform = sigc::slot<std::optional<T_to> (const T_from&)>;

  // GValue* or Glib::ValueBase& in SlotTransform?
  // Binding_transform_callback_common() is simpler and faster with GValue*.
  // No need to copy between GValue and Glib::ValueBase. ValueBase would only
  // be marginally better for users of bind_property_value(). Users would want
  // Value<T_source> and Value<T_target>, meaning that bind_property_value()
  // would have to be a template function. Most users would probably still
  // prefer bind_property(). bind_property_value() is public partly because
  // it's a good place to present documentation common to all the
  // bind_property() overloads.
  // See also https://gitlab.gnome.org/GNOME/glibmm/issues/61
  /** Creates a binding between @a source_property and @a target_property,
   * allowing you to set the transformation functions to be used by the binding.
   *
   * If @a flags contains Glib::Binding::Flags::BIDIRECTIONAL then the binding will be mutual:
   * if @a target_property changes then the @a source_property
   * will be updated as well. The @a transform_from function is only used in case
   * of bidirectional bindings, otherwise it will be ignored.
   *
   * The binding will automatically be removed when either the source or the
   * target instance is deleted. To remove the binding without affecting the
   * source and the target you can call unbind() on the returned Binding instance.
   *
   * A Glib::ObjectBase instance can have multiple bindings.
   *
   * If you supply transformation functions, it is usually easier to use one of the
   * bind_property() overloads, to avoid the use of GValue in the transformation functions.
   *
   * @param source_property The source property to bind.
   * @param target_property The target property to bind.
   * @param flags Flags to pass to Binding.
   * @param transform_to The transformation function from the source to the target,
   *        or an empty slot to use the default.
   * @param transform_from The transformation function from the target to the source,
   *        or an empty slot to use the default.
   * @return The Binding instance representing the binding between the two
   *         Glib::ObjectBase instances, or <tt>nullptr</tt> in case of error.
   *
   * @newin{2,44}
   */
  static Glib::RefPtr<Binding> bind_property_value(
    const PropertyProxy_Base& source_property,
    const PropertyProxy_Base& target_property,
    Flags flags = Flags::DEFAULT,
    const SlotTransform& transform_to = {},
    const SlotTransform& transform_from = {});

  /** Creates a binding between @a source_property and @a target_property.
   *
   * @param source_property The source property to bind.
   * @param target_property The target property to bind.
   * @param flags Flags to pass to Binding.
   * @return The Binding instance representing the binding between the two
   *         Glib::ObjectBase instances, or <tt>nullptr</tt> in case of error.
   *
   * @see bind_property_value()
   *
   * @newin{2,44}
   */
  static Glib::RefPtr<Binding> bind_property(
    const PropertyProxy_Base& source_property,
    const PropertyProxy_Base& target_property,
    Flags flags = Flags::DEFAULT)
  {
    return bind_property_value(source_property, target_property, flags);
  }

  /** Creates a binding between @a source_property and @a target_property,
   * allowing you to set a transformation function to be used by the binding.
   *
   * @param source_property The source property to bind.
   * @param target_property The target property to bind.
   * @param flags Flags to pass to Binding.
   * @param transform_to The transformation function from the source to the target,
   *        or an empty slot to use the default.
   * @return The Binding instance representing the binding between the two
   *         Glib::ObjectBase instances, or <tt>nullptr</tt> in case of error.
   *
   * @tparam T_source Type of the source property. Must be a type that can be
   *         stored in a Glib::Value<T_source> object.
   * @tparam T_target Type of the target property. Must be a type that can be
   *         stored in a Glib::Value<T_target> object.
   * @tparam T_functor_to Type of functor that translates from the source to the target.
   *         Must be convertible to SlotTypedTransform<T_source, T_target>.
   *
   * @see bind_property_value()
   *
   * @newin{2,44}
   */
  template <typename T_source, typename T_target, typename T_functor_to>
  static Glib::RefPtr<Binding> bind_property(
    const PropertyProxy<T_source>& source_property,
    const PropertyProxy<T_target>& target_property,
    Flags flags,
    const T_functor_to& transform_to)
  {
    SlotTypedTransform<T_source, T_target> slot_transform_to = transform_to;

    return bind_property_value(source_property, target_property, flags,
      slot_transform_to.empty() ? SlotTransform() : TransformProp<T_source, T_target>(slot_transform_to));
  }

  /** Creates a binding between @a source_property and @a target_property,
   * allowing you to set a transformation function to be used by the binding.
   *
   * @param source_property The source property to bind.
   * @param target_property The target property to bind.
   * @param flags Flags to pass to Binding.
   * @param transform_to The transformation function from the source to the target,
   *        or an empty slot to use the default.
   * @return The Binding instance representing the binding between the two
   *         Glib::ObjectBase instances, or <tt>nullptr</tt> in case of error.
   *
   * @tparam T_source Type of the source property. Must be a type that can be
   *         stored in a Glib::Value<T_source> object.
   * @tparam T_target Type of the target property. Must be a type that can be
   *         stored in a Glib::Value<T_target> object.
   * @tparam T_functor_to Type of functor that translates from the source to the target.
   *         Must be convertible to SlotTypedTransform<T_source, T_target>.
   *
   * @see bind_property_value()
   *
   * @newin{2,44}
   */
  template <typename T_source, typename T_target, typename T_functor_to>
  static Glib::RefPtr<Binding> bind_property(
    const PropertyProxy<T_source>& source_property,
    const PropertyProxy_WriteOnly<T_target>& target_property,
    Flags flags,
    const T_functor_to& transform_to)
  {
    SlotTypedTransform<T_source, T_target> slot_transform_to = transform_to;

    return bind_property_value(source_property, target_property, flags,
      slot_transform_to.empty() ? SlotTransform() : TransformProp<T_source, T_target>(slot_transform_to));
  }

  /** Creates a binding between @a source_property and @a target_property,
   * allowing you to set a transformation function to be used by the binding.
   *
   * @param source_property The source property to bind.
   * @param target_property The target property to bind.
   * @param flags Flags to pass to Binding.
   * @param transform_to The transformation function from the source to the target,
   *        or an empty slot to use the default.
   * @return The Binding instance representing the binding between the two
   *         Glib::ObjectBase instances, or <tt>nullptr</tt> in case of error.
   *
   * @tparam T_source Type of the source property. Must be a type that can be
   *         stored in a Glib::Value<T_source> object.
   * @tparam T_target Type of the target property. Must be a type that can be
   *         stored in a Glib::Value<T_target> object.
   * @tparam T_functor_to Type of functor that translates from the source to the target.
   *         Must be convertible to SlotTypedTransform<T_source, T_target>.
   *
   * @see bind_property_value()
   *
   * @newin{2,44}
   */
  template <typename T_source, typename T_target, typename T_functor_to>
  static Glib::RefPtr<Binding> bind_property(
    const PropertyProxy_ReadOnly<T_source>& source_property,
    const PropertyProxy<T_target>& target_property,
    Flags flags,
    const T_functor_to& transform_to)
  {
    SlotTypedTransform<T_source, T_target> slot_transform_to = transform_to;

    return bind_property_value(source_property, target_property, flags,
      slot_transform_to.empty() ? SlotTransform() : TransformProp<T_source, T_target>(slot_transform_to));
  }

  /** Creates a binding between @a source_property and @a target_property,
   * allowing you to set a transformation function to be used by the binding.
   *
   * @param source_property The source property to bind.
   * @param target_property The target property to bind.
   * @param flags Flags to pass to Binding.
   * @param transform_to The transformation function from the source to the target,
   *        or an empty slot to use the default.
   * @return The Binding instance representing the binding between the two
   *         Glib::ObjectBase instances, or <tt>nullptr</tt> in case of error.
   *
   * @tparam T_source Type of the source property. Must be a type that can be
   *         stored in a Glib::Value<T_source> object.
   * @tparam T_target Type of the target property. Must be a type that can be
   *         stored in a Glib::Value<T_target> object.
   * @tparam T_functor_to Type of functor that translates from the source to the target.
   *         Must be convertible to SlotTypedTransform<T_source, T_target>.
   *
   * @see bind_property_value()
   *
   * @newin{2,44}
   */
  template <typename T_source, typename T_target, typename T_functor_to>
  static Glib::RefPtr<Binding> bind_property(
    const PropertyProxy_ReadOnly<T_source>& source_property,
    const PropertyProxy_WriteOnly<T_target>& target_property,
    Flags flags,
    const T_functor_to& transform_to)
  {
    SlotTypedTransform<T_source, T_target> slot_transform_to = transform_to;

    return bind_property_value(source_property, target_property, flags,
      slot_transform_to.empty() ? SlotTransform() : TransformProp<T_source, T_target>(slot_transform_to));
  }

  /** Creates a binding between @a source_property and @a target_property,
   * allowing you to set the transformation functions to be used by the binding.
   *
   * @param source_property The source property to bind.
   * @param target_property The target property to bind.
   * @param flags Flags to pass to Binding.
   * @param transform_to The transformation function from the source to the target,
   *        or an empty slot to use the default.
   * @param transform_from The transformation function from the target to the source,
   *        or an empty slot to use the default.
   * @return The Binding instance representing the binding between the two
   *         Glib::ObjectBase instances, or <tt>nullptr</tt> in case of error.
   *
   * @tparam T_source Type of the source property. Must be a type that can be
   *         stored in a Glib::Value<T_source> object.
   * @tparam T_target Type of the target property. Must be a type that can be
   *         stored in a Glib::Value<T_target> object.
   * @tparam T_functor_to Type of functor that translates from the source to the target.
   *         Must be convertible to SlotTypedTransform<T_source, T_target>.
   * @tparam T_functor_from Type of functor that translates from the target to the source.
   *         Must be convertible to SlotTypedTransform<T_target, T_source>.
   *
   * @see bind_property_value()
   *
   * @newin{2,44}
   */
  template <typename T_source, typename T_target, typename T_functor_to, typename T_functor_from>
  static Glib::RefPtr<Binding> bind_property(
    const PropertyProxy<T_source>& source_property,
    const PropertyProxy<T_target>& target_property,
    Flags flags,
    const T_functor_to& transform_to,
    const T_functor_from& transform_from)
  {
    SlotTypedTransform<T_source, T_target> slot_transform_to = transform_to;
    SlotTypedTransform<T_target, T_source> slot_transform_from = transform_from;

    return bind_property_value(source_property, target_property, flags,
      slot_transform_to.empty() ? SlotTransform() : TransformProp<T_source, T_target>(slot_transform_to),
      slot_transform_from.empty() ? SlotTransform() : TransformProp<T_target, T_source>(slot_transform_from));
  }

#m4 _CONVERSION(`GObject*',`Glib::ObjectBase*',`Glib::wrap_auto($3)')
  _WRAP_METHOD(Glib::ObjectBase* get_source(), g_binding_get_source,
    newin "2,44", deprecated "Use dup_source() instead.")
  _WRAP_METHOD(const Glib::ObjectBase* get_source() const, g_binding_get_source,
    constversion, newin "2,44", deprecated "Use dup_source() instead.")

  // Don't use Glib::RefPtr<Glib::Object> Glib::wrap(GObject* object, bool take_copy).
  // The object may be an interface object, e.g. Gio::Action. Such an object can't be
  // cast to Glib::Object. https://gitlab.gnome.org/GNOME/glibmm/-/issues/93
#m4 _CONVERSION(`GObject*',`Glib::RefPtr<Glib::ObjectBase>',`Glib::make_refptr_for_instance<Glib::ObjectBase>(Glib::wrap_auto($3))')
  _WRAP_METHOD(Glib::RefPtr<Glib::ObjectBase> dup_source(), g_binding_dup_source, newin "2,70")
  _WRAP_METHOD(Glib::RefPtr<const Glib::ObjectBase> dup_source() const, g_binding_dup_source,
    constversion, newin "2,70")

  _WRAP_METHOD(Glib::ustring get_source_property() const, g_binding_get_source_property, newin "2,44")

  _WRAP_METHOD(Glib::ObjectBase* get_target(), g_binding_get_target,
    newin "2,44", deprecated "Use dup_target() instead.")
  _WRAP_METHOD(const Glib::ObjectBase* get_target() const, g_binding_get_target,
    constversion, newin "2,44", deprecated "Use dup_target() instead.")

  _WRAP_METHOD(Glib::RefPtr<Glib::ObjectBase> dup_target(), g_binding_dup_target, newin "2,70")
  _WRAP_METHOD(Glib::RefPtr<const Glib::ObjectBase> dup_target() const, g_binding_dup_target,
    constversion, newin "2,70")

  _WRAP_METHOD(Glib::ustring get_target_property() const, g_binding_get_target_property, newin "2,44")
  _WRAP_METHOD(Flags get_flags() const, g_binding_get_flags, newin "2,44")

  /** Explicitly releases the binding between the source and the target
   * property expressed by this %Binding instance.
   *
   * The binding is also released if either the source object or the target
   * object is deleted.
   *
   * @newin{2,44}
   */
  void unbind();
  _IGNORE(g_binding_unbind)

  _WRAP_PROPERTY("flags", Flags, newin "2,44")
  _WRAP_PROPERTY("source", Glib::RefPtr<Glib::ObjectBase>, newin "2,44")
  _WRAP_PROPERTY("source-property", Glib::ustring, newin "2,44")
  _WRAP_PROPERTY("target", Glib::RefPtr<Glib::ObjectBase>, newin "2,44")
  _WRAP_PROPERTY("target-property", Glib::ustring, newin "2,44")

private:
  // The functor TransformProp can be implicitly converted to a SlotTransform
  // and used in a call to bind_property_value().
  template <typename T_from, typename T_to>
  class TransformProp
  {
  public:
    explicit TransformProp(const SlotTypedTransform<T_from, T_to>& slot) : typed_transform(slot) {}

    // g++-10 with optimization level -O1 or higher warns about possible use of
    // an uninitialized variable when tests/glibmm_binding/main.cc is compiled.
    // I don't understand why. Don't optimize this function. /Kjell 2020-12-08
    //   sigc++-3.0/sigc++/functors/slot.h:226:21: warning: ‘<anonymous>’ may be
    //   used uninitialized in this function [-Wmaybe-uninitialized]
    //   226 |     return T_return();
#if __GNUC__ >= 10
  __attribute__ ((optimize(0)))
#endif
    bool operator()(const GValue* from_value, GValue* to_value)
    {
      Glib::Value<T_from> from_glib_value;
      from_glib_value.init(from_value);
      const auto to = typed_transform(from_glib_value.get());

      if (!to.has_value())
        return false;

      Glib::Value<T_to> to_glib_value;
      to_glib_value.init(to_value);
      to_glib_value.set(*to);
      g_value_copy(to_glib_value.gobj(), to_value);
      return true;
    }

  private:
    SlotTypedTransform<T_from, T_to> typed_transform;
  };
};

} // namespace Glib
