#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include "config.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tracker-dbus.h"
#include "tracker-indexer.h"
#include "tracker-glob.h"

#define GPNULL ((Aglob *)NULL)

#define dispose(p) if (!(p)) ; else (free(p), (p) = (void*)0)

#define last_ptn(ptn)	((ptn).type == GLOB_CHAR && (ptn).u.cp == NULL)

#define inrange(rp,c)	((rp) == NULL || ( (rp) [(c) / CHAR_BIT] & (1 << ((c) % CHAR_BIT))) != 0)

extern Tracker	*tracker;


static char *
range(char *string, char **rpp)
{
	char	*rp;
	int	invert;
	int	i;

	if (rpp != NULL) { 
		rp = (char *) malloc (RANGESIZE);
		memset ((rp), 0, (RANGESIZE));	
	}	

	if ((invert = (*string == '^')))
		string++;

	while (*string != '\0' && *string != ']') {
		char	c1,c2;

		if (*string == '\\')
			if (*++string == '\0')
				break;
			else
				c1 = *string++;
		else
			if (*string == '-')
				c1=0;
			else
				c1 = *string++;

	
		if (*string != '-')
			c2=c1;
		else
			if (*++string == '\\')
				if (*++string == '\0')
					break;
				else
					c2 = *string++;
			else
				if (*string == '-' || *string == ']')
					c2 = CHARSPERRANGE - 1;
				else
					c2 = *string++;

		if (rpp != NULL) {
			for (i = c1; i <= c2; i++)
				rp[i / CHAR_BIT] |= (1 << (i % CHAR_BIT));
		}
	}

	if (*string != ']') {
		if (rpp != NULL)
			dispose (rp);
		return NULL;
	}
	else
		string++;

	if (invert && rpp != NULL) {
		for (i = 0; i < RANGESIZE; i++)
			rp[i] ^= ~0; 
	}

	if (rpp != NULL) {
		*rpp = rp;
	}
	
	return string;
}


static char *
subglob_parse (char *string, int inner, Aglob **ptnp) 
{
	char *cp, *cp2;
	int nptns;
	Aglob	*ptns, *ptnp2;
	int invert;

	cp = string;
	nptns = 1;	/* <-- allows for terminator */
	while (*cp != '\0' && (!inner || *cp != '}')) {
		switch (*cp) { 
			case '\\' : 
					if (*++cp != '\0')
						cp++;
					break;
		 	case '[' : 
					if ((cp2 = range (cp+1, NULL)) != NULL)
						cp = cp2;
					else
						cp++;
					break;
		  	case '!' : 
					if (*++cp != '\0')
						nptns--;
					break;
		   	case '?' :
		  	case '*' :
					cp++;
					break;
		 	case '{' :
					if ((cp2 = subglob_parse (cp+1, TRUE, GPNULL)) != NULL)
						cp = cp2;
					else
						cp++;
					break;
		    	default :
					cp++;
					break;
		  }
		nptns++;
	}

	/* not a sub-pattern */
	if (inner && *cp != '}')
		return NULL;

	if (ptnp == NULL) {
		if (inner)
			cp++;
		return cp;
	}

	ptns = ((Aglob *) malloc ((nptns) * sizeof (Aglob)));
	ptnp2 = ptns;
	cp = string;
	invert = FALSE;
	while (*cp != '\0' && (!inner || *cp != '}')) {
		Aglob	ptn;
		int is_invert;

		is_invert = FALSE;
		switch (*cp) {

			case '\\':
				if (cp[1] != '\0')
					cp++;

			 	ptn.type = GLOB_CHAR;
			 	ptn.u.cp = cp;
			 	cp++;
				break;

		    case '[': 
				if ((cp2 = range (cp+1, &ptn.u.rp)) != NULL) { 
					ptn.type = GLOB_RANGE;
					cp = cp2;
				}
				else {
					ptn.type = GLOB_CHAR;
			 		ptn.u.cp = cp;
				 	cp++;
				 }
				break;

		    case '!':
				if (*++cp != '\0') {
					invert ^= TRUE;
					is_invert = TRUE;
				}
				else { 
					ptn.type = GLOB_CHAR;
					ptn.u.cp = cp - 1;
				  }
				break;

		    case '?': 
				invert = FALSE;
				ptn.type = GLOB_RANGE;
				ptn.u.rp = NULL;
				cp++;
				break;

		    case '*':
				ptn.type = GLOB_STAR;
				cp++;
				break;

		    case '{': 
				if ((cp2 = subglob_parse (cp+1,TRUE, &ptn.u.ptn)) != NULL) {
					ptn.type = GLOB_PTN;
					cp = cp2;
				}
				else {
					ptn.type = GLOB_CHAR;
					ptn.u.cp = cp;
					cp++;
				}
				break;

		    default: 
				ptn.type = GLOB_CHAR;
				ptn.u.cp = cp;
				cp++;
				break;
		  }

		  if (!is_invert) {
			ptn.invert = invert;
			invert = FALSE;
			*ptnp2++ = ptn;
		}
	}

	ptnp2->type = GLOB_CHAR;
	ptnp2->u.cp = NULL;

	if (*cp != '\0' && inner)
		cp++;

	*ptnp = ptns;

	return cp;
}


Aglob *
glob_parse (char *string)
{
	Aglob	*ptn;
	char *cp;

	if ((cp = subglob_parse (string, FALSE, &ptn)) == NULL)
		return NULL;

	return ptn;
}


gboolean
globsto (char *string, Aglob ptn[])
{
	gboolean match;

	for (match = TRUE; match && !last_ptn(*ptn); ptn++)
		switch(ptn->type) {
	
			case GLOB_CHAR:
				match = ((*string == ptn->u.cp[0]) ^ ptn->invert);
				string++;
				break;
	
			case GLOB_RANGE:
				match = (inrange(ptn->u.rp,*string) ^ ptn->invert);
				string++;
				break;

			case GLOB_STAR: 
				if (ptn->invert)
					match = FALSE;
				else {
					char *tail;
					for (tail = string + strlen (string); tail >= string; tail--)
						if (globsto (tail, ptn+1))
							break;

					match = (tail >= string);
					while (!last_ptn(ptn[1]))
						ptn++;
					string += strlen (string);
				}
				break;

			case GLOB_PTN: 
				{
					char	*tail;
				 	for (tail = string + strlen (string); tail >= string; tail--)
						if (globsto (tail, ptn+1)) {
							char	c;
							c = *tail;
							*tail = '\0';
							match = (globsto (string, ptn->u.ptn) ^ ptn->invert);
							*tail = c;
							if (match)
								break;
						}

					match = (tail >= string);
					while (!last_ptn(ptn[1]))
						ptn++;
					string += strlen (string);
				}
				break;

			default: 
				break;
		}
	
	if (match && *string == '\0' && last_ptn(*ptn))
		return TRUE;
	else 
		return FALSE;
}

