/*  maketex.C   */
/*  Copyright 1991 Mountain Math Software  */
/*  All Rights Reserved                    */
#include "ObjProGen/cpyrght_exe.h"
#include <stdlib.h>
#include <fstream.h>
#include <string.h>
#include <ctype.h>
#include "dirfiles.h"
#include "mkstr.h"
#include "linlist.h"
#include "texutil.h"

#define LogOut cerr
class FilesInOneList ;
class AllLists {
	FilesInOneList ** AllListFiles ;
	FilesInOneList * CurrentFilesList ;
	char ** TheDirExcludeFiles ;
	char ** TheExcludeFiles ;
	const char * SubDir ;
	void Exclude(const char * Exc) ;
	void AddIfNeeded(FilesInOneList * Files);
public:
	AllLists();
	void ProcessTeXDirectory(int index, const char * arg) ;
	void ProcessTeXFile(int index, const char * arg) ;
	void ExcludeTeXFile(int index, const char * arg) ;
	void ExcludeDirTeXFile(int index, const char * arg) ;

	FilesInOneList * FindFilesList(const char * list_file) ;
	void ReadDirectories();
	void ProcessExcludes();
	void WriteTeXIncludeFiles();
	void SetCurrentFilesList(const char * list_file=0) ;
	void SpecifySubDir(const char * Sub) {SubDir=Sub;}
	int ToBeExcluded(const char * FullName) const ;
};

AllLists AllTheLists ;


struct TeXFile {
	char * SortName ;
	const char * FullFileName ;
	TeXFile(const char * file=0);
	void WriteTeXIncludeFile(ostream& Out);
};

void TeXFile::WriteTeXIncludeFile(ostream& Out)
{
	Out << "\\input{" << FullFileName << "}\n" ;
}

TeXFile::TeXFile(const char * file):
	FullFileName(file),
	SortName(0)
{
	// LogOut << "Constructing TeXFile `" << file << "'\n" ;
	if (!file) return ;
	ifstream TheFile(file);
	if (!TheFile.good()) {
		cerr << "Cannot read file `" << file << "'.\n" ;
		return ;
	}
	char Buf[1024];
	int NextGood = 0 ;
	Buf[0] = 0 ;
	while (TheFile.good()) {
		// LogOut << "`" << Buf << "'\n" ;
		if (NextGood) {
			for (char * ptr = Buf; *ptr;ptr++) if (!isalnum(*ptr)) {
				*ptr = '\0' ;
				break ;
			}
			int Length = strlen(Buf) ;
			if (!Length) break ;
			SortName = new char[Length+1] ;
			strcpy(SortName,Buf);
			return ;
		}
		if (!strcmp(Buf,"\\subsection{{\\tt")) NextGood = 1 ;
		else if (!strcmp(Buf,"\\subsubsection{{\\tt")) NextGood = 1 ;
		TheFile >> Buf ;
	}
	cerr << "Cannot find node name in file `" << file << "'.\n" ;
}

class FilesInOneList {
	const char * ListFile ;
	char ** TheDirectories ;
	TeXFile ** TheTeXFiles ;
public:
	FilesInOneList(const char * list_file);
	void AddDirectory(const char * ToAdd)
		{TheDirectories = (char **) AddToList(ToAdd,
			(void**)TheDirectories);}
	void AddTeXFile(TeXFile * ToAdd)
		{TheTeXFiles = (TeXFile **) AddToList(ToAdd,
			(void**)TheTeXFiles);}
	const char * GetListFile() const { return ListFile; }
	void ReadDirectories(const char * SubDir);
	void ReadDirectory(const char * Dir);
	void WriteTeXIncludeFiles();
	void Exclude(const char * Exc);
	void LogOutDump(const char * Msg=0);
};

FilesInOneList::FilesInOneList(const char * list_file):
	ListFile(list_file),
	TheDirectories(0),
	TheTeXFiles(0)
{
}

void FilesInOneList::Exclude(const char * Exc)
{
	int Count = 0 ;
	for (TeXFile ** TheFile = TheTeXFiles ; *TheFile; TheFile++, Count++) 
		if (!strcmp((*TheFile)->FullFileName,Exc)) {
		// LogOut << "Excluding one entry.\n" ;
		delete *TheFile ;
		TeXFile ** WriteTo = TheFile ;
		for (TeXFile ** SkipFile = TheFile+1; *SkipFile; SkipFile++)
			*(WriteTo++) = *SkipFile ;
		*WriteTo = 0 ;
	}
}

int TeXFileSort(const void * a, const void * b)
{
	const TeXFile ** A = (const TeXFile **) a ;
	const TeXFile ** B = (const TeXFile **) b ;
	if (!A) return -1 ;
	if (!B) return 1 ;
	const char * Aa = (*A)->SortName;
	// LogOut << "Sort name a = " << Aa << "\n" ;
	const char * Bb = (*B)->SortName;
	// LogOut << "Sort name b = " << Bb << "\n" ;
	if (!Aa) return -1 ;
	if (!Bb) return 1 ;
	return TeXstrcmp(Aa,Bb);
}

void FilesInOneList::LogOutDump(const char * Msg)
{
	cerr << "Dump of list `" << GetListFile() << "'" ;
	if (Msg) cerr << " at " << Msg << ".\n" ;
	else cerr << ".\n" ;

	if (!TheTeXFiles) cerr << "NULL list pointer.\n" ;
	else for (TeXFile ** FileX = TheTeXFiles; *FileX; FileX++) {
		cerr << (*FileX)->SortName << " " <<
			(*FileX)->FullFileName << "\n" ;
	}
}

void FilesInOneList::WriteTeXIncludeFiles()
{
	// LogOutDump("WriteTeXIncludeFiles start");

	if (!TheTeXFiles) {
		cerr << "Warning: no files found for `" << GetListFile()
			<< "'.\n";
		cerr << "This file will no be created or changed!\n" ;
		return ;
	}
	qsort((char *) TheTeXFiles,ListLength((const void **)TheTeXFiles),
		sizeof(*TheTeXFiles),TeXFileSort);
	ofstream Write(ListFile);
	if (!Write.good()) {
		cerr << "Cannot create TeX include list file `" <<
			ListFile << "'.\n" ;
		exit(1);
	}
	for (TeXFile ** File = TheTeXFiles; *File; File++)
		(*File)->WriteTeXIncludeFile(Write);
}

void FilesInOneList::ReadDirectory(const char * Dir)
{
	// LogOut << "Reading directory `" << Dir << "'\n" ;
	const char * Suffix = ".tex" ;
	const char ** FileList = FileListFromDirectory(Dir,Suffix);
	int Missing = 1 ;
	if (FileList) if (*FileList) Missing = 0 ;
	if (Missing) {
		cerr << "There are no files with suffix `" <<
			Suffix << "' in directory `" << Dir << "'.\n" ;
		return ;
	}
	TeXFile ** Files = new TeXFile * [ListLength((const void **)FileList)+1];
	int i = 0 ;
	for (const char ** OneFile = FileList; *OneFile; OneFile++) {
		const char * FullName = Concatenate(Dir,"/",*OneFile) ;
		// LogOut << "Reading `" << FullName << "'\n" ;
		if (AllTheLists.ToBeExcluded(FullName)) {
			// LogOut << "Excluding file `" << FullName << "'\n" ;
			delete (char *) FullName ;
			continue ;
		}
		Files[i++] = new TeXFile(FullName);
	}
	Files[i] = 0 ;
	// LogOutDump("Before adding Files");
	TheTeXFiles = (TeXFile **) AddListToList((const void **)Files,
		(void ** const)TheTeXFiles);
	// LogOutDump("Before after Files");
}

void usage();

void FilesInOneList::ReadDirectories(const char * SubDir)
{
	if (!TheDirectories) {
		cerr << "No directories specifed for list file `"
			<< ListFile << "'.\n" ;
		usage();
		return ;
	}
	for (const char ** OneDirectory = (const char **) TheDirectories;
		*OneDirectory; OneDirectory++) {
			const char * Dir = *OneDirectory ;
			char * Temp = 0 ;
			if (SubDir) Dir = Temp = Concatenate(Dir,"/",SubDir);
			ReadDirectory(Dir);
			delete Temp ;
	}
}

typedef void (*ProcessArgument)(int index, const char * Argument);


void AllLists::ReadDirectories()
{
	if (!AllListFiles) {
		cerr << "No directories specifed. Execution aborted.\n" ;
		usage();
		exit(1);
	}
	for (FilesInOneList ** OneList = AllListFiles; *OneList; OneList++)
		(*OneList)->ReadDirectories(SubDir);
}

void AllLists::Exclude(const char * Exc)
{
	// LogOut << "Excluding `" << Exc << "'.\n" ;
	for (FilesInOneList ** Files = AllListFiles ; *Files ; Files++)
		(*Files)->Exclude(Exc);
}

void AllLists::ProcessExcludes()
{
	if (TheExcludeFiles) for (const char ** Exc = (const char **) TheExcludeFiles;
		*Exc; Exc++) Exclude (*Exc);
}

void AllLists::WriteTeXIncludeFiles()
{
	for (FilesInOneList ** OneList = AllListFiles; *OneList; OneList++)
		(*OneList)->WriteTeXIncludeFiles();
}


FilesInOneList * AllLists::FindFilesList(const char * list_file)
{
	FilesInOneList ** ReturnPtr ;
	if (AllListFiles) for (ReturnPtr = AllListFiles; * ReturnPtr;
		ReturnPtr++) if (!strcmp(list_file,(*ReturnPtr)->GetListFile()))
			return *ReturnPtr ;
	return 0 ;
}

void AllLists::AddIfNeeded(FilesInOneList * Files)
{
	if(FindFilesList(Files->GetListFile())) return ;
	AllListFiles =
		(FilesInOneList **) AddToList(CurrentFilesList,
			(void **)AllListFiles);
}

void AllLists::SetCurrentFilesList(const char * list_file)
{
	if (CurrentFilesList) AddIfNeeded(CurrentFilesList) ;
	if (!list_file) return ;
	if (CurrentFilesList = FindFilesList(list_file)) return ;
	CurrentFilesList = new FilesInOneList(list_file);
	// LogOut << "Created CurrentFilesList for "<< list_file << "\n" ;
}

AllLists::AllLists():
	AllListFiles(0),
	CurrentFilesList(0),
	TheExcludeFiles(0),
	TheDirExcludeFiles(0),
	SubDir(0)
{
}


void AllLists::ProcessTeXDirectory(int index, const char * arg)
{
	// LogOut<<"AllLists::ProcessTeXDirectory("<<index<<", "<<arg<<")\n";
	if (!index) SetCurrentFilesList(arg);
	else CurrentFilesList->AddDirectory(arg) ;
}

void AllLists::ProcessTeXFile(int index, const char * arg)
{
	// LogOut << "ProcessTeXDirectory(" << index << ", " << arg << ")\n" ;
	if (!index) SetCurrentFilesList(arg);
	else CurrentFilesList->AddTeXFile(new TeXFile(arg));
}

void AllLists::ExcludeDirTeXFile(int, const char * arg)
{
	TheDirExcludeFiles = (char **) AddToList(arg,
		(void **)TheDirExcludeFiles);
}

void AllLists::ExcludeTeXFile(int, const char * arg)
{
	TheExcludeFiles = (char **) AddToList(arg,(void**) TheExcludeFiles);
}

int AllLists::ToBeExcluded(const char * FullName) const
{
	// LogOut << "Testing to exclude `" << FullName << "'.\n" ;
	if (TheDirExcludeFiles)
		for (const char ** Check = (const char **) TheDirExcludeFiles; *Check; Check++){
			// LogOut << "`" << *Check << "'\n" ;
			if (!strcmp(*Check,FullName)) return 1;
		}
	return 0 ;
}

void ProcessTeXDirectory(int index, const char * arg)
{
	AllTheLists.ProcessTeXDirectory(index,arg);
}

void ProcessTeXFile(int index, const char * arg)
{
	AllTheLists.ProcessTeXFile(index,arg) ;
}

void ExcludeDirTeXFile(int index, const char * arg)
{
	AllTheLists.ExcludeDirTeXFile(index,arg) ;
}

void ExcludeTeXFile(int index, const char * arg)
{
	AllTheLists.ExcludeTeXFile(index,arg) ;
}

void SpecifySubDir(int, const char * arg)
{
	AllTheLists.SpecifySubDir(arg);
}

enum CommandArguments {NoArguments,OneArgument,OneOrMoreArguments,
		TwoOrMoreArguments} ;

struct CommandOption {
	const char * Command ;
	const char * Usage ;
	const char * Meaning ;
	CommandArguments TheArguments ;
	ProcessArgument DoProcessArgument ;
	void usage();
};

void CommandOption::usage()
{
	cerr << Command << " " << Usage << " : " << Meaning << "\n" ; 
}

CommandOption TheCommandOptions[] = {
	{"-t","dest_file directory","destination file and directory to process",
		TwoOrMoreArguments,ProcessTeXDirectory},
	{"-f","file1 [file2 ... filen]",
		"file(s) to include",
		OneOrMoreArguments,ProcessTeXFile},
	{"-x","file1 [file2 ... filen]","path of file(s) to exclude",
		OneOrMoreArguments,ExcludeTeXFile},
	{"-e","file1 [file2 ... filen]","file(s) to exclude from directory",
		OneOrMoreArguments,ExcludeDirTeXFile},
	{"-s","suffix","suffix for directory search",OneArgument,SpecifySubDir},
	{0}
};

void usage()
{
	cerr << "\n`maketex' creates mutliple lists of files. One list is created\n" ;
	cerr << " for each `-t' option. These can be followed by a `-e'\n" ;
	cerr << " or `-x' option to exclude files or a `-f' option to add files.\n" ;
	cerr << " You must first specify a suffix with `-s'.\n\n" ;
	cerr << "Following are the allowed options:\n" ; 
	for (CommandOption * pt = TheCommandOptions ; pt->Command; pt++)
		pt->usage();
	exit(1);
}

enum CommandState {FindCommand,FindSingleArgument,FindFirstArgument,
	FindAtLeastOneMoreArgument, FindArgumentList,NoCommandFound} ;

class AllCommandOptions {
	CommandOption * TheCommandOptions ;
	int ErrorCount ;
	ProcessArgument CurrentProcessing ;
	CommandState TheState ;
	int ArgumentIndex ;
	CommandState CheckForUnexpectedCommand(const char * Token);
	CommandState ProcessOption(const char * Token);
	CommandState ProcessSingleArgument(const char * arg) ;
	CommandState ProcessSpecialFirstArgument(const char * arg) ;
	CommandState ProcessAtLeastOneArgument(const char * arg);
	CommandState ProcessNextArgument(const char * arg);
public:
	AllCommandOptions(CommandOption * opt):
		TheCommandOptions(opt),
		ErrorCount(0),
		CurrentProcessing(0),
		TheState(FindCommand) {}
	CommandOption * GetOption(const char * Token);
	int ProcessCommandArguments(const int argc, const char ** argv);
};

AllCommandOptions AllOptions(TheCommandOptions);

CommandState AllCommandOptions::CheckForUnexpectedCommand(const char * Token)
{
	CommandOption * ThisOption = GetOption(Token) ;
	if (!ThisOption) return NoCommandFound ;
	cerr<<"Command option `"<<Token<<"' found where argument expected.\n";
	return ProcessOption(Token) ;
}

CommandState AllCommandOptions::ProcessSingleArgument(const char * arg) 
{
	CommandState Check = CheckForUnexpectedCommand(arg);
	if (Check != NoCommandFound) return Check ;
	(*CurrentProcessing)(ArgumentIndex, arg);
	return FindCommand ;
}

CommandState AllCommandOptions::ProcessSpecialFirstArgument(const char * arg) 
{
	CommandState Check = CheckForUnexpectedCommand(arg);
	if (Check != NoCommandFound) return Check ;
	(*CurrentProcessing)(ArgumentIndex++, arg);
	return FindAtLeastOneMoreArgument ;
}

CommandState AllCommandOptions::ProcessAtLeastOneArgument(const char * arg) 
{
	CommandState Check = CheckForUnexpectedCommand(arg);
	if (Check != NoCommandFound) return Check ;
	(*CurrentProcessing)(ArgumentIndex++, arg);
	return FindArgumentList ;
}

CommandState AllCommandOptions::ProcessNextArgument(const char * arg)
{
	if (GetOption(arg)) return ProcessOption(arg);
	(*CurrentProcessing)(ArgumentIndex++, arg);
	return FindArgumentList ;
}


CommandState AllCommandOptions::ProcessOption(const char * Token)
{
	CommandOption * ThisOption = GetOption(Token);
	if (!ThisOption) {
		ErrorCount++;
		cerr << "Unknown command option `" << Token << "'.\n" ;
		usage();
		return FindCommand ;
	}
	ArgumentIndex = 0 ;
	CurrentProcessing = ThisOption->DoProcessArgument ;
	switch (ThisOption->TheArguments) {
case NoArguments:
		return FindCommand ;
case OneArgument:
		return FindSingleArgument ;
case OneOrMoreArguments:
		return FindArgumentList;
case TwoOrMoreArguments:
		return FindFirstArgument ;
	}
	return FindCommand ; // keep VC++ happy
}

CommandOption * AllCommandOptions::GetOption(const char * Token)
{
	CommandOption * Option ;
	for (Option = TheCommandOptions; Option->Command; Option++)
		if (!strcmp(Option->Command,Token)) return Option ;
	return 0;
}
	
	
int AllCommandOptions::ProcessCommandArguments(const int argc,
	const char ** argv)
{
	TheState = FindCommand ;
	for (int i = 1 ; i < argc ; i++) switch (TheState) {
case FindCommand:
		TheState = ProcessOption(argv[i]);
		break ;
case FindSingleArgument:
		TheState = ProcessSingleArgument(argv[i]);
		break ;
case FindFirstArgument:
		TheState = ProcessSpecialFirstArgument(argv[i]);
		break ;
case FindAtLeastOneMoreArgument:
		TheState = ProcessAtLeastOneArgument(argv[i]);
		break ;
case FindArgumentList:
		TheState = ProcessNextArgument(argv[i]);
		break ;
default:
case NoCommandFound:
		cerr << "State switch internal error: " << (int) TheState <<
			"\n" ;
		exit(1);
	}
	switch(TheState) {
case FindCommand:
case FindArgumentList:
		break ;
case FindSingleArgument:
case FindFirstArgument:
case FindAtLeastOneMoreArgument:
		ErrorCount++ ;
		cerr << "The arguments for the last command are incomplete.\n" ;
		break ;
default:
case NoCommandFound:
		cerr << "State switch internal error: " << (int) TheState <<
			"\n" ;
		exit(1);
	}
	if (ErrorCount) return 0 ;
	return 1 ;
}

void main(const int argc, const char ** argv)
{
	if (!AllOptions.ProcessCommandArguments(argc,argv)) exit(1);
	AllTheLists.SetCurrentFilesList();
	AllTheLists.ReadDirectories();
	AllTheLists.ProcessExcludes();
	AllTheLists.WriteTeXIncludeFiles();
}
