#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include "fileschanged.h"
#include "monitor.h"
#include "node.h"
#include "list.h"
#include "opts.h"
#include "handlers.h"

/*
 * monitor.c:
 *
 *   Basic operations are `open' and `close' on the FAMConnection.
 *
 *     monitor_open (FAMConnection *c);
 *     monitor_close (FAMConnection *c);
 *
 *   To do anything, you have to `open' the monitor first.
 *
 *   While in an open state (eg, we are connected to the FAM server),
 *   we may `begin', `stop' or `pause' monitoring for certain files.  These 
 *   operations require a LIST of files that we want to begin, stop, or pause 
 *   monitoring of, and the open FAMConnection.
 *
 *     monitor_begin(FAMConnection *c, void *list);
 *     monitor_stop (FAMConnection *c, void *list);
 *     monitor_pause_toggle (FAMConnection *c, void *list);
 *
 *   After files have begun to be monitored, the FAM server will be notifying
 *   us with events about the files that have somehow changed.  The main loop
 *   of fileschanged is looped around the `handle_events' function. This 
 *   function needs the open FAMConnection, and the LIST of files that we want 
 *   to handle events for.  It also needs a SECS_TO_WAIT_FOR_PENDING parameter.
 *   This is the maximum number of seconds to wait for a message to arrive from
 *   the FAM server.  After that it needs the SECS_TO_HANDLE_PENDING parameter.
 *   This is the maximum number of seconds to process messages from the server
 *   before giving up.  If this value is -1, then it never gives up.  
 *
 *   Note that `begin' also calls `handle_events'.  This is because the server 
 *   can get backlogged trying to send us notifications, when we're not ready
 *   for them.
 *   SECS_TO_HANDLE_PENDING is not -1 when it's called from monitor_begin -- 
 *   the idea being that we don't want to wait for the all of the messages from
 *   the server before beginning to monitor more files.
 *
 *     monitor_handle_events (FAMConnection *c, void *list, 
 *       int secs_to_wait_for_pending, int secs_to_handle_pending);
 *
 *   This function connects to the handlers via handle_event().
 */
int 
monitor_open (FAMConnection *c)
{
  return FAMOpen (c);
}

int 
monitor_close (FAMConnection *c)
{
  return FAMClose (c);
}

int 
monitor_begin (FAMConnection *c, void *list)
{
  int retval;
  unsigned int i;
  unsigned int count;
  struct node_t *node;
  list_count (list, &count);
  for(i = 0; i < count; i++)
    {
      list_get_element (list, i, &node);
      if (S_ISDIR (node->statbuf.st_mode))
	{
	  //printf ("%04d monitoring directory: '%s'\n", i, node->filename);
	  retval = FAMMonitorDirectory (c, node->filename, &node->request,
					(void *) node);
	  //printf ("FAMMonitorDirectory returns %d (reqnum %d)\n", retval, node->request.reqnum);
	}
      else if (S_ISREG (node->statbuf.st_mode))
	{
	  //printf ("%04d monitoring file: '%s'\n", i, node->filename);
	  retval = FAMMonitorFile (c, node->filename, &node->request,
				   (void *) node);
	  //printf ("FAMMonitorFile returns %d (reqnum %d)\n", retval, node->request.reqnum);
	}
      monitor_handle_events (c, list, 0, 30);
    }
  return 0;
}

static int 
monitor_do (FAMConnection *c, void *list, int (*FAMFunc)(FAMConnection *fc, const FAMRequest *fr))
{
  unsigned int i;
  unsigned int count;
  struct node_t *node;
  list_count (list, &count);
  for(i = 0; i < count; i++)
    {
      list_get_element (list, i, &node);
      FAMFunc (c, &node->request);
      monitor_handle_events (c, list, 0, 30);
    }
  return 0;
}

int 
monitor_stop (FAMConnection *c, void *list)
{
  return monitor_do (c, list, FAMCancelMonitor);
}

int 
monitor_pause_toggle (FAMConnection *c, void *list)
{
  int retval;
  static int paused;
  int (*FAMFunc)(FAMConnection *fc, const FAMRequest *fr);
  if (paused)
    FAMFunc = FAMResumeMonitor;
  else
    FAMFunc = FAMSuspendMonitor;
  retval = monitor_do (c, list, FAMFunc);
  paused = !paused;
  return retval;
}

static int 
monitor_handle_event (FAMConnection *c, void *list, int time_limit)
{
  int retval;
  FAMEvent e;
  time_t ending_time;
  time_t current_time;
  ending_time = time(NULL) + time_limit;
  while ((ending_time >= (current_time = time (NULL))) || (time_limit == -1))
    {
      if (FAMPending (c))
	{
	  retval = FAMNextEvent (c, &e);
	  if (retval < 0)
	    return -1;
	}
      else
	{
	  //second chance for the FAM server.  yay.
	  sleep (0);
	  if (!FAMPending (c))
	    break;
	}
      switch (e.code)
	{
	case FAMExists:
	case FAMEndExist:
	case FAMAcknowledge:
	case FAMMoved:
	  continue;
	case FAMChanged:
	case FAMDeleted:
	case FAMStartExecuting:
	case FAMStopExecuting:
	case FAMCreated:
	    {
	      retval = handle_event (c, list, &e, current_time);
	      if (retval < 0)
		return -1;
	      break;
	    }
	}
    }
  return 0;
}

int 
monitor_handle_events (FAMConnection *c, void *list, int secs_to_wait_for_pending, int secs_to_handle_pending)
{
  fd_set rfds;
  int numfds;
  struct timeval *tv_ptr = NULL;
  struct timeval tv;
  int retval;
  tv.tv_sec = secs_to_wait_for_pending; //wait for fam events for 2 seconds.
  tv.tv_usec = 500; //always add a bit here.
  tv_ptr = &tv;
  FD_ZERO (&rfds);
  FD_SET (c->fd, &rfds);
  numfds = select (FD_SETSIZE, &rfds, NULL, NULL, tv_ptr);
  if (numfds == 1)
    {//if a fam event happened,
      retval = monitor_handle_event (c, list, secs_to_handle_pending); 
      //get notifications for 30secs tops.
      if (retval < 0)
	{
	  return -1;
	}
    }
  return 0;
}
