/*
** General Purpose Expression Filter Code.
**
** You need a BitHook and a FilterSpec to customize this.
**
** Your data needs to have the ability to flag certain entries
** with certain bits and select some of them with mask and match
** values, just as UMS. This is absolutely necessary for a fast
** and efficient filter handling!
**
** This code is prepared to have complexity values for expressions.
** Complex expressions (that might take a long time to evaluate)
** would be calculated after less complex expressions already
** clipped away a considerable amount of data.
**
** sumstools don't make use of these complexity values for now,
** all complexity values are set to 0.
*/

#include <exec/types.h>

#include <utility/hooks.h>

#include <dos/dos.h>
#include <dos/datetime.h>
#include <exec/types.h>
#include <libraries/ums.h>

#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/locale_protos.h>
#include <clib/alib_protos.h>


#ifdef _DCC
	#include <proto/exec.h>
	#include <proto/dos.h>
	#include <proto/locale.h>
#else
	#include <pragmas/exec_pragmas.h>
	#include <pragmas/dos_pragmas.h>
	#include <pragmas/locale_pragmas.h>
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#include <exec/memory.h>

#include "filter.h"

#define between(a,x,b) ((UBYTE)(x)>=(UBYTE)(a) && (UBYTE)(x)<=(UBYTE)(b))


struct Expression
{
	struct Expression *left;
	struct Expression *right;

	struct Hook *hook;
	ULONG uid;
	APTR udata;

	UBYTE relation;
	UBYTE complexity;

	char data[1];
};

#define REL_NONE      0
#define REL_FIRST     1
#define REL_LOWEQ     1
#define REL_HIGHEQ    2
#define REL_LOWER     3
#define REL_HIGHER    4
#define REL_NOTEQUAL  5
#define REL_EQUAL     6
#define REL_LAST      6

#define CON_FIRST     7
#define CON_OR        7
#define CON_AND       8
#define CON_LAST      8

#define OPEN_BRACE    9
#define CLOSE_BRACE   10

struct FilterTokenize
{
	char *name;
	char token;
};

static struct FilterTokenize Tokens[] =
{
	{ "OR" , CON_OR       },
	{ "|"  , CON_OR       },
	{ "AND", CON_AND      },
	{ "&"  , CON_AND      },
	{ "<=" , REL_LOWEQ    },
	{ ">=" , REL_HIGHEQ   },
	{ "<"  , REL_LOWER    },
	{ ">"  , REL_HIGHER   },
	{ "!=" , REL_NOTEQUAL },
	{ "<>" , REL_NOTEQUAL },
	{ "="  , REL_EQUAL    },
	{ "("  , OPEN_BRACE   },
	{ ")"  , CLOSE_BRACE  },
	{ NULL , 0            }
};


static char *FilterTokenize(char *spec,struct FilterTokenize *tokens)
{
	struct FilterTokenize *x;
	char *str = spec;
	BOOL anf = FALSE;
	int len;

	while (*str)
	{
		if (*str=='"')
		{
			anf = !anf;
			memmove(str,str+1,strlen(str));
		}
		else if (!anf)
		{
			if (*str==' ')
			{
				memmove(str,str+1,strlen(str));
			}
			else
			{
				for (x=tokens;x->name;x++)
				{
					if (!strnicmp(str,x->name,len=strlen(x->name)))
					{
						*str = x->token;

						if (len>1)
							memmove(str+1,str+len,strlen(str+len)+1);

						break;
					}
				}

				if (!x->name)
					str++;
			}
		}
		else
		{
			str++;
		}
	}

	return(spec);
}


static struct FilterSpec *FindFilterSpec(struct FilterSpec *spec,char *name,int len)
{
	struct FilterSpec *fs;

	for (fs=spec;fs->keyword;fs++)
		if (!(strnicmp(fs->keyword,name,len)))
			return(fs);

	return(NULL);
}


#define FCERROR ((char *)-1)

static char *findconj(char *str,int len)
{
	int blevel = 0;

	while (len--)
	{
		if (!blevel && between(CON_FIRST,*str,CON_LAST))
		{
			return(str);
		}
		else if (*str==OPEN_BRACE)
		{
			blevel++;
		}
		else if (*str==CLOSE_BRACE)
		{
			if (!blevel) return(FCERROR);
			blevel--;
		}
		str++;
	}

	return(blevel ? FCERROR : NULL);
}


static VOID deleteexp(struct Expression *exp)
{
	if (exp)
	{
		deleteexp(exp->left);
		deleteexp(exp->right);
		FreeVec(exp);
	}
}


static APTR createexp(LONG *err,struct Hook *hook,struct FilterSpec *spec,char *str,int len)
{
	char *c;
	struct Expression *exp;

	if (len==-1) len=strlen(str);

	if (len<1)
	{
		*err = FIL_ERROR_EMPTYEXPRESSION;
		return(NULL);
	}

	if ((c=findconj(str,len)) == FCERROR)
	{
		*err = FIL_ERROR_UNMATCHEDBRACES;
		return(NULL);
	}

	if (c)
	{
		if (exp=AllocVec(sizeof(struct Expression),MEMF_CLEAR))
		{
			if (exp->left = createexp(err,hook,spec,str,c-str))
			{
				if (exp->right = createexp(err,hook,spec,c+1,len-(c-str)-1))
				{
					exp->hook = hook;
					exp->relation = *c;
					return(exp);
				}
			}
			deleteexp(exp);
		}
		else
			*err = FIL_ERROR_OUTOFMEMORY;

		return(NULL);
	}
	else if (str[0]==OPEN_BRACE)
	{
		if (str[len-1]!=CLOSE_BRACE)
		{
			*err = FIL_ERROR_UNMATCHEDBRACES;
			return(NULL);
		}
		return(createexp(err,hook,spec,str+1,len-2));
	}
	else if (len>=3)
	{
		int l;
		struct FilterSpec *fs;

		c = str;

		while (*c && !between(REL_FIRST,*c,REL_LAST))
			c++;

		if (!*c)
		{
			*err = FIL_ERROR_MISC;
			return(NULL);
		}

		if ((l = c - str)<1)
		{
			*err = FIL_ERROR_MISC;
			return(NULL);
		}

		if (!(fs = FindFilterSpec(spec,str,l)))
		{
			*err = FIL_ERROR_UNKNOWNKEYWORD;
			return(NULL);
		}

		str += l+1;
		len -= l+1;

		if (len<0)
		{
			*err = FIL_ERROR_MISC;
			return(NULL);
		}

		if (!(exp=AllocVec(sizeof(struct Expression)+len,MEMF_CLEAR)))
		{
			*err = FIL_ERROR_OUTOFMEMORY;
			return(NULL);
		}

		exp->hook       = hook;
		exp->uid        = fs->id;
		exp->udata      = fs->data;
		exp->relation   = *c;
		exp->complexity = 0;
		if (len>0) strncpy(exp->data,str,len);

		return(exp);
	}

	*err = FIL_ERROR_MISC;
	return(NULL);
}


static LONG filterexp(struct Expression *exp,APTR data,ULONG mask,ULONG match,ULONG setbit)
{
	int err;
	ULONG help = setbit<<1;

	/*printf("fexp: mask %08lx match %08lx set %08lx\n",mask,match,setbit);*/

	if (help & (FIL_LASTBIT>>2)) return(FIL_ERROR_TOODEEP);

	mask |= setbit;

	switch (exp->relation)
	{
		case CON_AND:
		{
			ULONG help2 = help<<1;

			if (exp->left->complexity <= exp->right->complexity)
			{
				if (err=filterexp(exp->left ,data,mask,match,help)) return(err);
				if (err=filterexp(exp->right,data,mask|help,match|help,help2)) return(err);
			}
			else
			{
				if (err=filterexp(exp->right,data,mask,match,help)) return(err);
				if (err=filterexp(exp->left ,data,mask|help,match|help,help2)) return(err);
			}
			if (err=CallHook(exp->hook,data,exp->uid,exp->udata,mask|help|help2,match|help|help2,setbit,help2,FIL_ACTION_BITS,NULL)) return(err);
			if (err=CallHook(exp->hook,data,exp->uid,exp->udata,mask|help,match|help,0,help,FIL_ACTION_BITS,NULL)) return(err);
		}
		break;

		case CON_OR:
		{
			if (exp->left->complexity <= exp->right->complexity)
			{
				if (err=filterexp(exp->left ,data,mask,match,setbit)) return(err);
				if (err=filterexp(exp->right,data,mask,match,setbit)) return(err);
			}
			else
			{
				if (err=filterexp(exp->right,data,mask,match,setbit)) return(err);
				if (err=filterexp(exp->left ,data,mask,match,setbit)) return(err);
			}
		}
		break;

		default:
		{
			switch (exp->relation)
			{
				case REL_EQUAL:
				{
		 			if (err=CallHook(exp->hook,data,exp->uid,exp->udata,mask,match,setbit,0,FIL_ACTION_EQUAL,exp->data)) return(err);
				}
				break;

				case REL_NOTEQUAL:
				{
		 			if (err=CallHook(exp->hook,data,exp->uid,exp->udata,mask,match,help,0,FIL_ACTION_EQUAL,exp->data)) return(err);
		 			if (err=CallHook(exp->hook,data,exp->uid,exp->udata,mask|help,match,setbit,0,FIL_ACTION_BITS,NULL)) return(err);
		 			if (err=CallHook(exp->hook,data,exp->uid,exp->udata,mask|help,match|help,0,help,FIL_ACTION_BITS,NULL)) return(err);
				}
				break;

				case REL_LOWEQ:
				{
		 			if (err=CallHook(exp->hook,data,exp->uid,exp->udata,mask,match,setbit,0,FIL_ACTION_EQUAL,exp->data)) return(err);
				}
				case REL_LOWER:
				{
		 			if (err=CallHook(exp->hook,data,exp->uid,exp->udata,mask,match,setbit,0,FIL_ACTION_LOWER,exp->data)) return(err);
				}
				break;

				case REL_HIGHER:
				{
		 			if (err=CallHook(exp->hook,data,exp->uid,exp->udata,mask,match,help,0,FIL_ACTION_EQUAL,exp->data)) return(err);
				}
				case REL_HIGHEQ:
				{
		 			if (err=CallHook(exp->hook,data,exp->uid,exp->udata,mask,match,help,0,FIL_ACTION_LOWER,exp->data)) return(err);
		 			if (err=CallHook(exp->hook,data,exp->uid,exp->udata,mask|help,match,setbit,0,FIL_ACTION_BITS,NULL)) return(err);
		 			if (err=CallHook(exp->hook,data,exp->uid,exp->udata,mask|help,match|help,0,help,FIL_ACTION_BITS,NULL)) return(err);
				}
				break;

			}
		}
		break;
	}

	return(FIL_ERROR_OK);
}



/*
** That's the master function. You call only this one from outside.
*/

LONG FilterExpression(struct Hook *hook,struct FilterSpec *spec,char *str,APTR data,ULONG mask,ULONG match,ULONG setbit)
{
	LONG err;
	struct Expression *exp;
	char *dup;

	if (dup=AllocVec(strlen(str)+1,MEMF_ANY))
	{
		strcpy(dup,str);

		if (exp=createexp(&err,hook,spec,FilterTokenize(dup,Tokens),strlen(dup)))
		{
			err=filterexp(exp,data,mask,match,setbit);
			deleteexp(exp);
		}

		FreeVec(dup);
	}

	return(err);
}
