/* -------------------------------------------------------------------- */
/* SMS Client, send messages to mobile phones and pagers		*/
/*									*/
/* process.c								*/
/*									*/
/*  Copyright (C) 1997,1998,1999 Angelo Masci				*/
/*									*/
/*  This library is free software; you can redistribute it and/or	*/
/*  modify it under the terms of the GNU Library General Public		*/
/*  License as published by the Free Software Foundation; either	*/
/*  version 2 of the License, or (at your option) any later version.	*/
/*									*/
/*  This library 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	*/
/*  Library General Public License for more details.			*/
/*									*/
/*  You should have received a copy of the GNU Library General Public	*/
/*  License along with this library; if not, write to the Free		*/
/*  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.	*/
/*									*/
/*  You can contact the author at this e-mail address:			*/
/*									*/
/*  angelo@styx.demon.co.uk						*/
/*									*/
/* -------------------------------------------------------------------- */
/* $Id$
   -------------------------------------------------------------------- */

#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "common/common.h"
#include "logfile/logfile.h"
#include "process.h"
#include "parser/gs_token.h"
#include "parser/gs_translate.h"
#include "driver/driver.h"
#include "fread.h"
#include "error.h"

/* -------------------------------------------------------------------- */

#define MAX_PATH 256

/* -------------------------------------------------------------------- */

#define TF_UNKNOWN	0
#define TF_CONTROL	1
#define TF_DATA		2

/* -------------------------------------------------------------------- */

struct file_list_struct
{
	char	*name,
		*data,
		*dest_id;

	int	type,
		data_len;

	time_t	mtime;

	TOKEN_HEAP
		*heap;

	struct 	file_list_struct
		*next;
};

typedef struct file_list_struct FILE_LIST;

/* -------------------------------------------------------------------- */

static FILE_LIST *add_front_file_item(FILE_LIST *list, char *name, time_t mtime, int type);
static FILE_LIST *qsort_list(FILE_LIST *list);
static FILE_LIST *get_first_control_file(FILE_LIST *list);
static FILE_LIST *get_next_control_file(FILE_LIST *list);

static void dump_list(FILE_LIST *list);
static void free_list(FILE_LIST *list);

static int reject_file(char *file, char *queue_dir, char *error_dir);
static int remove_file(char *file, char *queue_dir, char *error_dir);
static int num_items_in_list(FILE_LIST *list);
static int file_item_compare(const void *item1, const void *item2);
static FILE_LIST *generate_file_list(int *num, char *queue);

/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
static FILE_LIST *add_front_file_item(FILE_LIST *list, char *name, time_t mtime, int type)
{
	FILE_LIST *item;


	item = (FILE_LIST *)malloc(sizeof(FILE_LIST));
	if (item == NULL)
	{
		lprintf(LOG_ERROR, "malloc() failed\n");
		exit(EMALLOC);
	}

	item->name = (char *)malloc(sizeof(char) * (strlen(name) +1));
	if (item->name == NULL)
	{
		lprintf(LOG_ERROR, "malloc() failed\n");
		exit(EMALLOC);
	}

	strcpy(item->name, name);

	item->mtime = mtime;
	item->type  = type;
	item->next  = list;
	item->heap  = NULL;
	item->data  = NULL;

	return item;
}


/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
static int num_items_in_list(FILE_LIST *list)
{
	int count;
	FILE_LIST 
		*item;

	count = 0;
	item = list;
	while(item != NULL)
	{
		count++;
		item = item->next;
	}

	return count;
}


/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
static int file_item_compare(const void *item1, const void *item2)
{
	time_t 	mtime_1,
		mtime_2;

	mtime_1 = (*(FILE_LIST **)item1)->mtime; 
	mtime_2 = (*(FILE_LIST **)item2)->mtime;

	if (mtime_1 > mtime_2)
	{	return 1;
	}
	if (mtime_1 < mtime_2)
	{	return -1;
	}

	return strcmp((*(FILE_LIST **)item1)->name, (*(FILE_LIST **)item2)->name);
}


/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
static FILE_LIST *qsort_list(FILE_LIST *list)
{
	int 	count,
		i;
	FILE_LIST 
		**file_list_array,
		*item;	


	if (list == NULL)
	{	return NULL;
	}

	count = num_items_in_list(list);
	
	file_list_array = (FILE_LIST **)malloc(sizeof(FILE_LIST *) * count);
	if (file_list_array == NULL)
	{
		lprintf(LOG_ERROR, "malloc() failed\n");
		exit(EMALLOC);
	}

	item = list;
	for (i=0; item != NULL; i++)
	{	file_list_array[i] = item;
		item = item->next;
	}

	qsort(file_list_array, count, sizeof(FILE_LIST *), file_item_compare);

	list = file_list_array[0];
	for (i=0; i<count-1; i++)
	{	file_list_array[i]->next = file_list_array[i+1];
	}

	file_list_array[i]->next = NULL;

	free(file_list_array);	
	return list;
}


/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
static void dump_list(FILE_LIST *list)
{
	FILE_LIST 
		*item;

	char	*stype;

	
	if (list == NULL)
	{	return;
	}

	item = list;
	while(item != NULL)
	{
		if (item->type == TF_CONTROL)
		{	stype = "CONTROL";
		}
		else
		if (item->type == TF_DATA)
		{	stype = "DATA";
		}
		else
		{	stype = "UNKNOWN";
		}

		lprintf(LOG_VERBOSE, "%s %d %s\n", item->name, (int)item->mtime, stype);
		item = item->next;
	}
}


/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
static FILE_LIST *get_first_control_file(FILE_LIST *list)
{
	FILE_LIST
		*item;

	item = list;
	while (item != NULL)
	{
		if (strncmp(item->name, "cf", 2) == 0)
		{	return item;
		}

		item = item->next;
	}

	return NULL;
}


/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
static FILE_LIST *get_next_control_file(FILE_LIST *list) 
{
	FILE_LIST
		*item;


	if (list == NULL)
	{	return NULL;
	}

	item = list->next;
	while (item != NULL)
	{
		if (strncmp(item->name, "cf", 2) == 0)
		{	return item;
		}

		item = item->next;
	}

	return NULL;
}


/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
static void free_list(FILE_LIST *list)
{
	FILE_LIST 
		*prev_item,
		*item;


	if (list == NULL)
	{	return;
	}

	item = list;
	while(item != NULL)
	{
		prev_item = item;
		item = item->next;

		if (prev_item->heap != NULL)
		{	free_heap(prev_item->heap);
		}

		free(prev_item->name);
		free(prev_item);
	}
}


/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */

extern char 	queue_queuedir[],
		queue_errordir[];

static void *access_get_first(void *list);
static void *access_get_next(void *node);
static char *access_get_number(void *node);
static char *access_get_message(void *node);


static void *access_get_first(void *list)
{
/* check item is valid else get next*/

	lprintf(LOG_VERYVERBOSE, "Getting first file\n");

	return (void *)get_first_control_file((FILE_LIST *)list);
}

static void *access_get_next(void *node)
{
/* check item is valid else get next*/

	FILE_LIST
		*item,
		*new_list;
	int 	num;



	item = get_next_control_file((FILE_LIST *)node);
	if (item == NULL)
	{
		/* ------------------------------------------------------------ */
		/* We have processed all of the file in the list. 		*/
		/* We should now check that new files/items haven't been added	*/
		/* while we were busy sending. If they were then we want to add	*/
		/* them to out list of files left to process.			*/
		/* 								*/
		/* NOTE - As files which contained error have been moved to the */
		/* appropriate errors directory we needn't worry about		*/
		/* processing the same file twice.				*/
		/* ------------------------------------------------------------ */


		lprintf(LOG_VERYVERBOSE, "Last item processed, checking for additional files...\n");

		new_list = generate_file_list(&num, queue_queuedir);
		if (new_list == NULL)
		{
			if (num != 0)
			{
				lprintf(LOG_ERROR, "Generating file list\n");
				exit(-1);
			}
		}
		else
		{	lprintf(LOG_VERYVERBOSE, "Found %d additional files\n", num);
		}		


		/* ------------------------------------------------------------ */
		/* We must skip to the end of our list as the current 		*/
		/* item may not be the last!					*/
		/* Now we append the new list if any.				*/
		/* ------------------------------------------------------------ */

		item = (FILE_LIST *)node;
		while(item->next != NULL)
		{	item = item->next;
		}

		item->next = new_list;
		item       = item->next;
	}

	return item;
}

static char *access_get_number(void *node)
{
	return get_strvalue(((FILE_LIST *)node)->heap, "destination_id");
}

static char *access_get_message(void *node)
{
#if 0
	sleep(5);
#endif
	return ((FILE_LIST *)node)->data;
}

static void access_set_delivery(void *node, int result)
{
	FILE_LIST
		*item;

	item = (FILE_LIST *)node;


	/* if delivery successful, remove file. */
	/* if failed move to error directory	*/

	if (result == 0)
	{
		lprintf(LOG_VERBOSE, "Successfully delivered '%s'\n", item->name);

		if (remove_file(item->name, queue_queuedir, queue_errordir) == -1)
		{
			lprintf(LOG_WARNING, "Failed to Remove '%s'\n", item->name);
			exit(-1);
		}
	}
	else
	{	lprintf(LOG_WARNING, "Failed to deliver '%s'\n", item->name);

		if (reject_file(item->name, queue_queuedir, queue_errordir) == -1)
		{
			lprintf(LOG_WARNING, "Failed to Move '%s'\n", item->name);
			exit(-1);
		}
	}
}


/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
int process_queue(char *queue, char *error, DEVICE_ENTRY *device)
{
	FILE_LIST 
		*file_list = NULL;

	int	num;


	while ((file_list = generate_file_list(&num, queue)) != NULL)
	{

		/* ------------------------------------------------------------ */
		/* Dispatch driver function to deliver messages.		*/
		/* ------------------------------------------------------------ */
	
		(*device->main)(file_list, 
		                access_get_first,
		                access_get_next,
		                access_get_number,
		                access_get_message,
		                access_set_delivery,
		                device->env);



		lprintf(LOG_VERYVERBOSE, "Finished sending messages.\n");


		/* ------------------------------------------------------------ */


		free_list(file_list);
	}	


	if (num == 0)
	{	lprintf(LOG_VERBOSE, "No files to process.\n");
	}
	else
	{	lprintf(LOG_ERROR, "Processing queue '%s'.\n", queue);
		return -1;
	}

	return 0;
}


/* -------------------------------------------------------------------- */
/* Remove file from from 'queue' directory 				*/
/* 									*/
/* Return Values:							*/
/*	 0 Success							*/
/*	-1 Failure							*/
/* -------------------------------------------------------------------- */
static int remove_file(char *file, char *queue_dir, char *error_dir)
{
	char	nname[512],
		dname[512];

	int	dfres, 
		cfres;


	if ((file[0] == 'c') &&
	    (file[1] == 'f'))
	{
		strcpy(dname, file);

		dname[0] = 'd';
		dname[1] = 'f';

		lprintf(LOG_VERBOSE, "Removing '%s'\n", dname);

		strcpy(nname, queue_dir);
		sms_filecat(nname, dname);

		dfres = unlink(nname);
	}

	lprintf(LOG_VERBOSE, "Removing '%s'\n", file);

	strcpy(nname, queue_dir);
	sms_filecat(nname, file);

	cfres = unlink(nname);

	return (dfres || cfres);
}



/* -------------------------------------------------------------------- */
/* Move file from from 'queue' directory into 'error' directory		*/
/* 									*/
/* Return Values:							*/
/*	 0 Success							*/
/*	-1 Failure							*/
/* -------------------------------------------------------------------- */
static int reject_file(char *file, char *queue_dir, char *error_dir)
{
	char	nname[512],
		oname[512],
		dname[512];

	int	dfres, 
		cfres;


	if ((file[0] == 'c') &&
	    (file[1] == 'f'))
	{
		strcpy(dname, file);

		dname[0] = 'd';
		dname[1] = 'f';

		lprintf(LOG_WARNING, "Moving '%s' to '%s'\n", dname, queue_errordir);

		strcpy(oname, queue_dir);
		sms_filecat(oname, dname);

		strcpy(nname, error_dir);
		sms_filecat(nname, dname);

		dfres = rename(oname, nname);
	}

	lprintf(LOG_WARNING, "Moving '%s' to '%s'\n", file, queue_errordir);

	strcpy(oname, queue_dir);
	sms_filecat(oname, file);

	strcpy(nname, error_dir);
	sms_filecat(nname, file);

	cfres = rename(oname, nname);

	return (dfres || cfres);
}


/* -------------------------------------------------------------------- */
/* Generate sorted list of entries in queue directory.			*/
/* Parse each entry and add a token_heap to it.				*/
/*									*/
/* Return Values:							*/
/* 	NULL and 'num' set to -1 on error				*/
/* -------------------------------------------------------------------- */
static FILE_LIST *generate_file_list(int *num, char *queue)
{
	DIR 	*dir;
	struct 	dirent 
		*entry;
	struct 	stat 
		status;
	char 	cwd[MAX_PATH+1];
	char	dname[512],
		*data,
		*dest_id;


	FILE_LIST 
		*file_list = NULL;

	int	type,
		data_len;

	TOKEN_HEAP
		*heap;



	*num = 0;

	dir = opendir(queue);
	if (dir == NULL)
	{
		*num = -1;

		lprintf(LOG_ERROR, "Failed to open directory '%s'\n", queue);
		return NULL;
	}

	if (getcwd(cwd, MAX_PATH+1) == NULL)
	{
		*num = -1;

		lprintf(LOG_ERROR, "getcwd() failed\n");
		return NULL;
	}

	if (chdir(queue) != 0)
	{
		*num = -1;

		lprintf(LOG_ERROR, "chdir() failed\n");
		return NULL;
	}

	while((entry = readdir(dir)) != NULL)
	{
		if ((strcmp(entry->d_name, ".") != 0) &&
		    (strcmp(entry->d_name, "..") != 0))
		{
			if (stat(entry->d_name, &status) != 0)
			{
				lprintf(LOG_ERROR, "stat() failed\n");
				return NULL;
			}

			if (!S_ISDIR(status.st_mode))
			{
				if (strncmp(entry->d_name, "cf", 2) == 0)
				{	type = TF_CONTROL;
				}
				else
				if (strncmp(entry->d_name, "df", 2) == 0)
				{	type = TF_DATA;
				}
				else	
				{	type = TF_UNKNOWN;
				}

				if (type != TF_UNKNOWN)
				{

if (type == TF_CONTROL)
{

/* IF file is a control file parse it.		*/
/* IF control file is invalid DO NOT add it	*/
/* to the file list.				*/


			heap = gs_parse_file(entry->d_name);
			if (heap == NULL)
			{	lprintf(LOG_WARNING, "Rejecting file '%s'\n", entry->d_name);

				if (reject_file(entry->d_name, queue_queuedir, queue_errordir) == -1)
				{
					lprintf(LOG_WARNING, "Failed to Move '%s'\n", entry->d_name);
					exit(-1);
				}
			}
			else
			{

/* validate entries in control file. 		*/
/* IF not valid DO NOT add to list.		*/

/* It might be nice to slurp the contents	*/
/* of the data file into 'item'			*/


	if ((dest_id = get_strvalue(heap, "destination_id")) == NULL)
	{
		lprintf(LOG_VERBOSE, "Could not find 'destination_id'\n");

		if (reject_file(entry->d_name, queue_queuedir, queue_errordir) == -1)
		{
			lprintf(LOG_WARNING, "Failed to Move '%s'\n", entry->d_name);
			exit(-1);
		}
	}
	else
	{
				strcpy(dname, entry->d_name);
				dname[0] = 'd';
				dname[1] = 'f';

				lprintf(LOG_VERBOSE, "Reading Datafile '%s'\n", dname);

				data = slurp_file(dname, &data_len);
				if (data == NULL)
				{
					lprintf(LOG_VERBOSE, "Failed to read datafile '%s'\n", dname);

					if (reject_file(entry->d_name, queue_queuedir, queue_errordir) == -1)
					{
						lprintf(LOG_WARNING, "Failed to Move '%s'\n", entry->d_name);
						exit(-1);
					}
				}
				else
				{
					file_list = add_front_file_item(file_list, entry->d_name, status.st_mtime, type);

					file_list->heap     = heap;
					file_list->data     = data;
					file_list->data_len = data_len;
					file_list->dest_id  = dest_id;

					*num = *num +1;
				}
			}
	}
}
				}
				else
				{	lprintf(LOG_WARNING, "Found file '%s', expecting Control or Data File\n", entry->d_name);
				}
			}
		}
	}


	if (closedir(dir) != 0)
	{
		*num = -1;

		lprintf(LOG_ERROR, "closedir() failed\n");
		return NULL;
	}


	if (file_list != NULL)
	{
		file_list = qsort_list(file_list);
	}	

	if (chdir(cwd) != 0)
	{
		*num = -1;

		lprintf(LOG_ERROR, "chdir() failed\n");
		return NULL;
	}

	dump_list(file_list);
	return file_list;
}


