/* 
   Copyright 1994-2003 Free Software Foundation, Inc.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
   02111-1307, USA.  

   You may contact the author at:

   mailto::camille@bluegrass.net

   or by snail mail at:

   David Lindauer
   850 Washburn Ave Apt 99
   Louisville, KY 40222
*/
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include        "lists.h"
#include "expr.h"
#include "c.h"
#include "gen386.h"
#include "diag.h"
#include "lists.h"

#define DEBUG_VERSION 3.1

enum omf_type {
    THEADR = 0x80,		       /* module header */
    COMENT = 0x88,		       /* comment record */

    LINNUM = 0x95,                     /* line number record */
    LNAMES = 0x96,		       /* list of names */

    SEGDEF = 0x99,		       /* segment definition */
    GRPDEF = 0x9A,		       /* group definition */
    EXTDEF = 0x8C,		       /* external definition */
    PUBDEF = 0x91,		       /* public definition */
    COMDEF = 0xB0,		       /* common definition */

    LEDATA = 0xA1,		       /* logical enumerated data */
		LIDATA = 0xA3,
    FIXUPP = 0x9D,		       /* fixups (relocations) */

    MODEND = 0x8A		       /* module end */
} ;

extern FILELIST *incfiles ;
extern VIRTUAL_LIST *virtualFirst ;
extern int prm_debug ;
extern FILE *outputFile ;
extern char version[] ;
extern char *infile ;
extern EMIT_TAB segs[MAX_SEGS] ;
extern LIST *libincludes;
extern LINEBUF *linelist ;
extern HASHREC **gsyms;
extern LIST *localfuncs ;
extern int prm_bss ;
extern int outcode_base_address ;

static int segxlattab[MAX_SEGS] ;
static int dgroupVal ;
static int extSize, extIndex ;
static char extbuf[1024] ;

static char *segnames[] = {
	0, "_TEXT","_DATA","_BSS","_CONST","_STRING","cstartup","crundown","cppinit","cppexit",
   "$$TYPES","$$SYMBOLS","$$BROWSE"
} ;
static char *segclasses[] = {
	0, "CODE", "DATA","BSS", "CONST", "STRING", "INITDATA","EXITDATA","CPPINIT", "CPPEXIT",
   "DEBTYP", "DEBSYM", "BROWSE"
} ;
static char DgroupText[] = "DGROUP" ;
void emit_record(enum omf_type type, char *data, int len)
{
	int i,cs = 0 ;
	char buf[1100];
	buf[0] = type ;
	*(short *)(buf+1) = len + 1 ;
	memcpy(buf+3,data,len) ;
	for (i=0 ; i < len+3; i++)
		cs += buf[i] ;
	buf[len+3] = - cs ;
	fwrite(buf,len+4,1,outputFile) ;
}

void omfFileName(void)
{
	char buf[256] ;
	strcpy(buf+1,infile) ;
	buf[0] = strlen(infile) ;
	emit_record(THEADR, buf, buf[0] + 1) ;
}

void omfTranslatorName(void)
{
	char buf[100] ;
	*(short *)buf = 0 ;
   sprintf(buf+3,"CC386 Version %s (LADSoft)",version) ;
	buf[2] = strlen(buf+3) ;
	emit_record(COMENT, buf,3 + buf[2]) ;
}

void omfFileTime(char *file)
{
	unsigned short time,date ;
	char buf[256] ;
	int fd ;
	*(short *)buf = 0xe900 ;
	if (file) {
		if (_dos_open(file,0,&fd )) {
			time = 0 ;
			date = 0 ;
		} else {
			_dos_getftime(fd,&date,&time) ;
			_dos_close(fd) ;
		}
		*(short *)(buf+2) = time ;
		*(short *)(buf+4) = date ;
		memcpy(buf+7,file,strlen(file)) ;
		buf[6] = strlen(file) ;
		emit_record(COMENT,buf,7 + buf[6]) ;
	} else
		emit_record(COMENT,buf,2) ;
}
void omfDebugMarker(void)
{
   if (prm_debug) {
      char buf[3] ;
      buf [0] = 0x40 ;
      buf[1] = 0xa2 ;
      emit_record(COMENT,buf,2) ;
   }
}
void omfLNames(void)
{
	char buf[1024],*p = buf ;
	int pos=1,i ;
	*p++ = 0 ;
	for (i=1 ; i < MAX_SEGS; i++) {
		if (segs[i].curlast || i < 4) {
			segxlattab[i] = pos++ ;
			*p = strlen(segnames[i]) ;
			strcpy(p+1,segnames[i]) ;
			p += *p + 1 ;
			*p = strlen(segclasses[i]) ;
			strcpy(p+1, segclasses[i]) ;
			p += *p + 1 ;
			
		} else
			segxlattab[i] = 0 ;
	}
	dgroupVal = pos ;
	*p = strlen(DgroupText );
	strcpy(p+1,DgroupText );
	p += *p + 1 ;
	emit_record(LNAMES,buf,p-buf) ;
}
void omfLibMod(void)
{
  	        if (libincludes) {
    	        while (libincludes) {
								char buf[256], *p ;
								p = strrchr(libincludes->data,'\\') ;
								if (p)
									p++ ;
								else
									p = libincludes->data ;
								buf[0] = 0xa3 ;
								buf[1] = strlen(p) ;
								strcpy(buf+2,p) ;
								emit_record(COMENT,buf,buf[1]+2) ;
        	      libincludes = libincludes->link;
          	  }
	          }
}
void omfSegs(void)
{
	char buf[100] ;
	int i;
	for (i=1; i < MAX_SEGS; i++) {
		if (segxlattab[i]) {
			buf[0] = 0x29 ;
			*(int *)(buf +1) = segs[i].curlast ;
			buf[5] = segxlattab[i]*2 ;
			buf[6] = segxlattab[i]*2+1 ;
			buf[7] = 1 ;
			emit_record(SEGDEF, buf, 8) ;
		}
	}
}
void omfComDefs(void)
{
	VIRTUAL_LIST *v = virtualFirst ;
	char buf[256],*p ;
	while (v) {
		p = buf ;
		*p = strlen(v->sp->name) ;
		strcpy(p+1,v->sp->name) ;
		p+= (*p) + 1 ;
		*p++ = 0;
		*p++ = segxlattab[codeseg] ;
		if (v->seg->curlast > 127) {
			*p++ = 0x81 ;
			*(short *)p++ = v->seg->curlast ;
		} else
			*p++ = v->seg->curlast ;
		emit_record(COMDEF,buf,p-buf) ;
		v = v->next ;
	}
}
void omfGroups(void)
{
	char buf[100],*p = buf+1 ;
	buf[0] = dgroupVal * 2 ;
	if (segxlattab[2]) {
		*p++ = 0xff ;
		*p++ = segxlattab[2] ;
	}
	if (segxlattab[3]) {
		*p++ = 0xff ;
		*p++ = segxlattab[3] ;
	}
	if (segxlattab[4]) { 
		*p++ = 0xff ;
		*p++ = segxlattab[4] ;
	}
	if (segxlattab[5]) { 
		*p++ = 0xff ;
		*p++ = segxlattab[5] ;
	}
	emit_record(GRPDEF, buf, p-buf) ;
}
void FlushExtBuf(void)
{
	if (extSize) {
		emit_record(EXTDEF,extbuf,extSize) ;
		extSize = 0 ;
	}
}
void omfputext(SYM *sp)
{
	char buf[200] ;
	putsym(buf,sp,sp->name) ;
	sp->value.i = extIndex++ ;
	if (strlen(buf) + extSize > 1024-2) 
		FlushExtBuf() ;
	extbuf[extSize] = strlen(buf) ;
	strcpy(extbuf+extSize+1,buf) ;
	extSize += extbuf[extSize] + 1 ;
	extbuf[extSize++] = 0 ;
}
void omfExtDefs(void)
{
	int i ;
	SYM *sp ;
	VIRTUAL_LIST *v = virtualFirst;
   LIST *lf = localfuncs ;
	extIndex = 1 ;
	extSize = 0 ;
					while (v) {
						v->sp->value.i = extIndex++ + 0xc0000000 ;
						v = v->next ;
					}
          for (i=0; i < HASHTABLESIZE; i++) {
            if ((sp=(SYM *) gsyms[i]) != 0) {
              while (sp) {
                if( sp->storage_class == sc_externalfunc 
										&& sp->extflag && !(sp->tp->cflags & DF_INTRINS)) {
									omfputext(sp) ;
      	        }
            	  if( sp->storage_class == sc_external && sp->extflag) {
									omfputext(sp) ;
	              }
                 if (sp->storage_class == sc_defunc) {
                     SYM *sp1 = sp->tp->lst.head ;
                     while (sp1) {
                        if( sp1->storage_class == sc_externalfunc 
                              && sp1->extflag && !(sp1->tp->cflags & DF_INTRINS)) {
                           omfputext(sp1) ;
                       }
                       sp1 = sp1->next ;
                     }
                 }
        	      sp = sp->next;
          	  }
	          }
  	      }
            
              while (lf) {
                        sp = lf->data ;
                if( sp->storage_class == sc_externalfunc 
										&& sp->extflag && !(sp->tp->cflags & DF_INTRINS)) {
									omfputext(sp) ;
      	        }
               lf = lf->link ;
          	  }
 	FlushExtBuf() ;
}
void omfputimport(SYM *sp)
{
	char buf[256],obuf[256] ;
   int l,l1 ;
	putsym(buf,sp,sp->name) ;
   l = strlen(buf) ;
   *(short *)obuf = 0xa000 ;
   obuf[2] = 1 ; // import
   obuf[3] = 0 ; // import by name
   obuf[4] = l;
   strcpy(obuf+5,buf) ;
   obuf[5 + l] = l1 = strlen(sp->importfile) ;
   strcpy(obuf+6 + l, sp->importfile) ;
   obuf[6+l+l1] = l ;
   strcpy(obuf+7+l+l1,buf) ;

   emit_record(COMENT,obuf,7 + 2*l + l1) ;
}
void omfImports(void)
{
	SYM *sp ;
	int i ;
         for (i=0; i < HASHTABLESIZE; i++) {
        	  if ((sp=(SYM *) gsyms[i]) != 0) {
          	  while (sp) {
                 if(sp->importable && sp->importfile) {
                           omfputimport(sp) ;
	              }
  	            sp = sp->next;
    	        }
      	    }
        	}
}
int omfgetseg(SYM *sp)
{
   if( !sp->tp)
      return dataseg ;
	switch(sp->storage_class) {
		case sc_static:
		case sc_global:
      case sc_member:
			if (sp->tp->type == bt_func || sp->tp->type == bt_ifunc)
				return codeseg ;
			/* fall through */ 
		case sc_external:
			if (sp->tp->cflags & DF_CONST)
				return constseg ;
			else
				if (sp->init || !prm_bss)
					return dataseg ;
				else 
					return bssxseg ;
		case sc_externalfunc:
      case sc_label:
			return codeseg ;
		default:
			DIAG("Unknown segment type in omfGetSeg") ;
			return codeseg ;
	}
	// also fix the value.i field of local funcs...
}
void omfputpub(SYM *sp)
{
	char buf[256],obuf[256] ;
	int seg = omfgetseg(sp),group,pos ;
	putsym(buf,sp,sp->name) ;
	if (seg != 1)
		group = 1 ;
	else
		group = 0 ;
	obuf[0] = group ;
	obuf[1] = seg ;
	pos = (obuf[2] = strlen(buf))+ 3 ;
	strcpy(obuf+3,buf) ;
   *(int *)(obuf + pos) = sp->offset ;
	obuf[pos+4] = 0 ;
	emit_record(PUBDEF,obuf,pos+5) ;
}
void omfPublics(void)
{
	SYM *sp ;
   LIST *lf = localfuncs ;
	int i ;
         for (i=0; i < HASHTABLESIZE; i++) {
        	  if ((sp=(SYM *) gsyms[i]) != 0) {
          	  while (sp) {
                 if(sp->storage_class == sc_global && !(sp->value.classdata.cppflags & PF_INLINE)) {
									omfputpub(sp) ;
	              }
  	            sp = sp->next;
    	        }
      	    }
        	}
   while(lf) {
      SYM *sp = lf->data ;
      if(sp->storage_class == sc_global && !(sp->value.classdata.cppflags & PF_INLINE)) {
         omfputpub(sp) ;
      }
      lf = lf->link ;
   }
}
void omfputexport(SYM *sp)
{
	char buf[256],obuf[256] ;
   int l ;
	putsym(buf,sp,sp->name) ;
   l = strlen(buf) ;
   *(short *)obuf = 0xa000 ;
   obuf[2] = 2 ; // export
   obuf[3] = 0 ; // export flags
   obuf[4] = l;
   strcpy(obuf+5,buf) ;
   buf[5 + l] = l ;
   strcpy(obuf+6 + l, buf) ;

   emit_record(COMENT,obuf,6 + 2*l) ;
}
void omfExports(void)
{
	SYM *sp ;
	int i ;
         for (i=0; i < HASHTABLESIZE; i++) {
        	  if ((sp=(SYM *) gsyms[i]) != 0) {
          	  while (sp) {
                 if(sp->storage_class == sc_global && sp->exportable) {
                           omfputexport(sp) ;
	              }
  	            sp = sp->next;
    	        }
      	    }
        	}
}
void omfPassSeperator(void)
{
	char buf[3] ;
	buf [0] = 0x40 ;
  buf[1] = 0xa2 ;
  buf[2] = 0x01 ;
  emit_record(COMENT,buf,3) ;
}
int put_ident (char *buf, int ident)
{
	char *p = buf ;
	if (ident & 0xc0000000)
		*p++ = 0xc0 ;
	ident &= 0x7fff ;
	*p++ = ident > 127 ? (ident >> 8) | 0x80 : ident ;
	if (ident > 127)
		*p++ = ident ;
	return p - buf ;
}
int omfFixups(int seg, EMIT_LIST *rec, char *data, char *buf, int *len)
{
	int indent = 0, pos = 0 ;
   int fixeddat = FALSE ;
	FIXUP *fixups = rec->fixups ;
	while (fixups) {
		SYM *sp ;
		int rel = FALSE,iseg ;
		int fixdat = 0,tdat ;
		int offset = fixups->address - rec->address ;
		int locat = (offset >> 8) + (offset << 8) ;
      int segrel = FALSE ;
		switch(fixups->fmode) {
			case fm_relsymbol:
				rel = TRUE ;
			case fm_symbol:
				if (fixups->sym->storage_class == sc_abs) {
					if (rel)
						DIAG("Relative absolute in omfFixups") ;
					else
                  *(int *)(rec->data + offset) += sp->offset ;
					continue ;
				}
				iseg = omfgetseg(fixups->sym) ;
				{
					SYM *sp = fixups->sym ;
               if (sp->mainsym)
                  sp = sp->mainsym ;
               if (sp->storage_class == sc_global || sp->storage_class == sc_static ||
							(sp->storage_class == sc_external || sp->storage_class == sc_externalfunc) 
								&& !sp->extflag) {
						if (rel) {
							if (iseg == seg) {
                        *(int *)(data + offset) = sp->offset -fixups->address - 4 ;
							} else {
								locat |= 0xa4 ;
								fixdat = 0x54 ;
								tdat = segxlattab[iseg] ;
							}
						} else {
							locat |= 0xe4 ;
							fixdat = 0x04 ;
							tdat = segxlattab[iseg] ;
                     *(int *)(data +offset) += sp->offset ;
                     segrel = TRUE ;
						}
						/* segment relative */
					} else {
						if (rel) {
							if (iseg == seg)
							locat |= 0xa4 ;
							fixdat = 0x56 ;
                     tdat = sp->value.i ;
						} else {
							locat |= 0xe4 ;
							fixdat = 0x06 ;
                     tdat = sp->value.i ;
						}
						/* extdef relative */
					}
				}
				break ;

			case fm_rellabel:
				rel = TRUE ;
			case fm_label:
				iseg = LabelSeg(fixups->label) ;
				if (rel) {
							if (iseg == seg)
								*(int *)(data + offset) = LabelAddress(fixups->label) -fixups->address - 4 ;
							else {
								locat |= 0xa4 ;
								fixdat = 0x54 ;
								tdat = segxlattab[iseg] ;
							}
				} else {
               *(int *)(data + offset) += LabelAddress(fixups->label) ;
					locat |= 0xe4 ;
					fixdat = 0x04 ;
					tdat = segxlattab[iseg] ;
               segrel = TRUE ;
				}
				/* segment relative */
				break ;
		}
		if (fixdat) {
			int len = 4 + (tdat > 127) ;
         if (!fixeddat && pos + len >= 1024) {
            fixeddat = TRUE ;
				indent = 1024 - pos ;
				pos = 1024 ;
			}
			buf[pos++] = locat & 0xff ;
			buf[pos++] = locat >> 8 ;
			if ((fixdat & 0x70) == 0x00) {
            if (segrel && (tdat == 2 || tdat == 3 || tdat == segxlattab[4] || tdat == segxlattab[5])) {
					buf[pos++] = fixdat  | 0x10; /* grouped, 1st group */
					buf[pos++] = 1 ;
				} else
					buf[pos++] = fixdat | 0x50 ;
			} else
				buf[pos++] = fixdat ;
			pos += put_ident(buf+pos,tdat) ;
		}
		fixups = fixups->next ;
	}
	*len = pos ;
	return indent ;
}
void omfComDefData(void)
{
	VIRTUAL_LIST *v = virtualFirst ;
   char buf[1500],*p ;
	while (v) {
		EMIT_LIST *rec = v->seg->first ;
            while(rec) {
					int indent ;
					int len ;
					char fbuf[2048] ;
					p = buf ;
               p+= put_ident(p,v->sp->value.i) ;
					*((int *)(p))++ = rec->address ;
					memcpy(p,rec->data,rec->filled) ;
					indent = omfFixups(virtseg, rec,p,fbuf, &len) ;
					emit_record(LEDATA,buf,rec->filled+p-buf) ;
					if (len)
						if (len < 1024)
							emit_record(FIXUPP,fbuf,len) ;
						else {
							emit_record(FIXUPP,fbuf,1024 - indent) ;
							emit_record(FIXUPP,fbuf+1024, len - 1024) ;
						}
					rec = rec->next ;
				}
		v = v->next ;
	}
}
void omfData(void)
{
	char buf[1100] ;
	int i ;
	for (i=1; i < MAX_SEGS; i++)
		if (segxlattab[i] && segs[i].curlast) {
			if (i == bssxseg) {
				buf[0] = segxlattab[i] ;
            *(int *)(buf+1) = 0 ; /* offset */
            *(int *)(buf+5) = segs[i].curlast ; /* repeat count */
            *(short *)(buf+9) = 0 ; /* block count */
            *(buf+11) = 1 ; /* num bytes */
            *(buf+12) = 0 ; /* data */
				emit_record(LIDATA,buf,13) ;
			} else {
				EMIT_LIST *rec = segs[i].first ;
				while(rec) {
					int indent ;
					int len ;
					char fbuf[2048] ;
					buf[0] = segxlattab[i] ;
					*(int *)(buf+1) = rec->address ;
					memcpy(buf+5,rec->data,rec->filled) ;
					indent = omfFixups(i, rec,buf+5,fbuf, &len) ;
					emit_record(LEDATA,buf,rec->filled+5) ;
					if (len)
						if (len < 1024)
							emit_record(FIXUPP,fbuf,len) ;
						else {
							emit_record(FIXUPP,fbuf,1024 - indent) ;
							emit_record(FIXUPP,fbuf+1024, len - 1024) ;
						}
					rec = rec->next ;
				}
			}
		}
}
void omfSourceFile(char *file, int num)
{
	unsigned short time,date ;
	char buf[256] ;
	int fd ;
	*(short *)buf = 0xe800 ;
	buf[2] = num ;
	buf[3] = strlen(file) ;
	strcpy(buf+4,file) ;
		if (_dos_open(file,0,&fd )) {
			time = 0 ;
			date = 0 ;
		} else {
			_dos_getftime(fd,&date,&time) ;
			_dos_close(fd) ;
		}
		*(short *)(buf+4+buf[3]) = time ;
		*(short *)(buf+6+buf[3]) = date ;
		emit_record(COMENT,buf,8 + buf[3]) ;
}
void omfLineNumbers(int file) 
{
	LINEBUF *l = linelist ;
	char buf[1100],*p ;
   int lastnum = -1 ;
	while (l) {
		p = buf ;
		*p++ = 0 ;
		*p++ = segxlattab[codeseg] ;
		while (l && p - buf < 1024 -6) {
			if (l->file == file) {
//            if (l->address != lastnum) {
               *(short *)p = l->lineno ;
               *(int *)(p+2) = l->address ;
               p += 6 ;
               lastnum = l->address ;
//            }
         } 
			l = l->next ;
		}
		if (p-buf > 2) {
			emit_record(LINNUM,buf,p-buf) ;
		}
	}
}
void omfEmitLineInfo(void)
{
	int i,q ;
	FILELIST *l ;
	omfSourceFile(infile,1) ;
	omfLineNumbers(0) ;
	for (i=1,q=2,l=incfiles; l; i++,l=l->link)
		if (l->hascode) {
			omfSourceFile(l->data,q++) ;
			omfLineNumbers(i) ;
		}
}
void omfModEnd(void)
{
	char buf = 0 ;
	emit_record(MODEND,&buf,1) ;
}
void output_obj_file(void)
{
	LIST *l = incfiles ;
   extIndex = extSize = 0;
	omfFileName() ;
	omfTranslatorName() ;
	omfFileTime(infile) ;
	while (l) {
		omfFileTime(l->data) ;
		l = l->link ;
	}
	omfFileTime(0) ;
   omfDebugMarker() ;
	omfLibMod() ;
	omfLNames() ;
	omfSegs() ;
	omfComDefs() ;
	omfGroups() ;
  omfExtDefs() ;
  omfImports() ;
	omfPublics() ;
   omfExports() ;
  omfPassSeperator() ;
	omfComDefData() ;
	omfData() ;
	if (prm_debug)
		omfEmitLineInfo() ;
	omfModEnd() ;
}