/*
** Copyright (C) 1995, Enterprise Integration Technologies Corp.        
** All Rights Reserved.
** Kevin Hughes, kevinh@eit.com 
** 3/11/94
**
** Released under the GPL by EIT
**
** Heavily hacked for Harvest
*/

#include "swish.h"
#include "merge.h"
#include "error.h"
#include "file.h"
#include "search.h"
#include "string.h"
#include "index.h"
#include "hash.h"
#include "mem.h"

/* The main merge functions - it accepts three file names.
** This is a bit hairy. It basically acts as a zipper,
** zipping up both index files into one.
*/

void readmerge(file1, file2, outfile)
     char *file1;
     char *file2;
     char *outfile;
{
	int i, j, indexfilenum1, indexfilenum2, result, totalfiles,
		skipwords, skipfiles;
	long limit1, limit2, fileinfo1, fileinfo2, offsetstart;
	char line[MAXSTRLEN];
	struct indexentry *ip1, *ip2, *ip3;
	struct indexentry *buffer1, *buffer2;
	FILE *fp1, *fp2, *fp3;
	struct metaMergeEntry *metaFile1, *metaFile2;

	/* remapVar is used into addindexfilelist and need to be
	** initialized each time two indexes are merged.
	*/
	remapVar = 0;
	metaFile1 = metaFile2 = NULL;

	if ((fp1 = fopen(file1, "r")) == NULL) {
		sprintf(errorstr, "Couldn't read the index file \"%s\".",
		file1);
		progerr(errorstr);
	}
	if (!isokindexheader(fp1)) {
		sprintf(errorstr, "\"%s\" has an unknown format.",
		file1);
		progerr(errorstr);
	}
	if ((fp2 = fopen(file2, "r")) == NULL) {
		sprintf(errorstr, "Couldn't read the index file \"%s\".",
		file2);
		progerr(errorstr);
	}
	if (!isokindexheader(fp2)) {
		sprintf(errorstr, "\"%s\" has an unknown format.",
		file2);
		progerr(errorstr);
	}

	ip1 = ip2 = ip3 = NULL;
	buffer1 = buffer2 = NULL;
	if (verbose)
		printf("Counting files... ");
	indexfilenum1 = getindexfilenum(fp1);
	indexfilenum2 = getindexfilenum(fp2);
	totalfiles = indexfilenum1 + indexfilenum2;
	if (verbose) {
		printf("%d files.\n", indexfilenum1 + indexfilenum2);
		printf("Reading stopwords...");
	}
	readoffsets(fp1);
	readstopwords(fp1);
	limit1 = offsets[STOPWORDPOS];
	fileinfo1 = offsets[FILELISTPOS];
	metaFile1 = readMergeMeta(metaFile1,fp1);

	readoffsets(fp2);
	readstopwords(fp2);
	limit2 = offsets[STOPWORDPOS];
	fileinfo2 = offsets[FILELISTPOS];
	metaFile2 = readMergeMeta(metaFile2,fp2);

        /* Create the merged list and modify the
	   individual ones with the new meta index
        */	   
	metaEntryList = createMetaMerge(metaFile1, metaFile2);

	if (verbose)
		printf("\nReading file info...");

	totalfiles=indexfilenum1+indexfilenum2;
/*
	fseek(fp1, fileinfo1, 0);
	for (i = 1; i <= indexfilenum1; i++) {
		fgets(line, MAXSTRLEN, fp1);
		addindexfilelist(i, line, &totalfiles);
	}
	fseek(fp2, fileinfo2, 0);
	for (i = 1; i <= indexfilenum2; i++) {
		fgets(line, MAXSTRLEN, fp2);
		addindexfilelist(i + indexfilenum1, line, &totalfiles);
	}
*/

	if ((fp3 = fopen(outfile, "w")) == NULL) {
		sprintf(errorstr,
		"Couldn't write the merged index file \"%s\".",
		outfile);
		progerr(errorstr);
	}

	if (verbose)
		printf("\nMerging words... ");

	printheader(fp3, outfile, 0, totalfiles);

        offsetstart = ftell(fp3);
        for (i = 0; i < MAXCHARS; i++)
                fprintf(fp3, "%016li", offsets[i]);
        fputc('\n', fp3);

	readoffsets(fp1);
	readoffsets(fp2);

	for (i = 0; i < MAXCHARS; i++)
		offsets[i] = 0;

	skipwords = 0;
	while (1) {
		if (buffer1 == 	NULL) {
			ip1 = readindexline(fp1, limit1,metaFile1);
			if (ip1 == NULL) {
			  if (ip2 == NULL) {
			    break;
			  }
			}
			buffer1 = ip1;
		}
		if (buffer2 == NULL) {
			ip2 = readindexline(fp2, limit2,metaFile2);
			if (ip2 == NULL){
			  if (ip1 == NULL) {
			    break;
			  }
			}
			else 
			  addfilenums(ip2, indexfilenum1);
			buffer2 = ip2;
		}
		if (ip1 == NULL)
		  result = 1;
		else if (ip2 == NULL)
		  result = -1;
		else
		  result = strcmp(ip1->word, ip2->word);
		if (!result) {
			ip3 = mergeindexentries(ip1, ip2);
			printindexentry(ip3, fp3);
			freeindexentry(ip1);
			freeindexentry(ip2);
			freeindexentry(ip3);
			ip1 = ip2 = ip3 = NULL;
			buffer1 = buffer2 = NULL;
			skipwords++;
		}
		else if (result < 0) {
			printindexentry(ip1, fp3);
			freeindexentry(ip1);
			buffer1 = NULL;
			ip1 = NULL;
		}
		else {
			printindexentry(ip2, fp3);
			freeindexentry(ip2);
			buffer2 = NULL;
			ip2 = NULL;
		}
	}

	if (verbose) {
		if (skipwords)
			printf("%d redundant word%s.", skipwords,
			(skipwords == 1) ? "" : "s");
		else
			printf("no redundant words.");
	}

        printstopwords(fp3);

	if (verbose)
		printf("\nMerging file info... ");

	offsets[FILELISTPOS] = ftell(fp3);

	fseek(fp1, fileinfo1, 0);
	for (i= 1; i <= indexfilenum1; i++) {
		addtofilehashlist(i-1, ftell(fp3));
		fgets(line, MAXSTRLEN, fp1);
		fputs(line, fp3);
	}
	fseek(fp2, fileinfo2, 0);
	for (i= 1; i <= indexfilenum2; i++) {
		addtofilehashlist(i+indexfilenum1-1, ftell(fp3));
		fgets(line, MAXSTRLEN, fp2);
		fputs(line, fp3);
	}
/*
	for (i = j = 1; i <= indexfilenum1 + indexfilenum2; i++)
		if (getmap(i) == j) {
			addtofilehashlist(j++ - 1, ftell(fp3));
			fprintf(fp3, "%s", lookupindexfilenum(i));
		}
*/
	skipfiles = (indexfilenum1 + indexfilenum2) - totalfiles;
	if (verbose) {
		if (skipfiles)
			printf("%d redundant file%s.", skipfiles,
			(skipfiles == 1) ? "" : "s");
		else
			printf("no redundant files.");
	}
	printfileoffsets(fp3);
	printMetaNames(fp3);

	fseek(fp3, offsetstart, 0);
        for (i = 0; i < MAXCHARS; i++)
                fprintf(fp3, "%016li", offsets[i]);
	fclose(fp3);

	fclose(fp1);
	fclose(fp2);

	if (verbose)
		printf("\nDone.\n");
}

/* Gets the number of files in an index file.
*/

int getindexfilenum(fp)
     FILE *fp;
{
	int i;
	char line[MAXSTRLEN];

	readoffsets(fp);
	fseek(fp, offsets[FILELISTPOS], 0);

	i = 0;
	while(ftell(fp) != offsets[FILEOFFSETPOS]) {
		fgets(line, MAXSTRLEN, fp);
		i++;
	}

	return i;
}

/* This adds an offset to the file numbers in a particular
** result list. For instance, file 1 has file numbers going from
** 1 to 10, but so does file 2, so I have to add 10 to all the
** file numbers in file 2 before merging.
*/

void addfilenums(ip, num)
     struct indexentry *ip;
     int num;
{
	struct resultMerge *rp;

	rp = ip->result;
	while (rp != NULL) {
		rp->filenum = encodefilenum(decodefilenum(rp->filenum) + num);
		rp = rp->next;
	}
}

/* This reads the next line in the index file and puts the results
** in a result structure.
*/

struct indexentry *readindexline(FILE *fp, long limit, 
				 struct metaMergeEntry * metaFile)
{
        int i, c, x, countnum, rank=0, filenum=0, attribute;
        char fileword[MAXWORDLEN];
        struct resultMerge *rp;
	struct indexentry *ip;
	struct metaMergeEntry* tmp;

        rp = NULL;

	if (limit == ftell(fp))
		return NULL;
        for (i = 0; (c = fgetc(fp)) != 0; ) {
                if (c == ':') {
                        fileword[i] = '\0';
                        break;
                }
                else
                        fileword[i++] = c;
        }

        countnum = 1;

        ungetc(c, fp);
        while ((c = fgetc(fp)) != 0) {
                x = 0;
                do {
                        c = fgetc(fp);
                        if (c == 0)
                                break;
                        x *= 128;
                        x += c & 127;
                } while (c & 128);
		if (c == 0)
			break;
                if (x) {
                        if (countnum == 1) {
                                filenum = x;
                                countnum++;
                        }
                        else if (countnum == 2) {
                                rank = x;
                                countnum++;
                        }
                        else if (countnum == 3) {
			        attribute = x;
				/*Need to modify metaName with new list*/
				for(tmp=metaFile;tmp;tmp=tmp->next) {
				  if (tmp->oldIndex == attribute) {
				    attribute = tmp->newIndex;
				    break;
				  }
				}
                                rp = addtoresultlistMerge(rp, filenum,
				rank, attribute);
				countnum = 1;
                        }
                }
        }

	ip = (struct indexentry *) emalloc(sizeof(struct indexentry));
	ip->word = (char *) mystrdup(fileword);
	ip->result = rp;

        return ip;
}


/* This simply concatenates two information lists that correspond
** to a word found in both index files.
*/

struct indexentry *mergeindexentries(struct indexentry *ip1, 
				     struct indexentry *ip2)
{
	struct resultMerge *newrp, *rp1, *rp2;
	struct indexentry *ep;

	rp1 = ip1->result;
	rp2 = ip2->result;
	newrp = NULL;

	while (rp1 != NULL) {
		newrp = addtoresultlistMerge(newrp, rp1->filenum, rp1->rank, 
					     rp1->attribute);
		rp1 = rp1->next;
	}
	while (rp2 != NULL) {
		newrp = addtoresultlistMerge(newrp, rp2->filenum, rp2->rank, 
					     rp2->attribute);
		rp2 = rp2->next;
	}

	ep = (struct indexentry *) emalloc(sizeof(struct indexentry));
	ep->word = (char *) mystrdup(ip1->word);
	ep->result = newrp;

	return ep;
}

/* This prints a new word entry into the merged index file,
** removing redundant file information as it goes along.
*/

void printindexentry(ip, fp)
     struct indexentry *ip;
     FILE *fp;
{
	int i, num, attribute;
	struct resultMerge *rp;

	for (i = 0; indexchars[i] != '\0'; i++)
		if ((ip->word)[0] == indexchars[i] && !offsets[i])
			offsets[i] = ftell(fp);
	fprintf(fp, "%s:", ip->word);
	rp = ip->result;
	while (rp != NULL) {
		num = rp->filenum;
		attribute = rp->attribute;
		compress(num, fp);
		compress(rp->rank, fp);
		compress(rp->attribute,fp);
		rp = rp->next;
	}
	fputc(0, fp);
}

#ifdef NDEF
/* This associates a number with a new number.
** This function is used to remap file numbers from index
** files to a new merged index file.
*/

void remap(oldnum, newnum)
     int oldnum;
     int newnum;
{
	unsigned hashval;
	char tmpstr[MAXSTRLEN];
	struct mapentry *mp;

	mp = (struct mapentry *) emalloc(sizeof(struct mapentry));
	mp->oldnum = oldnum;
	mp->newnum = newnum;

	sprintf(tmpstr, "%d", oldnum);
	hashval = bighash(tmpstr);
	mp->next = mapentrylist[hashval];
	mapentrylist[hashval] = mp;
}


/* This retrieves the number associated with another.
*/


int getmap(num)
     int num;
{
	unsigned hashval;
	char tmpstr[MAXSTRLEN];
	struct mapentry *mp;

	sprintf(tmpstr, "%d", num);
	hashval = bighash(tmpstr);
	mp = mapentrylist[hashval];

	while (mp != NULL) {
		if (mp->oldnum == num)
			return mp->newnum;
		mp = mp->next;
	}
	return num;
}
#endif


/* This marks a number as having been printed.
*/

void marknum(num)
     int num;
{
	unsigned hashval;
	char tmpstr[MAXSTRLEN];
	struct markentry *mp;

	mp = (struct markentry *) emalloc(sizeof(struct markentry));
	mp->num = num;

	sprintf(tmpstr, "%d", num);
	hashval = bighash(tmpstr);
	mp->next = markentrylist[hashval];
	markentrylist[hashval] = mp;
}

#ifdef NDEF
/* Same thing but for merge only */
void marknumMerge(num, attribute)
     int num;
     int attribute;
{
        unsigned hashval;
        char tmpstr[MAXSTRLEN];
        struct markentryMerge *mp;
 
        mp = (struct markentryMerge *) emalloc(sizeof(struct markentryMerge));
        mp->num = num;
	mp->attribute = attribute;
 
        sprintf(tmpstr, "%d", num);
        hashval = bighash(tmpstr);
        mp->next = markentrylistMerge[hashval];
        markentrylistMerge[hashval] = mp;
}      
#endif

/* Has a number been printed?
*/

int ismarked(num)
     int num;
{
	unsigned hashval;
	char tmpstr[MAXSTRLEN];
	struct markentry *mp;

	sprintf(tmpstr, "%d", num);
	hashval = bighash(tmpstr);
	mp = markentrylist[hashval];

	while (mp != NULL) {
		if (mp->num == num)
			return 1;
		mp = mp->next;
	}
	return 0;
}

#ifdef NDEF
int ismarkedMerge(int num,int attribute)
{
        unsigned hashval;
        char tmpstr[MAXSTRLEN];
        struct markentryMerge *mp;
 
        sprintf(tmpstr, "%d", num);
        hashval = bighash(tmpstr);
        mp = markentrylistMerge[hashval];
 
        while (mp != NULL) {
                if ( (mp->num == num) && (mp->attribute == attribute) )
                        return 1;
                mp = mp->next;
	      }
        return 0;
      }
#endif

/* Initialize the marking list.
*/

void initmarkentrylist()
{
	int i;
	struct markentry *mp;

	for (i = 0; i < BIGHASHSIZE; i++) {
		mp = markentrylist[i];
		if (mp != NULL)
			free(mp);
		markentrylist[i] = NULL;
	}
}

#ifdef NDEF
void initmarkentrylistMerge()
{
        int i;
        struct markentryMerge *mp;
 
        for (i = 0; i < BIGHASHSIZE; i++) {
                mp = markentrylistMerge[i];
                if (mp != NULL)
                        free(mp);
                markentrylistMerge[i] = NULL;
	      }
}      


/* Initialize the main file list.
*/

void initindexfilehashlist()
{
  int i;
  struct indexfileinfo *ip;
  
  for (i = 0; i < BIGHASHSIZE; i++) {
    ip = indexfilehashlist[i];
    if (ip != NULL)
      free(ip);
    indexfilehashlist[i] = NULL;
  }
}

/* Initialize the mapentrylist 
*/

void initmapentrylist()
{
  int i;
  struct mapentry *ip;
  
  for (i = 0; i < BIGHASHSIZE; i++) {
    ip = mapentrylist[i];
    if (ip != NULL)
      free(ip);
    mapentrylist[i] = NULL;
  }
}

#endif

/* Frees up used index entries, my best attempt at memory management...
** I still have bytes leaking elsewhere...
*/

void freeindexentry(ip)
     struct indexentry *ip;
{
	struct resultMerge *rp, *oldp;

	free(ip->word);
	rp = ip->result;
	while (rp != NULL) {
		oldp = rp;
		rp = rp->next;
		free(oldp);
	}
	free(ip);
}

/* Translates a file number into something that can be compressed.
*/

int encodefilenum(num)
     int num;
{
	int i, j;

	for (i = j = 0; i != num; i++) {
		j++;
		if (!(j % 128))
			j++;
	}
	return j;
}

/* Translates a compressed file number into a correct file number.
*/

int decodefilenum(num)
     int num;
{
        int i, extra;

        for (i = 1, extra = 0; i < num; i++)
                if (!(i % 128)) {
                        extra++;
                        i++;
                }
        num -= extra;

	return num;
}

/* Similar to addtoresultlist, but also adding the meta name
*/

struct resultMerge *addtoresultlistMerge(struct resultMerge *rp, 
					 int filenum, int rank,int attribute)
{
        struct resultMerge *newnode;
        static struct resultMerge *head;
 
        newnode = (struct resultMerge *) emalloc(sizeof(struct resultMerge));
        newnode->filenum = filenum;
        newnode->rank = rank;
	newnode->attribute = attribute;
        newnode->next = NULL;
 
        if (rp == NULL)
                rp = newnode;
        else
                head->next = newnode;
 
        head = newnode;
 
        return rp;
}

/* Reads the meta names from the index. Needs to be different from
** readMetaNames because needs to zero out the counter.
*/
struct metaMergeEntry* readMergeMeta(metaFile,fp)
     struct metaMergeEntry* metaFile;
     FILE* fp;
{     
  int i, c, counter;
  char word[MAXWORDLEN];
  
  counter = 0;
  fseek(fp, offsets[METANAMEPOS], 0);
  for (i = 0; (c = fgetc(fp)) != '\n' && c != EOF; ){
    if (!isspace(c))
      word[i++] = c;
    else {
      word[i] = '\0';
      metaFile = addMetaMerge(metaFile, word,&counter);
      i = 0;
    }
  }
  return metaFile;
}

/* Adds an entry to the list of meta names for one index, 
** setting the new index to 0 - it will then be set by 
** createMetaMerge.
*/

struct metaMergeEntry* addMetaMerge(metaFile, metaWord,counter)
     struct metaMergeEntry* metaFile;
     char* metaWord;
     int* counter;
{
  int i;
  struct metaMergeEntry* newEntry;
  struct metaMergeEntry* tmpEntry;
  
  if (*counter == 0)
    *counter = 2;
  else if ((*counter) == 1 ||  (!((*counter) % 128)) )
   (*counter)++;
  for( i=0; metaWord[i]; i++)
    metaWord[i] =  tolower(metaWord[i]);
  newEntry = (struct metaMergeEntry*) emalloc(sizeof(struct metaMergeEntry));
  newEntry->metaName = (char*)mystrdup(metaWord);
  newEntry->oldIndex = (*counter)++;
  newEntry->newIndex = 0;
  newEntry->next = NULL;
  if (metaFile)
    {
      for(tmpEntry=metaFile;tmpEntry->next!=NULL;tmpEntry=tmpEntry->next)
        ;
      tmpEntry->next = newEntry;
    }
  else
    metaFile = newEntry;
  
  return metaFile;
}

/* Creates a list of all the meta names in the indexes
*/
struct metaEntry* createMetaMerge(metaFile1, metaFile2)
     struct metaMergeEntry* metaFile1;
     struct metaMergeEntry* metaFile2;
{
  struct metaMergeEntry* tmpEntry;
  int counter;

  metaEntryList = NULL;
  counter = 0;
  for (tmpEntry=metaFile1;tmpEntry;tmpEntry=tmpEntry->next)
    metaEntryList = addMetaMergeList(metaEntryList,tmpEntry,&counter);
  
  for (tmpEntry=metaFile2;tmpEntry;tmpEntry=tmpEntry->next)
    metaEntryList = addMetaMergeList(metaEntryList,tmpEntry,&counter);

  return metaEntryList;
}

/* Adds an entry to the merged meta names list and changes the
 ** new index in the idividual file entry
 */

struct metaEntry* addMetaMergeList(metaEntryList,metaFileEntry,count)
     struct metaEntry* metaEntryList;
     struct metaMergeEntry* metaFileEntry;
     int* count;
{
  int i, wordExists, newIndex;
  struct metaEntry* newEntry;
  struct metaEntry* tmpEntry;
  struct metaEntry* last;
  char *metaWord, *compWord;
  
  wordExists = 0;
  if ((*count) == 0)
    *count = 2;
  else if ((*count) == 1 ||  (!((*count) % 128)) )
    (*count)++;
  metaWord = metaFileEntry->metaName;
  
  for( i=0; metaWord[i]; i++)
    metaWord[i] =  tolower(metaWord[i]);
  if (metaEntryList)
    {
      for(tmpEntry=metaEntryList;tmpEntry;tmpEntry=tmpEntry->next)
	{
	  if (tmpEntry->next == NULL)
	    last = tmpEntry;
	  compWord = tmpEntry->metaName;
	  if (!strcmp(compWord,metaWord) ) 
	    {
	      wordExists = 1;
	      newIndex = tmpEntry->index;
	      break;
	    }
	}
      if (wordExists)
	metaFileEntry->newIndex = newIndex;
      else {
	newEntry = (struct metaEntry*) emalloc(sizeof(struct metaEntry));
	newEntry->metaName = (char*)mystrdup(metaWord);
	newEntry->index = *count;
	newEntry->next = NULL;
	metaFileEntry->newIndex = (*count)++;
	last->next = newEntry;
      }
    }
  else {
    newEntry = (struct metaEntry*) emalloc(sizeof(struct metaEntry));
    newEntry->metaName = (char*)mystrdup(metaWord);
    newEntry->index = *count;
    newEntry->next = NULL;
    metaFileEntry->newIndex = (*count)++;
    metaEntryList = newEntry;
  }
  return metaEntryList;
}
  
  
    
  
