/*
    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: TMPLIST.C $
   $Locker: ska $	$Name: v3_2 $	$State: Rel $

	Temporary list file.

	Holds the information pair Symbol/Page#.

	File structure:
	struct {
		unsigned flags;
		unsigned pageNr;
		byte symLen;
		char symbol[];
	};
	The symbol is terminated by an additional '\0', which is NOT
	counted within symLen.


*/

#ifndef _MICROC_
#include <malloc.h>
#include <assert.h>
#include <string.h>
#include <dos.h>
#include <io.h>
#else
#include <file.h>
#endif
#include <stdio.h>
#include <ctype.h>
#include <fmemory.h>
#include "tmplist.h"
#include "lib.h"
#include "list.h"
#include "types.h"
#include "yerror.h"

extern char *mktmp(char *str);

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

static char *fname = NULL;
static FILEP fp;
unsigned maxName = 0;	/* max length of a symbol's name */
unsigned minPages = 1;			/* min amount of dir Pages required */
static int pageLeft = PAGELEFT;	/* àmount of bytes, left on current page */
word maxOBJ;			/* maximum page size so far */
word cntOBJ;			/* number of OBJs processed so far */
word Msegment, Moffset, Mitems;
FLAG Mcounter;
unsigned dispInfo = 0;	/* maximal count for search heuristic (disabled) */
dword padbytes;			/* number of pad bytes calculated by pages() */

void Sclose(void)
/* close & delete temporary list file */
{	DB_ENTER("Sclose");

	if(fname) {
		chkHeap()
		Close(fp);
		unlink(fname);
		U_free(fname);
		fname = NULL;
		maxName = 0;
		chkHeap()
	}

	DB_EXIT
}

void Sopen(void)
/* open the temporary list file.
   This file holds the symbol names and the page on which this symbol
   starts. 
*/
{	DB_ENTER("Sopen");
	if((fname = mktmp(NULL)) == NULL)
		error(E_creatTmp);
#ifdef _MICROC_
	if((fp = open(fname, F_READ | F_WRITE)) == 0)
#else
	if((fp = fopen(fname, "w+b")) == NULL)
#endif
		fatal(E_openFile, fname);
	chkHeap()
	cntOBJ = maxOBJ = 0;
	DB_EXIT
}

void Swrite(struct tmpList *h)
/* write the symbol/page# information into the temp list file.
*/
{	int len;
#define p (cS(struct tmpList *)ybuf)

	DB_ENTER("Swrite");

	if(skipModule)	/* this information is to be ignored */
		DB_EXIT

	if(upCaseIt) {	/* case-insensitve directory */
		len = h->symLen;
		while(len--) h->symbol[len] = toupper(h->symbol[len]);
	}

	DB_PRINT("arg", ("module = %s", h->symbol));

	len = h->symLen;
	chkHeap()
	if(h->flags & TL_THEADR) {	/* add the trailing '!' */
		memcpy(p, h, len + sizeof(struct tmpList));
		memcpy(&(h = p)->symbol[p->symLen++], "!", 2);
	}
	chkHeap()
	len = h->symLen;
	if(ww(h, sizeof(struct tmpList) + len, fp))
		fatal(E_writeFile, fname);

	if(len > maxName) maxName = len;

	/* calculate min page amount */
	if(++len & 1) ++len;	/* must have even length */
	if(len > pageLeft) {		/* page breaK */
#ifndef NDEBUG
		informative("Min dir pages: %u", minPages);
#endif
		++minPages;
		pageLeft = PAGELEFT - PAGECALCSKIP - len + pageLeft;
	}
	else pageLeft -= len;
#undef p
	DB_EXIT
}

void Srewind(void)
{	DB_ENTER("Srewind");
	Rewind(fp, fname);
	chkHeap()
	DB_EXIT
}

int Sread(struct tmpList *h)
/* Read the next symbol entry from the temporary list file.
   Emit en error message if failed.
   Return:
   	*h filled with the entry;
   	== 0 if no entry found
*/
{	DB_ENTER("Sread");
	do {
		chkHeap()
		if(rr(h, sizeof(struct tmpList), fp))
			DB_RETURN( NUL);
	} while(h->flags & TL_SIZE);
	chkHeap()
	if(rr(&(h->symbol[1]), h->symLen, fp))
		fatal(E_readFile, fname);
	DB_PRINT("inf", ("symbol = %s", h->symbol));
	chkHeap()
	DB_RETURN( !NUL);
}

#ifdef _MICROC_
void SwriteSize(dword *size)
#else
void SwriteSize(dword size)
#endif
/* write a record into the temporary list holding the size of the
	OBJ module immediately above. */
{	struct tmpList h;

	DB_ENTER("SwriteSize");
	DB_PRINT("arg", ("size = %lu", size));

	h.flags = TL_SIZE;
#ifdef _MICROC_
	longcpy(&h.pageNr, size);
	if(size->hi)				/* pages are limited to 64kB */
		maxOBJ = 0xffff;
	else if(size->lo > maxOBJ)				/* longest OBJ? */
		maxOBJ = size->lo;
#else
	*(dword*)&h.pageNr = size;
	if(size > 0xfffful)				/* pages are limited to 64kB */
		maxOBJ = 0xffff;
	else if((word)size > maxOBJ)				/* longest OBJ? */
		maxOBJ = (word)size;
#endif
	++cntOBJ;
	if(ww(aS(h), sizeof(h), fp))
		fatal(E_writeFile, fname);
	DB_EXIT
}

int SgetSize(dword *size, int saveFPos)
{	struct tmpList h;
	FLAG found;

#ifdef _MICROC_
	unsigned high, low;			/* current file pointer */

	if(saveFPos && ltell(fp, &high, &low))
#else
	long fpos;

	DB_ENTER("SgetSize");

	if(saveFPos && (fpos = ftell(fp)) == -1l)
#endif
		DB_RETURN( 0);

	found = NUL;

	while(!rr(aS(h), sizeof(h), fp))
		if(h.flags & TL_SIZE) {
#ifdef _MICROC_
			longcpy(size, &h.pageNr);
#else
			*size = *(dword*)&h.pageNr;
#endif
			found = !NUL;
			break;
		}
		else	/* skip the symbol name */
#ifdef _MICROC_
			if(lseek(fp, 0, h.symLen, 1))
#else
			if(fseek(fp, h.symLen, 1))
#endif
				break;

#ifdef _MICROC_
	if(saveFPos && lseek(fp, high, low, 0))
#else
	if(saveFPos && fseek(fp, fpos, 0))
#endif
		fatal(E_accessFile, fname);

	DB_RETURN( found);
}

void mkLstFile(char *fnam)
/* Construct the list file from the temporary file.
	The extension defaults to "LST". */
{	struct tmpList *h;
	FILE *lst;
	FLAG module;
	dword modLen;
	char *dr, *path, *name, *ext;

	DB_ENTER("mkLstFile");

	h = getmem(sizeof(struct tmpList) + maxName);
	Srewind();
	chkHeap()
	if(!fsplit(fnam, &dr, &path, &name, &ext, 0)
	 || (!ext && (fnam = fmerge(NULL, dr, path, name, "LST")) == NULL))
	 	fatal(E_noMem);
	chkHeap()
	informative(M_createLstFile, fnam);
	if((lst = fopen(fnam, "wt")) == NULL)
		error(E_openFile, fnam);
	chkHeap()
	while(Sread(h)) {
		if(!(module = h->flags & TL_THEADR))	/* not a module name */
			fputc('\t', lst);			/* indent */
		fwrite(&(h->symbol[0]), h->symLen - (module? 1: 0), 1, lst);
		if(h->flags & TL_THEADR) {				/* search the size record */
			fputs("\t\tsize = ", lst);
			if(SgetSize(aS(modLen), 1)) {
#ifdef _MICROC_
				ltoa(modLen, ybuf, 10);
				fputs(ybuf, lst);
#else
				fprintf(lst, "%lu", modLen);
#endif
			}
			else fputs("???", lst);
		}
		fputc('\n', lst);
	}
	chkHeap()
	if(ferror(lst))
		fatal(E_writeFile, fnam);
	fclose(lst);
	U_free(h);
	chkHeap()
	if(!ext) U_free(fnam);
	chkHeap()
	U_free(dr); U_free(path); U_free(name); U_free(ext);
	chkHeap()
	DB_EXIT
}

void Mclose(void)
{	DB_ENTER("Mclose");
	DB_PRINT("arg", ("cache deallocated"));
	clrList();
	DB_EXIT
}

static void Minit(void)
/* The module size cache is a buffer system to hold the sizes in memory
	if possible, but have the interface if this failed.

	Initialize the cache.
*/
{	USEREGS
	dword size;

	DB_ENTER("Minit");

	Mclose();		/* Remove a possibly active cache */

	/* Create cache */
	/* Per stored OBJ 4 bytes are necessary, allocation unit is 16 bytes ->
			cachesize = cntOBJ * 4 / 16 + 1 
		The additional paragraphe is for the number of elements modulo 4
		and 2 bytes for the far block chaining. */
	if(!(Msegment = Falloc((cntOBJ >> 2) + 1))) {	/* Failed */
		warning(W_lowMemMinit);
		Mcounter = !NUL;
		DB_EXIT
	}

	DB_PRINT("inf", ("cache initialized"));

	/* Copy the sizes into the segment */
	Srewind();
	Moffset = 2;		/* skip over the far block chaining word */
	while(SgetSize(aS(size), 0)) {
		_fmemcpy(MK_FP(Msegment, Moffset), TO_FP(aS(size)), 4);
		if((Moffset += 4) & 0x8000)
			Moffset &= 0x7fff, Msegment += 0x800;
	}
	Mcounter = NUL;
	DB_EXIT
}

static void Mrewind(void)
{	if((Msegment = farBlkHead) != 0)
		Moffset = 2, Mitems = cntOBJ;
	else Srewind();
}

static int Mread(dword *size)
/* Get next entry */
{	if(farBlkHead) {
		if(Mitems--) {
			_fmemcpy(TO_FP(size), MK_FP(Msegment, Moffset), 4);
			if((Moffset += 4) & 0x8000)
				Moffset &= 0x7fff, Msegment += 0x800;
			return 1;
		}
	}
	else return SgetSize(size, 0);
	return 0;
}

static word pages(word PageSize)
/* Calculates the amount of pages needed in order to store all the OBJ
	modules in a library with a page size of PageSize.
	Return:	0: failure
			otherwise: amount of pages
*/
{	dword size;
#ifdef _MICROC_
	dword libPages, pageCnt;

	longsetu(pageCnt, PageSize);
#else
	word libPages;
#define pageCnt PageSize
#endif

	longsetu(libPages, 2);			/* the two standard LIB frame pages */
	longsetu(padbytes, PageSize - MINPAGESIZE);
	Mrewind();
	while(Mread(aS(size))) {
#ifdef _MICROC_
		longdiv(size, pageCnt);
		if(longtst(Longreg)) {	/* last page is not fully-filled */
			if(!++size.lo)		/* ++size overflow of 0xffff */
				break;
			longadd(padbytes, Longreg);
		}
		longadd(libPages, size);
		if(size.hi || libPages.hi)
			return 0;		/* OBJ module too large for the LIB */
#else
		padbytes += size % pageCnt;
		size = size / pageCnt + (size % pageCnt != 0);
		if(size > 0xffff || (size += libPages) > 0xffff) 
			return 0;
		libPages = (word)size;
#undef pageCnt
#endif
	}
#ifdef _MICROC_
	return libPages.lo;
#else
	return libPages;
#endif
}

static void dispPS(MSGID id, word pagesize)
/* display the information derived from a page size.
	The size of the data area of a library counts one page less than
	returned by pages(), because the last page will be dynamically filled
	to align to the library directory. */
{	dword size;
	word slackarea;
#define xbuf ybuf+64
#ifdef _MICROC_
	dword ps;

	size.hi = ps.hi = 0;
	size.lo = pages(ps.lo = pagesize) - 1;
	longmul(size, ps);
	ps.lo = 3; 				/* the minimum size of the last page */
	/* calculate the amount of bytes to the next 512 byte boundary */
	slackarea = DIR_PAGE_SIZE - size.lo % DIR_PAGE_SIZE;
	if(alignDir) {
		ps.lo = slackarea;
		longadd(size, ps);
	}
	ltoa(size, ybuf, 10);
	ltoa(padbytes, xbuf, 10);
#else
	DB_ENTER("dispPS");

	/* The last page has always 3 bytes, the rest is the slack area */
	size = (long)pagesize * (pages(pagesize) - 1) + 3;
	slackarea = DIR_PAGE_SIZE - (word)size % DIR_PAGE_SIZE;
	if(alignDir) size += slackarea;
	sprintf(ybuf, "%ld", size);
	sprintf(xbuf, "%ld", padbytes);
#endif

	beautify(ybuf);
	beautify(xbuf);

	message(stdout, alignDir? M_aliPageSize: M_pageSize
	 , msgLock(id), pagesize, ybuf, slackarea, xbuf);
	msgUnlock(id);
#undef xbuf
	DB_EXIT
}

void dispPageInfo(int update)
/* determine the optimal page size.
	If update is !NUL, this amount is placed into pageSizeNew */
{	dword size, optSize;
	word minPage, maxPage;
	word pageCnt, optPages;
#ifdef _MICROC_
	dword libPages;

	longclr(libPages);
	optSize.lo = 0xffff;
	optSize.hi = 0x7fff;
#else
	word libPages;

	DB_ENTER("dispPageInfo");

	optSize = 0x7ffffffful;
#endif

	if(update && pageSizeNew == 0xffff)	/* this is the absolutely maximum */
		error(E_libTooLong);

	informative(M_pageInfo);

	Minit();

	if(dispInfo == 0xffff) {		/* brute force, bypass heuristic */
		minPage = MINPAGESIZE;
		maxPage = maxOBJ;
	}
	else {
		/* The heuristic shall limit the amount of pages sizes that
			will be tested in a brute force method.
			The lowest page size is determined, which allows to create
			the library. From there dispInfo sizes will be tested.

			Determining will start with the current page size, if it
			allows the creation, the range MINPAGESIZE .. pagesize; if
			it doesn't allow the creation, the range pagesize .. maxOBJ
			is checked.

			The range will be halfed as long as the first possible
			page size is determined.
		*/
		if(pages(pageSizeNew)) {	/* current page size OK */
			maxPage = pageSizeNew;
			minPage = MINPAGESIZE;
		}
		else {						/* current page size to small */
			minPage = pageSizeNew;
			if(!pages(maxPage = maxOBJ)) {
				if(update) error(E_libTooLong);
				warning(E_libTooLong);
				return;
			}
		}

		while(minPage != maxPage) {
			/* optPage := middest page size, to avoid integer overflow
				don't calculate with (maxPage + pageCnt) >> 1 */
			optPages = ((maxPage - minPage) >> 1) + minPage;
			if(optPages == minPage)
				break;
			if(pages(optPages)) /* OK */
				maxPage = optPages;
			else minPage = optPages;
		}
	}
	while(!pages(minPage))
		++minPage;

	maxPage = (maxOBJ - minPage < dispInfo)? maxOBJ: minPage + dispInfo;

	if(!isatty(fileno(stdout)) || !dispDoing
	 || getNoiseLevel(ENoise_informative) != NOISE_ALLOW)
		/* output redirected or output suppressed, no counter */
		Mcounter = NUL;
	else if(dispInfo > PAGEINFO_COUNTER)	/* display the counter? */
		Mcounter = !NUL;

	for(pageCnt = minPage - 1, optPages = 0; ++pageCnt <= maxPage;) {
		/* calculate library size */
		if(Mcounter)
			printf("%u \r", maxPage - pageCnt);

#ifdef _MICROC_
		if(libPages.lo = pages(pageCnt)) {
			longsetu(size, pageCnt);
			longmul(size, libPages);
			if(longcmp(size, optSize) < 0) {
				/* verify, if this is better */
#else
		if((libPages = pages(pageCnt)) != NULL) {		/* OBJs fit into library */
			size = (dword)libPages * pageCnt;
			if(size < optSize) {
#endif
				/* yes, set it */
				optPages = pageCnt;
				longcpy(optSize, size);
			}
		}
	}

	if(Mcounter)		/* overwrite the counter on the display */
		fputs("        \r", stdout);

	message(stdout, M_HeadPageSize);
	if(pageSizeNew != pageSizeOld && pageSizeOld)
		dispPS(M__Original, pageSizeOld);
	dispPS(M__Current, pageSizeNew);
	dispPS(M__Minimal, minPage);
	if(maxOBJ)
		dispPS(M__Maximal, maxOBJ);
	if(optPages) {
		if(dispInfo != 1)
			dispPS(M__Optimal, optPages);
	}
	else
		if(update) error(E_libTooLong);
		else warning(E_libTooLong);
	message(stdout, M_dirSize, dirPagesNew);
	if(update)
		pageSizeNew = (optPages <= pageSizeNew)	/* Hmm, the best page size
				 lays below the current one; assume a constant difference */
			? (((unsigned)pageSizeNew <= 0xffff - CONST_RESTART)
				? pageSizeNew + CONST_RESTART: 0xffff)
			: optPages;
	Mclose();
	DB_EXIT
}
