/*
 * LibInputSynth
 * Copyright 2018 Collabora Ltd.
 * Author: Christoph Haag <christoph.haag@collabora.com>
 * SPDX-License-Identifier: MIT
 */

#include "inputsynth.h"
#include <gmodule.h>

typedef struct _InputSynthPrivate
{
  GObjectClass parent_class;
  GModule *module;
} InputSynthPrivate;

G_DEFINE_TYPE_WITH_PRIVATE (InputSynth, input_synth, G_TYPE_OBJECT)

#ifndef API_VERSION
#error BUILD SYSTEM ERROR, MISSING API_VERSION define!
#endif

#ifndef PLUGIN_DIR
#error BUILD SYSTEM ERROR, MISSING PLUGIN_DIR DEFINE!
#endif

static void
input_synth_init (InputSynth *self)
{
  (void) self;
}

static InputSynth *
_load_library (InputsynthBackend backend)
{
  const gchar *plugin_dir;

  plugin_dir = g_getenv ("LIBINPUTSYNTH_PLUGINDIR");
  if (!plugin_dir || !*plugin_dir)
    plugin_dir = PLUGIN_DIR;

  InputSynth *(*new_func) (void);

  gchar *func_name = NULL;
  GString *module_name = g_string_new ("inputsynth_");
  switch (backend)
    {
    case INPUTSYNTH_BACKEND_XDO:
      func_name = "input_synth_xdo_new";
      g_string_append (module_name, "xdo");
      break;
    case INPUTSYNTH_BACKEND_XI2:
      func_name = "input_synth_xi2_new";
      g_string_append (module_name, "xi2");
      break;
    case INPUTSYNTH_BACKEND_WAYLAND_CLUTTER:
      func_name = "input_synth_wayland_clutter_new";
      g_string_append (module_name, "wayland_clutter");
      break;
    }

  g_debug ("Load module %s from path %s\n", module_name->str, plugin_dir);

  GModule *module;
  gchar *module_path = NULL;
  module_path = g_module_build_path (plugin_dir, module_name->str);
  g_debug ("Opening Module path: %s\n", module_path);
  module = g_module_open (module_path, G_MODULE_BIND_LAZY);
  if(!module)
    {
      g_printerr ("Unable to load module. Does %s exist?\n", module_name->str);
      g_string_free (module_name, FALSE);
      return NULL;
    }
  if(! g_module_symbol (module, func_name, (gpointer *)&new_func))
    {
      g_printerr ("Unable to get function reference: %s\n", g_module_error());
      return NULL;
    }
  InputSynth *synth = new_func();
  if (synth != NULL)
    {
      InputSynthPrivate *priv = input_synth_get_instance_private (synth);
      priv->module = module;
    }

  return synth;
}

InputSynth *
input_synth_new (InputsynthBackend backend)
{
  gboolean supported = g_module_supported();
  if (!supported)
    {
      g_printerr ("Module loading not supported on this platform!\n");
      return NULL;
    }

  return _load_library (backend);
}

static void
input_synth_finalize (GObject *gobject)
{
  (void) gobject;
  /* TODO: when arriving here, we are likely being finalized by a child, so
   * we can't actually close the module here because it's the branch we are
   * sitting on*/

  /*
  InputSynth *self = INPUT_SYNTH (gobject);
  g_debug ("Closing module %s\n", g_module_name (self->module));
  g_module_close (self->module);
  */
}

static void
input_synth_class_init (InputSynthClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->finalize = input_synth_finalize;
}

/**
 * input_synth_move_cursor:
 * @self: The #InputSynth
 * @x: The x coordinate to move to
 * @y: The y coordinate to move to
 */
void
input_synth_move_cursor (InputSynth *self, int x, int y)
{
  InputSynthClass *klass = INPUT_SYNTH_GET_CLASS (self);
  g_return_if_fail (klass->move_cursor != NULL);
  klass->move_cursor (self, x, y);
}

/**
 * input_synth_click:
 * @self: The #InputSynth
 * @x: The x coordinate to click at (may have no effect depending on backend)
 * @y: The y coordinate to click at (may have no effect depending on backend)
 * @button: The button to click.
 * The usual bindings are:
 *  * Button 1: Left Click
 *  * Button 2: Right Click
 *  * Button 3: Middle (Mouse Wheel) Click
 *  * Button 4: Scroll Up
 *  * Button 5: Scroll Down
 *  * Button 6: Scroll Left
 *  * Button 7: Scroll right
 * @press: Whether to press or release the button
 */
void
input_synth_click (InputSynth *self, int x, int y,
                        int button, gboolean press)
{
  InputSynthClass *klass = INPUT_SYNTH_GET_CLASS (self);
  g_return_if_fail (klass->click != NULL);
  klass->click (self, x, y, button, press);
}

/**
 * input_synth_character:
 * @self: The #InputSynth
 * @c: The ASCII character to synthesize.
 */
void
input_synth_character (InputSynth *self, char c)
{
  InputSynthClass *klass = INPUT_SYNTH_GET_CLASS (self);
  g_return_if_fail (klass->character != NULL);
  klass->character (self, c);
}

/**
 * input_synth_get_backend_name:
 * @self: The #InputSynth
 * Returns: A purely informational descriptive name for the backend
 * currently in use. Useful when testing backend loading.
 */
GString *
input_synth_get_backend_name (InputSynth *self)
{
  InputSynthClass *klass = INPUT_SYNTH_GET_CLASS (self);
  g_return_val_if_fail (klass->get_backend_name != NULL, NULL);
  return klass->get_backend_name (self);
}
