#include <stdio.h>
#include <memory.h>
#include <string.h>
#include <ctype.h>
#include "common.h"
#include "module.h"
#include "allocate.h"
#include "segment.h"
#include "public.h"
#include "extern.h"
#include "virdef.h"
#include "lnames.h"
#include "modend.h"
#include "data.h"
#include "fixup.h"

#define OBJECT_BUFFER_SIZE 1032
extern BOOL prm_case_sensitive;
extern uint modnumber;

char *modname;			/* Module name of current module */

static BYTE objectbuffer[OBJECT_BUFFER_SIZE]; /* Record buffer */
static uint offset;				/* Current offset in file */

void ReadHeader(BOOL is32,BYTE *buffer, int size, char *name, uint pass);

// List of defined OMF records.  Bit defs:
//  TE_REPORT: Generate a warning message for this record
//  TE_HAS32:  Low bit may be set indicating 32-bit data
//  TE_IGNORE: Ignore the record
//
static TYPEXEC main_exec_list[] = {
  { THEADR, 0, ReadHeader },
  { LHEADR, TE_REPORT | TE_IGNORE,0 },
  { MODEND, TE_HAS32, ModEnd },
  { EXTDEF, 0, ExternDefinitions },
  { PUBDEF, TE_HAS32, PublicDefinitions },
  { LINNUM, TE_IGNORE | TE_HAS32, 0 },
  { LNAMES, 0, LnameDefinitions },
  { SEGDEF, TE_HAS32, SegmentDefinitions },
  { GRPDEF, TE_IGNORE, 0 },
  { FIXUPP, TE_HAS32, ReadFixups },
  { LEDATA, TE_HAS32, EnumeratedData },
  { LIDATA, TE_HAS32, IteratedData },
  { COMDEF, 0, VIRDEFDefinitions },
  { BAKPAT, TE_REPORT | TE_HAS32, 0 },
  { LEXTDEF, TE_REPORT, LocalExternDefinitions },
  { LPUBDEF, TE_REPORT | TE_HAS32, LocalPublicDefinitions },
  { LCOMDEF, TE_REPORT, 0 },
  { CEXTDEF, TE_REPORT | TE_ERROR, 0 },
  { COMDAT, TE_REPORT | TE_HAS32 | TE_ERROR, 0 },
  { LINSYM, TE_REPORT | TE_HAS32 | TE_IGNORE, 0 },
  { ALIAS, TE_REPORT, 0 },
  { NBKPAT, TE_REPORT | TE_HAS32 | TE_ERROR, 0 },
  { LLNAMES, 0, LocalLnameDefinitions },
  { VERNUM, TE_REPORT | TE_IGNORE, 0 },
  { VENDEXT, TE_REPORT | TE_IGNORE, 0 },
	{ 0, 0, 0 }
} ;
// COMENT class subtypes
//
static TYPEXEC coment_exec_list[] = {
  { COMENT | (C_XLATE << 8 ), TE_IGNORE, 0 },
  { COMENT | (C_MEMODEL << 8 ), TE_IGNORE | TE_REPORT, 0 },
  { COMENT | (C_DEFLIB << 8), TE_REPORT, 0 },
  { COMENT | (C_COMENTA0 << 8), TE_REPORT | TE_IGNORE, 0 },
  { COMENT | (C_OMFDEBUG << 8 ), TE_IGNORE, 0 },
  { COMENT | (C_LINKPASS << 8 ), TE_IGNORE, 0 },
  { COMENT | (C_LIBMOD << 8),  TE_IGNORE, 0 },
  { COMENT | (C_EXESTR<< 8), TE_REPORT | TE_IGNORE, 0 },
  { COMENT | (C_INCERR << 8), TE_REPORT, 0 },
  { COMENT | (C_NOPAD << 8), TE_REPORT | TE_IGNORE , 0 },
  { COMENT | (C_WKEXT << 8), TE_REPORT, 0 },
  { COMENT | (C_LZEXT << 8), TE_REPORT, 0 },
  { COMENT | (C_COMMENT << 8), TE_REPORT | TE_IGNORE, 0 },
  { COMENT | (C_COMPILER << 8), TE_REPORT | TE_IGNORE, 0 },
  { COMENT | (C_DATE << 8), TE_REPORT | TE_IGNORE, 0 },
  { COMENT | (C_USER << 8), TE_REPORT | TE_IGNORE, 0 },
  { COMENT | (C_TIME << 8), TE_REPORT | TE_IGNORE, 0 },
  { COMENT | (C_MAINTIME << 8), TE_IGNORE, 0 },
  { COMENT | (C_DEPENDTIME << 8), TE_IGNORE, 0 },
  { COMENT | 0xe000, TE_IGNORE, 0 },
  { COMENT | 0xe100, TE_IGNORE, 0 },
  { COMENT | 0xe300, TE_IGNORE, 0 },
  { COMENT | 0xe600, TE_IGNORE, 0 },
  { COMENT | 0xea00, TE_IGNORE, 0 },
  { COMENT | 0xf900, TE_IGNORE, 0 },
  { COMENT | 0xfa00, TE_IGNORE, 0 },
  { COMENT | 0xfb00, TE_IGNORE, 0 },
  { 0, 0, 0 }
};
/*
 * Quit with a message if a bad record is encountered
 */
static void BadObjectFile(void)
{
 	fatal("Bad Object module %s near file offset 0x%x",
		modname,offset);
}
/*
 * Quit with a message if object record too small
 */
void CheckSize(int size)
{
  if (size < 0)
    BadObjectFile();
}
/*
 * Perform THEADR function
 */
static void ReadHeader(BOOL is32,BYTE *buffer, int size, char *name, uint pass)
{
  modname = ReadName(&buffer, &size, FALSE);
  CheckSize(size);
}
/*
 * Read data from the file, quit with an error if EOF
 */
uint ReadFile(void *buffer, uint count, FILE *file, char *name)
{
  if (fread(buffer,1,count,file) != count)
    fatal("Failed read or end of file in file %s\n",name);
  return(FALSE);
}  
/*  
 * Read and execute an object record
 */
static uint ReadObjectRecord(FILE *infile, char *name, int *deltaoffset, uint pass)
{
  uint select = 0;
  int size = 0;
  TYPEXEC *p;
  BYTE *bufpos = objectbuffer;

  /* Read the next record */
  if (ReadFile(&select,1, infile,name))		/* Record type */
    return(MODQUIT);
  if (ReadFile(&size,2,infile,name))		/* Length */
    return(MODQUIT);
  if (size > OBJECT_BUFFER_SIZE)
		return(MODERR);
  if (ReadFile(&objectbuffer, size, infile,name)) /* Data + checksum */
    return(MODQUIT);

  // We don't calculate the checksum, they can put anything they want

  // Inform caller of record size
  *deltaoffset = 3 + size;

  // Record size for the data = recordsize - checksum size
  size = size-1;
   
  // If it's a comment record change the selector
  if (select == COMENT) {

    bufpos++;			// IGNORE record type field
    select += (*bufpos++) << 8; // Add in comment class field
    size -= 2;	 // TWO less data
    p = coment_exec_list;  // Use the comment dispatch table
  }
  else
    p = main_exec_list; // Use the main dispatch table

  /* If any library records are encountered assume this is a library
   * module.  Better have the 0xF0 as the first byte of the file or
   * the results are undefined!!!!!
   */
  if ((select &0xf0) == 0xf0)
    return(select);

  /* Search the dispatch table */

  while (p->select) {
    if (p->select == (select & 0xfffe)) {
			uint temp = p->flags;
      if (temp & TE_REPORT)
				if (pass == 1)
          printf("Record type %x ignored at offset 0x%x in module %s\n",select,offset , modname);
      if (temp& TE_ERROR)
        return(MODERR);
			if ((temp & TE_IGNORE) || !p->run)	// Undefined functions are ignored, better have the TE_REPORT bit set!!!
			  return(MODIGNORE);
			if ((select & 1 )&& (!(temp & TE_HAS32)))
        return(MODERR);

      // Dispatch the function
      p->run(select & 1, bufpos,size,modname,pass);
      break;
    }
		p++;
  }
  // Return an error if not found
  if (p->select == 0)
    return(MODERR);

  // Return the record type if was found
  return(select);
}
/*
 * Reads an index from a record
 */
uint ReadIndex(BYTE **bufpos, uint *size)
{
  uint rv;
  if (**bufpos & 0x80) {
    rv = ((*(*bufpos)++ & 0x7f) << 8) + **bufpos;
    (*size)--;
  }
	else
    rv = **bufpos;
  (*size)--;
  (*bufpos)++;
  return(rv);
}
/*
 * Read a byte from a record
 */
uint ReadByte(BYTE **bufpos, uint *size)
{
  uint rv;
  rv = **bufpos;
  (*size)--;
  (*bufpos)++;
  return(rv);
}
/*
 * Read a word (16 bit) from a record
 */
uint ReadWord(BYTE **bufpos, uint *size)
{
  uint rv = *(*bufpos)++ +  (*(*bufpos)++ << 8);
  (*size)-=2;
  return(rv);
}
/*
 * Read a dword
 */
uint ReadDword(BYTE **bufpos, uint *size)
{
  uint rv = ReadWord(bufpos, size) + (ReadWord(bufpos, size) << 16);
  return(rv);
}
/*
 * Read a word or dword, depending on is32 flag
 */
uint ReadSizedValue(BYTE **bufpos, uint *size, BOOL is32)
{
  if (is32)
    return(ReadDword(bufpos, size));
  else
    return(ReadWord(bufpos, size));
}
/*
 * Read a name from a record
 * Set MANGLE = true for local definitions
 */
char *ReadName(char **bufpos, uint *size, BOOL mangle)
{
  uint len, manglen = 0;
  char *string,buffer[80];

  // Design the mangle with spaces and the module number
  if (mangle) {
    sprintf(buffer," %x ",modnumber);
    manglen = strlen(buffer);
  }
  len = *(*bufpos)++;
  string = (char *)AllocateMemory(manglen+len+1);

	// Add in the mangled name
  if (manglen)
    strncpy(string, buffer, manglen);

  // The defined name
  memcpy(string+manglen,*bufpos,len);
  (*size)-= len + 1;
  (*bufpos)+= len;

  string[len] = 0;

  // If not case sensitive we uppercase everything

  if (!prm_case_sensitive) {
    char *modpos = string;
    int i;
    for (i=0; i < len; i++) {
      *modpos = (char)toupper(*modpos);
      modpos++;
    }
  }
  return(string);
}
/*
 * Read in a module
 */
uint ReadModule(FILE *infile, char *name, int pass)
{
  int newoffset;
  uint temp, done=FALSE;
  offset = 0;
  modname = 0;
  while (!done) {
    switch(temp = ReadObjectRecord(infile, name, &newoffset,pass)) {
      case MODERR:
				BadObjectFile();
      case MODQUIT:
			case LIBHEAD:
			case LIBEND:
			case LIBEXT:
      case MODEND:
			case MODEND+1:
			  done = TRUE;
				break;
	    case (C_LINKPASS << 8) + COMENT:
        if (pass == 1)
          done = TRUE;
			  break;
	  }
	  offset += newoffset;
	}
  // Module names are collected in pass 1, already resident at the start
  // of pass 2
  if (pass == 2)
    DeallocateMemory(modname);
  PublicModuleRundown(pass);
	ExternModuleRundown(pass);
  LnameModuleRundown(pass);
  SegmentModuleRundown(pass);
  return(temp);
}