/* Copyright (C) 2003-2014 The gtkmm 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 <https://www.gnu.org/licenses/>.
 */

_CONFIGINCLUDE(gtkmmconfig.h)

#include <vector>
#include <type_traits>
#include <utility>
#include <giomm/application.h>
#include <giomm/menumodel.h>
#include <giomm/menu.h>
#include <gtkmm/window.h>

_DEFS(gtkmm,gtk)
_PINCLUDE(giomm/private/application_p.h)

#m4 _PUSH(SECTION_CC_PRE_INCLUDES)
// Needed before gtkmm/private/application_p.h is included.
typedef struct _GtkWindow GtkWindow;
#m4 _POP()

namespace Gtk
{
/** A high-level API for writing applications.
 *
 * The %Gtk::Application class handles many aspects of a GTK application
 * in a convenient fashion, without enforcing a one-size-fits-all model.
 *
 * Currently, %Gtk::Application handles GTK and gtkmm initialization, application
 * uniqueness, session management, provides some basic scriptability and
 * desktop shell integration by exporting actions and menus, and manages a
 * list of toplevel windows whose life-cycle is automatically tied to the
 * life-cycle of your application.
 *
 * While %Gtk::Application works fine with plain Gtk::Window%s, it is recommended
 * to use it together with Gtk::ApplicationWindow.
 *
 * ## Initialization
 *
 * A typical %Gtk::Application will create a window in its
 * Gio::Application::signal_activate(), Gio::Application::signal_open()
 * or Gio::Application::signal_command_line() handlers. Note that all
 * of these signals may be emitted multiple times, so handlers must
 * be careful to take existing windows into account.
 *
 * ## Automatic resources
 *
 * %Gtk::Application will automatically load menus from the `GtkBuilder`
 * resource located at "gtk/menus.ui", relative to the application's
 * resource base path (see Gio::Application::set_resource_base_path()).
 * The menu with the ID "menubar" is taken as the application's
 * menubar. Additional menus (most interesting submenus) can be named
 * and accessed via get_menu_by_id() which allows for
 * dynamic population of a part of the menu structure.
 *
 * Note that automatic resource loading uses the resource base path
 * that is set at construction time and will not work if the resource
 * base path is changed at a later time.
 *
 * It is also possible to provide the menubar manually using set_menubar().
 * Use the base Gio::ActionMap interface to add actions, to respond to the user
 * selecting these menu items.
 *
 * %Gtk::Application will also automatically setup an icon search path for
 * the default icon theme by appending "icons" to the resource base
 * path. This allows your application to easily store its icons as
 * resources. See Gtk::IconTheme::add_resource_path() for more information.
 *
 * If there is a resource located at `gtk/help-overlay.ui` which
 * defines a Gtk::ShortcutsWindow with ID `help_overlay` then
 * %Gtk::Application associates an instance of this shortcuts window with
 * each Gtk::ApplicationWindow and sets up the keyboard accelerator
 * <kbd>Control</kbd>+<kbd>?</kbd> to open it. To create a menu item that
 * displays the shortcuts window, associate the item with the action
 * `win.show-help-overlay`.
 *
 * %Gtk::Application will also automatically set the application id as the
 * default window icon. Use Gtk::Window::set_default_icon_name() or
 * Gtk::Window::property_icon_name() to override that behavior.
 *
 * ## State saving
 *
 * %Gtk::Application registers with a session manager if possible and
 * offers various functionality related to the session life-cycle,
 * such as state saving.
 *
 * State-saving functionality can be enabled by setting
 * property_support_save() to true.
 *
 * In order to save and restore per-window state, applications must
 * connect to signal_restore_window() and
 * handle Gtk::ApplicationWindow::signal_save_state(). There
 * are also signal_restore_state() and
 * signal_save_state() signals, which can be used
 * for global state that is not connected to any window.
 *
 * %Gtk::Application automatically saves state before app shutdown, and by
 * default periodically auto-saves app state (as configured by
 * property_autosave_interval()). Applications can
 * also call save() themselves at opportune times.
 *
 * ## Inhibiting
 *
 * An application can block various ways to end the session with
 * the inhibit() method. Typical use cases for
 * this kind of inhibiting are long-running, uninterruptible operations,
 * such as burning a CD or performing a disk backup. The session
 * manager may not honor the inhibitor, but it can be expected to
 * inform the user about the negative consequences of ending the
 * session while inhibitors are present.
 *
 * ## See Also
 *
 * - [Using GtkApplication](https://developer.gnome.org/documentation/tutorials/application.html)
 * - [Gtkmm tutorial: Building applications](https://gnome.pages.gitlab.gnome.org/gtkmm-documentation/chapter-building-applications.html)
 *
 * @note
 * Most widgets can't safely be created before the application has been registered
 * (Gio::Application::register_application() called) or activated
 * (Gio::Application::signal_activate() emitted).
 * They can't safely be deleted after run() or make_window_and_run() returns.
 *
 * @newin{3,4}
 */
class GTKMM_API Application
  : public Gio::Application
{
protected:
  _CLASS_GOBJECT(Application, GtkApplication, GTK_APPLICATION, Gio::Application, GApplication, , , GTKMM_API)

protected:
  _CUSTOM_CTOR_CAST()

  /** Creates a new Application instance.
   *
   * This initializes gtkmm (and the GObject type system) for you.
   * The global locale is set as specified by Glib::set_init_to_users_preferred_locale().
   *
   * If non-empty, the application ID must be valid. See Gio::Application::id_is_valid().
   *
   * If no application ID is given then some features (most notably application
   * uniqueness) will be disabled. A null application ID is only allowed with
   * gtkmm 3.6 or later.
   *
   * @param application_id A valid application ID.
   * @param flags The application flags.
   *
   * @newin{3,4}
   */
  explicit Application(const Glib::ustring& application_id = {}, Gio::Application::Flags flags = Gio::Application::Flags::NONE);
  _IGNORE(gtk_application_new)
  _IGNORE(gtk_application_window_new)

public:
  _WRAP_ENUM(InhibitFlags, GtkApplicationInhibitFlags, decl_prefix GTKMM_API)

  /** Choose which %SearchEntry to use.
   *
   * There are two classes to choose from when a pointer to a GtkSearchEntry
   * object comes from a gtk function (for instance in GtkBuilder), and a C++ wrapper
   * shall be created: Gtk::SearchEntry or Gtk::SearchEntry2.
   *
   * To have any effect, this function must be called before any %Gtk::Application
   * instance is created, typically early in the main() function.
   * Not calling this function has the same effect as calling it with
   * @a search_entry2 = <tt>false</tt>.
   *
   * This nuisance has become necessary due to a bug in gtkmm4.
   * The bug can't be fixed by correcting %SearchEntry. It would break both
   * ABI and API, and some working applications.
   *
   * @param search_entry2 If <tt>true</tt>, a GtkSearchEntry will be wrapped
   *        in a Gtk::SearchEntry2. If <tt>false</tt>, a GtkSearchEntry will be
   *        wrapped in a Gtk::SearchEntry.
   *
   * @newin{4,14}
   */
  static void wrap_in_search_entry2(bool search_entry2 = true);

  /** Creates a new Application instance.
   *
   * This initializes gtkmm (and the GObject type system) for you.
   * The global locale is set as specified by Glib::set_init_to_users_preferred_locale().
   *
   * If non-empty, the application ID must be valid. See Gio::Application::id_is_valid().
   *
   * If no application ID is given then some features (most notably application
   * uniqueness) will be disabled. A null application ID is only allowed with
   * gtkmm 3.6 or later.
   *
   * @param application_id A valid application ID.
   * @param flags The application flags.
   *
   * @newin{3,4}
   */
  static Glib::RefPtr<Application> create(const Glib::ustring& application_id = {}, Gio::Application::Flags flags = Gio::Application::Flags::NONE);

#m4 _CONVERSION(`GList*',`std::vector<Window*>',`Glib::ListHandler<Window*>::list_to_vector($3, Glib::OWNERSHIP_NONE)')
  _WRAP_METHOD(std::vector<Window*> get_windows(), gtk_application_get_windows)

#m4 _CONVERSION(`GList*',`std::vector<const Window*>',`Glib::ListHandler<const Window*>::list_to_vector($3, Glib::OWNERSHIP_NONE)')
  _WRAP_METHOD(std::vector<const Window*> get_windows() const, gtk_application_get_windows)

  // Concerning the note on quit(), see https://bugzilla.gnome.org/show_bug.cgi?id=731126
  // Concerning the return from run() on hidden windows, see
  // https://bugzilla.gnome.org/show_bug.cgi?id=639931 and Gtk::Widget_Class::hide_callback().
  /** Adds a window to the Gtk::Application.
   *
   * If all the windows managed by Gtk::Application are closed (hidden) or
   * removed from the application then the call to run() will return,
   * unless Gio::Application::hold() has been called.
   *
   * This call is equivalent to calling Gtk::Window::set_application().
   *
   * Normally, the connection between the application and the window
   * will remain until the window is closed (hidden) or destroyed, but you can explicitly
   * remove it with remove_window().
   *
   * @note If you call Gio::Application::quit() while a window is connected to
   * the application, and then return from main() without removing the window
   * from the application, the application's destructor will not be called.
   *
   * @newin{3,4}
   *
   * @param window A toplevel window to add to the application.
   */
  _WRAP_METHOD(void add_window(Window& window), gtk_application_add_window)

  _WRAP_METHOD(void remove_window(Window& window), gtk_application_remove_window)

  /** Starts the application.
   *
   * @param argc The argc from main() (or 0 if @a argv is <tt>nullptr</tt>).
   * @param argv The argv from main(), or <tt>nullptr</tt>.
   * @return The exit status.
   *
   * @see Gio::Application::run()
   *
   * @newin{3,4}
   */
  int run(int argc, char** argv);

  /** Starts the application.
   *
   * @return The exit status.
   *
   * @see Gio::Application::run()
   *
   * @newin{3,4}
   */
  int run();

  /** Starts the application, creates and presents a window.
   *
   * A window of type T_Window is constructed and added to the application
   * in a signal_activate() handler. The window is deleted when it is hidden
   * or removed from the application. The method returns when the window is hidden,
   * unless other windows have been added but not removed.
   *
   * The window is only constructed on the first activate signal. Any activations
   * thereafter only cause the window to be raised.
   *
   * @tparam T_Window The type of window to present. Must be Gtk::Window or a class type
   *                  that inherits from Gtk::Window.
   * @param argc The argc from main() (or 0 if @a argv is <tt>nullptr</tt>).
   * @param argv The argv from main(), or <tt>nullptr</tt>.
   * @param args Arguments to T_Window's constructor, if any.
   * @return The exit status.
   *
   * @see Gio::Application::run()
   *
   * @newin{3,98}
   */
  template <typename T_Window, typename... T_Args>
  int make_window_and_run(int argc, char** argv, T_Args&&... args);

  /** Get the window, constructed by make_window_and_run().
   */
  Window* get_run_window() { return m_run_window; }
  /** Get the window, constructed by make_window_and_run().
   */
  const Window* get_run_window() const { return m_run_window; }

  _WRAP_METHOD(Glib::RefPtr<Gio::MenuModel> get_menubar(), gtk_application_get_menubar, refreturn)
  _WRAP_METHOD(Glib::RefPtr<const Gio::MenuModel> get_menubar() const, gtk_application_get_menubar, refreturn, constversion)

  _WRAP_METHOD(void set_menubar(const Glib::RefPtr<Gio::MenuModel>& menubar), gtk_application_set_menubar)

  _WRAP_METHOD(guint inhibit(Window& window, InhibitFlags flags, const Glib::ustring& reason), gtk_application_inhibit)
  _WRAP_METHOD(void uninhibit(guint cookie), gtk_application_uninhibit)

  _WRAP_METHOD(Window* get_window_by_id(guint id), gtk_application_get_window_by_id, refreturn)
  _WRAP_METHOD(const Window* get_window_by_id(guint id) const, gtk_application_get_window_by_id, refreturn, constversion)

  _WRAP_METHOD(Window* get_active_window(), gtk_application_get_active_window, refreturn)
  _WRAP_METHOD(const Window* get_active_window() const, gtk_application_get_active_window, refreturn, constversion)

#m4 _CONVERSION(`char**',`std::vector<Glib::ustring>',`Glib::ArrayHandler<Glib::ustring>::array_to_vector($3, Glib::OWNERSHIP_DEEP)')
  _WRAP_METHOD(std::vector<Glib::ustring> list_action_descriptions() const, gtk_application_list_action_descriptions)

#m4 _CONVERSION(`char**',`std::vector<Glib::ustring>',`Glib::ArrayHandler<Glib::ustring>::array_to_vector($3, Glib::OWNERSHIP_DEEP)')
  _WRAP_METHOD(std::vector<Glib::ustring> get_accels_for_action(const Glib::ustring& detailed_action_name) const, gtk_application_get_accels_for_action)

  _WRAP_METHOD(std::vector<Glib::ustring> get_actions_for_accel(const Glib::ustring& accel) const, gtk_application_get_actions_for_accel)

#m4 _CONVERSION(`const std::vector<Glib::ustring>&',`const char*-const*',`Glib::ArrayHandler<Glib::ustring>::vector_to_array($3).data ()')
  _WRAP_METHOD(void set_accels_for_action(const Glib::ustring& detailed_action_name, const std::vector<Glib::ustring>& accels), gtk_application_set_accels_for_action)

  /** Sets a keyboard accelerator that will trigger the
   * given action. It will be the primary
   * accelerator, which may be displayed in the UI.
   *
   * See unset_accels_for_action().
   *
   * @param detailed_action_name A detailed action name, specifying an action
   * and target to associate accelerators with.
   * @param accel An accelerator in the format understood by gtk_accelerator_parse().
   *
   * @newin{3,12}
   */
  void set_accel_for_action(const Glib::ustring& detailed_action_name, const Glib::ustring& accel);

  /** Unsets a keyboard accelerator that will trigger the
   * given action. See set_accels_for_action() and set_accel_for_action().
   *
   * @param detailed_action_name A detailed action name, specifying an action
   * and target to associate accelerators with.
   *
   * @newin{3,12}
   */
  void unset_accels_for_action(const Glib::ustring& detailed_action_name);

  _WRAP_METHOD(Glib::RefPtr<Gio::Menu> get_menu_by_id(const Glib::ustring& id), gtk_application_get_menu_by_id, refreturn)
  _WRAP_METHOD(Glib::RefPtr<const Gio::Menu> get_menu_by_id(const Glib::ustring& id) const, gtk_application_get_menu_by_id, refreturn, constversion)
  _WRAP_METHOD(void save(), gtk_application_save)
  _WRAP_METHOD(void forget(), gtk_application_forget)

  _WRAP_PROPERTY("menubar", Glib::RefPtr<Gio::MenuModel>)
  _WRAP_PROPERTY("register-session", bool,
    deprecated "4.20: This property is ignored. GTK always registers with the session manager.")
  _WRAP_PROPERTY("screensaver-active", bool, newin "3,94")
  _WRAP_PROPERTY("active-window", Window*)
  _WRAP_PROPERTY("support-save", bool)
  _WRAP_PROPERTY("autosave-interval", unsigned int)

  _WRAP_SIGNAL(void window_added(Window* window), "window-added")
  _WRAP_SIGNAL(void window_removed(Window* window), "window-removed")
  // no_default_handler because the wrapped C signal has no default handler.
  _WRAP_SIGNAL(void query_end(), "query-end", no_default_handler)

#m4 _CONVERSION(`GVariant*',`const Glib::VariantBase&',`Glib::wrap($3, true)')
#m4 _CONVERSION(`GVariantDict*',`const Glib::RefPtr<Glib::VariantDict>&',`Glib::wrap($3, true)')
  // TODO: Remove the following 3 no_default_handler when we can break ABI.
  _WRAP_SIGNAL(void restore_window(RestoreReason reason, const Glib::VariantBase& state), "restore-window", no_default_handler)
  _WRAP_SIGNAL(bool save_state(const Glib::RefPtr<Glib::VariantDict>& dict), "save-state", no_default_handler)
  _WRAP_SIGNAL(bool restore_state(RestoreReason reason, const Glib::VariantBase& state), "restore-state", no_default_handler)

private:
  /** This is just a way to call Glib::init() before calling a Glib::Object ctor,
   * so that glibmm's GQuarks are created before they are used.
   */
  const Glib::Class& custom_class_init();

  Window* m_run_window = nullptr;
};

template <typename T_Window, typename... T_Args>
int Application::make_window_and_run(int argc, char** argv, T_Args&&... args)
{
  static_assert(std::is_base_of<Window, T_Window>::value);

  signal_activate().connect([this, &args...] () {
    if (!m_run_window)
    {
      // The created window is managed. Thus, the C++ wrapper is deleted
      // by Gtk::Object::destroy_notify_() when the C window is destroyed.
      // https://gitlab.gnome.org/GNOME/gtkmm/-/issues/114
      auto window = make_managed<T_Window>(std::forward<T_Args>(args)...);
      m_run_window = window;
      add_window(*window);
      window->present();
    } else {
      m_run_window->present();
    }
  });

  signal_window_removed().connect([this] (Window* window) {
    if (window == m_run_window)
    {
      if (window)
        window->destroy();
      m_run_window = nullptr;
    }
  });

  return Gio::Application::run(argc, argv);
}

} // namespace Gtk
