/* GIMP - The GNU Image Manipulation Program
 * 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 <string.h>

#include <libgimp/gimp.h>

#include "script-fu-server.h"

#include "libscriptfu/script-fu-lib.h"
#include "libscriptfu/script-fu-intl.h"


#define SCRIPT_FU_SERVER_TYPE (script_fu_server_get_type ())
G_DECLARE_FINAL_TYPE (ScriptFuServer, script_fu_server, SCRIPT, FU_SERVER, GimpPlugIn)

struct _ScriptFuServer
{
  GimpPlugIn parent_instance;
};

static GList          * script_fu_server_query_procedures (GimpPlugIn           *plug_in);
static GimpProcedure  * script_fu_server_create_procedure (GimpPlugIn           *plug_in,
                                                           const gchar          *name);

static GimpValueArray * script_fu_server_outer_run        (GimpProcedure        *procedure,
                                                           GimpProcedureConfig  *config,
                                                           gpointer              run_data);
static void             script_fu_server_run_init         (GimpProcedure        *procedure,
                                                           GimpRunMode           run_mode);


G_DEFINE_TYPE (ScriptFuServer, script_fu_server, GIMP_TYPE_PLUG_IN)

GIMP_MAIN (SCRIPT_FU_SERVER_TYPE)
DEFINE_STD_SET_I18N


static void
script_fu_server_class_init (ScriptFuServerClass *klass)
{
  GimpPlugInClass *plug_in_class = GIMP_PLUG_IN_CLASS (klass);

  plug_in_class->query_procedures = script_fu_server_query_procedures;
  plug_in_class->create_procedure = script_fu_server_create_procedure;
  plug_in_class->set_i18n         = STD_SET_I18N;
}

static void
script_fu_server_init (ScriptFuServer *script_fu_server)
{
}

static GList *
script_fu_server_query_procedures (GimpPlugIn *plug_in)
{
  GList *list = NULL;

  list = g_list_append (list, g_strdup ("plug-in-script-fu-server"));

  return list;
}

static GimpProcedure *
script_fu_server_create_procedure (GimpPlugIn  *plug_in,
                                   const gchar *name)
{
  GimpProcedure *procedure = NULL;

  /* The run func script_fu_server_outer_run is defined in this source file. */
  procedure = gimp_procedure_new (plug_in, name,
                                  GIMP_PDB_PROC_TYPE_PLUGIN,
                                  script_fu_server_outer_run, NULL, NULL);

  gimp_procedure_set_menu_label (procedure, _("_Start Server..."));
  gimp_procedure_add_menu_path (procedure,
                                "<Image>/Filters/Development/Script-Fu");

  gimp_procedure_set_documentation (procedure,
                                    _("Server for remote Script-Fu "
                                      "operation"),
                                    "Provides a server for remote "
                                    "script-fu operation. NOTE that for "
                                    "security reasons this procedure's "
                                    "API was changed in an incompatible "
                                    "way since GIMP 2.8.12. You now have "
                                    "to pass the IP to listen on as "
                                    "first parameter. Calling this "
                                    "procedure with the old API will "
                                    "fail on purpose.",
                                    name);
  gimp_procedure_set_attribution (procedure,
                                  "Spencer Kimball & Peter Mattis",
                                  "Spencer Kimball & Peter Mattis",
                                  "1997");

  gimp_procedure_add_enum_argument (procedure, "run-mode",
                                    "Run mode",
                                    "The run mode",
                                    GIMP_TYPE_RUN_MODE,
                                    GIMP_RUN_INTERACTIVE,
                                    G_PARAM_READWRITE);

  gimp_procedure_add_string_argument (procedure, "ip",
                                      "IP",
                                      "The IP on which to listen for requests",
                                      NULL,
                                      G_PARAM_READWRITE);

  gimp_procedure_add_int_argument (procedure, "port",
                                   "Port",
                                   "The port on which to listen for requests",
                                   0, G_MAXINT, 0,
                                   G_PARAM_READWRITE);

  /* FUTURE: gimp_procedure_add_file_argument, but little benefit, need change script-fu-server.c */
  gimp_procedure_add_string_argument (procedure, "logfile",
                                      "Log File",
                                      "The file to log activity to",
                                      NULL,
                                      G_PARAM_READWRITE);

  return procedure;
}


/*
 * Test cases:
 *
 * Normal starting is from GIMP GUI: "Filters>Development>Script-Fu>Start server...""
 * Expect a dialog to enter IP, etc.
 * Expect a console msg: "ScriptFu server: initialized and listening...""
 *
 * Does not have settings.  After the above,
 * Expect "Filters>Repeat Last" and "Reshow Last" to be disabled (greyed out)
 *
 * Execute the server from headless GIMP:
 * gimp -i --batch-interpreter='plug-in-script-fu-eval'
 " -c '(plug-in-script-fu-server 1 "127.0.0.1" 10008 "/tmp/gimp-log")'
 *
 * Execute the binary from command line fails with:
 * "script-fu-server is a GIMP plug-in and must be run by GIMP to be used"
 *
 * The PDB procedure plug-in-script-fu-server CAN be called from another procedure
 * (but shouldn't be.)
 * Expect plug-in-script-fu-server to never return
 */

/*
 * ScriptFu Server listens and responds to clients.
 * The server responds with a success indication,
 * and a string representation of the result
 * (a representation in Lisp, human-readable but also interpretable.)
 * A client may also expect side-effects, e.g. on images and image files.
 *
 * The server has its own logging.
 * Its logging defaults to stdout, but optionally to a file.
 * The server logs errors in interpretation of the stream from the client.
 *
 * A client may quit the server by eval "(gimp-quit)"
 * Otherwise, the server blocks on IO from the client.
 *
 * A server that dies leaves the client with a broken connection,
 * but does not affect extension-script-fu.
 *
 * A server is a child process of a GIMP process.
 * Quitting or killing the parent process also kills the server.
 */
static GimpValueArray *
script_fu_server_outer_run (GimpProcedure        *procedure,
                            GimpProcedureConfig  *config,
                            gpointer              run_data)
{
  GimpValueArray *return_vals = NULL;
  GimpRunMode     run_mode    = GIMP_RUN_NONINTERACTIVE;

  g_object_get (config, "run-mode", &run_mode, NULL);
  script_fu_server_run_init (procedure, run_mode);

  /* Remind any users watching the console. */
  g_debug ("Starting. Further logging by server might be to a log file.");

  /*
   * Call the inner run func, defined in script-fu-server.c
   * !!! This does not return unless a client evals "(gimp-quit)"
   */
  return_vals = script_fu_server_run (procedure, config);

  /*
   * The server returns SUCCESS but no other values (to the caller)
   * if it was quit properly.
   * The server also returns ERROR if it can't start properly.
   */
  g_assert (return_vals != NULL);
  return return_vals;
}

/* Init the server's interpreter.
 *
 * See below, we do load /scripts.
 * So the server serves those scripts directly, instead of via PDB calls
 * to extension-script-fu.
 * Thus there are two "servers" of the same script files.
 * Version 2 of the SF server also loads /scripts.
 *
 * Alternatively (not loading /scripts), only extension-script-fu serves /scripts,
 * and the SF server calls extension-script-fu via PDB calls.
 */
static void
script_fu_server_run_init (GimpProcedure *procedure,
                           GimpRunMode    run_mode)
{
  GList      *path;
  GimpPlugIn *plug_in = gimp_procedure_get_plug_in (procedure);

  /* The path to init and compat scripts, and /scripts. */
  path = script_fu_search_path ();

  /* Init the interpreter, not allow scripts to register in the PDB.
   * This loads init and compat scripts, but not /scripts.
   */
  script_fu_init_embedded_interpreter (path, FALSE, run_mode, FALSE);

  /* Load /scripts, their defined run functions.
   * Then a call to a script is not a PDB call, and does not require run_mode arg.
   * The yielded repr is of the last evaluated expression of the script.
   * Otherwise, as a PDB call would yield the repr of a call to a void func: #t.
   */
  script_fu_find_and_register_scripts (plug_in, path);

  g_list_free_full (path, (GDestroyNotify) g_object_unref);
}
