#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "common.h"
#include "map.h"
#include "link.h"
#include "module.h"
#include "allocate.h"
#include "segment.h"
#include "public.h""
#include "list.h"
#include "extern.h"
#include "libs.h"
#include "lnames.h"
#include "modend.h"
#include "data.h"
#include "map.h"

#define VERSION 110

// Command line parameters
BOOL prm_bss = FALSE;								// True if to write BSS segment
BOOL prm_packcode = FALSE;					// True if code packing enabled
uint prm_base = 0x1000;							// Target base address
uint prm_mapfile = MAP_NORMAL;			// Map file type
BOOL prm_case_sensitive = FALSE;		// True if case sensitive
uint prm_errcount = 25;							// Max errors to collect
char *prm_searchpath = 0;						// Search path for libraries
BOOL prm_nodefaultlibs = FALSE;			// TRUE if to ignore default lib records
BOOL prm_response = FALSE;					// TRUE if response file is loaded

// Global varieables
uint modnumber=0;										// module number, increments sequentially
LIST *liblist = 0;								// List of libraries
LIST *objlist = 0;								// List of object files
char *exefile = 0;									// EXE file name
char *mapfile = 0;									// MAP file name

char *usage_text = "[/m/s/c/x/n/B/E/L/P] file list [@filename]";

void BoolSetup(char select, char *string);
void ErrorSetup(char select, char *string);
void SearchSetup(char select, char *string);
static void BaseSetup(char select, char *string);

ARGLIST ArgList[] = {
  { 'c', ARG_BOOL, BoolSetup },
  { 'i', ARG_BOOL, BoolSetup },
  { 'n', ARG_BOOL, BoolSetup },
  { 'm', ARG_BOOL, BoolSetup },
  { 's', ARG_BOOL, BoolSetup },
  { 'x', ARG_BOOL, BoolSetup },
	{ 'B' , ARG_CONCATSTRING, BaseSetup },
  { 'E', ARG_CONCATSTRING, ErrorSetup },
  { 'L', ARG_CONCATSTRING, SearchSetup },
  { 'P', ARG_BOOL, BoolSetup },
  { 0, 0, 0 }
} ;
/*
 * Setup for boolean command line args
 */
static void BoolSetup(char select, char *string)
{
  switch(select) {
    case 'c':
			prm_case_sensitive = TRUE;
			break;
		case 'i':
			prm_bss = TRUE;
			break;
		case 'n':
			prm_nodefaultlibs = TRUE;
			break;
		case 'm':
			prm_mapfile = MAP_PUBLICS;
			break;
		case 's':
			prm_mapfile = MAP_EXTENDED;
			break;
		case 'x':
			prm_mapfile = MAP_NONE;
			break;
		case 'P':
			prm_packcode = TRUE;
			break;
  }
}
/*
 * Setup for the /E switch
 */
static void ErrorSetup(char select, char *string)
{
  prm_errcount = atoi(string);
}
static void BaseSetup(char select, char *string)
{
  sscanf(string,"%x",&prm_base);
}
/*
 * Setup for library search paths /L
 */
static void SearchSetup(char select, char *string)
{
  uint len = strlen(string)+1;
  prm_searchpath = (char *)AllocateMemory(len);
  strcpy(prm_searchpath, string);
}
/*
 * Insert a file onto one of the lists.  .LIB files go on library list,
 *   anything else is assumed an .obj file regardless of extension
 */
static void InsertAnyFile(char *filename)
{
  char *newbuffer, buffer[100];

  /* Allocate buffer and make .obj if no extension */
	strcpy(buffer,filename);
  AddExt(buffer,".obj");
  newbuffer = (char *) AllocateMemory(strlen(buffer + 1));
  strcpy(newbuffer,buffer);

  /* Insert file */
  if (strstr(newbuffer,".lib"))
    AppendToList(&liblist,newbuffer);
  else
	  AppendToList(&objlist,newbuffer);
}
/*
 * Read a line of ascii text from a file
 *   Get rid of \n
 */
static void ReadLine(char *buffer, int count, FILE *file, char *name)
{
  char *pos;
  *buffer = 0;
  fgets(buffer,count,file);
  pos = buffer + strlen(buffer) -1;
  // The test is needed because my editor doesn't put CR/LF at the end of file
	if (*pos <32)
    *pos = 0;
}
/*
 * Check if trying to create an EXE or MAP file with the wrong extension
 */
static BOOL CheckInvalidExtension(char *buffer)
{
  if (strstr(buffer,".obj"))
    return(TRUE);
  if (strstr(buffer,".lib"))
    return(TRUE);
  return(FALSE);
}
/*
 * Read the response file
 *   FIRST LINE: EXE FILE
 *   SECOND LINE: EMPTY OR MAP FILE
 *   OTHER LINES: OBJ AND LIB files in any order
 */
static void ReadResponse(char *filename)
{
  FILE *in;
  char buffer[100];

  /* Open file */
  if ((in = fopen(filename,"r")) ==0)
    fatal("Missing or invalid response file %s", filename);

  /* Read EXE file name */
  ReadLine(buffer,100,in,filename);
  if (buffer[0] < 32)
    fatal("No output file specified");
    if (CheckInvalidExtension(buffer) || strstr(buffer,".MAP"))
    fatal("Invalid EXE filename: %s", buffer);
	AddExt(buffer,".EXE");
  exefile = (char *) AllocateMemory(strlen(buffer+1));
	strcpy(exefile, buffer);

  /* Read and verify map filename */
  ReadLine(buffer,100,in,filename);
  if (buffer[0] > 32) {
    if (CheckInvalidExtension(buffer) || strstr(buffer,".EXE"))
      fatal("Invalid MAP filename %s\n", buffer);
		AddExt(buffer,".MAP");
    mapfile = (char *)AllocateMemory(strlen(buffer+1));
	  strcpy(mapfile, buffer);
  }
  else
    prm_mapfile = MAP_NONE;

  /* Read and queue object and library file names */
  while (!feof(in)) {
    char *p = buffer;
    ReadLine(buffer,100,in,filename);
    if (buffer[0] < 32)
      continue;
    while (*p)
      *p++ = (char)tolower(*p);
    InsertAnyFile(buffer);
  }
  fclose(in);
}
/* 
 * Pass 1: Read the object files
 *   Also does auto-library determination
 */  
static void Pass1Objects(void)
{
  modnumber = 0;
  if (objlist) {
    LIST *p = objlist;
    while (p) {
      FILE *in;
      char *name;
      if ((in = fopen((char *)p->data,"rb")) == 0)
				fatal("Missing input module %s",(char *) p->data);
#ifdef DEBUG
			printf("Pass 1:Processing module %x:%s\n", modnumber,(char *)p->data);
#endif //DEBUG    
			if ((ReadModule(in,(char *)p->data,1) & 0xf0) == 0xf0) {
				// If it's a library file we move it to the library list
				LIST *q = p->link;
				name = UnlinkFromList(&objlist,p);
				AppendToList(&liblist,name);
				modnumber--;			// Reuse the same module number
				p = q;
			}
			else
				p = p->link;
		  fclose(in);
			modnumber++;
		}
  }
}
/*
 * Pass2 , read object files
 *   Libraries have already been found and moved at this point
 */
static void Pass2Objects(void)
{
  modnumber = 0;
  if (objlist) {
    LIST *p = objlist;
    while (p) {
      FILE *in;
      if ((in = fopen((char *)p->data,"rb")) == 0)
				fatal("Missing input module %s",(char *) p->data);
#ifdef DEBUG
			printf("Pass 2: Processing module %x:%s\n", modnumber,(char *)p->data);
#endif //DEBUG    
			ReadModule(in,(char *)p->data,2);
			p = p->link;
		  fclose(in);
			modnumber++;
		}
  }
}
/*
 * Main routine
 *   Read command line
 *   Make EXE and MAP filenames if not already extant
 *   Pass 1 init
 *   Pass 1
 *   Pass 1 rundown
 *   pass 2 init
 *   Pass 2
 *   Pass 2 rundown
 */
int main(int argc, char *argv[])
{
  int i;
  banner(VMSG("Link "));

  // Scan command line for switches
  if (!parse_args(&argc,argv,TRUE) || (argc == 1))
    usage(argv[0]);

  // Scan the command line for file names or response files
  for (i=1; i < argc; i++) {
    char *p = argv[i];
    while (*p)
      *p++ = (char)tolower(*p);
    if (argv[i][0] == '@') {
      if (prm_response)
        fatal("Too many response files");
      prm_response = TRUE;
      ReadResponse(&argv[i][1]);
    }
    else
		  InsertAnyFile(argv[i]);
  }

  // If no response file, make up EXE and MAP file names from first .obj file
  if (!prm_response) {
    LIST *p = objlist;
	  char buffer[100];

    // If no obj file go with the first lib file.  Well, we're guaranteed
    // to have something if we got past the usage!
    if (!objlist)
      p = liblist;

    // EXE file name
    strcpy(buffer, (char *)p->data);
		StripExt(buffer);
		AddExt(buffer,".EXE");
	  exefile= AllocateMemory(strlen(buffer) +1);
    strcpy(exefile,buffer);

    // MAP file name
		StripExt(buffer);
    AddExt(buffer,".MAP");
	  mapfile= AllocateMemory(strlen(buffer) +1);
    strcpy(mapfile,buffer);
  }
  // Pass 1
  PublicInit(1);
  ExternInit(1);
	SegmentInit(1);
  LnameInit(1);
  Pass1Objects();
	LocateLibraries(prm_searchpath, &liblist);
  SearchLibraries(liblist);
  EnumerateExterns();
	SegmentRundown(1);
  PublicRundown(1);
  ExternRundown(1);
  LnameRundown(1);
	
  OrderSegments(1);
	ResolvePublics(1);

	// Pass 2
  AllocateEXESpace();
	PublicInit(2);
  ExternInit(2);
	SegmentInit(2);
  LnameInit(2);
	Pass2Objects();
	LoadLibraries();
//  ApplyBackPatches();
  WriteEXEFile(exefile);
	DeallocateEXESpace();
  WriteMapFile(mapfile,prm_mapfile);
	SegmentRundown(2);
  PublicRundown(2);
	ExternRundown(2);
  LnameRundown(2);
  CheckEnd();

#ifdef DEBUG
	printf("EXE: %s\n",exefile);
	printf("MAP: %s\n",mapfile);
	printf("SEARCH: %s\n",prm_searchpath);
#endif 
  return(0);
}
  