#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <error.h>
#include "gettext.h"
#include <libgen.h>
#include <unistd.h>
#include "filelist.h"
#include "xrealpath.h"
#include "node.h"
#include "list.h"
#include "opts.h"
#include "listdirs.h"
extern struct arguments_t arguments;

/*
 * filelist.c:
 *   int filelist_populate (void *list_of_files_to_monitor);
 *
 * Fill LIST_OF_FILES_TO_MONITOR which is an instantianted list from list.[ch], 
 * with entries that are node_t from node.[ch].  Each entry represents a file
 * that we want to monitor.  The source data for this list is either from 
 * files specified on the command-line, or from a filelist specified by the -l
 * option.
 *
 * Returns 0 upon success, or non-zero upon error.
 *
 */
static int 
for_every_filename (int (*for_every_file)(int (*)(void *, char *), void *list), int (*add_it_to_the)(void *list, char *filename), void *list)
{
  for_every_file (add_it_to_the, list);
  return 0;
}

static int 
on_the_command_line (int (*process_file)(void *list, char *filename), void *list)
{
  unsigned int i;
  int retval;
  for(i = 0; i < arguments.arraylen; i++)
    {
      retval = process_file (list, arguments.args[i]);
    }
  return 0;
}

static void 
chop (char *line)
{
  char *tmp;
  tmp = strpbrk (line, "\r\n");
  if (tmp)
    tmp[0] = '\0';
  return;
}

static int 
in_filelist_file (int (*process_file)(void *list, char *filename), void *list)
{
  //read the list of files from a file.
  FILE *fileptr;
  char *filename;
  char *line = NULL;
  size_t n = 0;
  int retval;
  filename = arguments.fileschanged.filelist_filename;
  if (strcmp (filename, "-") == 0)
    fileptr = stdin;
  else
    {
      fileptr = fopen (filename, "r");
      if (!fileptr)
	error(1, 0, _("Error: couldn't open '%s'\n"), filename);
    }
  while (getline (&line, &n, fileptr) > 0)
    {
      chop (line);
      if (line[0] != '#')
	retval = process_file (list, line);
      free (line); line = NULL;
      n = 0;
    }

  if (fileptr != stdin)
    fclose (fileptr);
  return 0;
}

static int 
add_it_to_the (void *list, char *filename)
{
  int retval;
  struct node_t node;
  retval = node_new (&node, filename);
  if (retval == 0)
    {
      if ((S_ISDIR (node.statbuf.st_mode)) && 
	  (arguments.fileschanged.recursive))
	{
	  void *dirs = NULL;
	  struct node_t *dir = NULL;
	  unsigned int count;
	  unsigned int i;
	  //add all dirs below and including this one.

	  list_init (&dirs);
	  retval = listdirs (&node, dirs);
	  if (retval != 0)
	    return -1;
	  list_count (dirs, &count);
	  for(i = 0; i < count; i++)
	    {
	      list_get_element (dirs, i, &dir);
	      retval = list_add (list, dir);
	    }
	  list_free(dirs);

	}
      else if ((S_ISREG (node.statbuf.st_mode)) || 
	       (S_ISDIR (node.statbuf.st_mode)))
	{
	  //add this file.
	  retval = list_add (list, &node);
	}

      //printf("%s\n",node.filename);

      node_free (&node);
    }

  return 0;
}

static int
remove_duplicates (void *list)
{
  unsigned int i;
  unsigned int count;
  struct node_t *node = NULL;
  struct node_t *lastdir = NULL;
  list_count (list, &count);
  for(i = 0; i < count; i++)
    {
      list_get_element (list, i, &node);
      if (S_ISDIR (node->statbuf.st_mode))
	lastdir = node;
      else if ((S_ISREG (node->statbuf.st_mode)) && (lastdir))
	{
	  char *file;
	  file = strdup (node->filename);
	  if (file)
	    {
	      char *dir;
	      dir = dirname (file);
	      if (strcmp (dir, lastdir->filename) == 0)
		{
		  node_free (node);
		}

	      free (file);
	    }
	  else
	    return -1;
	}
    }

  for(i = 0; i < count; i++)
    {
      list_get_element (list, i, &node);
      if (node_is_empty (node))
	{
	  list_remove_element (list, i);
	}
    }
  return 0;
}

int 
filelist_populate (void *list_of_files_to_monitor)
{
  int retval = 0;
  if (arguments.fileschanged.filelist)
    retval = for_every_filename (in_filelist_file, add_it_to_the, 
				 list_of_files_to_monitor);
  else if (arguments.fileschanged.filestomonitor)
    retval = for_every_filename (on_the_command_line, add_it_to_the, 
				 list_of_files_to_monitor);
  else
    return -1;
  //okay we have our list now.
  //now remove files for which there are directories.

  list_sort (list_of_files_to_monitor);
  if (remove_duplicates (list_of_files_to_monitor) != 0)
    return -2;

  return retval;
}
