/*
 * $Id$
 *
    Copyright (c) 2006-2019 Chung, Hyung-Hwan. All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:
    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.

    THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


#if defined(NO_RECURSION)
typedef struct stack_node_t stack_node_t;
#endif

struct glob_t
{
	cbimpl_t cbimpl;
	void* cbctx;

	qse_mmgr_t* mmgr;
	qse_cmgr_t* cmgr;
	int flags;

	str_t path;
	str_t tbuf; /* temporary buffer */

#if defined(INCLUDE_MBUF)
	qse_mbs_t mbuf;
#endif

	int expanded;
	int fnmat_flags;

#if defined(NO_RECURSION)
	stack_node_t* stack;
	stack_node_t* free;
#endif
};

typedef struct glob_t glob_t;

struct segment_t
{
	segment_type_t type;

	const char_t* ptr;
	qse_size_t    len;

	char_t sep; /* preceeding separator */
	unsigned int wild: 1;  /* indicate that it contains wildcards */
	unsigned int esc: 1;  /* indicate that it contains escaped letters */
	unsigned int next: 1;  /* indicate that it has the following segment */
};

typedef struct segment_t segment_t;

#if defined(NO_RECURSION)
struct stack_node_t
{
	qse_size_t tmp;
	qse_size_t tmp2;
	qse_dir_t* dp;
	segment_t seg;

	stack_node_t* next;
};
#endif

#if defined(INCLUDE_MBUF)
static qse_mchar_t* wcs_to_mbuf (glob_t* g, const qse_wchar_t* wcs, qse_mbs_t* mbs)
{
	qse_size_t ml, wl;

	if (qse_wcstombswithcmgr (wcs, &wl, QSE_NULL, &ml, g->cmgr) <= -1 ||
	    qse_mbs_setlen (mbs, ml) == (qse_size_t)-1) return QSE_NULL;

	qse_wcstombswithcmgr (wcs, &wl, QSE_MBS_PTR(mbs), &ml, g->cmgr);
	return QSE_MBS_PTR(mbs);
}
#endif

static int path_exists (glob_t* g, const char_t* name)
{
#if defined(_WIN32)

	/* ------------------------------------------------------------------- */
	#if !defined(INVALID_FILE_ATTRIBUTES)
	#define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
	#endif

	#if defined(CHAR_IS_MCHAR)
	return (GetFileAttributesA(name) != INVALID_FILE_ATTRIBUTES)? 1: 0;
	#else
	return (GetFileAttributesW(name) != INVALID_FILE_ATTRIBUTES)? 1: 0;
	#endif
	/* ------------------------------------------------------------------- */

#elif defined(__OS2__)

	/* ------------------------------------------------------------------- */
	FILESTATUS3 fs;
	APIRET rc;
	const qse_mchar_t* mptr;

#if defined(CHAR_IS_MCHAR)
	mptr = name;
#else
	mptr = wcs_to_mbuf (g, name, &g->mbuf);
	if (mptr == QSE_NULL) return -1;
#endif

	rc = DosQueryPathInfo (mptr, FIL_STANDARD, &fs, QSE_SIZEOF(fs));

	return (rc == NO_ERROR)? 1:
	       (rc == ERROR_PATH_NOT_FOUND)? 0: -1;
	/* ------------------------------------------------------------------- */

#elif defined(__DOS__)

	/* ------------------------------------------------------------------- */
	unsigned int x, attr;
	const qse_mchar_t* mptr;

#if defined(CHAR_IS_MCHAR)
	mptr = name;
#else
	mptr = wcs_to_mbuf (g, name, &g->mbuf);
	if (mptr == QSE_NULL) return -1;
#endif

	x = _dos_getfileattr (mptr, &attr);
	return (x == 0)? 1: 
	       (errno == ENOENT)? 0: -1;
	/* ------------------------------------------------------------------- */

#elif defined(macintosh)
	HFileInfo fpb;
	const qse_mchar_t* mptr;

#if defined(CHAR_IS_MCHAR)
	mptr = name;
#else
	mptr = wcs_to_mbuf (g, name, &g->mbuf);
	if (mptr == QSE_NULL) return -1;
#endif

	QSE_MEMSET (&fpb, 0, QSE_SIZEOF(fpb));
	fpb.ioNamePtr = (unsigned char*)mptr;
	
	return (PBGetCatInfoSync ((CInfoPBRec*)&fpb) == noErr)? 1: 0;
#else

	/* ------------------------------------------------------------------- */
#if defined(HAVE_LSTAT)
	qse_lstat_t st;
#else
	qse_stat_t st;
#endif
	const qse_mchar_t* mptr;

#if defined(CHAR_IS_MCHAR)
	mptr = name;
#else
	mptr = wcs_to_mbuf (g, name, &g->mbuf);
	if (mptr == QSE_NULL) return -1;
#endif

#if defined(HAVE_LSTAT)
	return (QSE_LSTAT (mptr, &st) <= -1)? 0: 1;
#else
	/* use stat() if no lstat() is available. */
	return (QSE_STAT (mptr, &st) <= -1)? 0: 1;
#endif

	/* ------------------------------------------------------------------- */
#endif
}


static int get_next_segment (glob_t* g, segment_t* seg)
{
	if (seg->type == NONE)
	{
		/* seg->ptr must point to the beginning of the pattern
		 * and seg->len must be zero when seg->type is NONE. */
		if (IS_NIL(seg->ptr[0]))
		{
			/* nothing to do */
		}
		else if (IS_SEP(seg->ptr[0]))
		{
			seg->type = ROOT;
			seg->len = 1;
			seg->next = IS_NIL(seg->ptr[1])? 0: 1;
			seg->sep = T('\0');
			seg->wild = 0;
			seg->esc = 0;
		}
	#if defined(_WIN32) || defined(__OS2__) || defined(__DOS__)
		else if (IS_DRIVE(seg->ptr))
		{
			seg->type = ROOT;
			seg->len = 2;
			if (IS_SEP(seg->ptr[2])) seg->len++;
			seg->next = IS_NIL(seg->ptr[seg->len])? 0: 1;
			seg->sep = T('\0');
			seg->wild = 0;
			seg->esc = 0;
		}
	#endif
		else
		{
			int escaped = 0;
			seg->type = NORMAL;
			seg->sep = T('\0');
			seg->wild = 0;
			seg->esc = 0;
			do
			{
				if (escaped) escaped = 0;
				else
				{
					if (IS_ESC(seg->ptr[seg->len])) 
					{
						escaped = 1;
						seg->esc = 1;
					}
					else if (IS_WILD(seg->ptr[seg->len])) seg->wild = 1;
				}

				seg->len++;
			}
			while (!IS_SEP_OR_NIL(seg->ptr[seg->len]));
			seg->next = IS_NIL(seg->ptr[seg->len])? 0: 1;
		}
	}
	else if (seg->type == ROOT)
	{
		int escaped = 0;
		seg->type = NORMAL;
		seg->ptr = &seg->ptr[seg->len];
		seg->len = 0;
		seg->sep = T('\0');
		seg->wild = 0;
		seg->esc = 0;

		while (!IS_SEP_OR_NIL(seg->ptr[seg->len])) 
		{
			if (escaped) escaped = 0;
			else
			{
				if (IS_ESC(seg->ptr[seg->len])) 
				{
					escaped = 1;
					seg->esc = 1;
				}
				else if (IS_WILD(seg->ptr[seg->len])) seg->wild = 1;
			}
			seg->len++;
		}
		seg->next = IS_NIL(seg->ptr[seg->len])? 0: 1;
	}
	else
	{
		QSE_ASSERT (seg->type == NORMAL);

		seg->ptr = &seg->ptr[seg->len + 1];
		seg->len = 0;
		seg->wild = 0;
		seg->esc = 0;
		if (IS_NIL(seg->ptr[-1])) 
		{
			seg->type = NONE;
			seg->next = 0;
			seg->sep = T('\0');
		}
		else
		{
			int escaped = 0;
			seg->sep = seg->ptr[-1];
			while (!IS_SEP_OR_NIL(seg->ptr[seg->len])) 
			{
				if (escaped) escaped = 0;
				else
				{
					if (IS_ESC(seg->ptr[seg->len])) 
					{
						escaped = 1;
						seg->esc = 1;
					}
					else if (IS_WILD(seg->ptr[seg->len])) seg->wild = 1;
				}
				seg->len++;
			}
			seg->next = IS_NIL(seg->ptr[seg->len])? 0: 1;
		}
	}

	return seg->type;
}

static int handle_non_wild_segments (glob_t* g, segment_t* seg)
{
	while (get_next_segment(g, seg) != NONE && !seg->wild)
	{
		QSE_ASSERT (seg->type != NONE && !seg->wild);

		if (seg->sep && str_ccat (&g->path, seg->sep) == (qse_size_t)-1) return -1;
		if (seg->esc)
		{
			/* if the segment contains escape sequences,
			 * strip the escape letters off the segment */

			cstr_t tmp;
			qse_size_t i;
			int escaped = 0;

			if (STR_CAPA(&g->tbuf) < seg->len &&
			    str_setcapa (&g->tbuf, seg->len) == (qse_size_t)-1) return -1;

			tmp.ptr = STR_PTR(&g->tbuf);
			tmp.len = 0;

			/* the following loop drops the last character 
			 * if it is the escape character */
			for (i = 0; i < seg->len; i++)
			{
				if (escaped)
				{
					escaped = 0;
					tmp.ptr[tmp.len++] = seg->ptr[i];
				}
				else
				{
					if (IS_ESC(seg->ptr[i])) 
						escaped = 1;
					else
						tmp.ptr[tmp.len++] = seg->ptr[i];
				}
			}

			if (str_ncat (&g->path, tmp.ptr, tmp.len) == (qse_size_t)-1) return -1;
		}
		else
		{
			/* if the segmetn doesn't contain escape sequences,
			 * append the segment to the path without special handling */
			if (str_ncat (&g->path, seg->ptr, seg->len) == (qse_size_t)-1) return -1;
		}

		if (!seg->next && path_exists (g, STR_PTR(&g->path)) > 0)
		{
			/* reached the last segment. match if the path exists */
			if (g->cbimpl (STR_XSTR(&g->path), g->cbctx) <= -1) return -1;
			g->expanded = 1;
		}
	}

	return 0;
}

static int search (glob_t* g, segment_t* seg)
{
	qse_dir_t* dp;
	qse_size_t tmp, tmp2;
	qse_dir_ent_t ent;
	int x;

#if defined(NO_RECURSION)
	stack_node_t* r;

entry:
#endif

	dp = QSE_NULL;

	if (handle_non_wild_segments (g, seg) <= -1) goto oops;

	if (seg->wild)
	{
		int dir_flags = DIR_CHAR_FLAGS;
		if (g->flags & QSE_GLOB_SKIPSPCDIR) dir_flags |= QSE_DIR_SKIPSPCDIR;

		dp = qse_dir_open (
			g->mmgr, 0, (const qse_char_t*)STR_PTR(&g->path),
			dir_flags, QSE_NULL);
		if (dp)
		{
			tmp = STR_LEN(&g->path);

			if (seg->sep && str_ccat (&g->path, seg->sep) == (qse_size_t)-1) goto oops;
			tmp2 = STR_LEN(&g->path);

			while (1)
			{
				str_setlen (&g->path, tmp2);

				x = qse_dir_read (dp, &ent);
				if (x <= -1) 
				{
					if (g->flags & QSE_GLOB_TOLERANT) break;
					else goto oops;
				}
				if (x == 0) break;

				if (str_cat (&g->path, (const char_t*)ent.name) == (qse_size_t)-1) goto oops;

				if (strnfnmat (STR_CPTR(&g->path,tmp2), seg->ptr, seg->len, g->fnmat_flags) > 0)
				{
					if (seg->next)
					{
				#if defined(NO_RECURSION)
						if (g->free) 
						{
							r = g->free;
							g->free = r->next;
						}
						else
						{
							r = QSE_MMGR_ALLOC (g->mmgr, QSE_SIZEOF(*r));
							if (r == QSE_NULL) goto oops;
						}
						
						/* push key variables that must be restored 
						 * into the stack. */
						r->tmp = tmp;
						r->tmp2 = tmp2;
						r->dp = dp;
						r->seg = *seg;

						r->next = g->stack;
						g->stack = r;

						/* move to the function entry point as if
						 * a recursive call has been made */
						goto entry;

					resume:
						;

				#else
						segment_t save;
						int x;

						save = *seg;
						x = search (g, seg);
						*seg = save;
						if (x <= -1) goto oops;
				#endif
					}
					else
					{
						if (g->cbimpl (STR_XSTR(&g->path), g->cbctx) <= -1) goto oops;
						g->expanded = 1;
					}
				}
			}

			str_setlen (&g->path, tmp);
			qse_dir_close (dp); dp = QSE_NULL;
		}
	}

	QSE_ASSERT (dp == QSE_NULL);

#if defined(NO_RECURSION)
	if (g->stack)
	{
		/* the stack is not empty. the emulated recusive call
		 * must have been made. restore the variables pushed
		 * and jump to the resumption point */
		r = g->stack;
		g->stack = r->next;

		tmp = r->tmp;
		tmp2 = r->tmp2;
		dp = r->dp;
		*seg = r->seg;

		/* link the stack node to the free list 
		 * instead of freeing it here */
		r->next = g->free;
		g->free = r;

		goto resume;
	}

	while (g->free)
	{
		/* destory the free list */
		r = g->free;
		g->free = r->next;
		QSE_MMGR_FREE (g->mmgr, r);
	}
#endif

	return 0;

oops:
	if (dp) qse_dir_close (dp);

#if defined(NO_RECURSION)
	while (g->stack)
	{
		r = g->stack;
		g->stack = r->next;
		qse_dir_close (r->dp);
		QSE_MMGR_FREE (g->mmgr, r);
	}

	while (g->free)
	{
		r = g->stack;
		g->free = r->next;
		QSE_MMGR_FREE (g->mmgr, r);
	}
#endif
	return -1;
}

int glob (const char_t* pattern, cbimpl_t cbimpl, void* cbctx, int flags, qse_mmgr_t* mmgr, qse_cmgr_t* cmgr)
{
	segment_t seg;
	glob_t g;
	int x;

	QSE_MEMSET (&g, 0, QSE_SIZEOF(g));
	g.cbimpl = cbimpl;
	g.cbctx = cbctx;
	g.mmgr = mmgr;
	g.cmgr = cmgr;
	g.flags = flags;

#if defined(_WIN32) || defined(__OS2__) || defined(__DOS__)
	g.fnmat_flags |= QSE_STRFNMAT_IGNORECASE;
	g.fnmat_flags |= QSE_STRFNMAT_NOESCAPE;
#else
	if (flags & QSE_GLOB_IGNORECASE) g.fnmat_flags |= QSE_STRFNMAT_IGNORECASE;
	if (flags & QSE_GLOB_NOESCAPE) g.fnmat_flags |= QSE_STRFNMAT_NOESCAPE;
#endif
	if (flags & QSE_GLOB_PERIOD) g.fnmat_flags |= QSE_STRFNMAT_PERIOD;

	if (str_init (&g.path, mmgr, 512) <= -1) return -1;
	if (str_init (&g.tbuf, mmgr, 256) <= -1) 
	{
		str_fini (&g.path);
		return -1;
	}

#if defined(INCLUDE_MBUF)
	if (qse_mbs_init (&g.mbuf, mmgr, 512) <= -1) 
	{
		str_fini (&g.path);
		str_fini (&g.path);
		return -1;
	}
#endif

	QSE_MEMSET (&seg, 0, QSE_SIZEOF(seg));
	seg.type = NONE;
	seg.ptr = pattern;
	seg.len = 0;

	x = search (&g, &seg);

#if defined(INCLUDE_MBUF)
	qse_mbs_fini (&g.mbuf);
#endif
	str_fini (&g.tbuf);
	str_fini (&g.path);

	if (x <= -1) return -1;
	return g.expanded;
}
