/*
 *   Copyright (C) 1988-2006 Lucent Technologies Inc. and others.
 *   All Rights Reserved.
 */

/*	cscope - interactive C symbol cross-reference
 *
 *	directory searching functions
 */

#include <sys/types.h>	/* needed by stat.h */
#include <sys/stat.h>	/* stat */
#include "global.h"
#include "ndir.h"	/* dirent and directory access macros */
#include "vp.h"		/* vpdirs and vpndirs */

#if defined(__BORLANDC__) || defined(FOR_WIN32)
#define	DIRSEPS	" ,;"	/* directory list separators -- MSDOS */
#else
#define	DIRSEPS	" ,:"	/* directory list separators */
#endif
#define	DIRINC	10	/* directory list size increment */
#define HASHMOD	2003	/* must be a prime number */
#define	SRCINC	HASHMOD	/* source file list size increment */
			/* largest known database had 22049 files */

char	**incdirs;		/* #include directories */
char	**srcdirs;		/* source directories */
char	**srcfiles;		/* source files */
int	nincdirs;		/* number of #include directories */
int	mincdirs = DIRINC;	/* maximum number of #include directories */
int	nsrcdirs;		/* number of source directories */
int	msrcdirs = DIRINC;	/* maximum number of source directories */
int	nsrcfiles;		/* number of source files */
int	msrcfiles = SRCINC;	/* maximum number of source files */

static	struct	listitem {	/* source file table entry */
	char	*file;
	struct	listitem *next;
} *srcfiletable[HASHMOD];

#if defined( __STDC__ ) || defined( __BORLANDC__ )
void	sourcedir(char *dirlist);
void	includedir(char *dirlist);
void	makefilelist(void);
void	getsrcfiles(char *vpdir, char *dir);
BOOL	issrcfile(char *file);
void	incfile(char *file, char type);
BOOL	infilelist(char *file);
void	addsrcfile(char *path);
void	freefilelist(void);
#else
void	incfile();
void	getsrcfiles();
BOOL	issrcfile();
#endif

/* add a source directory to the list for each view path source directory */

void
sourcedir(dirlist)
char	*dirlist;
{
	struct	stat	statstruct;
	char *sdirlist;
	register char	*dir;

	sdirlist = stralloc(dirlist);	/* don't change environment variable text */
	
	/* parse the directory list */
	dir = strtok(sdirlist, DIRSEPS);
	while (dir != NULL) {

		/* make sure it is a directory (must exist in current view path node) */
		if (stat(compath(dir), &statstruct) == 0 && 
		    (statstruct.st_mode & S_IFDIR)) {
			if (srcdirs == NULL) {
				srcdirs = (char **) mymalloc(msrcdirs * sizeof(char *));
			}
			else if (nsrcdirs == msrcdirs) {
				msrcdirs += DIRINC;
				srcdirs = (char **) myrealloc((char *) srcdirs, msrcdirs * sizeof(char *));
			}
			srcdirs[nsrcdirs++] = stralloc(dir);
		}
		dir = strtok((char *) NULL, DIRSEPS);
	}
	free(sdirlist);
}

/* add a #include directory to the list for each view path source directory */

void
includedir(dirlist)
char	*dirlist;
{
	struct	stat	statstruct;
	char *idirlist;
	register char	*dir;

	idirlist = stralloc(dirlist);	/* don't change environment variable text */
	
	/* parse the directory list */
	dir = strtok(idirlist, DIRSEPS);
	while (dir != NULL) {

		/* make sure it is a directory (must exist in current view path node) */
		if (stat(compath(dir), &statstruct) == 0 && 
		    (statstruct.st_mode & S_IFDIR)) {
			if (incdirs == NULL) {
				incdirs = (char **) mymalloc(mincdirs * sizeof(char *));
			}
			else if (nincdirs == mincdirs) {
				mincdirs += DIRINC;
				incdirs = (char **) myrealloc((char *) incdirs, 
					mincdirs * sizeof(char *));
			}
			incdirs[nincdirs++] = stralloc(dir);
		}
		dir = strtok((char *) NULL, DIRSEPS);
	}
	free(idirlist);
}

/* make the source file list */

void
makefilelist()
{
	static	BOOL	firstbuild = YES;	/* first time through */
	FILE	*names;			/* name file pointer */
	char	dir[PATHLEN + 1];
	char	path[PATHLEN + 1];
	struct	stat	statstruct;
	register char	*file;
	register char	*s;
	register int	i, j;

	/* if there are source file arguments */
	if (fileargc > 0) {
		
		/* put them in a list that can be expanded */
		for (i = 0; i < fileargc; ++i) {
			file = fileargv[i];
#if defined(__BORLANDC__) || defined(FOR_WIN32)
			compath(file);	/* uppercase, dirs separated by \ */
#endif
			if (infilelist(file) == NO) {
				if (vpaccess(file, READ) == 0) {
					addsrcfile(file);
				}
				else {
					(void) fprintf(stderr, "%s: cannot find file %s\n",
					    argv0, file);
					errorsfound = YES;
				}
			}
		}
		return;
	}
	/* see if a file name file exists */
	if (namefile == NULL && vpaccess(NAMEFILE, READ) == 0) {
		namefile = NAMEFILE;
	}
	/* if there is a file of source file names */
	if (namefile != NULL) {
		if ((names = vpfopen(namefile, "r")) == NULL) {
			cannotopen(namefile);
			myexit(1);
		}
		/* get the names in the file */
		while (fscanf(names, "%s", path) == 1) {
			if (*path == '-') {	/* if an option */
				i = path[1];
				switch (i) {
				case 'c':	/* ASCII characters only */
					compress = NO;
					break;
				case 'k':	/* Ignore /usr/include */
					kernelmode = YES;
					break;
				case 'q':	/* quick search */
					invertedindex = YES;
					break;
				case 'T':	/* truncate symbols to 8 characters */
					truncateto8 = YES;
					break;
				case 'I':	/* #include file directory */
				case 'p':	/* file path components to display */
					s = path + 2;		/* for "-Ipath" */
					if (*s == '\0') {	/* if "-I path" */
						(void) fscanf(names, "%s", path);
						s = path;
					}
					switch (i) {
					case 'I':	/* #include file directory */
						if (firstbuild == YES) {
							shellpath(dir, sizeof(dir), s);	/* expand $ and ~ */
							includedir(dir);
						}
						break;
					case 'p':	/* file path components to display */
						if (*s < '0' || *s > '9') {
							(void) fprintf(stderr, "%s: -p option in file %s: missing or invalid numeric value\n", 
								argv0, namefile);
						}
						dispcomponents = atoi(s);
						break;
					}
					break;
				default:
					(void) fprintf(stderr, "%s: only -c, -I, -k, -p, -q and -T options can be in file %s\n", 
						argv0, namefile);
				}
			}
			else if (vpaccess(path, READ) == 0) {
				addsrcfile(path);
			}
			else {
				(void) fprintf(stderr, "%s: cannot find file %s\n",
				    argv0, path);
				errorsfound = YES;
			}
		}
		(void) fclose(names);
		firstbuild = NO;
		return;
	}
	/* make a list of all the source files in the directories */
	for (i = 0; i < nsrcdirs; ++i) {
		s = srcdirs[i];
		getsrcfiles(s, s);
#if defined(__BORLANDC__)
		if (*s != '/' && *s != '\\' && *(s + 1) != ':') {	/* if it isn't a full path name */
#elif defined(FOR_WIN32)
		if (*s != '/' && *(s + 1) != ':') { 	/* if it isn't a full path name */
#else
		if (*s != '/') {	/* if it isn't a full path name */
#endif

			/* compute its path from any higher view path nodes */
			for (j = 1; j < vpndirs; ++j) {
				(void) sprintf(dir, "%s/%s", vpdirs[j], s);

				/* make sure it is a directory */
				if (stat(compath(dir), &statstruct) == 0 && 
				    (statstruct.st_mode & S_IFDIR)) {
					getsrcfiles(dir, s);
				}
			}
		}
	}
}

/* get the source file names in this directory */

void
getsrcfiles(vpdir, dir)
char	*vpdir;
char	*dir;
{
	register DIR	*dirfile;	/* directory file descriptor */
	register struct	dirent	*entry;	/* directory entry pointer */
	char	path[PATHLEN + 1];

#if defined(__BORLANDC__) || defined(FOR_WIN32)
	compath(vpdir);
#endif
	/* attempt to open the directory */
	if ((dirfile = opendir(vpdir)) != NULL) {

		/* read each entry in the directory */
		while ((entry = readdir(dirfile)) != NULL) {
			struct stat buf;
			
			/* if it is a source file not already found */
#ifdef __BORLANDC__
			(void) sprintf(path, "%s\\%s", dir, entry->d_name);
#else
			(void) sprintf(path, "%s/%s", dir, entry->d_name);
#endif
			if (stat(path,&buf) == 0 && (buf.st_mode & S_IFDIR)) {
				if (recurse_dir == YES && 
				    strcmp(".",entry->d_name) != 0 &&
				    strcmp("..",entry->d_name) != 0)
					getsrcfiles(path,path);
			} else if (
#if !defined(__BORLANDC__) && !defined(FOR_WIN32)
			    entry->d_ino != 0 &&
#endif
			    issrcfile(path) && infilelist(path) == NO) {
				addsrcfile(path);	/* add it to the list */
			}
		}
		closedir(dirfile);
	}
}

/* see if this is a source file */

BOOL
issrcfile(file)
char	*file;
{
	struct	stat	statstruct;
	char	*s;

	/* if there is a file suffix */
	if ((s = strrchr(file, '.')) != NULL && *++s != '\0') {
		
		/* if an SCCS or versioned file */
		if (file[1] == '.' && file + 2 != s) { /* 1 character prefix */
			switch (*file) {
			case 's':
			case 'S':
				return(NO);
			}
		}
		if (s[1] == '\0') {	/* 1 character suffix */
			switch (*s) {
			case 'c':
			case 'h':
			case 'i':
			case 'l':
			case 'm':	/* SDL */
			case 'y':
			case 'C':
			case 'G':
			case 'H':
			case 'L':
#ifdef __BORLANDC__
			case 'Y':	/* letters are all upper case in DOS */
#endif
				return(YES);
			}
		}
		else if (s[2] == '\0') {	/* 2 character suffix */
			if (*s == 'b' && s[1] == 'p' ||	/* breakpoint listing */
			    *s == 'q' &&
				(s[1] == 'c' || s[1] == 'h') || /* Ingres */
			    *s == 'l' && s[1] == 'h' || /* SDL */
			    *s == 'p' && s[1] == 'r' ||	/* SDL */
			    *s == 's' && s[1] == 'd') {	/* SDL */
			
				/* some directories have 2 character
				   suffixes so make sure it is a file */
				if (vpstat(file, &statstruct) == 0 && 
				    (statstruct.st_mode & S_IFREG)) {
					return(YES);
				}
			}
		}
		else if (strcmp(s,"cpp") == 0 || strcmp(s,"CPP") == 0) {
			/* MS visual C++ */
			return(YES);
		}
		else if (strcmp(s,"hpp") == 0 || strcmp(s,"HPP") == 0) {
			/* MS visual C++ */
			return(YES);
		}
#ifdef MULTILINGUAL
		else if (strcmp(s,"java") == 0 || strcmp(s,"JAVA") == 0) {
			/* Java */
			return(YES);
		}
		else if (strcmp(s,"ity") == 0 || strcmp(s,"oty") == 0) {
			/* 5ESS tty messages */
			return(YES);
		}
#endif
	}
	return(NO);
}

/* add an include file to the source file list */

#if defined( __STDC__ ) || defined( __BORLANDC__ )
void incfile(char *file, char type)
{
#else
void
incfile(file, type)
char	*file;
char	type;
{
#endif
	char	path[PATHLEN + 1];
	int	i;

#ifdef __BORLANDC__
	strtoupper(file);
#endif
	/* see if the file is already in the source file list */
	if (infilelist(file) == YES) {
		return;
	}
	/* look in current directory if it was #include "file" */
	if (type == '"' && vpaccess(file, READ) == 0) {
		addsrcfile(file);
	}
	else {
		/* search for the file in the #include directory list */
		for (i = 0; i < nincdirs; ++i) {
			
			/* don't include the file from two directories */
			(void) sprintf(path, "%s/%s", incdirs[i], file);
#if defined(__BORLANDC__) || defined(FOR_WIN32)
			compath(path);
#endif
			if (infilelist(path) == YES) {
				break;
			}
			/* make sure it exists and is readable */
			if (vpaccess(path, READ) == 0) {
				addsrcfile(path);
				break;
			}
		}
	}
}

/* see if the file is already in the list */

BOOL
infilelist(file)
char	*file;
{
	register struct	listitem *p;

#if defined(__BORLANDC__) || defined(FOR_WIN32)
	compath(file);
#endif

	for (p = srcfiletable[hash(compath(file)) % HASHMOD]; p != NULL; p = p->next) {
		if (strequal(file, p->file)) {
			return(YES);
		}
	}
	return(NO);
}

/* add a source file to the list */

void
addsrcfile(path)
char	*path;
{
	struct	listitem *p;
	int	i;

	/* make sure there is room for the file */
	if (nsrcfiles == msrcfiles) {
		msrcfiles += SRCINC;
		srcfiles = (char **) myrealloc((char *) srcfiles, msrcfiles * sizeof(char *));
	}
	/* add the file to the list */
	p = (struct listitem *) mymalloc(sizeof(struct listitem));
	p->file = stralloc(compath(path));
	i = hash(p->file) % HASHMOD;
	p->next = srcfiletable[i];
	srcfiletable[i] = p;
	srcfiles[nsrcfiles++] = p->file;
}

/* free the memory allocated for the source file list */

void
freefilelist()
{
	register struct	listitem *p, *nextp;
	register int	i;
	
	while (nsrcfiles > 0) {
		free(srcfiles[--nsrcfiles]);
	}
	for (i = 0; i < HASHMOD; ++i) {
		for (p = srcfiletable[i]; p != NULL; p = nextp) {
			nextp = p->next;
			free((char *) p);
		}
		srcfiletable[i] = NULL;
	}
}
#ifdef __BORLANDC__

/* convert a string in place to uppercase letters */

void
strtoupper(s)
register char	*s;
{
	for (; *s != '\0'; ++s) {
		*s = toupper(*s);
	}
}
#endif
