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

#define LINELEN 1024
#define FILENAMELEN 1024
#define BIGLINECOUNT 100000
#define SOURCELINECOUNT 10000

#define TAB "\t"
#define TABCH '\t'

static int debug = 0;

int readline(FILE *f, char *line) {
  int c;
  for (;;) {
    c = fgetc(f);

    //if (c == '\t') c = '_'; // FOR DEBUGGING ONLY
    //#undef TAB
    //#undef TABCH
    //#define TAB "_"
    //#define TABCH '_'

    if (c == EOF) return 0;
    if (c == '\n') break;
    *line++ = c&255;
  }
  *line = '\0';
  return 1;
}

int main(int argc, char **argv) {
  static char line[LINELEN];
  char armlistname[FILENAMELEN];
  char *line_to_save = NULL;
  char *decode[SOURCELINECOUNT];
  char MASTER_FILENAME[FILENAMELEN] = { '\0' };
  char target_filename[FILENAMELEN] = { '\0' };
  char current_filename[FILENAMELEN] = { '\0' };
  int MASTER_FILENO = 0, current_fileno = 1, current_lineno = 0;
  FILE *armlistfile;
  int /*keep_lines,*/ gotlines = 0;

  for (int i = 0; i < SOURCELINECOUNT; i++) {
    decode[i] = strdup("");
  }
  
  sprintf(armlistname, "%s.lst", argv[1]);
  armlistfile = fopen(armlistname, "r");
  if (armlistfile == NULL) {
    fprintf(stderr, "Cannot open %s - %s\n", armlistname, strerror(errno));
    exit(EXIT_FAILURE);
  }
  
  while (readline(armlistfile, line)) { // readline must remove \n from end of line
    char *q, *s = strdup(line);
    if (debug) fprintf(stderr, "[%s:%d] %s\n", current_filename, current_lineno, s);
    char *orig = s;
    /*
S|574|/home/gtoal/src/i2c/perms/visible.h|// WAS:
D|7389|.loc 2 574 0 discriminator 1
I|7390|3fea 81EC0001|           subl    $256, %esp
 7390|      0000
I|7391|3ff0 8DB578DE|           leal    -139656(%ebp), %esi
 7391|      FDFF
I|7392|3ff6 B9400000|           movl    $64, %ecx
 7392|      00
I|7393|3ffb 89E7    |           movl    %esp, %edi
I|7394|3ffd F3A5    |           rep movsl
I|7395|3fff E8FCFFFF|           call    _imp_printstring@PLT
 7395|      FF
J|7396|.LVL696:
J|7397|.LBE594:
J|7398|.LBE593:
D|7399|.loc 1 975 0 discriminator 1
I|7400|4004 81C4F800|           addl    $248, %esp
 7400|      0000
     */
    line_to_save = NULL;
    if (*s == 'D') {
      // Directive, e.g.
      // D|12055|.loc 1 1288 4 view .LVU4618
      // D|12059|.cfi_endproc
      // D|12062|.section        .rodata
      /*
[1:0] D|1|.file "nminimo.c"
Setting current_fileno to 0 from .file
[0:0] D|2|.text
[0:0] J|3|.Ltext0:
Saving:                 .Ltext0:
[0:0] P|5|BITS.6077:
Saving:                 BITS.6077:
[0:0] J|6|.LFB186:
Saving:                 .LFB186:
[0:0] D|7|.file 1 "nminimo.imp"
       */
      s = strchr(s, '|')+1;
      s = strchr(s, '|')+1;
      // TO DO: note if .loc switches to another file (not file 1)
      if (strncmp(s, ".loc", 4) == 0) {
          current_fileno = (int)atol(s+4);
          if (debug) fprintf(stderr, "Setting current_fileno to %d from .loc\n", current_fileno);
          //if (current_fileno == MASTER_FILENO) {
            q = strrchr(target_filename, '/'); ; strcpy(current_filename, (q ? q+1 : target_filename));
            if (debug) fprintf(stderr, "And setting CURRENT_FILENAME to %s from TARGET_FILENAME\n", current_filename);
          //}
      } else if (strncmp(s, ".file", 4) == 0) {
          current_fileno = (int)atol(s+5);
          if (current_fileno == 0) { // .file "filename.c"
            strcpy(MASTER_FILENAME, strchr(s, '"')+1); *strchr(MASTER_FILENAME, '"') = '\0'; // HACK to test.  Make robust if it works.
            s = strrchr(MASTER_FILENAME, '.');
            if (s && (strcmp(s, ".c") == 0)) strcpy(s, ".imp");
            q = strrchr(MASTER_FILENAME, '/'); if (q) memmove(MASTER_FILENAME, q+1, strlen(q+1)+1);
            if (debug) fprintf(stderr, "Setting MASTER_FILENAME to '%s' from .file\n", MASTER_FILENAME);
            strcpy(current_filename, MASTER_FILENAME);
            strcpy(target_filename, MASTER_FILENAME);
            if (debug) fprintf(stderr, "And setting TARGET_FILENAME and CURRENT_FILENAME#3 to %s\n", current_filename);
          } else { // .file 1 "filename.imp"
            if (MASTER_FILENAME[0] == '\0') { // .file 2 "filename.imp"
              strcpy(MASTER_FILENAME, strchr(s, '"')+1); *strchr(MASTER_FILENAME, '"') = '\0'; // HACK to test.  Make robust if it works.
              s = strrchr(MASTER_FILENAME, '.');
              if (s && (strcmp(s, ".imp") != 0)) {
                MASTER_FILENAME[0] = '\0'; // undo the damage
              } else {
                q = strrchr(MASTER_FILENAME, '/'); if (q) memmove(MASTER_FILENAME, q+1, strlen(q+1)+1);
                if (debug) fprintf(stderr, "Setting MASTER_FILENAME#2 to '%s' from .file\n", MASTER_FILENAME);
                strcpy(current_filename, MASTER_FILENAME);
                strcpy(target_filename, MASTER_FILENAME);
                if (debug) fprintf(stderr, "And setting TARGET_FILENAME and CURRENT_FILENAME#4 to %s\n", current_filename);
              }
            } else if (current_fileno == MASTER_FILENO) {
              strcpy(target_filename, strchr(s, '"')+1); *strchr(target_filename, '"') = '\0'; // HACK to test.  Make robust if it works.
              q = strrchr(target_filename, '/'); if (q) memmove(target_filename, q+1, strlen(q+1)+1);
              if (debug) fprintf(stderr, "Setting TARGET_FILENAME to %s\n", target_filename);
              if (strcmp(target_filename, MASTER_FILENAME) == 0) {
                MASTER_FILENO = current_fileno;
                if (debug) fprintf(stderr, "Setting MASTER_FILENO to %d from .file\n", current_fileno);
              }
              strcpy(current_filename, target_filename);
              if (debug) fprintf(stderr, "Setting CURRENT_FILENAME#1 to %s\n", current_filename);
            } else {
              strcpy(current_filename, strchr(s, '"')+1); *strchr(current_filename, '"') = '\0'; // HACK to test.  Make robust if it works.
              q = strrchr(current_filename, '/'); if (q) memmove(current_filename, q+1, strlen(q+1)+1);
              if (debug) fprintf(stderr, "Setting CURRENT_FILENAME#4 to %s\n", current_filename);
            }
          }
          if (debug) fprintf(stderr, "Setting current_fileno to %d from .file\n", current_fileno);
      } else if (
          (strncmp(s, ".cfi", 4) == 0) ||
          (strncmp(s, ".arch", 5) == 0) ||
          (strncmp(s, ".text", 5) == 0) ||
          (strncmp(s, ".fpu", 4) == 0) ||
          (strncmp(s, ".arm", 4) == 0) ||
          (strncmp(s, ".eabi_", 6) == 0) ||
          (strncmp(s, ".syntax", 7) == 0) ||
          (strncmp(s, ".file", 5) == 0)
        ) {
        // suppress some directives but not all
      } else {
        *--s = '\t';
        *--s = '\t';
        *--s = '\t';
        *--s = '\t';
        line_to_save = strdup(s); // or line_to_save = malloc(5+6+2+strlen(s)+1); sprintf(line_to_save, "     %06lX: %s", atol(offset), s);
        gotlines += 1;
        free(orig);
      }
    } else if (*s == 'P') {
      // Procedure, e.g.
      // P|8584|GETCOND.10:
      // P|8809|main:
      // P|12067|__PRETTY_FUNCTION__.47:
      s = strchr(s, '|')+1;
      s = strchr(s, '|')+1;
      *--s = '\t';
      *--s = '\t';
      line_to_save = strdup(s); // or line_to_save = malloc(5+6+2+strlen(s)+1); sprintf(line_to_save, "     %06lX: %s", atol(offset), s);
      gotlines += 1;
      free(orig);
    } else if (*s == 'J') {
      // Jump destination, e.g.
      // J|12047|.L762:
      // J|12060|.LFE199:
      // J|12080|.LC0:
      // J|12259|.Letext0:
      s = strchr(s, '|')+1;
      s = strchr(s, '|')+1;
      *--s = '\t';
      *--s = '\t';
      line_to_save = strdup(s); // or line_to_save = malloc(5+6+2+strlen(s)+1); sprintf(line_to_save, "     %06lX: %s", atol(offset), s);
      gotlines += 1;
      free(orig);
    } else if (*s == 'I') {
      // Instruction (or data), e.g.
      // I|1594|06ec FD030091|           mov     x29, sp
      // I|1597|06f0 01000090|           adrp    x1, :got:_imp_InStream;ldr      x1, [x1, :got_lo12:_imp_InStream]
      // I|1597|     210040F9|
      // I|1598|06f8 200000B9|           str     w0, [x1]
      // I|12253|00c8 00000000|          .xword  .L297
      // I|12253|     00000000|

      // TO DO: remove lines with "@ "

      s = strchr(s, '|')+1;
      s = strchr(s, '|')+1;
      s = strchr(s, '|')+1;
      *--s = '\t';
      *--s = '\t';
      line_to_save = strdup(s); // or line_to_save = malloc(5+6+2+strlen(s)+1); sprintf(line_to_save, "     %06lX: %s", atol(offset), s);
      gotlines += 1;
      free(orig);
    } else if (*s == 'S') {
      // Source, e.g.
      // S|1390|regression-compile-tmp/hal7502.imp|    PUTPOS = STARTPOS;  PUT CHAR(0)
      /*
 680 0390 81C30200              addl    $_GLOBAL_OFFSET_TABLE_, %ebx
 680      0000
 681 0396 89CE                  movl    %ecx, %esi
  98:nminimo.imp   ****   !print((t-start)/1000,5,3)
 682                            .loc 1 98 0
 683 0398 E8FCFFFF              call    CPUTIME@PLT
 683      FF
 684                    .LVL47:
 685 039d 89C7                  movl    %eax, %edi
 686                    .LVL48:
 687                    .LBB231:
 688                    .LBB232:
 572:/home/gtoal/src/i2c/perms/visible.h **** static inline void                     _imp_PRINTCH(char SYM) { _imp_printch(SYM); }
 573:/home/gtoal/src/i2c/perms/visible.h ****
 574:/home/gtoal/src/i2c/perms/visible.h **** static inline void                     _imp_PRINTSTRING(_imp_string S) { _imp_printstring(S); }
        */
      s = strchr(s, '|')+1;
      char *e = strchr(s, '|');
      *e++ = '\0';
      // TO DO: use the last lineno from the main file if .loc was not 1
//#ifndef PREVIOUS
      char *sp = e;
      e = strchr(sp, '|');
      if (e != NULL) {
        *e = '\0';
        strcpy(current_filename, sp); sp = strchr(current_filename, '|'); if (sp) *sp = '\0';
        q = strrchr(current_filename, '/'); if (q) memmove(current_filename, q+1, strlen(q+1)+1);
        if (debug) fprintf(stderr, "Setting CURRENT_FILENAME#2 to '%s'\n", current_filename);
        // Reason we are doing this is that we *do* want to print the decoded binary
        // BUT we *don't* want to print the source code of the static inline code from other header files.
//      line_to_save = strdup(sp+1); // ADDED!
      }
//#else
      if (strcmp(current_filename, target_filename) == 0) {
        s = strdup(line); s = strchr(s, '|')+1; e = strchr(s, '|'); *e++ = '\0';
        current_lineno = (int)atol(s);
        if (debug) fprintf(stderr, "*** SETTING CURRENT LINENO TO %d FOR FILE %s (#%d). ******************\n", current_lineno, current_filename, current_fileno);
      } else {
        if (debug) fprintf(stderr, "Ignoring current lineno %d (from '%s') for file '%s' (which is not '%s') (#%d).\n", (int)atol(s), s, current_filename, target_filename, current_fileno);
      }
//#endif
      free(orig);
    } else {
      //fprintf(stderr, "* Bad line: %s\n", s); exit(EXIT_FAILURE);
    }

    if (line_to_save != NULL) {
      char *oldline = decode[current_lineno];
      int newlen = strlen(oldline) + strlen(line_to_save) + 2;
      char *newline = malloc(newlen);
      sprintf(newline, "%s%s\n", oldline, line_to_save);
      decode[current_lineno] = newline;
      free(oldline);
      if (debug) fprintf(stderr, "Saving: %s\n", line_to_save);
    }
    
  }
  fclose(armlistfile);
  
  sprintf(armlistname, "%s.lis", argv[1]);
  armlistfile = fopen(armlistname, "r");
  if (armlistfile == NULL) {
    fprintf(stderr, "Cannot open %s - %s\n", armlistname, strerror(errno));
    exit(EXIT_FAILURE);
  }

  int last_output_line = -1;
  while (readline(armlistfile, line)) { // readline must remove \n from end of line
    if (strlen(line) > 4 && isdigit(line[4]) && (strcmp(&line[5], " Statements compiled") != 0)) {
      line[5] = '\0';
      int source_line = (int)atol(line);
      fprintf(stdout, "%5d %s\n", source_line, &line[7]);
      if (source_line > last_output_line) {
        for (int i = last_output_line+1; i <= source_line; i++) {
          if (*decode[i] != '\0') printf("%s", decode[i]); 
        }
      }
      last_output_line = source_line;
    } else {
      fprintf(stdout, "%s\n", line);
    }
  }
  fclose(armlistfile);
  for (int i = last_output_line+1; i < SOURCELINECOUNT; i++) {
    if (*decode[i] != '\0') printf("%s", decode[i]); 
  }
  
  exit(EXIT_SUCCESS);
  return EXIT_FAILURE;
}
