/*
 * Borland library format differs from OMF standard in the following ways:
 *   OMF specifies a header block of 512 bytes; each module will be zero
 *   padded so that the next one will occur on an even block boundary
 *
 *   Borland uses a 16-byte header and paragraph aligns the modules,
 *   this allows a shorter file
 *
 *   OMF specifies that names be stored as follows:
 *		1 byte count
 *    (count) bytes for name
 *    2 byte block number (multiply by 512) to get byte offset of module
 *
 *   Borland changes the block number to a paragraph number (multiply by 16)
 *     and then adds three bytes I'm unsure of to the data.  Possibly
 *     some sort of extended dictionary...
 *
 *   The case sensitive flag is left clear in Borland system libraries,
 *   I don't know if user-defined libraries get it or not
 *
 *   Library searching off the keys is done as follows:
 *     Go Block_X, Bucket_X
 *		 This bucket correct name?  yes- found
 *     no, increment by bucket count.
 *		 This bucket correct name?  yes -found
 *		 This bucket = first bucket? no- loop
 *		 yes, is block totally full?  no- done but not found
 *		 yes, go to next block
 *		 This block first block?  yes -done but not found
 *		 no - loop
 */


#include <stdio.h>
#include <string.h>
#include "common.h"
#include "list.h"
#include "allocate.h"
#include "error.h"
#include "segment.h"
#include "public.h"
#include "extern.h"
#include "libs.h"
#include "hash.h"
#include "module.h"

extern BOOL prm_case_sensitive;
extern HASHREC **ExternHash;
extern char *modname;

LIST *modulelist = 0; /* List of all modules loaded and the libraries they
											 * occur in.  This list is kept in sequential order
											 * the same order as the segment lists */

/*
 * Pull the next path off the path search list
 */
static char *parsepath(char *path, char *buffer)
{
  char *pos = path;

  /* Quit if hit a ';' */
  while (*pos) {
		if (*pos == ';') {
			pos++;
			break;
    }
		*buffer++ = *pos++;
	}
  *buffer = 0;

  /* Return a null pointer if no more data */
  if (*pos)
	  return(pos);

  return(0);
}
 /* 
 * Spit an error and unlink the library if not foune
 */
static void NotFound( LIST **list)
{
  LIST *p= *list;
  Error("Missing library %s, deleting from list", p->data);
	DeallocateMemory((*list)->data);
	*list = p->link;
	DeallocateMemory(p);
	
}
/*
 * For each library:
 * Search local directory and all directories in the search path
 *  until it is found or run out of directories
 */
void LocateLibraries(char *searchpath, LIST **list)
{
  while (*list) {
    BOOL found = FALSE;
		FILE *in;
		char *newpath = searchpath;

		/* Search local path */
    in = fopen((char *) (*list)->data,"r");
		if (in) {
			fclose(in);
			found = TRUE;
		}
		else {
			/* If no path specified we search along the search path */
			if (!strchr((char *)(*list)->data,'\\')) {
			  char buffer[200];
				while (newpath && !found) {
					/* Create a file name along this path */
				  newpath = parsepath(newpath,buffer);
					if (buffer[strlen(buffer)-1] != '\\')
						strcat(buffer,"\\");
				  strcat(buffer, (char *)(*list)->data);

					/* Check this path */
					in = fopen(buffer, "r");
					if (in) {
						fclose(in);
						DeallocateMemory( (*list)->data);
						(*list)->data = AllocateMemory(strlen(buffer)+1);
						strcpy((char *)(*list)->data, buffer);
						found = TRUE;
					}
				}
			}
		}
    if (!found) {
		  NotFound(list);		
		}
    list = *list;
	}
}
/*
 * Standard dictionary hash function as specified by OMF
 */
static void compute_hash(const char * name, int blocks, hash *h)
{
  int len = strlen(name);
  const char *pb = name, *pe = name + len;
  const ushort blank = ' ';

  uint block_x = len | blank, bucket_d = len | blank;
	uint block_d = 0, bucket_x = 0;

  while (1) {
    ushort cback = *(--pe) | blank;	// This is a cute way to get lowercase
																		// And uppercase names to hash the same way
		ushort cfront = *(pb++) | blank;
		bucket_x = ROTR(bucket_x, 2) ^ cback;
		block_d = ROTL(block_d, 2) ^ cback;
		if (--len == 0)
			break;
		block_x = ROTL(block_x,2) ^ cfront;
    bucket_d = ROTR(bucket_d,2) ^ cfront;
  }
  h->block_x = block_x % blocks;
  h->block_d = block_d % blocks;
	h->block_d = MAX(h->block_d, 1);
  h->bucket_x = bucket_x % LIB_BUCKETS;
  h->bucket_d = bucket_d % LIB_BUCKETS;
  h->bucket_d = MAX(h->bucket_d, 1);
}
/* 
 * Position us somewhere in the library file
 */

static void PositionTo(FILE *in, uint position)
{
  fpos_t thepos = position;
  fsetpos(in, &thepos);
}
/*
 * Read the library header
 */
static uint ReadLibraryHeader(FILE *in, char *name, uint *dict_offset,
		uint *dict_size, uint *flags)
{
  DICTHEADER header;
  PositionTo(in, 0);
  ReadFile(&header, sizeof(DICTHEADER), in, name);
  if (header.type != LIBHEAD)
    fatal("Library %s has bad format\n",name);

  
  *dict_offset = header.offset;
  *dict_size = header.size;
  *flags = header.flags;
  return(header.length + 3);
}
/* 
 * Load a module from a library once we have the paragraph number 
 */
static void LoadModule(FILE *in, uint paragraph, char *name,
			uint pass)
{
  PositionTo(in, paragraph*16);
  ReadModule(in, name, pass);
}
/*
 * Search the dictionary for the specified public
 * It's still unclear exactly what the librarian does, but it only seems
 * to apply the bucket shift once per block
 */
static char *SearchForPublic(FILE *in, uint dict_offset, uint dict_size, 
		char * public, char *name, uint *paragraph)
{
	static DICTPAGE thispage;
  hash h,orig;
  BOOL exit = FALSE;
  uint current_block=-1;

  *paragraph = 0;
  compute_hash(public, dict_size, &h);
	orig = h;

  while (!exit) {
    uint q;
		if (h.block_x != current_block) {
		  PositionTo(in, h.block_x *LIB_PAGESIZE+dict_offset);
      ReadFile(&thispage,LIB_PAGESIZE, in, name);
			current_block = h.block_x;
		}
		if ((q=thispage.f.htab[h.bucket_x]) == 0)
			if (thispage.f.fflag == 0xff) {
#ifdef DEBUG
					    printf("Diag: hash block shift searching public %s\n", public);
#endif DEBUG
				h.block_x = (h.block_x+h.block_d) %dict_size ;
				if (h.block_x == orig.block_x)
		      exit = TRUE;
			}
			else
			  return(0);
		else {
			BYTE *pos = &thispage.bytes[q*2];
			  if (!strncmp((char *)(pos+1),public, *pos)) {
					int namehash;
				  BYTE *dataoffset;
#ifdef DEBUG
				  printf("Found public %s in library %s\n",public, name);
#endif DEBUG
					dataoffset = pos + 1 + *pos;
					*paragraph = *((ushort *)dataoffset);
					namehash = *((BYTE *)dataoffset + 4);
					namehash = thispage.f.htab[namehash] * 2;
					if (namehash == 0)
						return(name);
					return((char *)&thispage.bytes[namehash]);
        }
				else {
#ifdef DEBUG
					printf("Diag: hash bucket shift searching public %s\n",public);
#endif DEBUG
					h.bucket_x = (h.bucket_x + h.bucket_d) %LIB_BUCKETS;
					if (h.bucket_x == orig.bucket_x) {
						if (thispage.f.fflag != 0xff)
							exit = TRUE;
						else {
#ifdef DEBUG
					    printf("Diag: hash block shift searching public %s\n", public);
#endif DEBUG
					    h.block_x = (h.block_x+h.block_d) %dict_size ;
					    if (h.block_x == orig.block_x)
		            exit = TRUE;
						}
					}
				}
		}
	}
	return(0);
}
/*
 * Add a module to the linked list of modules to be loaded in pass 2
 */
static void AddToModuleList(char *modulename, char *libname, uint paragraph)
{                           
  MODLIST *temp = AllocateMemory(sizeof(MODLIST));
  temp->module = modulename;
  temp->lib = libname;
  temp->paragraph = paragraph;
  AppendToList(&modulelist, temp);
}
/*
 * Read through a given library over and over until
 * no more publics can be resolved.  The library may have to be read
 * more than once because loading a module from it can result in
 * defining more publics.  However, each extern is flagged with libraries
 * it is not found in (up to 32 libraries allowed) to keep from repeated searches
 */
static void ResolveLibraryPublics(FILE *in, BOOL *ReadFromLibrary, 
		uint offset, uint size, char *name, uint libmask)
{
  int i;
  BOOL FoundModules = TRUE;
  while (FoundModules) {
    FoundModules = FALSE;
#ifdef DEBUG
          printf("Searching library %s\n",name);
#endif //DEBUG
    for (i=0; i < HASH_TABLE_SIZE; i++) {
      EXTERN *p;
		  if ((p = ExternHash[i])!=0)
        while (p) {
					uint paragraph;
					if (!(p->nonlibs & libmask)) {
				    SearchForPublic(in, offset,size,p->name, 
						  name,&paragraph);
					  if (paragraph) {
						  *ReadFromLibrary = TRUE;
						  FoundModules = TRUE;
						  LoadModule(in, paragraph, name,1);
#ifdef DEBUG
						  printf("Loaded library %s module %s\n",name,modname);
#endif DEBUG
						  AddToModuleList(modname, name, paragraph);
						  break;
						}
						else
							p->nonlibs |= libmask;
					}
#ifdef DEBUG
					else
						printf("Skipped search of %s in library %s\n",p->name, name);
#endif
					
					
				  p = p->link;
			  }
		}
	}
}
/*
 * Error- missing library
 */
static void MissingLibrary(char *name)
{
  fatal("Missing library %s", name);
}
/*
 * Pass 1 Library search
 *   Reads dictionary and does external matching
 *   searches all libraries continuously until no more modules are loaded
 */
void SearchLibraries(LIST *liblist)
{
  if (liblist) {
		int SomethingFound = TRUE;
		BOOL case_sensitive = FALSE;
    while(SomethingFound) {
			LIST *p = liblist;
			uint libmask = 1;
			SomethingFound = FALSE;
      while (p) {
  			FILE *in;
				uint offset, size, flags;
  			if ((in = fopen((char *)p->data,"rb")) == 0)
					MissingLibrary(p->data);
				ReadLibraryHeader(in, p->data, &offset, &size, &flags);
				if (flags & ~ LIB_CASE_SENSITIVE)
					Error("Unknown library flags %x", flags);
				case_sensitive |= (flags & LIB_CASE_SENSITIVE) && prm_case_sensitive;
 		    ResolveLibraryPublics(in,&SomethingFound, 
					offset, size,p->data, libmask);
			  fclose(in);
				libmask <<= 1;
        p=p->link;
			}
    }
		if (case_sensitive)
			Error("One or more case-sensitive libraries included in non-sensitive link");
  }
}
/*   
 * Pass 2 library load
 * Pass through the linked list and load all required modules
 */

void LoadLibraries(void)
{
  LIST *p = modulelist;
  char *lib = 0;
  FILE *in = 0;
  while (p) {
		MODLIST *m = p->data;
    if (m->lib != lib) {
			lib = m->lib;
#ifdef DEBUG
			printf("Loading library %s\n",lib);
#endif
			if (in)
				fclose(in);
			in = fopen(lib,"rb");
			if (!in) MissingLibrary(lib);
		}
#ifdef DEBUG
		printf("  Module %s\n",m->module);
#endif
		LoadModule(in, m->paragraph, m->module,2);
    p=p->link;
	}
	if (in)
    fclose(in);
}  