// Test custom implementation of an interface with properties.

#include <gtkmm.h>
#include <iostream>

class CustomAction :
  public Gio::Action, // has read-only properties
  public Gtk::Orientable, // has a read-write property
  public Glib::Object
{
protected:
  CustomAction(const Glib::ustring& name);

public:
  static Glib::RefPtr<CustomAction> create(const Glib::ustring& name);

  // A custom property.
  Glib::Property<Glib::ustring> custom_property;

private:
  void set_name(const Glib::ustring& name)
  {
    set_interface_property("name", name);
  }

public:
  // Trying (in vain) to set the value of a read-only property.
  // The object that owns the property must be able to set it.
  template <typename T>
  void set_interface_property(const Glib::ustring& name, const T& value);

  // This is meant to be used for getting the value of a write-only property.
  // It won't work, for the same reason that set_interface_property() doesn't work.
  template <typename T>
  T get_interface_property(const Glib::ustring& name) const;
};

CustomAction::CustomAction(const Glib::ustring& name)
: Glib::ObjectBase(typeid(CustomAction)),
  Glib::Object(),
  custom_property(*this, "custom-property", "Initial value.")
{
  // Does not compile. "name" is installed as a read-only property in GAction.
  // property_name() = name;

  // Does not work. "name" is installed as a read-only property in GAction.
  std::cout << "### Calling Glib::ObjectBase::set_property(\"name\", name)" << std::endl;
  set_property("name", name);
  std::cout << "### After set_property()" << std::endl;

  set_name(name);
  std::cout << "### After set_name()" << std::endl;
}

Glib::RefPtr<CustomAction> CustomAction::create(const Glib::ustring& name)
{
  return Glib::RefPtr<CustomAction>(new CustomAction(name));
}

template <typename T>
void CustomAction::set_interface_property(const Glib::ustring& name, const T& value)
{
  Glib::Value<T> gvalue;
  gvalue.init(Glib::Value<T>::value_type());

  gvalue.set(value);

  // This code has been copied from custom_set_property_callback() in property.cc
  // and changed.
  GType custom_type = G_OBJECT_TYPE(Glib::Object::gobj());

  Glib::Class::properties_type* props =
    static_cast<Glib::Class::properties_type*>(g_type_get_qdata(custom_type, Glib::Class::properties_quark));
  Glib::Class::properties_type::size_type props_size = 0;

  if (props)
    props_size = props->size();

  GObjectClass* obj_class = (GObjectClass*)g_type_class_peek(custom_type);

  GParamSpec* param_spec = g_object_class_find_property(obj_class, name.c_str());
  if (!param_spec)
  {
    std::cout << "CustomAction::set_interface_property(): No property named \"" << name << "\"" << std::endl;
    return;
  }

  // GParamSpec::param_id is private.
  // Unfortunately this does not work, because g_object_class_find_property()
  // returns a pointer to the original GParamSpec, installed with
  // g_object_interface_install_property(), not the one installed with
  // g_object_class_override_property().
  // param_spec->owner_type != custom_type and param_spec->param_id == 0.
  if (param_spec->owner_type == custom_type &&
     param_spec->param_id > 0 &&
     param_spec->param_id <= props_size)
  {
    // The property that has been overridden from an interface.
    g_value_copy(gvalue.gobj(), (*props)[param_spec->param_id - 1]);
    g_object_notify_by_pspec(Glib::Object::gobj(), param_spec);
  }
  else
  {
    // Either a custom property (Glib::Property<>) or a property in a base class.
    set_property(name, value);
  }
}

template <typename T>
T CustomAction::get_interface_property(const Glib::ustring& name) const
{
  Glib::Value<T> gvalue;
  gvalue.init(Glib::Value<T>::value_type());

  // This code has been copied from custom_get_property_callback() in property.cc
  // and changed.
  GType custom_type = G_OBJECT_TYPE(Glib::Object::gobj());

  Glib::Class::properties_type* props =
    static_cast<Glib::Class::properties_type*>(g_type_get_qdata(custom_type, Glib::Class::properties_quark));
  Glib::Class::properties_type::size_type props_size = 0;

  if (props)
    props_size = props->size();

  GObjectClass* obj_class = (GObjectClass*)g_type_class_peek(custom_type);

  GParamSpec* param_spec = g_object_class_find_property(obj_class, name.c_str());
  if (!param_spec)
  {
    std::cout << "CustomAction::get_interface_property(): No property named \"" << name << "\"" << std::endl;
    return;
  }

  // GParamSpec::param_id is private.
  // Unfortunately this does not work. See set_interface_property().
  if (param_spec->owner_type == custom_type && param_spec->param_id <= props_size)
  {
    // The property that has been overridden from an interface.
    g_value_copy((*props)[param_spec->param_id - 1], gvalue.gobj());
    return gvalue.get();
  }
  else
  {
    // Either a custom property (Glib::Property<>) or a property in a base class.
    T value;
    get_property(name, value);
    return value;
  }
}


int main(int, char**)
{
  Glib::RefPtr<Gtk::Application> app = Gtk::Application::create();

  Glib::RefPtr<CustomAction> custom_action1 = CustomAction::create("custom1");
  Glib::RefPtr<CustomAction> custom_action2 = CustomAction::create("custom2");
  Glib::RefPtr<Gio::SimpleAction> simple_action1 = Gio::SimpleAction::create_bool("simple1");
  Glib::RefPtr<Gio::SimpleAction> simple_action2 = Gio::SimpleAction::create_bool("simple2");

  std::cout << "\nCorrect values in parentheses.\n";
  std::cout << "custom_action1->get_name() = " << custom_action1->get_name() << " (custom1)\n";
  std::cout << "custom_action2->get_name() = " << custom_action2->get_name() << " (custom2)\n";
  std::cout << "simple_action1->get_name() = " << simple_action1->get_name() << " (simple1)\n";
  std::cout << "simple_action2->get_name() = " << simple_action2->get_name() << " (simple2)\n";

  std::cout << "custom_action1->property_name() = " << custom_action1->property_name() << " (custom1)\n";
  std::cout << "custom_action2->property_name() = " << custom_action2->property_name() << " (custom2)\n";
  std::cout << "simple_action1->property_name() = " << simple_action1->property_name() << " (simple1)\n";
  std::cout << "simple_action2->property_name() = " << simple_action2->property_name() << " (simple2)\n";

  custom_action1->set_orientation(Gtk::ORIENTATION_HORIZONTAL);
  custom_action2->set_orientation(Gtk::ORIENTATION_VERTICAL);
  std::cout << "custom_action1->get_orientation() = " << custom_action1->get_orientation() << " (0)\n";
  std::cout << "custom_action2->get_orientation() = " << custom_action2->get_orientation() << " (1)\n";

  custom_action1->property_orientation() = Gtk::ORIENTATION_VERTICAL;
  custom_action2->property_orientation() = Gtk::ORIENTATION_HORIZONTAL;
  std::cout << "custom_action1->property_orientation() = " << custom_action1->property_orientation() << " (1)\n";
  std::cout << "custom_action2->property_orientation() = " << custom_action2->property_orientation() << " (0)\n";

  custom_action1->custom_property.set_value("prop1");
  custom_action2->custom_property.set_value("prop2");
  std::cout << "custom_action1->custom_property.get_value() = " << custom_action1->custom_property.get_value() << " (prop1)\n";
  std::cout << "custom_action2->custom_property.get_value() = " << custom_action2->custom_property.get_value() << " (prop2)\n";

  return EXIT_SUCCESS;
}
