/* PIKA - Photo and Image Kooker Application
 * a rebranding of The GNU Image Manipulation Program (created with heckimp)
 * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
 *
 * Original copyright, applying to most contents (license remains unchanged): 
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include "config.h"
#include <glib.h>
#include <libpika/pika.h>

#include "tinyscheme/scheme-private.h"
#include "script-fu-types.h"
#include "script-fu-scripts.h"
#include "script-fu-script.h"

#include "script-fu-proc-factory.h"

/* Local functions */
static void  script_fu_add_menu_to_procedure (PikaProcedure *procedure,
                                              SFScript      *script);


/* Methods to register PDB procs. A factory makes objects, here PDB procedures.
 *
 * Used by the outer script-fu-interpreter
 *
 * This is in libscriptfu to hide the SFScript type from outer plugins.
 * These methods use instances of type SFScript as specs for procedures.
 *
 * FUTURE: migrate code.
 * There are two flavors of factory-like code: for PDBProcType TEMPORARY and PLUGIN.
 * extension-script-fu outer plugin only makes TEMPORARY
 * script-fu-interpreter outer plugin only makes PLUGIN type
 * This source file supports only script-fu-interpreter.
 * script_fu_find_scripts() in script-fu-scripts.c is also a factory-like method,
 * and could be extracted to a separate source file.
 * Maybe more code sharing between the two flavors.
 */


/* Create and return a single PDB procedure of type PLUGIN,
 * for the given proc name, by reading the script file in the given paths.
 * Also add a menu for the procedure.
 *
 * PDB proc of type PLUGIN has permanent lifetime, unlike type TEMPORARY.
 *
 * The list of paths is usually just one directory, a subdir of /plug-ins.
 * The directory may contain many .scm files.
 * The plugin manager only queries one .scm file,
 * having the same name as its parent dir and and having execute permission.
 * But here we read all the .scm files in the directory.
 * Each .scm file may register (and define run func for) many PDB procedures.
 *
 * Here, one name is passed, and though we load all the .scm files,
 * we only create a PDB procedure for the passed name.
 */
PikaProcedure *
script_fu_proc_factory_make_PLUGIN (PikaPlugIn  *plug_in,
                                    GList       *paths,
                                    const gchar *proc_name)
{
  SFScript      * script    = NULL;
  PikaProcedure * procedure = NULL;

  /* Reads all .scm files at paths, even though only one is pertinent.
   * The returned script_tree is also in the state of the interpreter,
   * we don't need the result here.
  */
  (void) script_fu_find_scripts_into_tree (plug_in, paths);

  /* Get the pertinent script from the tree. */
  script = script_fu_find_script (proc_name);

  if (script)
    {
      procedure = script_fu_script_create_PDB_procedure (
        plug_in,
        script,
        PIKA_PDB_PROC_TYPE_PLUGIN);
      script_fu_add_menu_to_procedure (procedure, script);
    }
  else
    {
      g_warning ("Failed to find script: %s.", proc_name);
    }
  return procedure;
}

 /* Traverse the list of scripts, for each defined name of a PDB proc,
  * add it list whose handle is given.
  *
  * Order is not important.  Could just as well prepend.
  *
  * This is a GTraverseFunction
  */
static gboolean
script_fu_append_script_names (gpointer      *foo G_GNUC_UNUSED,
                               GList         *scripts,
                               GList        **name_list)
{
  for (GList * list = scripts; list; list = g_list_next (list))
    {
      SFScript *script   = list->data;

      if ( !script_fu_is_defined (script->name))
        {
          g_warning ("Run function not defined, or does not match PDB procedure name: %s",
                     script->name);
          continue;
        }

      /* Must assign result from g_list_append back to name_list */
      *name_list = g_list_append ( (GList *) *name_list, g_strdup (script->name));
    }
  return FALSE; /* We traversed all. */
 }

/* Load script texts (.scm files) in the given paths.
 * Iterate over all loaded scripts to get the PDB proc names they define.
 * Return a list of the names.
 */
GList *
script_fu_proc_factory_list_names (PikaPlugIn *plug_in,
                                   GList      *paths)
{
  GList * result_list = NULL;
  GTree * script_tree = NULL;

  /* Load (eval) all .scm files in all dirs in paths. */
  script_tree = script_fu_find_scripts_into_tree (plug_in, paths);

  /* Iterate over the tree, adding each script name to result list */
  g_tree_foreach (script_tree,
                  (GTraverseFunc) script_fu_append_script_names,
                  &result_list);

  return result_list;
}

/* From scriptfu's internal data, add any menu to given procedure in the PDB.
 * Requires that a script was just eval'ed so that scriptfu's list of menus
 * declared in a script is valid.
 * Requires the proc exists in PDB.
 *
 * Not ensure the PDB proc has a menu, when no menu was defined in the script.
 *
 * Derived from script_fu_install_menu, but that is specific to TEMPORARY procs.
 * Also, unlike script_fu_install_menu, we don't nuke the menu list as we proceed.
 *
 * For each "create" of a procedure, the pika-script-fu-interpreter is started anew,
 * and a new script_menu_list is derived from the .scm file.
 * We don't traverse the menu list more than once per session, which soon exits.
 */
static void
script_fu_add_menu_to_procedure (PikaProcedure *procedure,
                                  SFScript      *script)
{
  GList    *menu_list;
  gboolean  did_add_menu = FALSE;

  menu_list = script_fu_get_menu_list ();
  /* menu_list might be NULL: for loop will have no iterations. */

  /* Each .scm file can declare many menu paths.
   * Traverse the list to find the menu path defined for the procedure.
   * Each SFMenu points to the procedure (SFScript) it belongs to.
   */
  for (GList * traverser = menu_list; traverser; traverser = g_list_next (traverser))
    {
      SFMenu *menu = traverser->data;
      if (menu->script == script)
        {
          g_debug ("Add menu: %s", menu->menu_path);
          pika_procedure_add_menu_path (procedure, menu->menu_path);
          did_add_menu = TRUE;
          break;
        }
    }

  /* Some procedures don't have menu path.
   * It is normal, but not common, to define procs of type PLUGIN that don't appear in the menus.
   * No part of PIKA defaults a menu path for procedures.
   * A menu label without a menu path is probably a mistake by the script author.
   */
  if ( ! did_add_menu )
    {
      /* Unusual for a .scm file to have no menu paths, but not an error. */
      g_debug ("No menu paths! Does the procedure name in script-fu-menu-register match?");
      /* FUTURE if the script defines a menu *label*, declare an error. */
    }
  /* script_menu_list is a reference we do not need to free. */
 }
