/*
 *  indextex.C from ObjectProDSP 0.1
 *  Copyright (C) 1994, Mountain Math Software. All rights reserved.
 *  
 *  This file is part of ObjectProDSP, a tool for Digital Signal
 *  Processing design, development and implementation. It is free
 *  software provided you use and distribute it under the terms of
 *  version 2 of the GNU General Public License as published
 *  by the Free Software Foundation. You may NOT distribute it or
 *  works derived from it or code that it generates under ANY
 *  OTHER terms.  In particular NONE of the ObjectProDSP system is
 *  licensed for use under the GNU General Public LIBRARY License.
 *  Mountain Math Software plans to offer a commercial version of
 *  ObjectProDSP for a fee. That version will allow redistribution
 *  of generated code under standard commercial terms.
 *  
 *  This program 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 version 2 of the GNU General
 *  Public License along with this program. See file COPYING. If not
 *  or if you wish information on commercial versions and licensing
 *  write Mountain Math Software, P. O. Box 2124, Saratoga, CA 95070,
 *  USA, or send us e-mail at: support@mtnmath.com.
 *  
 *  You may also obtain the GNU General Public License by writing the
 *  Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
 *  USA.  However if you received a copy of this program without the
 *  file COPYING or without a copyright notice attached to all text
 *  files, libraries and executables please inform Mountain Math Software.
 *  
 *  ObjectProDSP is a trademark of Mountain Math Software.
 */
#include "ObjProGen/cpyrght_exe.h"
#include <string.h>
#include <ctype.h>
#include <fstream.h>
#include <stdio.h>
#include <libc.h>
#include <unistd.h>

#include "linlist.h"
#include "lexnum.h"
#include "outtok.h"
#include "mkstr.h"
#include "texutil.h"
#include "texspec.h"

#define LogOut if (Debug) cerr 
int Debug = 0 ;

class IndexEntry ;
class LinearList ;

extern LinearList TheIndexEntrys ;

ofstream * OutFile = 0 ;
OutTokens * OutTok = 0 ;

FILE * InFile = 0 ;
char * InputFileName = 0 ;

int MaxEntry = 8 ;

struct IndexEntry {
	const char * Cite ;
	const char * Page ;
public:
	IndexEntry(const char * cite, const char * page);
	int Identical(IndexEntry * check);
	int SameCite(IndexEntry * check) ;
};

IndexEntry::IndexEntry(const char * cite, const char * page):
	Cite(MakeLitString(cite)),
	Page(MakeLitString(page))
{
	LogOut << "Found: " << Cite << ", " << Page << "\n" ;
}

int IndexEntry::Identical(IndexEntry * check)
{
	if (!check) return 0 ;
	LogOut << "`" << Cite << "' : `" << Page << "'\n" ;
	LogOut << "`" << check->Cite << "' : `" << check->Page << "'\n" ;
	int Ck = TeXstrcmp(check->Cite,Cite) ;
	int Ck2 = strcmp(check->Page,Page) ;
	int Return = !Ck && !Ck2 ;
	LogOut << "Identical (" << Ck << ", " << Ck2 << ") = " << Return<<"\n" ;
	return Return ;
}

int IndexEntry::SameCite(IndexEntry * check)
{
	if (!check) return 0 ;
	return !TeXstrcmp(check->Cite,Cite) ;
}

class LinearList {
	void ** Items ;
	int Size ;
	int Max ;
	const MinSize ;
	void Expand() ;
public:
	LinearList(void ** Initial = 0, int Size = 0, int min=32);
	void AddItem(void * Item);
	void ** Get() { return Items;}
	int Length() {return ListLength(Items);}
};

LinearList TheIndexEntrys ;

LinearList::LinearList(void ** Initial, int Size, int min):
	Items(0),
	Size(0),
	Max(0),
	MinSize(min>4?min:4)
	// Less than this and the expansion formula FAILS.
{
	if (Size) {
		Size = Max = Size ;
		Items = Initial ;
		Items[Size] = 0 ;
	} else {
		Items = Initial ;
		Size=Max = ListLength(Items);
	}
}

void LinearList::Expand()
{
	void ** Temp = new void * [1 + (Max = Max + (Max >> 2)) ] ;
	if (Items) for (int i = 0 ; i < Size+1 ; i++) Temp[i] = Items[i];
	delete Items ;
	Items = Temp ;
}

void LinearList::AddItem(void * Item)
{
	if (!Items) {
		Items = new void * [MinSize + 1] ;
		Items[0] = Item ;
		Size = 1 ;
		Max = MinSize ;
		return ;
	}
	if (Size >= Max) Expand() ;
	Items[Size++] = Item ;
}

int LineNumber ;
int WarningCount = 0 ;
int ErrorCount = 0 ;

void Warning()
{
	cerr << " at line " << LineNumber << "\n" ;
	WarningCount++ ;
}

void Error()
{
	cerr << " at line " << LineNumber << "\n" ;
	ErrorCount++ ;
}

void Fatal()
{
	cerr << " at line " << LineNumber << "\n" ;
	exit(1);
}

IndexEntry * Scan(char * Buf)
{
	int Length = strlen(Buf);
	char * Ptr = Buf + Length -1 ;
	if (*Ptr != '}') {
		cerr << "Entry not terminated by `}'" ;
		Warning();
		return 0 ;
	}
	*Ptr-- = '\0' ;
	int BracketDepth = 1 ;
	for ( ; Ptr >= Buf; Ptr--) if (*Ptr == '}') BracketDepth++ ;
		else if (*Ptr == '{') if (!--BracketDepth) break ;
	const char * Page = Ptr+1 ;
	int Error = 1 ;
	if (Ptr > Buf+1) if (*Ptr-- == '{') if (*Ptr == '}') Error = 0 ;
	if (Error) {
		cerr << "Cannot find page entry" ;
		cerr << "Buf:`" << Buf << "'\n" ;
		cerr << "Ptr:`" << Ptr << "'\n" ;
		Warning();
		return 0 ;
	}
	*Ptr = '\0' ;
	for (char * ClearBlank = Ptr - 1 ; ClearBlank >=Buf; ClearBlank++)
		if(isspace(*ClearBlank)) *ClearBlank = '\0' ; else break ;
	const char * Init = "\\indexentry{" ;
	if (strncmp(Buf,Init,strlen(Init))) {
		cerr << "Cannot find citation" ;
		Warning() ;
		return 0 ;
	}
	const char * Cite = Buf + strlen(Init);
	while (*Cite) if (!isspace(*Cite)) break ; else Cite++ ;
	if (!*Cite || !*Page) {
		cerr << "Partially NULL entry" ;
		Warning() ;
		return 0 ;
	}
	return new IndexEntry(Cite, Page);
}

void remove_underscore_expansion(char * buf)
{
	if (!buf) return ;
	if (!*buf) return ;
	static const char * to_remove =
		"\\unhbox \\voidb@x \\kern .06em \\vbox {\\hrule width.3em}" ;
	static int length= 0 ;
	if (!length) length = strlen(to_remove);
	int bl = strlen(buf);
	if (bl <= length) return ;
	for (char * pt = buf + bl - length ; pt >= buf ; pt--) {
		if (strncmp(pt,to_remove,length)) continue ;
		pt[0] = '_' ;
		strcpy(pt+1,pt+length);
	}
}

IndexEntry * GetNext()
{
	const BufSize = 2048 ;
	char Buf[BufSize + 1] ;
	if (!InFile) return 0 ;
	if (!fgets(Buf,BufSize,InFile)) return 0 ;
	remove_underscore_expansion(Buf);
	LineNumber++ ;
	int End = strlen(Buf) ;
	if (Buf[End-1] == '\n') Buf[End-1] = '\0' ;
	else if (End == BufSize) {
		cerr << "Line  > " << BufSize << " characters.\n" ;
		Fatal();
	} else {
		cerr << "Incomplete last line at line " ;
		Warning();
	}
	return Scan(Buf);
}

class PrintIndex {
	IndexEntry ** TheSortedEntrys ;
	int LastFirstCharacter ;
	IndexEntry * LastEntry ;
	int CheckIfTooMany(IndexEntry ** Start) ;
	int SkipFlag ;
	int AnyEntries ;
public:
	PrintIndex(IndexEntry ** entrys);
	void WriteEntry(IndexEntry ** Entry) ;
	void WriteIndex();
	void CheckNewCharacter(char C);
	int SameCite(IndexEntry ** next);
};

PrintIndex::PrintIndex(IndexEntry ** entrys):
	TheSortedEntrys(entrys),
	LastEntry(0),
	LastFirstCharacter(0),
	AnyEntries(0),
	SkipFlag(0)
{
}


int PrintIndex::CheckIfTooMany(IndexEntry ** Start)
{
	int RepeatCount = 1 ;
	IndexEntry * First = *Start++ ;
	const char * LastPage = "XXX" ;
	for (; *Start; Start++) {
		if ((*Start)->SameCite(First))
			if (strcmp(LastPage,(*Start)->Page)) RepeatCount++ ;
		LastPage = (*Start)->Page ;
		if (RepeatCount > MaxEntry) return 1 ;
	}
	return 0 ;
}

int PrintIndex::SameCite(IndexEntry** next)
{
	IndexEntry& Next = **next ;
	if (!Next.SameCite(LastEntry)) {
		SkipFlag = CheckIfTooMany(next);
		LastEntry = &Next ;
		return 0 ;
	}
	if (SkipFlag) return 1 ;
	LastEntry = &Next ;
	*OutFile << ", " << Next.Page ;
	return 1 ;
}

void PrintIndex::CheckNewCharacter(char C)
{
	if (tolower(C) == LastFirstCharacter) return ;
	if (AnyEntries) *OutFile << "\n\n\\vspace{.1in}\n\n" ;
	AnyEntries = 0 ;
	LastFirstCharacter = tolower(C) ;
}

static const char * EscapeUnderscore(const char * str)
{
    const buf_size = 8192;
    static char buf[buf_size+1] ;
    char * dest = buf ;
    for(const char * pt = str; *pt; pt++) {
        if(*pt == '_') {
			int do_escape = 1 ;
			if (pt > str) if (pt[-1] == '\\') do_escape = 0 ;
			if (do_escape) *dest++ = '\\' ;
		}
        *dest++ = *pt ;
    }
	*dest++ = '\0' ;
    return buf ;
}




void PrintIndex::WriteEntry(IndexEntry **  entry)
{
	IndexEntry& Entry = **entry ;
	LogOut << "Processing " << EscapeUnderscore(Entry.Cite) << ", " <<
		Entry.Page << "\n" ;
	if (Entry.Identical(LastEntry)) return ;
	LogOut << "Not identical\n" ;
	char First = Entry.Cite[0] ;
	CheckNewCharacter(First);
	if (SameCite(entry)) return ;
	if (SkipFlag) return ;
	LogOut << "Not same cite\n" ;
	*OutFile << "\n\\item " << EscapeUnderscore(Entry.Cite) << " " << Entry.Page ;
	AnyEntries = 1 ;
	LogOut << "\n\\item " <<EscapeUnderscore( Entry.Cite) << " " << Entry.Page << "\n" ;
}
	
void PrintIndex::WriteIndex()
{
	if (TheSortedEntrys) {
		*OutFile << "\\begin{theindex}\n" ;
		for (IndexEntry ** entry = TheSortedEntrys ; *entry; entry++)
			WriteEntry(entry);
		*OutFile << "\n\\end{theindex}\n" ;
	} else {
		cerr << "No citations found.\n" ;
		exit(0) ;
	}
}

void WriteIndex()
{
	PrintIndex DoPrint( (IndexEntry **) TheIndexEntrys.Get());
	DoPrint.WriteIndex();
}

static int SafeCompare(const char * a,const char * b)
{
	if (!a) if (!b) return 0; else return -1 ;
	if (!b) return 1 ;
	return TeXstrcmp(a,b);
}

static int SafeNumberCompare(const char * a,const char * b)
{
	if (!a) if (!b) return 0; else return -1 ;
	if (!b) return 1 ;
	int SizeDiff = strlen(a) - strlen(b) ;
	// leading zeros not allowed
	if (SizeDiff) return SizeDiff ;
	const char * PtrA = a ;
	const char * PtrB = b ;
	int Test ;
	while (*a && *b) if (Test = *a++ - *b++) return Test ;
	if (!*a) if (!*b) return 0 ; else return -1;
	return 1 ;
}

inline char * FromTeX(const char * a)
	{return TheTeXWordTranslator.Translate(a);}

static int TeXSafeCompare(const char * A, const char * B)
{
	char * AA = FromTeX(A);
	char * BB = FromTeX(B);
	int Return = SafeCompare(AA?AA:A,BB?BB:B);
	delete AA ;
	delete BB ;
	return Return ;
}

int IndexListSort(void * a, void * b)
{
	IndexEntry ** A = (IndexEntry **) a ;
	IndexEntry ** B = (IndexEntry **) b ;
	if (!A) return -1 ;
	if (!B) return 1 ;
	const char * Aa = (*A)->Cite ;
	const char * Bb = (*B)->Cite;
	int Return = TeXSafeCompare(Aa,Bb);
	if (Return) return Return ;
	return SafeNumberCompare((*A)->Page,(*B)->Page);
	
}

		
void SortIndex()
{
	IndexEntry ** Lst = (IndexEntry **) TheIndexEntrys.Get();
	if (!Lst) return ;
	qsort((char *)Lst,TheIndexEntrys.Length(),sizeof(*Lst),IndexListSort);
}
		
void ReadIdxFile()
{
	IndexEntry * NextEntry ;
	while(NextEntry = GetNext()) TheIndexEntrys.AddItem(NextEntry);
}

void Main()
{
	ReadIdxFile();
	SortIndex() ;
	WriteIndex();
}


const char * FileSuffix = ".idx" ;

static void Usage(const char * Pgm)
{
	cerr << "Usage is: " << Pgm << " [-d] file\n" ;
	cerr << "`-d' specifes debugging mode.\n" ;
	cerr << "`file' is a tex index file.\n" ;
	cerr << "`-w dir' specified directory for output file.\n" ;
	cerr << "`-l N' number of entry to delete entry (default = "
		<< MaxEntry << ").\n" ;
	cerr << "FileSuffix `" << FileSuffix <<
		"' will be appended to the name if missing.\n" ;
	exit (1);
}

main(int argc,char ** argv)
{
	int c;
	int errflg = 0;
	const char * OutputDirectory = 0;
	const char * OutputFileName = 0 ;
	const char * MaxEntryString = 0 ;

	while (( c = getopt(argc,argv,"dw:l:")) != EOF) switch(c) {
case 'd':
		Debug = 1 ;
		break ;
case 'w':
		OutputDirectory = MakeLitString(optarg);
		break ;
case 'l':
		MaxEntryString = MakeLitString(optarg);
default:
		errflg++;
		break ;
	}
	if (errflg) {
		cerr << "Invalid command line.\n" ;
		Usage(argv[0]);
	}
	for (; optind < argc; optind++) {
		if (InFile) {
			cerr << "Only one input file is allowed.\n" ;
			Usage(argv[0]);
		}
		InputFileName = new char[strlen(argv[optind])+1];
		strcpy(InputFileName,argv[optind]);
		InputFileName = AddSuffix(InputFileName,FileSuffix);

		InFile = fopen(InputFileName,"r");
		if (!InFile) {
			cerr << "Cannot open input file `" << InputFileName <<
				"'\n" ;
			Usage(argv[0]);
		}
	}
	if (!InputFileName) Usage(argv[0]);
	if (MaxEntryString) if (sscanf(MaxEntryString,"%d",&MaxEntry) != 1) {
		cerr << "Parameter for option `-l' is not an integer.\n" ;
		Usage(argv[0]);
	}

	OutputFileName = AddSuffix(RemoveSuffix(InputFileName,FileSuffix),
		".txx");
	if (OutputDirectory)  
		if (OutputDirectory[strlen(OutputDirectory)-1] == '/')
			OutputFileName = Concatenate(OutputDirectory,
				OutputFileName);
		else OutputFileName = Concatenate(OutputDirectory,
				"/",OutputFileName);
		
	OutFile = new ofstream(OutputFileName);
	if (!OutFile->good()) {
		cerr << "Cannot create output file `" << OutputFileName
			<< "'.\n" ;
		Usage(argv[0]);
	}
	OutTok = new OutTokens(OutFile,0,""," ","",80,1000000) ;

		
	
	Main();
	OutTok->FlushLine();

	delete OutFile ;
	exit(0);
}
