/*
     This generates a listing file of your C program with the generated assembly language
     and binary (in hex) next to each source line.  This is a facility that's usually
     available on most compilers but was lacking on the 6809 C compiler.  We make it
     work by running an extra compilation of the source with "-g" added, which adds
     stabs directives to the assembly code and resulting listing.  The code here
     merges that listing with the original C source.  It's not perfect because of
     the way that GCC displays 'inline' procedures.  It may be possible to shuffle
     the files around even more to remedy this, but I don't plan to for the moment -
     this seems good enough.  You can always "#define inline" to remove inlining to
     some extent, to get a better view of what the compiler is doing, though of course
     it will no longer exactly represent your compiled program.

     This is very hacky code.  It could be cleaned up a lot.  Read it in conjunction
     with the Makefile (and check README-SOURCE-MERGING.txt if you plan to extend
     this in any way.)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// TO DO: remove the noise associated with embedded asm() statements...
//        parameterise the program with argv.

char *getsourceline(FILE *f) {
  static char p[1024], *s;
  s = p;
  for (;;) {
    int c = fgetc(f);
    if (c == EOF) {
      *s = '\0';
      if (s == p) return NULL;
      return strdup(p);
    }
    if (c == '\r') continue;
    if (c == '\n') {
      *s = '\0';
      return strdup(p);
    }
    *s++ = c;
  }
}

void output(FILE *f, char *s) {
  while (*s != '\0') {
    int c = *s++;
    if (c == ' ' || c == '\t' || c == '\n' || ((c > 32) && (c < 127))) fputc(c, f);
  }
}

#define TARGET "source/main.c"
int main(int argc, char **argv) {
#define HUGE 10000
  //FILE *stabs = fopen("build/lib/main.stabs", "r");
  FILE *stabs = fopen("build/lib/main-stabs.lst", "r");
  FILE *source = fopen(TARGET, "r");
  char fname[128], *fp;
  typedef struct {
    char *source;
    char *assem;
  } combined;
  combined prog[HUGE];
  int source_line = 0;
  int capture = 0;
  
  prog[source_line].source = NULL;
  prog[source_line].assem = NULL;
  for (;;) {
    source_line += 1;
    prog[source_line].source = getsourceline(source);
    prog[source_line].assem = NULL;
    if (prog[source_line].source == NULL) {
      source_line -= 1;
      break;
    }
  }
  
  fprintf(stderr, "%d lines read.\n", source_line);

  // Now that we have the source code, annotate it with the asm!

  for (;;) {
    char *oline = getsourceline(stabs), *line;
    line = oline;
    if (line == NULL) break;
/*
                           3131         .area   .data
   0002                    3132 _workBuffer:
   0002 00 00 00 00 00 00  3133         .word   0,0,0,0,0,0,0,0
        00 00 00 00 00 00
        00 00 00 00
                           3134         .area   .bss
                           3135         .globl  _screenPoints
   0001                    3136 _screenPoints:  .blkb   30
 */
    if (strlen(line) >= strlen("   0002 00 00 00 00 00 00  3133 ")) {
      line += strlen("   0002 00 00 00 00 00 00  3133 ");
      line[-2] = ' ';
      line[-3] = ' ';
      line[-4] = ' ';
      line[-5] = ' ';
      line[-6] = ' ';
    }
    if (strncmp(line, ";\t.stabs", strlen(";\t.stabs")) == 0) {
      fprintf(stderr, "STABS: %s\n", line);
      // .stabs  "source/main.c",132,
      char *s = line+strlen(";\t.stabs");
      while (*s == ' ' || *s == '\t') s+=1;
      if (*s == '"') {
        fp = fname;
        s += 1;
        while (*s != '"') {
          *fp++ = *s++;
        }
        *fp = '\0';
        s += 1;
        if (strncmp(s, ",132,", strlen(",132,")) == 0) {
          fprintf(stderr, "File [[%s]] directive found: %s\n", fname, line);
          capture = (strcmp(fname, TARGET) == 0);
          if (capture) {
            fprintf(stderr, "(capturing asm)\n");
          } else {
            fprintf(stderr, "(skipping asm)\n");
          }
        } else {
          fprintf(stderr, "? Not wanted stabs: %s\n", line);
        }
      } else {
        fprintf(stderr, "* Malformed stabs directive: %s at [[%s]]\n", line, s);
      }
    } else if (strncmp(line, ";\t.stabd", strlen(";\t.stabd")) == 0) {
      // .stabd  68,0,1342
      fprintf(stderr, "STABD: %s\n", line);
      char *s = line+strlen(";\t.stabd");
      while (*s == ' ' || *s == '\t') s+=1;
      if (strncmp(s, "68,0,", strlen("68,0,")) == 0) {
        s += strlen("68,0,");
        fprintf(stderr, "Assembly code for line %s follows.\n", s);
        if (capture) capture = atoi(s);
      } else {
        fprintf(stderr, "* Malformed stabd directive: %s at [[%s]]\n", line, s);
      }
    } else if (strncmp(line, ";\t.stabn", strlen(";\t.stabn")) == 0) {
      //fprintf(stderr, "Ignore %s\n", line);
    } else if (strncmp(line, ";\t.stab", strlen(";\t.stab")) == 0) {
      fprintf(stderr, "? Warning: unknown stab directive %s\n", line);
    } else {
      if (capture) {
        char *temp_ptr, *s;
        int newlen;
        if (prog[capture].assem == NULL) {
          prog[capture].assem = strdup("");
        }
        newlen = strlen(prog[capture].assem)+strlen(oline)+2;
        temp_ptr = (char *)realloc(prog[capture].assem, newlen);
        if (temp_ptr == NULL) {
          fprintf(stderr, "Realloc %d failed.\n", newlen); exit(EXIT_FAILURE);
        }
        prog[capture].assem = temp_ptr;
        s = prog[capture].assem + strlen(prog[capture].assem);
        sprintf(s, "%s\n", oline);

      }
    }
    free(oline);
  }
  
  for (int i = 1; i <= source_line; i++) {
    fprintf(stdout, "                          %5d ", i);
    output(stdout, prog[i].source);
    fprintf(stdout, "\n");
    if (prog[i].assem != NULL) {
      output(stdout, prog[i].assem);
    }
  }
  
  exit(EXIT_SUCCESS);
}
