/*	fncall.c
 *  (c) Copyright 1991 by Ben Eng, All Rights Reserved
 *
 */

#include <clib/exec_protos.h>

#include <ctype.h>
#include <scdir.h>

#include "make.h"
#include "depend.h"

#if FNCALLS

/*	pattern matching routine */
int
ismatch( char *pat, char *text )
{
	char *wild;
	int len;

	debugprintf( 7, ( "ismatch(%s,%s)\n", pat, text ));

	wild = find_token( pat, '%' );

	if( !wild) return( strcmp( pat, text ) ? 0 : 1 );

	/* the length of the part of pat before the % */
	len = (int)(wild - pat);

	if( len && strncmp( pat, text, len )) return( 0 ); /* no match */

	/* the length of the part of pat after the % */
	len = (int)(pat + strlen(pat) - wild - 1);
	if( len && strncmp( wild + 1, text + strlen(text) - len, len ))
			return( 0 ); /* no match */

	return( 1 ); /* match */
}

/*	function calls (mac, string)
 *
 *	The string parameter is the rest of the arguments passed to the
 *	function call.  string is modifiable in fncalls because it is
 *	actually contained within the macroname array[] which isn't
 *	really used again after the fncall returns.
 *
 *	returns 0 if successful, 1 if error
 *	with side effect:  mac->expansion = strdup( result_string );
 *
 */

static char argument_missing[] = "argument missing: %s %s\n";
static char error_no_memory[] = "error:  no memory\n";

static int
fn_basename( struct macro *mac, char *string )
{
	char *filelist = NULL;
	char *out, *text, *cptr;
	int len = 0;
	char word[ MAXPATHNAME ];

	filelist = (char *)calloc( Param.MaxLine, 1 );
	if( !filelist ) {
		logfile( error_no_memory );
		return( 1 );
	}
	
	out = filelist;
	text = string;

	while( len < Param.MaxLine ) {
		text = parse_str( word, text, sizeof(word));
		if( !*word ) break; /* no more words in text */
		if( (cptr = strrchr( word, '.' )) && ( basename(word) <= cptr )) {
			*cptr = (char)0; /* truncate the word at the `.' */
		}
		len += strlen( word );
		if( len < Param.MaxLine ) {
			if( out != filelist ) {
				*out++ = ' ';
				len++;
			}
			strcpy( out, word );
			out = filelist + len;
		}
	} /* while */
	*out = (char)0;
	if( len > 0 ) mac->expansion = strdup( filelist );
	free( filelist );
	return( 0 );
}

static int
do_addfix( struct macro *mac, char *string, int how_fix )
{
	char *filelist = NULL;
	char *suf, *str = string;

	filelist = (char *)calloc( Param.MaxLine, 1 );
	if( !filelist ) {
		logfile( error_no_memory );
		return( 1 );
	}

	while( isspace( *str )) str++;
	suf = str;
	while( *str && *str != ',' && str[-1] != '\\' ) str++;
	if( *suf != ',' && *str == ',' && str[-1] != '\\' ) {
		char *out, *text, *cptr;
		int suflen, len = 0;
		char word[ MAXPATHNAME ];

		*str++ = (char)0; /* null terminate the suffix */
		suflen = strlen( suf );
		out = filelist;
		text = str;

		while( len < Param.MaxLine ) {
			text = parse_str( word, text, sizeof(word));
			if( !*word ) break; /* no more words in text */
			if( (strlen( word )+suflen) < sizeof(word)) {
				if( how_fix ) /* append the suffix */
					strcat( word, suf );
				else { /* prepend the prefix */
					len += strlen( suf );
					if( len + 1 < Param.MaxLine ) {
						if( out != filelist ) {
							*out++ = ' ';
							len++;
						}
						strcpy( out, suf );
						out = filelist + len;
					}
				}
			}
			len += strlen( word );
			if( len + 1 < Param.MaxLine ) {
				if( out != filelist & how_fix ) {
					*out++ = ' ';
					len++;
				}
				strcpy( out, word );
				out = filelist + len;
			}
		} /* while */
		*out = (char)0;
		if( len > 0 ) mac->expansion = strdup( filelist );
	}
	free( filelist );
	return( 0 );
}

static int
fn_addprefix( struct macro *mac, char *string )
{
	return( do_addfix( mac, string, 0 ));
}

static int
fn_addsuffix( struct macro *mac, char *string )
{
	return( do_addfix( mac, string, 1 ));
}

static int
do_filter( struct macro *mac, char *string, int how_filter )
{
	char *str = string;
	char *pat;
	int len;

	while( isspace( *str )) str++;
	pat = str;
	while( *str && *str != ',' && str[-1] != '\\' ) str++;
	if( *pat != ',' && *str == ',' && str[-1] != '\\' ) {
		char word[ 80 ];
		char *text, *out;

		*str++ = (char)0;

		if( !( mac->expansion = strdup( str )))
			return( 0 ); /* no mem */
		out = mac->expansion;

		for( text = out;; ) {
			text = parse_str( word, text, sizeof(word));
			if( !*word ) break; /* no more words in text */

			if( how_filter ) {
				if( ismatch( pat, word )) {
					out = text + 1;
				}
				else { /* no match remove it */
					while( isspace( *text )) text++;
					shift_string_left( out, (int)(text - out));
					text = out;
				}
			}
			else {
				if( ismatch( pat, word )) {
					while( isspace( *text )) text++;
					shift_string_left( out, (int)(text - out));
					text = out;
				}
				else { /* no match remove it */
					out = text + 1;
				}
			}
		} /* for */
		*out = (char)0;
		return( 0 );
	}
	logprintf( argument_missing, "filter", string );
	return( 1 );
}

static int
fn_filter( struct macro *mac, char *string )
{
	return( do_filter( mac, string, 1 ));
}

static int
fn_filter_out( struct macro *mac, char *string )
{
	return( do_filter( mac, string, 0 ));
}

static int
fn_findstring( struct macro *mac, char *string )
{
	char *find, *in, *str = string;
	int len;
	
	while( isspace( *str )) str++;
	find = str;
	while( *str && *str != ',' && str[-1] != '\\' ) str++;
	if( *find != ',' && *str == ',' && str[-1] != '\\' ) {
		*str++ = (char)0;
		in = str;
		while( *str ) str++;	/* find end string */
		len = strlen( find );
		str -= len;	/* str marks the end of searching */

		while( in <= str ) {
			/* logprintf( "looking for \"%s\" in \"%s\"\n", find, in ); */

			if( !strncmp( find, in, len )) { /* found */
				mac->expansion = strdup( find );
				break;
			}
			in++; 
		}
	}
	else {
		logprintf( argument_missing, "findstring", string );
		return( 1 );
	}
	return( 0 );
}

static int
fn_strip( struct macro *mac, char *str )
{
	while( isspace( *str )) str++;
	if( *str && (mac->expansion = strdup( str ))) {
		register char *d = mac->expansion;
		register char *s = mac->expansion;
		while( *s ) {
			*d++ = *s;
			if( isspace(*s)) while( isspace( *s)) s++;
			else s++;
		}
		if( isspace( *d )) {
			while( isspace( *d ) && d > mac->expansion ) d--;
			d++;
		}
		*d = (char)0;
	}
	return( 0 );
}

static int
fn_subst( struct macro *mac, char *string )
{
	char *str = string;
	char *from, *to, *text;
	int len;
	
	while( isspace( *str )) str++;
	from = str;
	while( *str && *str != ',' && str[-1] != '\\' ) str++;
	if( *from != ',' && *str == ',' && str[-1] != '\\' ) {
		*str++ = (char)0;
		to = str;
		while( *str && *str != ',' && str[-1] != '\\' ) str++;
		if( *to != ',' && *str == ',' && str[-1] != '\\' && str[ 1 ] ) {
			*str++ = (char)0;
			text = str;
			while( *str ) str++;	/* find end string */
			len = strlen( from );

			if( strlen( to ) != len ) {
				logprintf( "subst: args not the same size %s,%s\n",
					from, to );
				return( 1 );
			}

			text = mac->expansion = strdup( text );
			str = text + strlen( text ) - len; /* end of search */
			while( text <= str ) {
				if( !strncmp( from, text, len )) { /* found */
					strncpy( text, to, len );
				}
				text++; 
			}
			return( 0 );
		}
	}
	logprintf( argument_missing, "subst", string );
	return( 1 );
}

static int
fn_wildcard( struct macro *mac, char *string )
{
	char *filelist = NULL;
	char *pat = string;

	filelist = (char *)calloc( Param.MaxLine, 1 );
	if( !filelist ) {
		logfile( error_no_memory );
		return( 1 );
	}
	while( isspace( *pat )) pat++;
	if( *pat ) {
		char *out = filelist;
		char *fn;
		int len = 0;

		while( len < Param.MaxLine ) {
			if( !(fn = scdir( pat ))) break; /* no more */
			if( out != filelist ) {
				*out++ = ' ';
				len++;
			}
			len += strlen( fn );
			if( len < Param.MaxLine ) {
				strcpy( out, fn );
				out = filelist + len;
			}
		}
		if( len > 0 ) mac->expansion = strdup( filelist );
		scdir_abort();
	}
	free( filelist );
	return( 0 );
}


int
do_word( struct macro *mac, char *string, int word )
{
	char *begin, *end;

	if( begin = find_word( string, word )) {
		end = begin;
		while( *end && !isspace( *end )) end++;
		*end = (char)0; /* null terminate past the end of word */
		mac->expansion = strdup( begin );
	}
	return( 0 );
}

static int
fn_firstword( struct macro *mac, char *string )
{
	while( isspace( *string )) string++;
	return( do_word( mac, string, 1 ));
}

static int
fn_word( struct macro *mac, char *string )
{
	char *comma;
	int word;

	while( isspace( *string )) string++;
	if( *string && (comma = find_token( string, ',' ))) {
		*comma = (char)0;
		word = atoi( string );
		/* else word not found */
		return( do_word( mac, comma + 1, word ));
	}
	logprintf( argument_missing, "word", string );
	return( 1 );
}

static int
fn_words( struct macro *mac, char *string )
{
	char buf[ 20 ];
	sprintf( buf, "%d", count_args( string ));
	mac->expansion = strdup( buf );
	return( 0 );
}

static int
fn_notimp( struct macro *mac, char *string )
{
	logfile( "Function call not implemented\n" );
	return( 1 );
}

/*************************************************************************/

/* sorted array for binary search */
#define MAX_FNCALL 21
static struct fncall fncarray[ MAX_FNCALL ] = {
	"addprefix",	fn_addprefix,
	"addsuffix",	fn_addsuffix,
	"basename",		fn_basename,
	"dir",			fn_notimp,
	"filter",		fn_filter,
	"filter-out",	fn_filter_out,
	"findstring",	fn_findstring,
	"firstword",	fn_firstword,
	"foreach",		fn_notimp,
	"join",			fn_notimp,
	"notdir",		fn_notimp,
	"origin",		fn_notimp,
	"patsubst",		fn_notimp,
	"shell",		fn_notimp,
	"sort",			fn_notimp,
	"strip",		fn_strip,
	"subst",		fn_subst,
	"suffix",		fn_notimp,
	"wildcard",		fn_wildcard,
	"word",			fn_word,
	"words",		fn_words
};

/*	binary search to find the function */
struct fncall *
find_fncall( char *name )
{
	register struct fncall *array = fncarray;
	int first = 0L;
	int last = MAX_FNCALL - 1;
	int mid;
	int diff;

	/* binary search */
	while( first <= last ) {
		mid = (first+last) / 2;
		diff = strcmp( name, array[ mid ].name );
		if( !diff )
			return( &array[ mid ] ); /* found */
		if( first == last ) break; /* not found */
		if( diff < 0 ) last = mid - 1;
		else first = mid + 1;
	}
	return( NULL ); /* not found */
}

#else

/*	No FNCALLS */
struct fncall *
find_fncall( char *name )
{
	return( NULL ); /* not found */
}

#endif

