/* gimptool in C
 * Copyright (C) 2001--2005 Tor Lillqvist
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/*
 * Gimptool rewritten in C, primarily for Win32, where end-users who might
 * want to build and install a plug-in from source don't necessarily have
 * any Bourne-compatible shell to run the gimptool script in.
 *
 * Yes, this program leaks dynamically allocated strings without
 * hesitation. So what, it runs only for a minimal time.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <glib.h>

static gboolean silent = FALSE;
static gboolean dry_run = FALSE;
static gchar *prefix;
static gchar *exec_prefix;

static gchar *env_cc;
static gboolean msvc_syntax = FALSE;
static gchar *env_cflags;
static gchar *env_ldflags;
static gchar *env_libs;

#ifdef G_OS_WIN32
#define EXEEXT ".exe"
#else
#define EXEEXT ""
#endif

#ifdef G_OS_WIN32
#define IS_EXECUTABLE(mode) 1
#else
#define IS_EXECUTABLE(mode) (S_IXUSR & st.st_mode)
#endif

#ifdef G_OS_WIN32
#define USE_PKG_CONFIG yep
#else
#if defined (GLIB_CHECK_VERSION) && GLIB_CHECK_VERSION (1, 3, 10)
#define USE_PKG_CONFIG also yep
#endif
#endif

#ifdef G_OS_WIN32
#define COPY win32_command ("copy")
#define REMOVE win32_command ("del")
#else
#define COPY "cp"
#define REMOVE "rm -f"
#endif

static struct {
  gchar *option;
  gchar *value;
} dirs[] = {
  { "prefix", "/mit/gimp-2.2" },
  { "exec-prefix", "${prefix}" },
  { "bindir", "${exec_prefix}/bin" },
  { "sbindir", "${exec_prefix}/sbin" },
  { "libexecdir", "${exec_prefix}/libexec" },
  { "datadir", "${prefix}/share" },
  { "sysconfdir", "${prefix}/etc" },
  { "sharedstatedir", "${prefix}/com" },
  { "localstatedir", "${prefix}/var" },
  { "libdir", "${exec_prefix}/lib" },
  { "infodir", "${prefix}/info" },
  { "mandir", "${prefix}/man" },
  { "includedir", "${prefix}/include" },
  { "gimpplugindir", "${exec_prefix}/lib/gimp/2.0" },
  { "gimpdatadir", "${prefix}/share/gimp/2.0" }
};

#ifdef G_OS_WIN32

static gchar *
win32_command (gchar *command)
{
  gchar *comspec = getenv ("COMSPEC");

  if (!comspec)
    comspec = "command.com";

  return g_strdup_printf ("%s /c %s", comspec, command);
}

#endif

static gboolean
starts_with (gchar *string,
	     gchar *test)
{
  return strncmp (string, test, strlen (test)) == 0;
}

static gboolean
starts_with_dir (gchar *string,
		 gchar *test)
{
  return starts_with (string, g_strconcat (test, "/", NULL)) ||
    strcmp (string, test) == 0;
}

static gchar *
get_prefix (gchar slash)
{
#ifdef G_OS_WIN32
  /* Don't use the build-time /mit/gimp-2.2, but deduce the
   * installation-time prefix from where gimp.exe can be found.
   */

  gchar *path;
  char *p, *q, *r;

  path = g_find_program_in_path ("gimp-2.2.exe");

  if (path == NULL)
    path = g_find_program_in_path ("gimp.exe");
  
  if (path != NULL)
    {
      r = strrchr (path, G_DIR_SEPARATOR);
      if (r != NULL)
	{
	  *r = '\0';
	  if (strlen (path) >= 4 &&
	      g_ascii_strcasecmp (r - 4, G_DIR_SEPARATOR_S "bin") == 0)
	    {
	      r[-4] = '\0';
	      prefix = path;
#ifdef G_OS_WIN32
	      if (slash == '/')
		{
		  /* Use forward slashes, less quoting trouble in Makefiles */
		  while ((p = strchr (prefix, '\\')) != NULL)
		    *p = '/';
		}
#endif
	      return prefix;
	    }
	}
    }

  fprintf (stderr, "Cannot determine GIMP 2.2 installation location\n");
  exit (1);
#else
  return "/mit/gimp-2.2";
#endif
}

static gchar *
get_exec_prefix (gchar slash)
{
#ifdef G_OS_WIN32
  if (exec_prefix != NULL)
    return exec_prefix;

  /* On Win32, exec_prefix is always same as prefix. Or is it? Maybe not,
   * but at least in tml's prebuilt stuff it is. If somebody else does
   * it another way, feel free to hack this.
   */
  return (exec_prefix = get_prefix (slash));
#else
  return "${prefix}";
#endif
}

static gchar *
expand_and_munge (gchar *value)
{
  if (starts_with_dir (value, "${prefix}"))
    value = g_strconcat ("/mit/gimp-2.2", value + strlen ("${prefix}"), NULL);
  else if (starts_with_dir (value, "${exec_prefix}"))
    value = g_strconcat ("${prefix}", value + strlen ("${exec_prefix}"), NULL);
  if (starts_with_dir (value, "${prefix}"))
    value = g_strconcat (get_exec_prefix ('/'), value + strlen ("${prefix}"), NULL);

  if (starts_with_dir (value, "/mit/gimp-2.2"))
    value = g_strconcat (get_prefix ('/'), value + strlen ("/mit/gimp-2.2"), NULL);

  return value;
}

static void
find_out_env_flags (void)
{
  gchar *p;

  if ((p = getenv ("CC")) != NULL && *p != '\0')
    env_cc = p;
  else if (msvc_syntax)
    env_cc = "cl -MD";
  else
    env_cc = "gcc";

  if (g_strncasecmp (env_cc, "cl", 2) == 0)
    msvc_syntax = TRUE;

  if ((p = getenv ("CFLAGS")) != NULL)
    env_cflags = p;
  else if (msvc_syntax && g_strncasecmp ("gcc", "cl", 2) != 0)
    /* Don't use CFLAGS for wrong compiler... */
    env_cflags = "";
  else
    env_cflags = "-g -O2 -Wall";

  if ((p = getenv ("LDFLAGS")) != NULL)
    env_ldflags = p;
  else if (msvc_syntax && g_strncasecmp ("gcc", "cl", 2) != 0)
    /* Ditto for LDFLAGS */
    env_ldflags = "";
  else
    env_ldflags = "";

  if ((p = getenv ("LIBS")) != NULL && *p != '\0')
    env_libs = p;
  else
    env_libs = "";
}

static void
usage (int exit_status)
{
  printf ("\
Usage: gimptool [OPTION]...\n\
\n\
General options:\n\
  --help                  print this message\n\
  --quiet, --silent       don't echo build commands\n\
  --version               print the version of GIMP associated with this script\n\
  -n, --just-print, --dry-run, --recon\n\
                          don't actually run any commands; just print them\n\
Developer options:\n\
  --cflags                print the compiler flags that are necessary to\n\
                          compile a plug-in\n\
  --libs                  print the linker flags that are necessary to link a\n\
                          plug-in\n\
  --prefix=PREFIX         use PREFIX instead of the installation prefix that\n\
                          GIMP was built when computing the output for --cflags\n\
                          and --libs\n\
  --exec-prefix=PREFIX    use PREFIX instead of the installation exec prefix\n\
                          that GIMP was built when computing the output for\n\
                          --cflags and --libs\n\
  --msvc-syntax           print flags in MSVC syntax\n\
\n\
Installation directory options:\n\
  --prefix --exec-prefix --bindir --sbindir --libexecdir --datadir --sysconfdir\n\
  --sharedstatedir --localstatedir --libdir --infodir --mandir --includedir\n\
  --gimpplugindir --gimpdatadir\n\
\n\
The --cflags and --libs options can be appended with -noui to get appropriate\n\
settings for plug-ins which do not use GTK+.\n\
\n\
User options:\n\
  --build plug-in.c               build a plug-in from a source file\n\
  --install plug-in.c             same as --build, but installs the built\n\
                                  plug-in as well\n\
  --install-bin plug-in           install a compiled plug-in\n\
  --install-script script.scm     install a script-fu script\n\
\n\
  --uninstall-bin plug-in         remove a plug-in again\n\
  --uninstall-script plug-in      remove a script-fu script\n\
\n\
The --install and --uninstall options have \"admin\" counterparts (with\n\
prefix --install-admin instead of --install) that can be used instead to\n\
install/uninstall a plug-in or script in the site directory instead of a\n\
user directory.\n\
\n\
For plug-ins which do not use GTK+, the --build and --install options can be\n\
appended with -noui for appropriate settings. For plug-ins that use GTK+ but\n\
not libgumpui, append -nogimpui.\n");
  exit (exit_status);
}
  
static gchar *
one_line_output (gchar *program,
		 gchar *args)
{
  FILE *pipe = popen (g_strconcat (program, " ", args, NULL), "r");
  char line[1000];

  if (pipe == NULL)
    {
      fprintf (stderr, "Cannot run %s\n", program);
      exit (1);
    }

  fgets (line, sizeof (line), pipe);
  if (line [strlen (line) - 1] == '\n')
    line [strlen (line) - 1] = '\0';
  if (line [strlen (line) - 1] == '\r')
    line [strlen (line) - 1] = '\0';

  pclose (pipe);

  return g_strdup (line);
}

#ifdef USE_PKG_CONFIG

static gchar *
pkg_config (gchar *args)
{
  if (msvc_syntax)
    return one_line_output ("pkg-config --msvc-syntax", args);
  else
    return one_line_output ("pkg-config", args);
}

#endif

static gchar *
glib_config (gchar *args)
{
#ifdef USE_PKG_CONFIG
  return pkg_config (g_strconcat (args, " glib-2.0", NULL));
#else
  return one_line_output ("glib-config", args);
#endif
}

static gchar *
gtk_config (gchar *args)
{
#ifdef USE_PKG_CONFIG
  return pkg_config (g_strconcat (args, " gtk+-2.0", NULL));
#else
  return one_line_output ("gtk-config", args);
#endif
}

static gchar *
get_cflags (void)
{
  gchar *gtk_cflags = gtk_config ("--cflags");

  /* Yes, do use forward slashes here also on Windows. The compilers
   * accept it, and it means less quoting trouble when using backquoted
   * invokations of gimptool in Makefiles.
   */
  return g_strdup_printf ("-I%s/include/gimp-2.0 %s", get_prefix ('/'), gtk_cflags);
}

static void
do_cflags (void)
{
  printf ("%s\n", get_cflags ());
}

static gchar *
get_cflags_noui (void)
{
  gchar *glib_cflags = glib_config ("--cflags");

  return g_strdup_printf ("%s -I%s/include", glib_cflags, get_prefix ('/'));
}

static void
do_cflags_noui (void)
{
  printf ("%s\n", get_cflags_noui ());
}

static gchar *
get_libs (void)
{
  gchar *gtk_libs = gtk_config ("--libs");

  if (msvc_syntax)
    return g_strdup_printf ("/libpath:%s/lib gimpui-2.0.lib gimpwidgets-2.0.lib gimp-2.0.lib gimpcolor-2.0.lib gimpmath-2.0.lib gimpbase-2.0.lib %s", get_prefix ('/'), gtk_libs);
  else
    return g_strdup_printf ("-L%s/lib -lgimpui-2.0 -lgimpwidgets-2.0 -lgimp-2.0 -lgimpcolor-2.0 -lgimpmath-2.0 -lgimpbase-2.0 %s", get_prefix ('/'), gtk_libs);
}

static void
do_libs (void)
{
  printf ("%s\n", get_libs ());
}

static gchar *
get_libs_noui (void)
{
  gchar *glib_libs = glib_config ("--libs");

  if (msvc_syntax)
    return g_strdup_printf ("/libpath:%s/lib gimp-2.0.lib gimpcolor-2.0.lib gimpmath-2.0.lib gimpbase-2.0.lib %s", get_prefix ('/'), glib_libs);
  else
    return g_strdup_printf ("-L%s/lib -lgimp-2.0 -lgimpcolor-2.0 -lgimpmath-2.0 -lgimpbase-2.0 %s", get_prefix ('/'), glib_libs);
}

static void
do_libs_noui (void)
{
  printf ("%s\n", get_libs_noui ());
}

static void
maybe_run (gchar *cmd)
{
  if (!silent)
    printf ("%s\n", cmd);

  if (!dry_run)
    system (cmd);
}

static void
do_build_2 (gchar *cflags,
	    gchar *libs,
	    gchar *install_dir,
	    gchar *what)
{
  gchar *cmd;
  gchar *dest_dir;
  gchar *output_flag;
  gchar *dest_exe;
  gchar *here_comes_linker_flags;
  gchar *windows_subsystem_flag;
  gchar *p, *q, *r;

  if (install_dir != NULL)
    dest_dir = g_strconcat (install_dir, "/", NULL);
  else
    dest_dir = "";

  dest_exe = g_strdup (what);

  p = strrchr (dest_exe, '.');
  if (p == NULL || !(strcmp (p, ".c") == 0 || strcmp (p, ".cc") == 0 || strcmp (p, ".cpp") == 0))
    {
      fprintf (stderr, "plug-in source %s is not a C or C++ file?\n", what);
      exit (1);
    }

  *p = '\0';
  q = strrchr (dest_exe, G_DIR_SEPARATOR);
#ifdef G_OS_WIN32
  r = strrchr (dest_exe, '/');
  if (r != NULL && (q == NULL || r > q))
    q = r;
#endif
  if (q == NULL)
    q = dest_exe;
  else
    q++;
  
  dest_exe = g_strconcat (q, EXEEXT, NULL);

  if (msvc_syntax)
    {
      output_flag = "-Fe";
      here_comes_linker_flags = "-link";
      windows_subsystem_flag = "-subsystem:windows";
    }
  else
    {
      output_flag = "-o ";
      here_comes_linker_flags = "";
      windows_subsystem_flag = "-mwindows";
    }

  cmd = g_strdup_printf ("%s %s %s %s%s%s %s %s %s %s %s %s",
			 env_cc,
			 env_cflags,
			 cflags,
			 output_flag,
			 dest_dir,
			 dest_exe,
			 what,
			 here_comes_linker_flags,
			 env_ldflags,
			 windows_subsystem_flag,
			 libs,
			 env_libs);
  maybe_run (cmd);
}

static void
do_build (char *what)
{
  do_build_2 (get_cflags (), get_libs (), NULL, what);
}

static void
do_build_noui (char *what)
{
  do_build_2 (get_cflags_noui (), get_libs_noui (), NULL, what);
}

static void
do_build_nogimpui (char *what)
{
  do_build (what);
}

static gchar *
get_user_plugin_dir (gboolean forward_slashes)
{
#ifdef G_OS_WIN32
  /* In the --install-bin and --uninstall modes we want
   * to use backward slashes on Win32, because we invoke
   * the COPY and DEL commands directly with system().
   */

  const gchar slash = forward_slashes ? '/' : '\\';
#else
  const gchar slash = '/';
#endif

  return g_strdup_printf ("%s%c.gimp-2.2%cplug-ins",
			  g_get_home_dir (), slash, slash);
}

static void
do_install (char *what)
{
  do_build_2 (get_cflags (), get_libs (), get_user_plugin_dir (TRUE), what);
}

static void
do_install_noui (char *what)
{
  do_build_2 (get_cflags_noui (), get_libs_noui (), get_user_plugin_dir (TRUE), what);
}

static void
do_install_nogimpui (char *what)
{
  do_install (what);
}

static gchar *
get_sys_plugin_dir (gboolean forward_slashes)
{
#ifdef G_OS_WIN32
  const gchar slash = forward_slashes ? '/' : '\\';
#else
  const gchar slash = '/';
#endif

  return g_strdup_printf ("%s%clib%cgimp%c2.0%cplug-ins",
			  get_prefix (slash),
			  slash, slash, slash, slash);
}

static void
do_install_admin (char *what)
{
  do_build_2 (get_cflags (), get_libs (), get_sys_plugin_dir (FALSE), what);
}

static void
do_install_admin_noui (char *what)
{
  do_build_2 (get_cflags_noui (), get_libs_noui (), get_sys_plugin_dir (FALSE), what);
}

static void
do_install_admin_nogimpui (char *what)
{
  do_build_2 (get_cflags (), get_libs (), get_sys_plugin_dir (FALSE), what);
}

static void
do_install_bin_2 (gchar *dir,
		  gchar *what)
{
  maybe_run (g_strconcat (COPY, " ", what, " ", dir, NULL));
}

static void
do_install_bin (char *what)
{
  do_install_bin_2 (get_user_plugin_dir (FALSE), what);
}

static void
do_install_admin_bin (char *what)
{
  do_install_bin_2 (get_sys_plugin_dir (FALSE), what);
}

static void
do_uninstall (gchar *dir,
	      gchar *what)
{
  maybe_run (g_strconcat (REMOVE, " ", dir, G_DIR_SEPARATOR_S, what, NULL));
}

static gchar *
maybe_append_exe (gchar *what)
{
#ifdef G_OS_WIN32
  gchar *p = strrchr (what, '.');

  if (p == NULL || g_ascii_strcasecmp (p, ".exe") != 0)
    return g_strconcat (what, ".exe");
#endif

  return what;
}

static void
do_uninstall_bin (char *what)
{
  do_uninstall (get_user_plugin_dir (FALSE), maybe_append_exe (what));
}

static void
do_uninstall_admin_bin (char *what)
{
  do_uninstall (get_sys_plugin_dir (FALSE), maybe_append_exe (what));
}

static gchar *
get_user_script_dir (gboolean forward_slashes)
{
#ifdef G_OS_WIN32
  const gchar slash = forward_slashes ? '/' : '\\';
#else
  const gchar slash = '/';
#endif

  return g_strdup_printf ("%s%c.gimp-2.2%cscripts",
			  g_get_home_dir (), slash, slash);
}

static void
do_install_script (char *what)
{
  do_install_bin_2 (get_user_script_dir (FALSE), what);
}

static gchar *
get_sys_script_dir (gboolean forward_slashes)
{
#ifdef G_OS_WIN32
  const gchar slash = forward_slashes ? '/' : '\\';
#else
  const gchar slash = '/';
#endif

  return g_strdup_printf ("%s%cshare%cgimp%c%d.%d%cscripts",
			  get_prefix (slash),
			  slash, slash, slash,
			  2, 2,
			  slash);
}

static void
do_install_admin_script (char *what)
{
  do_install_bin_2 (get_sys_script_dir (FALSE), what);
}

static void
do_uninstall_script (char *what)
{
  do_uninstall (get_user_script_dir (FALSE), what);
}

static void
do_uninstall_admin_script (char *what)
{
  do_uninstall (get_sys_script_dir (FALSE), what);
}

int
main (int    argc,
      char **argv)
{
  gint argi;
  gint i;

  if (argc == 1)
    usage (0);

  /* First scan for flags that affect our behaviour globally, but
   * are still allowed late on the command line.
   */
  argi = 0;
  while (++argi < argc)
    {
      if (strcmp (argv[argi], "-n") == 0 ||
	  strcmp (argv[argi], "--just-print") == 0 ||
	  strcmp (argv[argi], "--dry-run") == 0 ||
	  strcmp (argv[argi], "--recon") == 0)
	dry_run = TRUE;
      else if (starts_with (argv[argi], "--prefix="))
	prefix = argv[argi] + strlen ("--prefix=");
      else if (starts_with (argv[argi], "--exec-prefix="))
	exec_prefix = argv[argi] + strlen ("--exec_prefix=");
      else if (strcmp (argv[argi], "--msvc-syntax") == 0)
	msvc_syntax = TRUE;
    }

  find_out_env_flags ();

  /* Second pass, actually do something. */
  argi = 0;
  while (++argi < argc)
    {
      for (i = 0; i < G_N_ELEMENTS (dirs); i++)
	if (strcmp (argv[argi], g_strconcat ("--", dirs[i].option, NULL)) == 0)
	  break;
      if (i < G_N_ELEMENTS (dirs))
	printf ("%s\n", expand_and_munge (dirs[i].value));
      else if (strcmp (argv[argi], "--help") == 0)
	usage (0);
      else if (strcmp (argv[argi], "--quiet") == 0 ||
	       strcmp (argv[argi], "--silent") == 0)
	silent = TRUE;
      else if (strcmp (argv[argi], "--version") == 0)
	{
	  printf ("%d.%d.%d\n", 2, 2, 11);
	  exit (0);
	}
      else if (strcmp (argv[argi], "-n") == 0 ||
	       strcmp (argv[argi], "--just-print") == 0 ||
	       strcmp (argv[argi], "--dry-run") == 0 ||
	       strcmp (argv[argi], "--recon") == 0)
	;			/* Already handled */
      else if (strcmp (argv[argi], "--cflags") == 0)
	do_cflags ();
      else if (strcmp (argv[argi], "--cflags-noui") == 0)
	do_cflags_noui ();
      else if (strcmp (argv[argi], "--cflags-nogimpui") == 0)
	do_cflags ();
      else if (strcmp (argv[argi], "--libs") == 0)
	do_libs ();
      else if (strcmp (argv[argi], "--libs-noui") == 0)
	do_libs_noui ();
      else if (starts_with (argv[argi], "--prefix="))
	;
      else if (starts_with (argv[argi], "--exec-prefix="))
	;
      else if (strcmp (argv[argi], "--msvc-syntax") == 0)
	;
      else if (strcmp (argv[argi], "--build") == 0)
	do_build (argv[++argi]);
      else if (strcmp (argv[argi], "--build-noui") == 0)
	do_build_noui (argv[++argi]);
      else if (strcmp (argv[argi], "--build-nogimpui") == 0)
	do_build_nogimpui (argv[++argi]);
      else if (strcmp (argv[argi], "--install") == 0)
	do_install (argv[++argi]);
      else if (strcmp (argv[argi], "--install-noui") == 0)
	do_install_noui (argv[++argi]);
      else if (strcmp (argv[argi], "--install-nogimpui") == 0)
	do_install_nogimpui (argv[++argi]);
      else if (strcmp (argv[argi], "--install-admin") == 0)
	do_install_admin (argv[++argi]);
      else if (strcmp (argv[argi], "--install-admin-noui") == 0)
	do_install_admin_noui (argv[++argi]);
      else if (strcmp (argv[argi], "--install-admin-nogimpui") == 0)
	do_install_admin_nogimpui (argv[++argi]);
      else if (strcmp (argv[argi], "--install-bin") == 0)
	do_install_bin (argv[++argi]);
      else if (strcmp (argv[argi], "--install-admin-bin") == 0)
	do_install_admin_bin (argv[++argi]);
      else if (strcmp (argv[argi], "--uninstall-bin") == 0)
	do_uninstall_bin (argv[++argi]);
      else if (strcmp (argv[argi], "--uninstall-admin-bin") == 0)
	do_uninstall_admin_bin (argv[++argi]);
      else if (strcmp (argv[argi], "--install-script") == 0)
	do_install_script (argv[++argi]);
      else if (strcmp (argv[argi], "--install-admin-script") == 0)
	do_install_admin_script (argv[++argi]);
      else if (strcmp (argv[argi], "--uninstall-script") == 0)
	do_uninstall_script (argv[++argi]);
      else if (strcmp (argv[argi], "--uninstall-admin-script") == 0)
	do_uninstall_admin_script (argv[++argi]);
      else
	{
	  fprintf (stderr, "Unrecognized switch %s\n", argv[argi]);
	  usage (1);
	}
    }
  exit (0);
}
/*
 * Local Variables:
 * mode: c
 * End:
 */
