/*
 * Severe restrictions on segment handling:
 *   First, we totally ignore the combine type as long as it is defined
 *   and not absolute.  Combining is done in the order a module is encountered
 *   during the link process.  It is important to keep the segment lists
 *   in sync with the librarion load module list by the way.
 *
 *   Align is supported.
 *
 *   Undefined classes generate an error.  This is a flat model linker and
 *   does not support all the intel segmentation crap
 *
 *   Debug records will be ignored if encountered.
 *   Class ordering:
 *     FILE HEADER
 *     CODE
 *     INITIALIZE DATA
 *     EXIT DATA
 *     DATA
 *		 CONST
 *     BSS DATA
 *     (STACK) : can't put data in stack, it is allocated automatically by os
 *     
 */
#include <stdio.h>
#include <string.h>
#include "common.h"
#include "module.h"
#include "allocate.h"
#include "hash.h"
#include "error.h"
#include "lnames.h"
#include "segment.h"
#include "list.h"
#include "header.h"

extern uint prm_base;
extern BOOL prm_packcode;
extern char *modname;
extern HASHREC ** PublicHash;

extern uint prm_base;
extern BOOL prm_packing;

uint BSS_size = 0; /* Size of BSS class */
uint BSS_base = 0; /* Base of BSS class */
uint total_exe_len = 0; /* EXE file length */
uint classlens[SUPPORTED_CLASSES] = { 0, };  /* Class lengths for map */
LIST *classlists[SUPPORTED_CLASSES] = { 0, }; /* Lists of all module segments, sorted by class */

static LIST *SegmentModuleList;	/* Linked list of the module segment lists */
static LIST *CurrentModule;		/* Current pos in module segment lists */
static LIST *ThisModuleSegments; /* Pointer to segments of current module */

/*
 * Check a classname against the classlist
 *  return 0 if not found, 1 if is an ignored class (debug classes)
 *  otherwise return a pointer to the class segment which the segment is
 *  to be added to
 */
static LIST **FindClass(char **classname)
{
  static char *classnames[SUPPORTED_CLASSES] = { 
			"CODE", "DATA", "TLSCBA", "INITDATA", "EXITDATA", "CONST", "BSS", "STACK", "DEBNAM",
			"DEBTYP", "DEBSYM" 
	};
  int i;
  for (i=0; i < SUPPORTED_CLASSES; i++) {
    if (!strcmp(classnames[i],*classname)) {
			*classname = classnames[i];
			if (i >= IGNORED_CLASSES)
				return((LIST *) 1);
			return(&classlists[i]);
		}
	}
	return(0);
}
/*
 * Read the OMF segment record
 */
static void ReadSegments(BOOL is32, BYTE *buffer, 
			int size, char *name, int pass, BOOL toMangle)
{
		if (pass == 1) {
      LIST *list;
      uint acbp;
		  uint seglength, segindex,classindex;
		  char *segname, *classname;
      SEGMENT *seg;

			// ACBP
      acbp = ReadByte(&buffer, &size);

		  // If absolute, read the segment and offset
      if (ACBP_A(acbp) == 0) {
        ReadWord(&buffer, &size);
			  ReadByte(&buffer, &size);
		  }	
    
      seglength = ReadSizedValue(&buffer, &size, is32);
      segindex = ReadIndex(&buffer, &size);
      classindex = ReadIndex(&buffer, &size);

		  // Read overlay index
      ReadIndex(&buffer, &size);

			// Lookup names in the lnames table.  Assumes LNAMES comes prior
			// to the segment definition to work properly.  Borland does this
      segname = GetLname(segindex);
      classname = GetLname(classindex);

			// Get out if they try to link in a USE-16 segment
      if (!ACBP_P(acbp))
        fatal("16-bit segments not supported in segment %s module %s", segname, name);

  		// I don't know what a big model is supposed to result in image 
			// wise, I just won't support
      if (ACBP_B(acbp))
			  fatal("Big segments not supported in segment %s module %s", segname, name);

			// Unsupported align types
		  switch(ACBP_A(acbp)) {
        case 0:
			  case 6:
			  case 7:
				  fatal("Unsupported align type %d in segment %s module %s", ACBP_A(acbp), segname, name);
		  };
			// Unsupported combine types.  Currently not supporting overlays to
			// make things easy
		  switch (ACBP_C(acbp)) {
			  case 1:
			  case 3:
				case 6:
			    fatal("Unsupported combine type %d in segment %s module %s", ACBP_C(acbp), segname, name);
		  };

			// Make sure class is ok
		  list = FindClass(&classname);
      if (!list)
        fatal("Unknown class type: %s in module %s", classname, name);
			
			// Passed all tests, allocate space for segment
      seg = (SEGMENT *)AllocateMemory(sizeof(SEGMENT));
      seg->segname = (char *)AllocateMemory(strlen(segname)+1);
      seg->classname = classname;
		  strcpy(seg->segname, segname);
		  seg->module = name;
		  seg->acbp = acbp;
			seg->VIRDEF_size = 0;
			seg->VIRDEF_base = 0;
			seg->VIRDEF_alloc = 0;
      if (list == (LIST **)1)
			  seg->length = 0;
			else {
				seg->length = seglength;
		    AppendToList(list, seg);
			}
		  AppendToList(&ThisModuleSegments, seg);
  		CheckSize(size);
		}
}
/* Pre pass init
 */
void SegmentInit(uint pass)
{
  if (pass == 1)
    SegmentModuleList = 0;
  else {
    CurrentModule = SegmentModuleList;
		ThisModuleSegments = CurrentModule->data;
	}
}
/*
 * Module rundown
 * Used to keep modules in sync with the segments loaded on first pass
 */
void SegmentModuleRundown(uint pass)
{  
#ifdef DEBUG
	LIST *p = ThisModuleSegments;
  if ((pass == 1) &&p){
    printf("Segment list\n");
		while(p) {
			SEGMENT *q = (SEGMENT *)p->data;
			printf("  %s:%s ACBP:%02x, length:%08x\n", q->classname, q->segname,
				q->acbp, q->length);
  		p=p->link;
		}
  }
#endif DEBUG
  if (pass == 1) {
	  AppendToList(&SegmentModuleList, ThisModuleSegments);
    ThisModuleSegments = 0;
  }
  else {
    CurrentModule = CurrentModule->link;
    if (CurrentModule)
      ThisModuleSegments = CurrentModule->data;
	}
  
}
// Align the data properly
// Requires that CODE be the first segment
static uint  align(uint line, uint address, uint acbp)
{
	// No align if code packing turned on and in code segment
  if ((line ==0) && (prm_packcode))
		return(address);
	switch(ACBP_A(acbp)) {
		case 2:
			address= (address+1) & 0xfffffffe;
			break;
		case 3:
			address = (address + 0x0f) & 0xfffffff0;
			break;
		case 4:
			address = (address + 0x0fff) & 0xfffff000;
			break;
		case 5:
			address = (address + 0x03) & 0xfffffffc;
			break;
	}
	return(address);
}
/*
 * Called to allocate space for the segments
 * Just steps through the linked lists assigning the segment base to the
 * current base and adding the segment len to the current base
 * Also allocates space for VIRDEFS for those segments which have them
 */
void OrderSegments(uint pass)
{
  int i;

  uint base = prm_base + sizeof(HEADER);
  uint lastbase;
#ifdef DEBUG
  printf("Segment Order: \n");
#endif
  for (i = 0; i < SUPPORTED_CLASSES; i++) {
    LIST *p = classlists[i];
    if (p)
		  base = align(i, base, ((SEGMENT *)(p->data))->acbp);
		lastbase = base;
		while (p) {
			SEGMENT *q = p->data;
			base = align(i, base, q->acbp);
			if (q->VIRDEF_size) {
				q->VIRDEF_base = base;
				q->VIRDEF_alloc = base;
				base += q->VIRDEF_size;
			  base = align(i, base, q->acbp);
			}
			q->absoffset = base;
			base += q->length;
#ifdef DEBUG
			printf("  %s:%s module %s offset %x\n", q->classname, q->segname,
				q->module, q->absoffset);
#endif
			p=p->link;
		}
    classlens[i] = base - lastbase;
	}
  total_exe_len = base - prm_base - classlens[BSS_CLASS];
  /* We make up for the align of the BSS class here */
  BSS_size = classlens[BSS_CLASS];
  if (classlists[BSS_CLASS])
	  BSS_base = ((SEGMENT *)(classlists[BSS_CLASS]->data))->absoffset;
  else
    BSS_base = align(i,total_exe_len, 0x60); // Paragraph align if it does not exist

#ifdef DEBUG
  printf("EXE file length: %x\n",total_exe_len);
  printf("BSS length: %x\n",BSS_size);
#endif
}
// Return the segment corresponding to a given index
// Use during pass 1 to hook publics to their corresponding segment
// Also to add to VIRDEF size variable (pass 1)
//	 and get VIRDEF base (pass 2)
SEGMENT *GetSegment(uint index)
{
  LIST *p = ThisModuleSegments;
  while(p) {
		if (!--index) 
		  return((SEGMENT *) p->data);
	  p = p->link;
  }
	fatal("Segment index out of range in module  %s", modname);
	return(0);
}
/*
 * Post-pass rundown
 */
void SegmentRundown(uint pass)
{
}
/*
 * Main routine to load OMF segment record
 */
void SegmentDefinitions(BOOL is32,BYTE *buffer, 
			int size, char *name, uint pass)
{
    ReadSegments(is32,buffer, size,name, pass, FALSE);
}