/* 
 * This program is free software. It comes without any warranty, to
 * the extent permitted by applicable law. You can redistribute it
 * and/or modify it under the terms of the Do What The Fuck You Want
 * To Public License, Version 2, as published by Sam Hocevar. See
 * http://sam.zoy.org/wtfpl/COPYING for more details.
 *
 * 2010 © Øyvind Kolås <pippin@gimp.org>
 */

#include <stdlib.h>
#include <glib.h>
#include <gio/gio.h>
#include <string.h>
#include <unistd.h>

static void sentry_usage (void)
{
  g_print (
"Usage: sentry [options] <file|dir> [file|dir ..] -- commandline to run\n"
"\n"
" sentry monitors the listed files or directories for changes, if such occur it\n"
" and invokes the commandline following -- each time something changes.\n"
"\n"
" Options: \n"
"   -t  N   throttle updates to maxium every N seconds, by default 2s\n"
"   -c      monitor for file creations in dir (occurances of %% in the\n"
"           commandline are replaced with the path of the newly created file.)\n"
"   -d      daemonize detach from terminal and run in background\n"
"   -v      be verbose, add more v's for more verbosity\n"
);
  exit (-1);
}

static GTimer *  timer            = NULL;
static guint     timeout_id       = 0;
static gdouble   throttle         = 4.0;
static gint      verbose          = 0;
static gboolean  first            = TRUE; 
static gboolean  monitor_creation = FALSE;
static gboolean  daemonize        = FALSE;

static gboolean timeout (gpointer user_data)
{
  if (verbose > 3)
    g_print ("running %s\n", (gchar*)user_data);
  system (user_data);
  g_timer_start (timer);
  timeout_id = 0;
  return FALSE;
}

static void something_changed (GFileMonitor     *monitor,
                               GFile            *file,
                               GFile            *other_file,
                               GFileMonitorEvent event_type,
                               gchar            *commandline)
{
  switch (event_type)
    {
      case G_FILE_MONITOR_EVENT_DELETED:
        {
          GFileInfo *info;

          info = g_file_query_info (file, "standard::*", 0, NULL, NULL);
          if (!info)
            return;

          /* deletion of regular files happens when writing changes
           * from a text editor
           */
          if (g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR)
            return;
          g_object_unref (info);
        }
      case G_FILE_MONITOR_EVENT_CREATED:
        if (monitor_creation)
          {
            GString *cmd = g_string_new ("");
            gchar *p;
            gchar *path = g_file_get_path (file);

            for (p=commandline; *p;p++)
              {
                if (*p == '%')
                  {
                    if (p[1] == '%')
                      {
                        g_string_append_c (cmd, '%');
                        p++;
                      }
                    else
                      g_string_append (cmd, path);
                  }
                else
                 g_string_append_c (cmd, *p);
              }

            if (verbose > 2)
              g_print ("running %s\n", cmd->str);
            system (cmd->str);
            g_string_free (cmd, TRUE);
            g_free (path);
          }
        break;
      case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
      case G_FILE_MONITOR_EVENT_CHANGED:
        {
          if (!monitor_creation && !timeout_id)
            {
              gdouble elapsed = g_timer_elapsed (timer, NULL);
              gdouble wait;
             
              if (first)
                {
                  first = FALSE;
                  wait = 0.0;
                }
              else
                {
                  wait = throttle - elapsed;
                }

              if (verbose)
                {
                  gchar *uri = g_file_get_uri (file);
                  g_print ("%s\n", uri);
                }

              if (wait <= 0.0)
                wait = 0.0;
              else if (verbose > 1)
                g_print ("waiting %f seconds\n", wait);

              timeout_id = g_timeout_add (wait * 1000, timeout, commandline);
            }
          }
      default:
        break;
    }
}

gint main
(gint    argc,
 gchar **argv)
{
  GString *commandline;
  GError  *error = NULL;
  gint     i;

  g_type_init ();
  commandline = g_string_new ("");
  timer = g_timer_new ();

  for (i = 1; argv[i] && !g_str_equal (argv[i], "--"); i++)
    {
      if (argv[i][0]=='-')
        {
          gint c;
          gboolean done = FALSE;
          for (c=1; !done && argv[i][c]; c++)
            {
              switch (argv[i][c])
                {
                  case 't':
                    throttle = atof (argv[i+1]);
                    i++;
                    done = TRUE;
                    break;
                  case 'c':
                    monitor_creation = TRUE;
                    break;
                  case 'd':
                    daemonize = TRUE;
                    break;
                  case 'v':
                    verbose++;
                    break;
                  default:
                    g_print ("unexpected option %c\n", argv[i][c]);
                    sentry_usage ();
                }
            }
        }
    }

  if (!argv[i] || !g_str_equal (argv[i], "--"))
    sentry_usage ();

  for (i++; argv[i]; i++)
    {
      g_string_append (commandline, g_shell_quote (argv[i]));
      g_string_append_c (commandline, ' ');
    }

  for (i = 1; argv[i] && !g_str_equal (argv[i], "--"); i++)
    {
      GFileMonitor *monitor;

      if (argv[i][0]=='-')
        {
          gint c;
          gboolean done = FALSE;
          for (c=1; !done && argv[i][c]; c++)
            {
              if (argv[i][c] == 't') 
                {
                  i++;
                  done = TRUE;
                }
            }
          continue;
        }

      monitor = g_file_monitor (g_file_new_for_commandline_arg (argv[i]),
                                G_FILE_MONITOR_NONE,
                                NULL, &error);
      if (error)
        {
          g_print ("Failed to create monitor for %s\n", argv[i]);
          return -1;
        }
      g_signal_connect (monitor, "changed", G_CALLBACK (something_changed),
                                            (void*)commandline->str);
    }

  if (daemonize)
    if (daemon (0, 0))
      {
        g_print ("Failed to daemonize\n");
        return -1;
      }

  g_main_loop_run (g_main_loop_new (NULL, FALSE));
  return 0;
}
