/*
 * Copyright (C) 1992 by Rush Record (rhr@clio.rice.edu)
 * 
 * This file is part of ED.
 * 
 * ED is free software; you can redistribute it and/or modify it under the terms
 * of the GNU General Public License as published by the Free Software Foundation.
 * 
 * ED is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with ED
 * (see the file COPYING).  If not, write to the Free Software Foundation, 675
 * Mass Ave, Cambridge, MA 02139, USA.
 */
#include "opsys.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include "file.h"
#ifdef NeXT
#include <sys/dir.h>
#else
#ifndef VMS
#include <dirent.h>
#endif
#endif

#include "memory.h"
#include "ctyp_dec.h"

#define CHARLES 0x79861876	/* magic number */

#ifdef VMS
typedef struct chardesc {unsigned short length,class;Char *string;} desc_node;
typedef struct chardesc *desc;
static Char wildcard[512],lastmatch[512];
#else
typedef struct namelist_str *namelist_ptr;
typedef struct namelist_str
{
	namelist_ptr next;
	Char *name;
} namelist_node;

typedef struct ff_str *ff_ptr;
typedef struct ff_str
{
	Int magic,matchcount;
	namelist_ptr firstname,curname;	/* these are loaded during scan. when user traverses, curname handles it. */
} ff_node;
#endif

/******************************************************************************\
|Routine: match_wild
|Callby: find_files
|Purpose: Provides VMS-style matching of file names to a template. Returns 1 on a match, else 0.
|Arguments:
|	template is the template string, which may contain * and % wildcards.
|	string is the string we are comparing to the template.
\******************************************************************************/
Int match_wild(template,string)
Char *template,*string;
{
	register Int remain_t,remain_s;
	register Char *t,*s,c;
	Int save_r_s,save_r_t;
	Char *save_t,*save_s;
#ifdef GNUDOS
	register Char sc;
#endif

	t = template;
	s = string;
	remain_t = strlen(t);
	remain_s = strlen(s);
	save_r_s = 0;
	while(1)
	{
		if(--remain_t < 0)
		{
			if(!remain_s)
				return(1);
		}
		else
		{
			if((c = *t++) == '*')
			{
				if(*t == '*')
				{
					t++;
					remain_t--;
				}
				else
				{
					if(!remain_t)
						return(1);
					save_s = s;
					save_t = t;
					save_r_s = remain_s;
					save_r_t = remain_t;
					continue;
				}
			}
			if(--remain_s < 0)
				return(0);
#ifdef GNUDOS
			sc = *s++;
			if(tolower(c) == tolower(sc))
#else
			if(c == *s++)
#endif
				continue;
			if(c == '?')
				continue;
		}
		if(--save_r_s < 0)
			return(0);
		save_s++;
		s = save_s;
		t = save_t;
		remain_s = save_r_s;
		remain_t = save_r_t;
	}
}

#ifndef VMS

/******************************************************************************\
|Routine: got_string
|Callby: find_files
|Purpose: Makes a buffer and copies a string into it.
|Arguments:
|	string is the string to be copied.
\******************************************************************************/
Char *got_string(string)
Char *string;
{
	Char *p;
	
	p = (Char *)imalloc(((string)? strlen(string) : 0) + 1);
	if(string)
		strcpy(p,string);
	else
		*p = '\0';
	return(p);
}

/******************************************************************************\
|Routine: ff_report_name
|Callby: find_files
|Purpose: Stores a validated, matching file name in dynamic memory.
|Arguments:
|	ff is the header of the (possibly empty) list of already-validated names.
|	name is the new name to append to the list.
\******************************************************************************/
void ff_report_name(ff,name)
ff_ptr ff;
Char *name;
{
	namelist_ptr p;
	Char *from,*to,c;
	
	if(!ff->firstname)
		ff->firstname = ff->curname = (namelist_ptr)&ff->firstname;
	p = ff->curname->next = (namelist_ptr)imalloc(sizeof(namelist_node));
	ff->curname = p;
	p->next = NULL;
	p->name = got_string(name);
	for(from = to = p->name;(c = *from++);)	/* eliminate double /s */
		if(c == '/')
		{
			if(*from != '/')
			{
				if(*from == '.' && *(from + 1) == '/')
				{
					*to++ = '/';
					from += 2;
				}
				else
					*to++ = c;
			}
		}
		else
			*to++ = c;
	*to = '\0';
	ff->matchcount++;
}

/******************************************************************************\
|Routine: ff_scan_file
|Callby: find_files
|Purpose: Generates a complete list of matching file names (in inode order).
|Arguments:
|	ff is the list header structure.
|	wild is a file specification possibly containing * or ? or ... wildcards.
\******************************************************************************/
void ff_scan_file(ff,wild)
ff_ptr ff;
Char *wild;
{
	Char *wil,wildcard;
	Char *pred,*foll,*full,*temp,*etc,*dirmatch,*temp2;
	Int lpred,lfoll,lname,letc;
	Char *p,*q;
	DIR *dp;	/* directory pointer */
	struct stat statbuf;
#ifdef NeXT
	struct direct *d;
#else
	struct dirent *d;
#endif
	
/* Algorithm:
 *
 *	if no wildcards:
 *		return(!stat(file))
 *	else if wild = /.../:
 *		find preceeding /: pred/.../foll
 *		if pred is null, pred = / (or /. if GNUDOS)
 *		opendir(pred), scan=pfile:
 *			if pfile is a directory:
 *				recurse with /pred/pfile/.../foll
 *				if foll contains /: folldir/folletc
 *					if pfile matches folldir
 *						recurse with pred/pfile/folletc
 *			else
 *				if foll does not contain /
 *					if pfile matches foll
 *						return a hit
 *	else
 *		find preceeding /: pred/foll
 *		if foll contains /:
 *			foll divides into dirmatch/etc
 *			if pred is null, pred = / (or /. if GNUDOS)
 *			opendir(pred), scan=pfile
 *				if pfile is a directory:
 *					if pfile matches dirmatch
 *						recurse with pred/pfile/foll
 *		else
 *			if pred is null, pred = / (or /. if GNUDOS)
 *			opendir(pred), scan=pfile
 *				if pfile matches foll
 *					return a hit
 */		
	wil = got_string(wild);	/* make a modifiable copy of the string */
	for(p = wil;*p;p++)
		if(*p == '*' || *p == '?' || !strncmp(p,"/.../",5))
			break;
	if(!*p)
	{
		if(!stat(wild,&statbuf))
			ff_report_name(ff,wild);
		ifree(wil);
		return;
	}
	if(*p == '/')	/* save the type of wildcard */
		p++;	/* advance to first . if it's a /.../ */
	wildcard = *p;
	while(--p != wil)	/* find the / preceeding the wildcard */
		if(*p == '/')
			break;
	*p++ = '\0';
	pred = got_string((*wil)? wil : (Char *)"/");	/* null predecessor means open root directory for scan */
	if(wildcard == '.')
	{
		while(*p++ != '/');	/* find the other end of the /.../ */
		foll = got_string((*p)? p : (Char *)"*");
		if((dp = opendir(pred)))
		{
			lpred = strlen(pred);
			lfoll = strlen(foll);
			while((d = readdir(dp)))	/* read entries */
			{
#ifndef GNUDOS
				if(!d->d_ino)	/* skip empty entries */
					continue;
#endif
				if(!strcmp(d->d_name,".") || !strcmp(d->d_name,".."))	/* ignore . and .. */
					continue;
/* build pred/name */
				full = (Char *)imalloc(lpred + 1 + (lname = strlen(d->d_name)) + 1);
				memcpy(full,pred,lpred);
				p = full + lpred;
				*p++ = '/';
				strcpy(p,d->d_name);
				if(!stat(full,&statbuf))	/* see if the file found is a directory */
				{
					if(S_ISDIR(statbuf.st_mode))
					{
						temp = (Char *)imalloc(lpred + 1 + lname + 5 + lfoll + 1);
						strcpy(temp,full);
						strcat(temp,"/.../");
						strcat(temp,foll);
						ff_scan_file(ff,temp);
						ifree(temp);
						if((p = (Char *)strchr(foll,'/')))
						{
							temp2 = got_string(foll);
							temp2[p - foll] = '\0';
							if(match_wild(temp2,d->d_name))
							{
								etc = got_string((*p)? p : (Char *)"*");
								temp = (Char *)imalloc(lpred + 1 + lname + 1 + strlen(etc) + 1);
								strcpy((q = temp),pred);
								q += lpred;
								*q++ = '/';
								strcpy(q,d->d_name);
								q += lname;
								*q++ = '/';
								strcpy(q,etc);	/* what if p is null string? */
								ff_scan_file(ff,temp);
								ifree(temp);
								ifree(etc);
							}
							ifree(temp2);
						}
					}
					else	/* it isn't a directory */
					{
						if(!strchr(foll,'/'))
						{
							if(match_wild(foll,d->d_name))
							{
								temp = (Char *)imalloc(lpred + 1 + lname + 1);
								strcpy((p = temp),pred);
								p += lpred;
								*p++ = '/';
								strcpy(p,d->d_name);
								ff_report_name(ff,temp);
								ifree(temp);
							}
						}
					}
				}	/* end of stat() block */
				ifree(full);
			}
			closedir(dp);
		}	/* end of opendir() block */
		ifree(foll);
	}
	else	/* wildcard is not /.../ */
	{
		foll = got_string((*p)? p : (Char *)"*");	/* p points past preceeding / */
		if((p = (Char *)strchr(foll,'/')))
		{
			*p++ = '\0';
			dirmatch = got_string(foll);
			etc = got_string((*p)? p : (Char *)"*");
			letc = strlen(etc);
			if((dp = opendir(pred)))
			{
				lpred = strlen(pred);
				while((d = readdir(dp)))	/* read entries */
				{
#ifndef GNUDOS
					if(!d->d_ino)	/* skip empty entries */
						continue;
#endif
					if(!strcmp(d->d_name,".") || !strcmp(d->d_name,".."))	/* ignore . and .. */
						continue;
/* build pred/name */
					full = (Char *)imalloc(lpred + 1 + (lname = strlen(d->d_name)) + 1);
					memcpy(full,pred,lpred);
					p = full + lpred;
					*p++ = '/';
					memcpy(p,d->d_name,lname);
					p += lname;
					*p = '\0';
					if(!stat(full,&statbuf))	/* see if the file found is a directory */
					{
						if(S_ISDIR(statbuf.st_mode))
						{
							if(match_wild(dirmatch,d->d_name))
							{
								temp = (Char *)imalloc(lpred + 1 + lname + 1 + letc + 1);
								strcpy((q = temp),pred);
								q += lpred;
								*q++ = '/';
								strcpy(q,d->d_name);
								q += lname;
								*q++ = '/';
								strcpy(q,etc);
								ff_scan_file(ff,temp);
								ifree(temp);
							}
						}	/* if it isn't a directory, we are not interested */
					}
					ifree(full);
				}
				closedir(dp);
			}
			ifree(etc);
			ifree(dirmatch);
		}
		else	/* foll contains no / */
		{
			if((dp = opendir(pred)))
			{
				lpred = strlen(pred);
				while((d = readdir(dp)))	/* read entries */
				{
#ifndef GNUDOS
					if(!d->d_ino)	/* skip empty entries */
						continue;
#endif
/* build pred/name */
					if(match_wild(foll,d->d_name))
					{
						full = (Char *)imalloc(lpred + 1 + (lname = strlen(d->d_name)) + 1);
						memcpy(full,pred,lpred);
						p = full + lpred;
						*p++ = '/';
						memcpy(p,d->d_name,lname);
						p += lname;
						*p = '\0';
						ff_report_name(ff,full);
						ifree(full);
					}
				}
				closedir(dp);
			}
		}	/* end of foll-contains-/ ifelse block */
		ifree(foll);
	}
	ifree(wil);
}
#endif
	
/******************************************************************************\
|Routine: find_files
|Callby: cfg file_list_i load_file
|Purpose: To initialize a wildcard search. Returns a nonzero context value
|         if successful, else zero.
|Arguments:
|	wild is the wildcard filename string.
\******************************************************************************/
Long find_files(wild)
Char  *wild;
{
#ifdef VMS
	desc_node a,b;
	Int status;
	Long context;
	Char *p,c;
	Char ret[512];

	strcpy(wildcard,wild);
	a.length = strlen(wildcard);
	a.class = 0x010e;
	a.string = wildcard;
	b.length = 128;
	b.class = 0x010e;
	b.string = ret;
	context = 0;
	if((status = lib$find_file(&a,&b,&context)) & 1)
	{
		if((p = strchr(ret,' ')))
			*p = '\0';
		strcpy(lastmatch,ret);
	}
	else
	{
		lastmatch[0] = '\0';
		context = 0;
	}
	return(context);
#else
	ff_ptr ff;
	Char *wil,*q;
	Char **tags;
	namelist_ptr p;
	Int n;
#ifdef GNUDOS
	Char c,*newwil,*w;
#endif

	wil = got_string(wild);
#ifdef GNUDOS
/*	Check for 2nd char ':' (if so, device letter 1st char, directory after)
 *	if directory spec starts with /, add /. to front
 *	else if directory spec starts with non ../ or ./ add ./
 */
	for(q = wil;(c = *q);q++)
		if(c == '\\')	/* convert \ to / */
			*q = '/';
	w = wil;
	newwil = (Char *)imalloc(strlen(wil) + 3);	/* make buffer for fixed-up file name */
	q = newwil;
	if(*(w + 1) == ':')	/* copy the device spec if present */
	{
		*q++ = *w++;
		*q++ = *w++;
	}
	*q = '\0';	/* terminate the device spec */
	if(*w == '/')		/* if the first char is /, prepend /. */
		strcat(q,"/.");
	else if(!(			/* if the first char is not ./ or ../, prepend ./ */
		(*w == '.' && *(w + 1) == '/') || 
		(*w == '.' && *(w + 1) == '.' && *(w + 2) == '/')
		))
		strcat(q,"./");
	strcat(q,w);
	ifree(wil);
	wil = newwil;
#else
	wil = got_string(wild);
	if(!(
		*wil == '/' || 
		(*wil == '.' && *(wil + 1) == '/') || 
		(*wil == '.' && *(wil + 1) == '.' && *(wil + 2) == '/')
		))	/* if the first char is not /, prepend ./ */
	{
		q = (Char *)imalloc(strlen(wil) + 3);
		strcpy(q,"./");
		strcat(q,wil);
		ifree(wil);
		wil = q;
	}
#endif
	ff = (ff_ptr)imalloc(sizeof(ff_node));
	ff->magic = CHARLES;
	ff->matchcount = 0;
	ff->firstname = NULL;
	ff_scan_file(ff,wil);
	ifree(wil);
/* sort namelist */
	if(ff->matchcount)
	{
		tags = (Char **)imalloc((ff->matchcount + 1) * sizeof(Char *));
		for(n = 0,p = ff->firstname;p;p = p->next)
			tags[n++] = p->name;
		cqsort_p(n,tags);
		for(n = 0,p = ff->firstname;p;p = p->next)
			p->name = tags[n++];
		ifree(tags);
	}
	ff->curname = ff->firstname;
	return((Long)ff);
#endif
}

/******************************************************************************\
|Routine: next_file
|Callby: cfg file_list_i load_file
|Purpose: Returns the next matching file name. Returns 1 on success, else zero.
|Arguments:
|	context is a context value previously returned by find_files.
|	retbuf gets the returned file name.
\******************************************************************************/
Int next_file(context,retbuf)
Long context;
Char *retbuf;
{
#ifdef VMS
	desc_node a,b;
	Char *p,c;

	if(lastmatch[0] == '\0')
		return(0);
	strcpy(retbuf,lastmatch);
	a.length = strlen(wildcard);
	a.class = 0x010e;
	a.string = wildcard;
	b.length = 128;
	b.class = 0x010e;
	b.string = lastmatch;
	if(lib$find_file(&a,&b,&context) & 1)
	{
		if((p = strchr(lastmatch,' ')))
			*p = '\0';
	}
	else
		lastmatch[0] = '\0';
	return(1);
#else
	namelist_ptr p;
	ff_ptr ff;
	
	ff = (ff_ptr)context;
	if(ff->magic != CHARLES)
	{
		retbuf[0] = '\0';
		return(0);
	}
	if(!(p = ff->curname))
	{
		ff->curname = ff->firstname;
		retbuf[0] = '\0';
		return(0);
	}
	ff->curname = p->next;
	strcpy(retbuf,p->name);
	return(1);
#endif
}

/******************************************************************************\
|Routine: find_files_end
|Callby: cfg file_list_i load_file
|Purpose: To clean up after find_file() is called.
|Arguments:
|	context is the context value used by find_file().
\******************************************************************************/
void find_files_end(context)
Long context;
{
#ifdef VMS
	if(context)
		lib$find_file_end(&context);
#else
	ff_ptr ff;
	namelist_ptr p,q;
	
	if((ff = (ff_ptr)context))
		if(ff->magic == CHARLES)
		{
			for(p = ff->firstname;p;)
			{
				q = p -> next;
				ifree(p->name);
				ifree(p);
				p = q;
			}
			ifree(ff);
		}
#endif
}

