/*
    LIB - a librarian for compatible OBJ/LIB files
    Copyright (C) 1995,1996  Steffen Kaiser

    This program 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; either version 2 of the License, or
    (at your option) any later version.

    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 the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* $RCSfile: MLIB.C $
   $Locker: ska $	$Name: v3_2 $	$State: Rel $

	write the LIB directory

	ybuf contains the currently active page

*/

#ifndef _MICROC_
#include <string.h>
#include <malloc.h>
#include <dos.h>
#endif
#include <portable.h>
#include "types.h"
#include "list.h"
#include "lib.h"
#include "tmplist.h"
#include "mlib.h"
#include "yerror.h"

#ifdef NOT_CIRCULAR
#define ROR(a,b) ((a) >> (b))
#define ROL(a,b) ((a) << (b))
#else
#define ROR(a,b) (((a) >> (b)) | ((a) << (16 - (b))))
//#define ROL(a,b) ROR((a), 16 - (b))
#define ROL(a,b) (((a) << (b)) | ((a) >> (16 - (b))))
#endif

#define page ybuf		/* current used page buffer */

#ifndef lint
static char const rcsid[] = 
	"$Id: MLIB.C 3.3 1999/02/17 05:18:08 ska Rel ska $";
#endif

static struct LIB_INFO *lib;	/* handle of library */
FLAG pageDirty = NUL;			/* last page modified? */

unsigned nextPrime(unsigned min)
{	int             *i;

    static int      Primes[] = {
	1, 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
	31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
	73, 79, 83, 89, 97, 101, 103, 107, 109, 113,
	127, 131, 137, 139, 149, 151, 157, 163, 167, 173,
	179, 181, 191, 193, 197, 199, 211, 223, 227, 229,
	233, 239, 241, 251, 257, 263, 269, 271, 277, 281,
	283, 293, 307, 311, 313, 317, 331, 337, 347, 349,
	353, 359, 367, 373, 379, 383, 389, 397, 401, 409,
	419, 421, 431, 433, 439, 443, 449, 457, 461, 463,
	467, 479, 487, 491, 499, 503, 509, 521, 523, 541,
	547, 557, 563, 569, 571, 577, 587, 593, 599, 601,
	607, 613, 617, 619, 631, 641, 643, 647, 653, 659,
	661, 673, 677, 683, 691, 701, 709, 719, 727, 733,
	739, 743, 751, 757, 761, 769, 773, 787, 797, 809,
	811, 821, 823, 827, 829, 839, 853, 857, 859, 863,
	877, 881, 883, 887, 907, 911, 919, 929, 937, 941,
    947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013,
    0};

	i = Primes;
    while(*++i && *i < min);
    return *i;
}

/* 	hash_name - generate the starting entry, page; delta entry, page
*	for a given name.  
*	sp	- starting page, returned
*	dp	- delta page, returned
*	se	- starting entry, returned
*	de	- delta entry, returned
*	symbol	- symbol to be hashed (Pascal string), input.
*/
void hash_name(unsigned int *sp, unsigned int *dp, unsigned int *se,
	  unsigned int *de, const byte *symbol)
{
    int		        i;
    byte			c;
    unsigned int    starting_page, delta_page, starting_entry, delta_entry;
    const byte		*start, *end;	/* pointer to start/end of symbol */

	DB_ENTER("hash_name");

    starting_page = delta_page = starting_entry = delta_entry = 0;

	end = symbol + (i = *(start = symbol));
    do {
		c = *start++ | 0x20;	/* lower case */
		starting_page = ROL(starting_page,2) ^ c;
		delta_entry = ROR(delta_entry,2) ^ c;
		c = *--end | 0x20;	/* lower case */
		starting_entry = ROR(starting_entry,2) ^ c;
		delta_page = ROL(delta_page,2) ^ c;
    } while(--i);

    starting_page %= dirPages;
    delta_page %= dirPages;
    starting_entry %= BUCKETS;
    delta_entry %= BUCKETS;

    if (!delta_page) delta_page = 1;
    if (!delta_entry) delta_entry = 1;

    *de = delta_entry;
    *sp = starting_page;
    *se = starting_entry;
    *dp = delta_page;

    DB_EXIT
}

void hopToPage(unsigned pageNr)
{	Lset(dirOffset, lib);
	while(pageNr--)
		Lseek(DIR_PAGE_SIZE, lib);
}

void writePage(unsigned p)
/* write page into the p'th page of the directory */
{	DB_ENTER("writePage");
	DB_PRINT("arg", ("page #%u", p));

	hopToPage(p);
	Lwrite(page, DIR_PAGE_SIZE, lib);
	pageDirty = NUL;

	DB_EXIT
}

void readPage(unsigned p)
/* re-write modified page and read page #p,
   if p == 0xffff, no page is read */
{	static unsigned lastPage = -1;
	DB_ENTER("readPage");
	DB_PRINT("arg", ("page #%u, lastPage #%u", p, lastPage));

	if(lastPage == p) DB_EXIT

	if(pageDirty)
		writePage(lastPage);

	if(p == 0xffff) DB_EXIT

	hopToPage(lastPage = p);
	Lread(page, DIR_PAGE_SIZE, lib);

	DB_EXIT
}

void initDir(void)
/* initialize directory:
	write dirPages zero'ed pages */
{	int i;

	DB_ENTER("initDir");
	DB_PRINT("inf", ("#pages = %u", dirPages));

#ifndef NDEBUG
	informative("Directory pages: %u", dirPages);
#endif
	memset(page, 0, DIR_PAGE_SIZE);
	memset(page, BUCKET_UNUSED, BUCKETS);	/* create zero page */
	page[BUCKETS] = (BUCKETS + 1) >> 1;

	for(i = 0; i < dirPages; ++i)
		writePage(i);

	DB_EXIT
}

int finishDir(void)
/* close direcptory:
	write dirty page
	fill BUCKET_EMPTY's
*/
{	int i, k, o;
	byte *p;

	DB_ENTER("finishDir");

	for(i = 0; i < dirPages; ++i) {
		readPage(i);
		if(memchr(page, BUCKET_EMPTY, BUCKETS)) {
		/* BUCKET_EMPTY indicate a bucket, which must be filled
		   in order to ensure proper symbol search.
		   simply let it point to an invalid name.
		   A name with no length is invalid. */

		   	/* create invalid name */
		   	if(((unsigned)page[BUCKETS] << 1) < DIR_PAGE_SIZE)
		   	/* space left to create one */
		   		page[(o = (unsigned)page[BUCKETS]++) << 1] = NUL;
		   	else {	/* space left => search for one */
		   		for(p = &page[BUCKETS + 1], k = PAGELEFT >> 1;
		   		 *p && k--; p += 2);
				if(*p) /* no invalid name possible => error */
					DB_RETURN( NUL);
				o = (p - page) >> 1;
			}

			for(k = 0; k < BUCKETS; ++k)
				if(page[k] == BUCKET_EMPTY)
					page[k] = o;
			pageDirty = !NUL;
		}
	}
	readPage(0xffff);	/* update last modified page */
	DB_RETURN( !NUL);
}

unsigned idxCopy(unsigned offset, void *address, unsigned len)
/* copy len bytes from address at offset from page and
   Return the offset behind
*/
{	memcpy(page + offset, address, len);
	return offset + len;
}

int emptyBucket(byte b)
/* test, if the bucket item b is ready to be filled */
{	return b == BUCKET_UNUSED || b == BUCKET_EMPTY;	}

/*	creatDir	- this routine generates the directory
*		  Returns a zero if the directory was generate
*		  properly.  Returns a 1 on success.
*		  Problems it encounters are, a full page or
*		  the inability to find a place for an entry.
*/
int createDir(struct LIB_INFO *Xlib)
{	unsigned sp, se, dp, de, e, j, k;
	struct tmpList *h;

	DB_ENTER("createDir");
	DB_PRINT("arg", ("lib = %s", Xlib->_name));

	lib = Xlib;
	initDir();
	chkHeap()
	if((h = cS(struct tmpList*)allocStk(sizeof(struct tmpList) + maxName)) == NULL)
		fatal(E_noMem);

	chkHeap()
	Srewind();
	while(Sread(h)) {

		chkHeap()
		if(h->flags & TL_THEADR)	/* an implizitly added module name */
			appList(CMD_TWICE, h->symbol, upCaseIt);	/* prevent warning */
		chkHeap()

		hash_name(&sp, &dp, &se, &de, &(h->symLen));
		for (j = 0; j < dirPages; j++, sp = (sp + dp) % dirPages) {
			readPage(sp);
			e = se;
	    	for (k = 0; k < BUCKETS; k++, e = (e + de) % BUCKETS)
				if(emptyBucket(page[e])) {	/* OK add symbol here */
					if(!page[BUCKETS]	/* page totally used up */
					 || ((unsigned)page[BUCKETS] << 1) + h->symLen + 3
					 		/* symbol length + length byte + page number */
					  >= DIR_PAGE_SIZE) {
					 /* page is full, mark all buckets to be filled */
					 	for(k = 0; k < BUCKETS; ++k)
					 		if(page[k] == BUCKET_UNUSED) {
					 			page[k] = BUCKET_EMPTY;
					 			pageDirty = !NUL;
					 		}
					 	break;			/* advance to next page */
					}
					page[BUCKETS] = (idxCopy(
					 idxCopy((unsigned)(page[e] = page[BUCKETS]) << 1,
					  &(h->symLen), h->symLen + 1),	/* copy symbol */
					 &(h->pageNr), 2)				/* copy page number */
					 + 1)	/* round up to lowest even, greater or equal */
					 >> 1;
					pageDirty = !NUL;
					goto nextSym;		/* advance to next symbol */
				}
				else /* test, if the symbol is twice in the LIB */
					if(!memcmp(page + ((unsigned)page[e] << 1), &(h->symLen), h->symLen + 1)) {
						if(inList(CMD_TWICE, h->symbol, upCaseIt) == NULL) {
							warning(E_dblSymbol, h->symbol);
							returnNumber = msgErrorNumber(E_nrDblSym);
							appList(CMD_TWICE, h->symbol, upCaseIt);
						}
						goto nextSym;	/* skip this */
					}
		}
		freeStk(h);
		DB_RETURN( NUL);		/* we found no place to put symbol to */
nextSym:;
    }
	freeStk(h);
    DB_RETURN( finishDir());
}
