/*
 * (c) Copyright 1992, 1993 by Panagiotis Tsirigotis
 * All rights reserved.  The file named COPYRIGHT specifies the terms 
 * and conditions for redistribution.
 */

static char RCSid[] = "$Id: strs.c,v 3.1 1993/06/13 02:50:39 panos Exp $" ;

#include <ctype.h>
#include <memory.h>
#include <varargs.h>

char *malloc() ;

#include "ss_impl.h"

/*
 * NOTE: The brute force method (with the __strs_bfops) must be always 
 * 		available so that we can switch to it if another method fails.
 */
extern struct ss_ops __strs_bfops ;
extern struct ss_ops __strs_rkops ;
extern struct ss_ops __strs_kmpops ;
extern struct ss_ops __strs_sbmops ;
extern struct ss_ops __strs_bmhops ;
extern struct ss_ops __strs_soops ;

/*
 * NOTE: This table is arranged according to increasing method number.
 *			This allows quick indexing into it using the user-provided
 *			method as a hint:
 *				if ( selection_table[ user_method ].method == user_method )
 *					FOUND
 *				else
 *					DO SEQUENTIAL SEARCH
 *			This allows both quick access and a change of method numbers
 *			in the future without requiring recompilation of programs in
 *			order to work with new versions of the library.
 */
static struct ss_select selection_table[] =
	{
		{ STRS_BF,					&__strs_bfops		},
		{ STRS_RK,					&__strs_rkops		},
		{ STRS_KMP,					&__strs_kmpops		},
		{ STRS_SBM,					&__strs_sbmops		},
		{ STRS_BMH,					&__strs_bmhops		},
		{ STRS_SO,					&__strs_soops		},
		{ 0,							0						}
	} ;

static char identity_map[ ALPHABET_SIZE ] ;
static char upper_to_lower_map[ ALPHABET_SIZE ] ;

static int tables_initialized ;

/*
 * This header is returned when an empty pattern is given to strs_setup.
 * The rest of the functions check ss_patlen and do nothing if that is zero.
 * ss_patlen in this header will be initialized to zero.
 */
static header_s empty_pattern_header ;


PRIVATE void initialize_tables()
{
	int i ;

	for ( i = 0 ; i < sizeof( upper_to_lower_map ) ; i++ )
	{
		if ( isascii( i ) && isupper( i ) )
			upper_to_lower_map[ i ] = i + 'a' - 'A' ;
		else
			upper_to_lower_map[ i ] = i ;
		identity_map[ i ] = i ;
	}
}


/*
 * Initializes header
 *
 * Note that 'pattern' does not need to be a NUL-terminated string.
 */
PRIVATE int init( hp, flags, pattern, patlen )
	register header_s		*hp ;
	int						flags ;
	char						*pattern ;
	int						patlen ;
{
	int requested_method = SS_GETMETHOD( flags ) ;
	register struct ss_select *selp ;

	if ( ! tables_initialized )
	{
		initialize_tables() ;
		tables_initialized = TRUE ;
	}

	/*
	 * Initialize header fields
	 */
	SS_FLAGS( hp ) = SS_GETFLAGS( flags ) ;
	SS_PATLEN( hp ) = patlen ;
	if ( SS_SWITCH( hp ) && patlen < 4 )
		SS_OPS( hp ) = &__strs_bfops ;		/* brute force */
	else
	{
		/*
		 * Determine ops
		 */
		if ( selection_table[ requested_method ].sel_method == requested_method )
			selp = &selection_table[ requested_method ] ;
		else
			for ( selp = &selection_table[ 0 ] ; selp->sel_ops ; selp++ )
				if ( requested_method == selp->sel_method )
					break ;
		if ( selp->sel_ops )
			SS_OPS( hp ) = selp->sel_ops ;
		else if ( SS_SWITCH( hp ) )
			SS_OPS( hp ) = &__strs_bfops ;		/* brute force */
		else
			return( SS_ERR ) ;
	}

	if ( SS_MALLOC( hp ) )
	{
		SS_PATTERN( hp ) = malloc( (unsigned)SS_PATLEN( hp ) ) ;
		if ( SS_PATTERN( hp ) == CHAR_NULL )
		{
			(void) free( (char *)hp ) ;
			return( SS_ERR ) ;
		}
		(void) memcpy( SS_PATTERN( hp ), pattern, (int)SS_PATLEN( hp ) ) ;
	}
	else
		SS_PATTERN( hp ) = pattern ;

	/*
	 * If the user asked for case-insensitive search, we create our own
	 * copy of the pattern in lower case. If the pattern is malloc'ed
	 * we overwrite, otherwise we malloc some memory and clear the
	 * STRS_NOMALLOC flag.
	 */
	if ( SS_IGNCASE( hp ) )
	{
		char *new_pattern ;
		register int i ;

		SS_SETMAP( hp, upper_to_lower_map ) ;

		if ( SS_MALLOC( hp ) )
			new_pattern = SS_PATTERN( hp ) ;
		else
		{
			new_pattern = malloc( (unsigned)SS_PATLEN( hp ) + 1 ) ;
			if ( new_pattern == CHAR_NULL )
				return( SS_ERR ) ;
			SS_SETMALLOC( hp ) ;			/* clears the STRS_NOMALLOC flag */
		}
		for ( i = 0 ; i < SS_PATLEN( hp ) ; i++ )
			new_pattern[ i ] = SS_MAP( hp, SS_PATTERN( hp )[ i ] ) ;
		SS_PATTERN( hp ) = new_pattern ;
	}
	else
		SS_SETMAP( hp, identity_map ) ;

	for ( ;; )
	{
		if ( SS_SETUP( hp ) == SS_OK )
			return( SS_OK ) ;
		else
		{
			if ( ! SS_SWITCH( hp ) || SS_OPS( hp ) == &__strs_bfops )
			{
				if ( SS_MALLOC( hp ) )
					(void) free( (char *)hp ) ;
				return( SS_ERR ) ;
			}
			SS_OPS( hp ) = &__strs_bfops ;
		}
	}
}


/*
 * Finalize header
 */
PRIVATE void fini( hp )
	header_s *hp ;
{
	SS_DONE( hp ) ;
	if ( SS_MALLOC( hp ) )
		(void) free( SS_PATTERN( hp ) ) ;
}


/*
 * Create a search handle
 */
strs_h strs_setup( flags, pattern, va_alist )
	int	flags ;
	char	*pattern ;
	va_dcl
{
	header_s		*hp ;
	int			patlen ;
	va_list		ap ;

	hp = HP( malloc( sizeof( *hp ) ) ) ;
	if ( hp == NULL )
		return( NULL_HANDLE ) ;
	
	if ( flags & STRS_PATLEN )
	{
		va_start( ap ) ;
		patlen = va_arg( ap, int ) ;
		va_end( ap ) ;
	}
	else
		patlen = strlen( pattern ) ;

	if ( patlen == 0 )
		return( (strs_h) &empty_pattern_header ) ;

	if ( init( hp, flags, pattern, patlen ) == SS_OK )
		return( (strs_h)hp ) ;
	else
	{
		free( (char *)hp ) ;
		return( NULL_HANDLE ) ;
	}
}


/*
 * Destroy a search handle
 */
void strs_done( handle )
	strs_h handle ;
{
	header_s *hp = HP( handle ) ;

	if ( SS_PATLEN( hp ) != 0 )
	{
		fini( hp ) ;
		(void) free( (char *) handle ) ;
	}
}


char *strs_match( handle, str, len )
	strs_h	handle ;
	char		*str ;
	int		len ;
{
	register header_s *hp = HP( handle ) ;

	if ( SS_PATLEN( hp ) == 0 )
		return( str ) ;
	if ( SS_PATLEN( hp ) > len )
		return( CHAR_NULL ) ;
	return( SS_MATCH( hp, str, len ) ) ;
}



char *strs_search( flags, str, len, pattern, va_alist )
	int	flags ;
	char	*str ;
	int	len ;
	char	*pattern ;			/* NUL-terminated */
	va_dcl
{
	header_s		t_header ;
	char			*p ;
	int			patlen ;
	va_list		ap ;

	if ( flags & STRS_PATLEN )
	{
		va_start( ap ) ;
		patlen = va_arg( ap, int ) ;
		va_end( ap ) ;
	}
	else
		patlen = strlen( pattern ) ;

	if ( patlen == 0 )
		return( str ) ;

	if ( patlen > len )
		return( CHAR_NULL ) ;

	if ( init( &t_header, flags | STRS_NOMALLOC, pattern, patlen ) == SS_OK )
	{
		p = SS_MATCH( &t_header, str, len ) ;
		fini( &t_header ) ;
		return( p ) ;
	}
	else
		return( CHAR_NULL ) ;
}


