#include "tsl.h"

/* This file is part of TSL.
 *
 * TSL 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.
 *
 * TSL 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 TSL; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

static char *fixword( char *start, char *end );

/* This function creates a new listitem structure */
struct listitem_struct *listitem_create( void *value1, void *value2 )
{
	struct listitem_struct *tempitem;	

	/* Allocate memory for the return structure */
	tempitem = calloc( 1, sizeof( struct listitem_struct ) );
	assert( tempitem != NULL );

	/* Initialize the item with the passed values */
	tempitem->value1 = value1;
	tempitem->value2 = value2;
	tempitem->id = SID_LISTITEM;
	/* next is set to NULL during the allocation */

	return tempitem;
}

/* This function creates a new list structure.  List structures contain lists of
	 listitem structures */
struct list_struct *list_create()
{
	struct list_struct *temp;

	/* Allocate memory for the structure */
	temp = calloc( 1, sizeof( struct list_struct ) );
	assert( temp != NULL );

	/* Identify the structure.*/
	temp->id = SID_LIST;

	return temp;
}


/* Add an element to a list */
void list_add( struct list_struct *list, void *value1, void *value2 )
{
	struct listitem_struct *item, *last;

	/* Sanity check */
	assert( list );
	assert( list->id == SID_LIST );

	/* Create an item from the specified values */
	item = listitem_create( value1, value2 );

	/* If there are no items, make this the first item in the list */
	if ( list->first == NULL )
	{
		list->first = item;
		list->current = list->first;
	} else {
		/* If there are items, we are going to append to the end */
		last = list->first;										/* Start at the beginning */
		while( last->next != NULL )						/* Loop until the last item is found */
			last = last->next;
		last->next = item;										/* Point the last item to this one */
	}

	list->count++;													/* Increase the number of objects */
}

/* Return the number of items in a list */
int list_count( struct list_struct *list )
{
	assert( list );
	assert( list->id == SID_LIST );
	return list->count;
}

/* Go back to the beginning of the list */
void list_rewind( struct list_struct *list )
{
	assert( list );
	assert( list->id == SID_LIST );
	list->current = list->first;
}

/* Retrieve the current item, 1st data value */
void *list_get1( struct list_struct *list )
{
	assert( list );
	assert( list->id == SID_LIST );
	return list->current->value1;
}

/* Retrieve the current item, 2nd data value */
void *list_get2( struct list_struct *list )
{
	assert( list );
	assert( list->id == SID_LIST );
	return list->current->value2;
}

/* Set value1 */
void list_set1( struct list_struct *list, void *value )
{
	assert( list );
	assert( list->id == SID_LIST );
	list->current->value1 = value;
}

/* Set value2 */
void list_set2( struct list_struct *list, void *value )
{
	assert( list );
	assert( list->id == SID_LIST );
	list->current->value2 = value;
}

/* Move to the next item, remain at end if on last item */
void list_next( struct list_struct *list )
{
	assert( list );
	assert( list->id == SID_LIST );
	if ( list->current->next != NULL )
		list->current = list->current->next;
}

/* Remove all elements in the list */
void list_clear( struct list_struct *list )
{
	struct listitem_struct *temp;
	int listcount, i;

	assert( list );
	assert( list->id == SID_LIST );
	/* Rewind to delete all of the elements in the list */
	listcount=list_count(list);
	list_rewind( list );

	/* Loop through each element and delete each one */
	for( i=0; i<listcount; ++i )
	{
		temp=list->current->next;			/* Get the next element */
		free( list->current );				/* Free the current element */
		list->current=temp;						/* Set "current" to the next item to delete */
	}

	/* Reset the structure so that it knows there are no more elements */
	list->first = NULL;		
	list->current = NULL;
	list->count = 0;
}

/* Create a list from a string */
struct list_struct *list_create_from_string( char *input )
{
	struct list_struct *temp;
	char *pos;														/* Pointer for processing input */
	int processing_word=0;								/* 1 if in the middle of a word */
	int quoted_word=0;										/* 1 if the word is quoted */
	char *word_start=NULL;								/* Starting position of the word */
	char *better_word;										/* The word after processing of special
																					 characters */
	assert( input );

	/* Create an empty list structure */
	temp = list_create();

	/* Loop through the string character by character, the words to the returned
		 list */
	for( pos=input; *pos!=0; ++pos )
	{
		/* If we are not in the middle of processing a word, we need to find the
			 start of the word */
		if ( !processing_word ) 
		{
			/* If this is the start of a new word, we need to process it */
			if ( *pos != ' ' && *pos != '\t' )
			{
				processing_word = 1;							/* We are now processing a word */
				word_start = pos;									/* This is the starting character */
				if ( *pos == '"' )								/* If this is a quoted word then we */
					quoted_word = 1;								/*   flag it accordingly */
				else
					quoted_word = 0;
			} 
			/* If this is not the start of a new word, we skip to the next character
				 as part of the for loop */
		} else {
			/* We are processing a word.  We need to check to see if we have found the
				 end of the word */
			if ( quoted_word == 1 )
			{
				/* We are processing a quoted word, look for the ending quote */
				if ( *pos == '"' )
				{
					/* Found the ending quote.  Fix any embeded newlines and such, and
						 add the word to the list */
					better_word=fixword( word_start, pos );
					list_add( temp, better_word, NULL );
					processing_word=0;
				}
			} else {
				/* We are processing a non-quoted word, look for whitespace */
				if ( *pos == ' ' || *pos == '\t' )
				{
					/* Found the trailing whitespace.  Add the word (without processing)
						 to the list */
					better_word=calloc( 1, pos-word_start+1 );
					assert( better_word != NULL );
					memcpy( better_word, word_start, pos-word_start );
					list_add( temp, better_word, NULL );
					processing_word=0;
				}
			}
		}
	}  

	/* We are done processing the passed string.  If we were in the middle of a
		 word, we need to close it now */
	if ( processing_word )
	{
		if ( quoted_word )
		{
			/* If a quoted string does not have a trailing quote, we have a syntax
				 problem */
			printf( "Warning:  End of string reached and trailing quote not found!"
					"(ignoring word)\n%s\n", input );
		} else {
			/* If this is an unquoted word without trailing whitespace, that's ok */
			better_word=calloc( 1, pos-word_start+1 );
			assert( better_word != NULL );
			memcpy( better_word, word_start, pos-word_start );
			list_add( temp, better_word, NULL );
		}
	}

	return temp;
}

/* Given a quoted word, return the word without quotes and fix any embeded
	 characters such as newlines and tabs. */
static char *fixword( char *start, char *end )
{
	char *buffer;								/* The new word */
	char *pos;									/* Position within the original word */
	char *bpos;									/* Position within the buffer */

	assert( start );
	assert( end );

	buffer = calloc( 1, 1024 );
	assert( buffer != NULL );

	for( pos=start+1, bpos=buffer; pos<end; ++pos, ++bpos )
	{
		/* Is there a leading escape character? */
		if ( *pos == '\\' )
		{
			/* Is this a carriage return? */
			if ( *(pos+1) == '0' )			
			{
				*bpos = 0;						/* Store the NULL */
				++pos;									/* Skip 2 chars in the original string */
			}
			/* Is this a carriage return? */
			else if ( *(pos+1) == 'r' )			
			{
				*bpos = 13;						/* Store the CR */
				++pos;									/* Skip 2 chars in the original string */
			}
			/* Is this a newline combination? */
			else if ( *(pos+1) == 'n' )			
			{
				*bpos = '\n';						/* Store the newline */
				++pos;									/* Skip 2 chars in the original string */
			}
			/* Look for the tab sequence */
			else if ( *(pos+1) == 't' )
			{
				*bpos = '\t';						/* Store the tab */
				++pos;									/* Skip 2 chars in the original string */
			}
			/* Is this a literal backslash? */
			else if ( *(pos+1) == '\\' )
			{
				*bpos = '\\';					/* Store the backslash */
				++pos;									/* Skip 2 chars in the original string */
			}
			/* Anything else is an unrecognized escape sequence, skip the escape */
			else
			{
				printf( "Escape sequence \\%c is not valid.  Ignoring escape character",
						*(pos+1) );
				fflush( stdout );
			}
		}
		else
		/* This is not an escape sequence.  Store the character verbatim */
		{
			*bpos = *pos;
		}
	}

	/* Resize the buffer for only the characters in the resulting buffer */
	buffer = realloc( buffer, strlen(buffer)+1 );
	return buffer;
}

/* This function will free all of the elements in a list */
void list_free( struct list_struct *list )
{
	struct listitem_struct *temp;
	int listcount, i;

	assert( list );
	assert( list->id == SID_LIST );
	/* Rewind to delete all of the elements in the list */
	listcount=list_count(list);
	list_rewind( list );

	/* Loop through each element and delete each one */
	for( i=0; i<listcount; ++i )
	{
		/* Free the pointers before "clearing" them. */
		if ( list_get1( list ) != NULL )
			free( list_get1( list ) );
		if ( list_get2( list ) != NULL )
			free( list_get2( list ) );

		/* Clear the current element and move on to the next */
		temp=list->current->next;			/* Get the next element */
		free( list->current );				/* Free the current element */
		list->current=temp;						/* Set "current" to the next item to delete */
	}

	/* Reset the structure so that it knows there are no more elements */
	list->first = NULL;		
	list->current = NULL;
	list->count = 0;
}

/* Return 1 if we are already at the last item, 0 if not */
int list_atlast( struct list_struct *list )
{
	assert( list );
	assert( list->id == SID_LIST );
	if ( list->current->next != NULL )
		return 0;
	return 1;
}

/* Find an item in a list structure.  Returns 0 on not found.  */
int list_findfirst( struct list_struct *list, char *searchfor )
{
	assert( list );
	assert( list->id == SID_LIST );

	list_rewind( list );
	return list_findnext( list, searchfor );
}

/* Find the next item in a list structure.  Returns 0 on not found.  */
int list_findnext( struct list_struct *list, char *searchfor )
{
	assert( list );
	assert( list->id == SID_LIST );

	/* If there are no items, then we can't possibly have a match */
	if ( list_count(list) == 0 )
		return 0;

	while( 1 )
	{
		if ( strcmp((char *)list_get1(list), searchfor) == 0 )
			return 1;
		if ( list_atlast(list) == 1 )
			return 0;
		list_next(list);
	}
}

