#include <stdlib.h>
#include <stdio.h>
#include "io.h"
#include "ex.h"
#include "res.h"
#include "st.h"

typedef struct FileBuf *FileBuf;
struct FileBuf {
   char *Name;
   long Loc, Files, Segs, Gaps, Syms, Exps, Relocs;
   char **File; Segment Seg; Gap G; Symbol *Sym;
};
FileBuf FTab; int Fs;

typedef struct Block *Block;
struct Block {
   char *Src, *Obj; Segment Seg; Block Next;
};

static Block *BHead;
static int *BSize;
static Segment USeg;

typedef struct Image *Image;
struct Image {
   word Base; long Size; char *Src, *Obj; word Line; long Loc;
   Image Prev, Next;
};
static Image IHead, ITail;

Exp *ExpBuf;

static Image FormImage(
   word Base, long Size, char *Src, char *Obj, word Line, long Loc
) {
   Image IP = (Image)Allocate(sizeof *IHead);
   IP->Base = Base, IP->Size = Size, IP->Src = Src, IP->Obj = Obj,
   IP->Line = Line, IP->Loc = Loc;
   return IP;
}

static void SetImage(void) {
   Block BP;
   for (IHead = ITail = 0, BP = BHead[0]; BP != 0; BP = BP->Next) {
      Segment Seg = BP->Seg; Image P, IP, NewI;
      for (IP = IHead; IP != 0; IP = IP->Next)
         if (IP->Base >= Seg->Base) break;
      P = (IP == 0)? ITail: IP->Prev;
      if (P != 0 && P->Base + P->Size > Seg->Base)
         FATAL("Overlapping segments at [%d] %s and [%d] %s.",
            P->Line, P->Src, Seg->Line, BP->Src
         );
      if (IP != 0 && Seg->Base + Seg->Size > IP->Base)
         FATAL("Overlapping segments at [%d] %s and [%d] %s.",
            Seg->Line, BP->Src, IP->Line, IP->Src
         );
      NewI = FormImage(
         Seg->Base, Seg->Size, BP->Src, BP->Obj, Seg->Line, Seg->Loc
      );
      if (P == 0) IHead = NewI; else P->Next = NewI;
      if (IP == 0) ITail = NewI; else IP->Prev = NewI;
      NewI->Next = IP, NewI->Prev = P;
   }
}

static void FitGap(Gap G) {
   Image P, IP, NewI; long End, EndP;
   word Base = G->Seg->Base + G->Offset; long Size = G->Size;
   for (IP = IHead; IP != 0; IP = IP->Next)
      if (IP->Base > Base) break;
   P = (IP == 0)? ITail: IP->Prev;
   if (P == 0) FATAL("Internal error (2).");
   End = Base + Size, EndP = P->Base + P->Size;
   if (EndP < End) FATAL("Internal error (3).");
   if (P->Base < Base) {
      P->Size = Base - P->Base;
      if (EndP > End) {
         NewI = FormImage(
            (word)End, EndP - End, P->Src, P->Obj, P->Line, P->Loc + P->Size
         );
         NewI->Next = IP, P->Next = NewI;
         NewI->Prev = P;
         if (IP == 0) ITail = NewI; else IP->Prev = NewI;
      }
   } else if (EndP > End) P->Base += Size, P->Size -= Size;
   else {
      if (P->Prev == 0) IHead = P->Next; else P->Prev->Next = P->Next;
      if (P->Next == 0) ITail = P->Prev; else P->Next->Prev = P->Prev;
      free(P);
   }
}

typedef struct Free *Free;
struct Free {
   word Base; long Size; Block BP;
};
static Free *FHead;
static int *FSize;

void SetFree(void) {
   int T;
   FHead = (Free *)Allocate(SEG_TYPES * sizeof *FHead);
   FSize = (int *)Allocate(SEG_TYPES * sizeof *FSize);
   for (T = 0; T < SEG_TYPES; T++) {
      Block P, B; Free F; long Base;
      FHead[T] = (Free)Allocate((BSize[T] + 1) * sizeof *FHead[T]);
      for (P = 0, B = BHead[T], F = FHead[T]; B != 0; P = B, B = B->Next) {
         Base = (P == 0)? AddrTab[T].Lo: (long) P->Seg->Base + P->Seg->Size;
         if (Base >= B->Seg->Base) continue;
         F->Base = Base, F->Size = (long)B->Seg->Base - Base, F->BP = P, F++;
      }
      Base = (P == 0)? AddrTab[T].Lo: (long) P->Seg->Base + P->Seg->Size;
      if (Base <= AddrTab[T].Hi)
         F->Base = Base, F->Size = (long)AddrTab[T].Hi + 1 - Base,
         F->BP = P, F++;
      FSize[T] = F - FHead[T];
   }
}

void PurgeFree(void) {
   int T;
   for (T = 0; T < SEG_TYPES; T++) free(FHead[T]);
   free(FHead), free(FSize);
}

void BestFit(char *Obj, Segment Seg) {
   byte T = Seg->Type; Free Best, FP; Block B;
   if (!Seg->Rel) return;
   StartLine = Seg->Line, StartF = Seg->File;
   for (Best = 0, FP = FHead[T]; FP < FHead[T] + FSize[T]; FP++) {
      if (
         Seg->Size <= FP->Size && (Best == 0 || FP->Size < Best->Size)
      ) Best = FP;
   }
   if (Best == 0) FATAL("Cannot fit segment.");
   B = (Block)Allocate(sizeof *B);
   B->Seg = Seg, B->Src = FileTab[StartF], B->Obj = Obj;
   if (Best->BP == 0)
      B->Next = BHead[T], BHead[T] = B;
   else
      B->Next = Best->BP->Next, Best->BP->Next = B;
   Seg->Rel = 0, Seg->Base = Best->Base,
   Best->BP = B, Best->Base += Seg->Size, Best->Size -= Seg->Size;
}

void FitSegs(FileBuf F) {
   int S; Segment Seg;
   FileTab = F->File;
   for (S = TYPES; S < F->Segs; S++) {
      Seg = F->Seg + S - TYPES, BestFit(F->Name, Seg);
   }
}

void SetSeg(void) {
   Segment S; int I;
   USeg = (Segment)Allocate(TYPES * sizeof *USeg);
   for (S = USeg; S < USeg + TYPES; S++)
      S->Line = S->File = 0, S->Rel = 0, S->Type = S - USeg,
      S->Base = 0, S->Size = 0, S->Loc = 0L;
   BHead = (Block *)Allocate(SEG_TYPES * sizeof *BHead);
   BSize = (int *)Allocate(SEG_TYPES * sizeof *BSize);
   for (I = 0; I < SEG_TYPES; I++) BHead[I] = 0, BSize[I] = 0;
}

void FreeBlocks(void) {
   int I;
   for (I = 0; I < SEG_TYPES; I++) {
      Block B, N;
      for (B = BHead[I]; B != 0; B = N) N = B->Next, free(B);
   }
   free(BHead), free(BSize);
}

void Fit(char *Obj, Segment Seg) {
   byte T = Seg->Type; Block Cur, Prev, B;
   if (Seg->Rel) return;
   for (Prev = 0, Cur = BHead[T]; Cur != 0; Prev = Cur, Cur = Cur->Next)
      if (Cur->Seg->Base >= Seg->Base) break;
   if (Prev != 0 && Prev->Seg->Base + Prev->Seg->Size > Seg->Base)
      ERROR("Overlapping segments: %s [%d] and %s [%d].",
         FileTab[Seg->File], Seg->Line, Prev->Src, Prev->Seg->Line
      );
   if (Cur != 0 && Cur->Seg->Base < Seg->Base + Seg->Size)
      ERROR("Overlapping segments: %s [%d] and %s [%d].",
         FileTab[Seg->File], Seg->Line, Cur->Src, Cur->Seg->Line
      );
   B = (Block)Allocate(sizeof *B);
   B->Src = FileTab[Seg->File], B->Obj = Obj, B->Seg = Seg, B->Next = Cur;
   if (Prev == 0) BHead[T] = B; else Prev->Next = B;
   BSize[T]++;
}

void GetHead(FileBuf F) {
   word MAGIC; long S; byte Buf[0x100];
   FILE *FP = fopen(F->Name, "rb+");
   if (FP == 0) FATAL("Cannot open %s.", F->Name);
   MAGIC = GetW(FP);
   if (MAGIC != 0x55aa) FATAL("Invalid object file %s.", F->Name);
   F->Loc = GetL(FP),
   F->Files = GetL(FP), F->Segs = GetL(FP),
   F->Gaps = GetL(FP), F->Syms = GetL(FP),
   F->Exps = GetL(FP), F->Relocs = GetL(FP);
   S = F->Loc + F->Files + F->Segs + F->Gaps + F->Syms + F->Exps + F->Relocs;
   if (GetL(FP) != S&0xffffffff) FATAL("Corrupt object file %s.", F->Name);
   F->File = (char **)Allocate((word)F->Files * sizeof *F->File);
   F->Seg = (F->Segs < TYPES)? 0: (Segment)Allocate((word)(F->Segs - TYPES) * sizeof *F->Seg);
   F->G = (Gap)Allocate((word)F->Gaps * sizeof *F->G);
   F->Sym = (Symbol *)Allocate((word)F->Syms * sizeof *F->Sym);
   FileTab = F->File;
   fseek(FP, F->Loc, SEEK_SET);
   for (S = 0; S < F->Files; S++) {
      word L;
      L = GetW(FP), fread(Buf, 1, L, FP), Buf[L] = '\0';
      F->File[S] = CopyS(Buf);
   }
   for (S = TYPES; S < F->Segs; S++) {
      Segment Seg = F->Seg + S - TYPES; word U;
      Seg->Line = GetW(FP), Seg->File = GetW(FP),
      U = GetW(FP), Seg->Size = GetW(FP),
      Seg->Base = GetW(FP), Seg->Loc = GetL(FP);
      Seg->Rel = (U >> 8)&1, Seg->Type = U&0xff;
      Fit(F->Name, Seg);
   }
   for (S = 0; S < F->Gaps; S++) {
      Gap G = F->G + S; word Sg;
      Sg = GetW(FP), G->Offset = GetW(FP), G->Size = GetW(FP);
      G->Seg = Sg < TYPES? &USeg[Sg]: &F->Seg[Sg - TYPES];
   }
   for (S = 0; S < F->Syms; S++) {
      Symbol Sym; byte B, Defined, Global, Address; word U, L, Offset;
      B = GetB(FP), U = GetW(FP), Offset = GetW(FP),
      L = GetW(FP);
      if (L > 0) fread(Buf, 1, L, FP);
      Buf[L] = '\0';
      Global = (B&8)? 1: 0, Defined = (B&4)? 1: 0, Address = (B&2)? 1: 0;
      if (!Global) {
         Sym = (Symbol)Allocate(sizeof *Sym);
         Sym->Variable = 0, Sym->Address = Address;
         Sym->Global = Global, Sym->Defined = Defined;
         Sym->Seg = !Address? 0: U < TYPES? &USeg[U]: &F->Seg[U - TYPES];
         Sym->Offset = Offset;
         Sym->Index = F - FTab;
         Sym->Name = CopyS(Buf);
      } else {
         Sym = LookUp(Buf);
         if (Sym->Defined && Defined)
            ERROR("Symbol %s redefined: %s %s.",
               Sym->Name, FTab[Sym->Index].Name, F->Name
            );
         else {
            if (Sym->Address) {
               Segment Seg = U < TYPES? &USeg[U]: &F->Seg[U - TYPES];
               if (!Address)
                  ERROR("Address %s redeclared as number: %s %s",
                     Sym->Name, FTab[Sym->Index].Name, F->Name
                  );
               else if (Sym->Seg->Type != Seg->Type)
                  ERROR("Type mismatch %s: %s %s",
                     Sym->Name, FTab[Sym->Index].Name, F->Name
                  );
            } else if (Address && Sym->Global)
               ERROR("Number %s redeclared as address: %s %s",
                  Sym->Name, FTab[Sym->Index].Name, F->Name
               );
            if (Defined || !Sym->Global) {
               Sym->Variable = 0, Sym->Address = Address;
               Sym->Global = Global, Sym->Defined = Defined;
               Sym->Seg = !Address? 0: U < TYPES? &USeg[U]: &F->Seg[U - TYPES],
               Sym->Offset = Offset;
               Sym->Index = F - FTab;
            }
         }
      }
      F->Sym[S] = Sym;
   }
   F->Loc = ftell(FP);
   fclose(FP);
}

typedef struct RList *RList;
struct RList {
   byte Size; long PC, Val; RList Next;
};
RList RHead;

void AddReloc(byte Size, long PC, long Val) {
   RList R = Allocate(sizeof *R), P, S;
   for (P = 0, S = RHead; S != 0; P = S, S = S->Next)
      if (S->PC >= PC) break;
   if (S != 0 && S->PC == PC) FATAL("Internal error (4)."); 
   if (P == 0) RHead = R; else P->Next = R;
   R->Next = S,
   R->Size = Size, R->PC = PC, R->Val = Val;
}

void SetRelocs(FileBuf F) {
   long S; struct Item IBuf; Exp E, NextE;
   FILE *FP = fopen(F->Name, "rb+");
   if (FP == 0) FATAL("Cannot open %s.", F->Name);
   FileTab = F->File; fseek(FP, F->Loc, SEEK_SET);
   ExpBuf = (Exp *)Allocate((unsigned)F->Exps * sizeof *ExpBuf);
   ExpInit();
   for (S = 0; S < F->Exps; S++) {
      Exp *EP = ExpBuf + S; byte Tag; Segment Seg; Exp A, B, C;
      word Line, File, U, Offset; byte Op;
      StartLine = GetW(FP), StartF = GetW(FP), Tag = GetB(FP);
      switch (Tag) {
         case NumX:
            Value = GetW(FP); *EP = MakeExp(NumX, Value);
         break;
         case AddrX:
            U = GetW(FP), Offset = GetW(FP);
            Seg = U < TYPES? &USeg[U]: &F->Seg[U - TYPES];
            *EP = MakeExp(AddrX, Seg, Offset);
         break;
         case SymX:
            U = GetW(FP); *EP = MakeExp(SymX, F->Sym[U]);
         break;
         case UnX:
            Op = GetB(FP),
            U = GetW(FP), A = ExpBuf[U],
            *EP = MakeExp(UnX, Op, A);
         break;
         case BinX:
            Op = GetB(FP),
            U = GetW(FP), A = ExpBuf[U],
            U = GetW(FP), B = ExpBuf[U],
            *EP = MakeExp(BinX, Op, A, B);
         break;
         case CondX:
            U = GetW(FP), A = ExpBuf[U],
            U = GetW(FP), B = ExpBuf[U],
            U = GetW(FP), C = ExpBuf[U],
            *EP = MakeExp(CondX, A, B, C);
         break;
      }
   }
   for (S = 0; S < F->Relocs; S++) {
      word U, PC;
      IBuf.Line = GetW(FP), IBuf.File = GetW(FP),
      IBuf.Tag = GetB(FP),
      U = GetW(FP), IBuf.E = ExpBuf[U],
      U = GetW(FP), IBuf.Seg = U < TYPES? &USeg[U]: &F->Seg[U - TYPES];
      IBuf.Offset = GetW(FP);
      Resolve(&IBuf);
      PC = (long)IBuf.Seg->Base + (long)IBuf.Offset;
      switch (IBuf.Tag) {
         case 'b': AddReloc(1, PC, LVal); break;
         case 'w': AddReloc(2, PC, LVal); break;
         default: FATAL("Internal error (5).");
      }
   }
   fclose(FP);
   for (E = ExpHead; E != 0; E = NextE) NextE = E->Next, free(E);
   free(ExpBuf);
}

/* HEX OUTPUT ROUTINES */
FILE *OutF = 0;
byte HexBuf[0x10]; int HexX;
long HexAddr;
void OpenHex(char *Hex) {
   OutF = fopen(Hex, "w");
   if (OutF == 0) FATAL("Cannot open output file.");
   HexX = 0;
}
void EndHex(void) {
   if (HexX > 0) {
      int H, Sum;
      Sum = (HexAddr&0xff) + ((HexAddr >> 8)&0xff) + HexX;
      fprintf(OutF, ":%02X%04X00", HexX, HexAddr);
      for (H = 0; H < HexX; H++)
         fprintf(OutF, "%02X", HexBuf[H]), Sum += HexBuf[H];
      fprintf(OutF, "%02X\n", (-Sum)&0xff);
      HexX = 0;
   }
}
void PutHex(long Addr, byte Hex) {
   if (HexX == 0) HexAddr = Addr;
   HexBuf[HexX++] = Hex;
   if (HexX == 0x10) {
      int H, Sum;
      Sum = (HexAddr&0xff) + ((HexAddr >> 8)&0xff) + HexX;
      fprintf(OutF, ":10%04X00", HexAddr);
      for (H = 0; H < HexX; H++)
         fprintf(OutF, "%02X", HexBuf[H]), Sum += HexBuf[H];
      fprintf(OutF, "%02X\n", (-Sum)&0xff);
      HexX = 0;
   }
}
void CloseHex(void) {
   EndHex();
   fprintf(OutF, ":00000001FF\n");
   fclose(OutF);
}

void GenImage(char *Hex) {
   Image IP; RList R; FILE *FP; char *File;
   FP = 0, File = 0;
   OpenHex(Hex);
   for (IP = IHead, R = RHead; IP != 0; IP = IP->Next) {
      long Addr;
      if (IP->Obj != File) {
         if (FP != 0) fclose(FP), FP = 0;
         FP = fopen(IP->Obj, "rb+");
         if (FP == 0) FATAL("Cannot open %s.", IP->Obj);
      }
      fseek(FP, IP->Loc, SEEK_SET);
      for (Addr = IP->Base; Addr < IP->Base + IP->Size; ) {
         if (R != 0 && R->PC < Addr) FATAL("Internal error (6).");
         if (R != 0 && R->PC == Addr) {
            switch (R->Size) {
               case 1: fgetc(FP); PutHex(Addr++, (byte)(R->Val&0xff)); break;
               case 2:
                  fgetc(FP), fgetc(FP);
                  PutHex(Addr++, (byte)((R->Val >> 8)&0xff)),
                  PutHex(Addr++, (byte)(R->Val&0xff));
               break;
               default: FATAL("Internal error (7).");
            }
            R = R->Next;
         } else {
            int Ch = fgetc(FP);
            if (Ch == EOF) FATAL("Internal error (8).");
            PutHex(Addr++, (byte)(Ch&0xff));
         }
      }
      if (IP->Next != 0 && IP->Base + IP->Size < IP->Next->Base) EndHex();
   }
   CloseHex();
}

void Link(char *Hex) {
   int A;
   Active = 1, Phase = 2;
   SymInit(); SetSeg();
   for (A = 0; A < Fs; A++) GetHead(FTab + A);
   for (Sym = NIL->Next[0]; Sym != NIL; Sym = Sym->Next[0])
      if (!Sym->Defined) ERROR("Unresolved external: %s.", Sym->Name);
   CHECK();
   InSeg = 1;
   SetFree();
   for (A = 0; A < Fs; A++) FitSegs(FTab + A);
   PurgeFree();
   InSeg = 0;
   CHECK();
   SetImage();
   for (A = 0; A < Fs; A++) {
      Gap G; FileBuf F = FTab + A;
      for (G = F->G; G < F->G + F->Gaps; G++) FitGap(G);
   }
   CHECK();
   FreeBlocks();
   InSeg = 1; RHead = 0;
   for (A = 0; A < Fs; A++) SetRelocs(FTab + A);
   InSeg = 0;
   CHECK();
   GenImage(Hex);
}
