#include <stdio.h>
#include <string.h>
#include <memory.h>
#include "memory.h"
#include "common.h"
#include "module.h"
#include "error.h"
#include "data.h"
#include "segment.h"
#include "public.h"
#include "extern.h"
#include "virdef.h"
#include "header.h"
#include "allocate.h"
#include "list.h"

extern BOOL prm_bss;
extern uint prm_base;
extern uint total_exe_len;
extern uint StartAddress;
extern uint BSS_size;
extern uint BSS_base;
extern BYTE *exebuffer;	/* Program data is stored here */
extern LIST *classlists[SUPPORTED_CLASSES];

SEGMENT *fixupseg = 0;		/* Segment for fixup references */
uint fixupbase;
static BOOL BadSeg(SEGMENT *p)
{
  if ((p->VIRDEF_size == 0) && (p->length == 0)) {
#ifdef DEBUG
    printf("Ignoring data in module %s class %s", p->module, p->classname);
#endif
		return(TRUE);
	}

  if (!strcmp(p->classname,"STACK")) {
		Error("Loading STACK seg with initialized data in module %s",
			p->module);
		return(TRUE);
  }
  return(FALSE);
}

/*
 * Handle enumerated data.  Error if too long, otherwise just copy to 
 * EXE buffer
 */
void EnumeratedData(BOOL is32, BYTE *buffer, 
			int size, char *name, uint pass)
{
  uint segment, offset, pos, length;
	SEGMENT *p;

  if (pass == 1) return;
  fixupseg = 0;

  /* Read segment and offset */
  segment = ReadByte(&buffer, &size);
	if (segment == VIRDEFSEG) {
		PUBLIC *e;
		segment = ReadByte(&buffer, &size);
		e = GetExtern(segment);
		p = e->segment;
		pos = e->absoffset;
		length = e->offset;
	}
	else {
		p = GetSegment(segment);
		pos = p->absoffset;
		length = p->length;
	}
  offset = ReadSizedValue(&buffer, &size, is32);

  if (BadSeg(p))
		return;

  if (offset +size > length)
    Error("Enumerated data load past segment end, module %s segment %s not loaded",
				name, p->segname);

  pos += offset;
#ifdef DEBUG
 	printf("Copying %x bytes of module %s to offset %x\n", size, name, 
				pos);
#endif

  /* Copy to buffer */
  memcpy(exebuffer+ pos-prm_base, buffer, size);
  fixupseg = p;
  fixupbase = pos;
}
/*
 * Handles iterated data fields recursively
 */
static uint Iterate(BOOL is32, uint pos, BYTE *buffer, uint size,
		 uint *address, char*name)
{
  uint repeatCount = ReadSizedValue(&buffer, &size, is32);
  uint blockcount = ReadWord(&buffer,&size);
	uint i, rv = 2 + (is32 ? 4 : 2);

  /* For each time repeated */
  for (i=0; i < repeatCount; i++)
    if (blockcount == 0) {
			/* If we get here, just copy the data */
      uint count = *buffer;
			memcpy(exebuffer + pos + *address-prm_base,buffer+1,count);
			*address += count;
			if (i == 0)
			  rv += count+1;
		}
		else {
			/* Otherwise we have multiple recursive blocks, do them one at a time */
			uint j, localsize = size,count;
			BYTE *localbuffer = buffer;
			for (j=0; j < blockcount; j++) {
				count = Iterate(is32,pos, localbuffer, localsize, address, name);
				if (i == 0)		
					rv += count;
				localbuffer+=count;
				localsize-=count;
			}
		}
  return(rv);
}
/*
 * Main iterated data routine, OMF 
 */
void IteratedData( BOOL is32, BYTE *buffer, int size, char *name, uint pass)
{
  uint segment, offset, pos, length;
	SEGMENT *p;
  if (pass == 1) return;
  fixupseg = 0;
  
  /* Read segment and offset */
  segment = ReadByte(&buffer, &size);
	if (segment == VIRDEFSEG) {
		PUBLIC *e;
		segment = ReadByte(&buffer, &size);
		e = GetExtern(segment);
		p = e->segment;
		pos = e->absoffset;
		length = e->offset;
	}
	else {
		p = GetSegment(segment);
		pos = p->absoffset;
		length = p->length;
	}
  offset = ReadSizedValue(&buffer, &size, is32);

	if (BadSeg(p))
		return;

  // Read the data
  size -=Iterate(is32, pos, buffer, size, &offset, name);

  if (offset > length)
    Error("Iterated data overflow in module %s segment %s",name, p->segname);
  CheckSize(size);
  fixupseg = p;
  fixupbase = pos;
}
// Allocate space for the exe file
void AllocateEXESpace(void)
{
  exebuffer = AllocateMemory(BSS_base+ BSS_size);
	memset(exebuffer,0xC4,total_exe_len+BSS_size);
}
// Deallocate it
void DeallocateEXESpace(void)
{
  DeallocateMemory(exebuffer);
}
/*
 * Get sizing for init and exit classes
 */
static void ReadIniExit(uint *start, uint *end, uint class)
{
  BOOL hastart = FALSE;
  LIST *p = classlists[class];
	*start = 0;
	*end = 0;
  if (!p)
    return;

  while (p) {
		SEGMENT *q = (SEGMENT *) p->data;
		if (!hastart) {
			hastart = TRUE;
			*start = q->absoffset;
		}
		if (!p->link)
			*end = q->absoffset + q->length;
		p = p->link;
	}
}
/*
 * Write data to the exe file
 */
void WriteEXEFile(char *name)
{
  FILE *out;
  HEADER *h = exebuffer;
  int len =total_exe_len;

  memset(h,0,sizeof(HEADER));
  h->codestart = StartAddress;
	h->bssbase = BSS_base;
	h->bsssize = BSS_size;
  h->ident = IDENT;

  ReadIniExit(&h->initdata, &h->initdataend, INIT_CLASS);
  ReadIniExit(&h->exitdata, &h->exitdataend, EXIT_CLASS);

	h->args = 0;
	h->envp = 0;

  if (prm_bss) {
    h->flags |= HFLAG_INITBSS;
    len =BSS_base + BSS_size - prm_base;  
  }
  if ((out = fopen(name,"wb")) == 0)
    fatal("Can't open EXE file %s for output", name);
  if (len != fwrite(exebuffer,1,len,out))
    fatal("Error writing EXE file %s",name);
  fclose(out);
}