/* listing.h - listing file */
/* (c) in 2020-2025 by Volker Barthelmann and Frank Wille */

#include "vasm.h"

#define MADDR(x) ((unsigned long long)((x)&taddrmask))
#define USEDMASK (USED|EXPORT|COMMON|WEAK|LOCAL|ABSLABEL)

typedef struct {
  const char *fmtname;
  void (*fmtfunction)(char *,section *);
} list_formats;

int produce_listing,listena,listnosyms;
int listformfeed=1,listlinesperpage=40;
listing *first_listing,*last_listing,*cur_listing;

static listing *prev_listing;
static int listbpl,listnoinc,listformat,listtitlecnt,listall,listlabelsonly;
static char **listtitles;
static int *listtitlelines;


int init_listing(void)
{
  listbpl = 64 / BITSPERBYTE;
  return 1;
}


int listing_option(char *arg)
{
  if (!strcmp("all",arg)) {
    listall = 1;
    return 1;
  }
  if (!strncmp("bpl=",arg,4)) {
    int val;

    sscanf(&arg[4],"%i",&val);
    if (val<1 || val>512/BITSPERBYTE)
      general_error(78,"-Lbpl");
    else
      listbpl = val;
    return 1;
  }
  if (!strncmp("fmt=",arg,4)) {
    set_listformat(&arg[4]);
    return 1;
  }
  if (!strcmp("lo",arg)) {
    listlabelsonly=1;
    return 1;
  }
  if (!strcmp("nf",arg)) {
    listformfeed=0;
    return 1;
  }
  if (!strcmp("ni",arg)) {
    listnoinc=1;
    return 1;
  }
  if (!strcmp("ns",arg)) {
    listnosyms=1;
    return 1;
  }
  if (arg[0] == 'l') {
    int i=arg[1]=='='?2:1;
    sscanf(&arg[i],"%i",&listlinesperpage);
    return 1;
  }
  return 0;
}

listing *new_listing(source *src,int line)
{
  listing *new = mymalloc(sizeof(*new));

  new->next = NULL;
  new->line = line;
  new->error = 0;
  new->atom = 0;
  new->sec = 0;
  new->pc = 0;
  new->src = src;

  if (first_listing) {
    last_listing->next = new;
    prev_listing = last_listing;
    last_listing = new;
  }
  else
    first_listing = last_listing = new;
  cur_listing = new;
  return new;
}

/* used for example to suppress output of NOLIST directives */
void del_last_listing(void)
{
  if (listena && prev_listing!=NULL) {
    myfree(last_listing);
    last_listing = prev_listing;
    last_listing->next = prev_listing = NULL;
  }
}

void set_listing(int on)
{
  listena = on && produce_listing;
}

void set_list_title(char *p,int len)
{
  listtitlecnt++;
  listtitles=myrealloc(listtitles,listtitlecnt*sizeof(*listtitles));
  listtitles[listtitlecnt-1]=mymalloc(len+1);
  strncpy(listtitles[listtitlecnt-1],p,len);
  listtitles[listtitlecnt-1][len]=0;
  listtitlelines=myrealloc(listtitlelines,listtitlecnt*sizeof(*listtitlelines));
  listtitlelines[listtitlecnt-1]=cur_src->line;
}

static size_t get_symbols(symbol **symlist,uint32_t flags)
{
  size_t cnt;
  symbol *sym;

  for (cnt=0,sym=first_symbol; sym; sym=sym->next) {
    if ((sym->flags & VASMINTERN) || *sym->name==' ')
      continue;
    if (listall || sym->type!=EXPRESSION || (sym->flags & flags)) {
      cnt++;
      if (symlist != NULL)
        *symlist++ = sym;
    }
  }
  return cnt;
}

static int namecmp(const void *s1,const void *s2)
{
  return nocase ? cistrcmp((*(symbol **)s1)->name,(*(symbol **)s2)->name) :
                  strcmp((*(symbol **)s1)->name,(*(symbol **)s2)->name);
}

static int valcmp(const void *s1,const void *s2)
{
  utaddr a1 = (*(symbol **)s1)->pc;
  utaddr a2 = (*(symbol **)s2)->pc;

  if (a1 == a2)
    return 0;
  return a1 > a2 ? 1 : -1;
}

#if VASM_CPU_OIL
static void print_list_header(FILE *f,int cnt)
{
  if(cnt%listlinesperpage==0){
    if(cnt!=0&&listformfeed)
      fprintf(f,"\f");
    if(listtitlecnt>0){
      int i,t;
      for(i=0,t=-1;i<listtitlecnt;i++){
        if(listtitlelines[i]<=cnt+listlinesperpage)
          t=i;
      }
      if(t>=0){
        int sp=(120-strlen(listtitles[t]))/2;
        while(--sp)
          fprintf(f," ");
        fprintf(f,"%s\n",listtitles[t]);
      }
      cnt++;
    }
    fprintf(f,"Err  Line Loc.  S Object1  Object2  M Source\n");
  }
}

static void write_listing_old(char *listname,section *first_section)
{
  FILE *f;
  int i,cnt=0;
  section *secp;
  listing *p;
  atom *a;
  symbol *sym;
  taddr pc;
  char rel;

  if(!(f=fopen(listname,"w"))){
    general_error(13,listname);
    return;
  }
  for(p=first_listing;p;p=p->next){
    if(!p->src||p->src->id!=0)
      continue;
    print_list_header(f,cnt++);
    if(p->error!=0)
      fprintf(f,"%04d ",p->error);
    else
      fprintf(f,"     ");
    fprintf(f,"%4d ",p->line);
    a=p->atom;
    while(a&&a->type!=DATA&&a->next&&a->next->line==a->line&&a->next->src==a->src)
      a=a->next;
    if(a&&a->type==DATA){
      int size=a->content.db->size;
      unsigned char *dp=a->content.db->data;
      pc=p->pc;
      fprintf(f,"%05lX %d ",(unsigned long)pc,(int)(p->sec?p->sec->idx:0));
      for(i=0;i<8;i++){
        if(i==4)
          fprintf(f," ");
        if(i<size){
          fprintf(f,"%02X",(unsigned char)*dp++);
          pc++;
        }else
          fprintf(f,"  ");
        /* append following atoms with align 1 directly */
        if(i==size-1&&i<7&&a->next&&a->next->align<=a->align&&a->next->type==DATA&&a->next->line==a->line&&a->next->src==a->src){
          a=a->next;
          size+=a->content.db->size;
          dp=a->content.db->data;
        }
      }
      fprintf(f," ");
      if(a->content.db->relocs){
        symbol *s=((nreloc *)(a->content.db->relocs->reloc))->sym;
        if(s->type==IMPORT)
          rel='X';
        else
          rel='0'+p->sec->idx;
      }else
        rel='A';
      fprintf(f,"%c ",rel);
    }else
      fprintf(f,"                           ");

    fprintf(f," %-.77s",p->txt);

    /* bei laengeren Daten den Rest ueberspringen */
    /* Block entfernen, wenn alles ausgegeben werden soll */
    if(a&&a->type==DATA&&i<a->content.db->size){
      pc+=a->content.db->size-i;
      i=a->content.db->size;
    }

    /* restliche DATA-Zeilen, wenn noetig */
    while(a){
      if(a->type==DATA){
        int size=a->content.db->size;
        unsigned char *dp=a->content.db->data+i;

        if(i<size){
          for(;i<size;i++){
            if((i&7)==0){
              fprintf(f,"\n");
              print_list_header(f,cnt++);
              fprintf(f,"          %05lX %d ",(unsigned long)pc,(int)(p->sec?p->sec->idx:0));
            }else if((i&3)==0)
              fprintf(f," ");
            fprintf(f,"%02X",(unsigned char)*dp++);
            pc++;
            /* append following atoms with align 1 directly */
            if(i==size-1&&a->next&&a->next->align<=a->align&&a->next->type==DATA&&a->next->line==a->line&&a->next->src==a->src){
              a=a->next;
              size+=a->content.db->size;
              dp=a->content.db->data;
            }
          }
          i=8-(i&7);
          if(i>=4)
            fprintf(f," ");
          while(i--){
            fprintf(f,"  ");
          }
          fprintf(f," %c",rel);
        }
        i=0;
      }
      if(a->next&&a->next->line==a->line&&a->next->src==a->src){
        a=a->next;
        pc=pcalign(a,pc);
        if(a->type==DATA&&a->content.db->relocs){
          symbol *s=((nreloc *)(a->content.db->relocs->reloc))->sym;
          if(s->type==IMPORT)
            rel='X';
          else
            rel='0'+p->sec->idx;
        }else
          rel='A';
      }else
        a=0;
    }
    fprintf(f,"\n");
  }
  fprintf(f,"\n\nSections:\n");
  for(secp=first_section;secp;secp=secp->next)
    fprintf(f,"%d  %s\n",(int)secp->idx,secp->name);
  if(!listnosyms){
    fprintf(f,"\n\nSymbols:\n");
    {
      symbol *last=0,*cur,*symo;
      for(symo=first_symbol;symo;symo=symo->next){
        cur=0;
        for(sym=first_symbol;sym;sym=sym->next){
          if(!last||cistrcmp(sym->name,last->name)>0)
            if(!cur||cistrcmp(sym->name,cur->name)<0)
              cur=sym;
        }
        if(cur){
          print_symbol(f,cur);
          fprintf(f,"\n");
          last=cur;
        }
      }
    }
  }
  if(errors==0)
    fprintf(f,"\nThere have been no errors.\n");
  else
    fprintf(f,"\nThere have been %d errors!\n",errors);
  fclose(f);
  for(p=first_listing;p;){
    listing *m=p->next;
    myfree(p);
    p=m;
  }
}
#else
static void write_listing_old(char *listname,section *first_section)
{
  int bytew = (BITSPERBYTE + 3) / 4;
  unsigned long i,maxsrc=0;
  FILE *f;
  section *secp;
  listing *p;
  atom *a;
  symbol *sym;
  taddr pc;

  if(!(f=fopen(listname,"w"))){
    general_error(13,listname);
    return;
  }
  for(p=first_listing;p;p=p->next){
    char err[6];
    if(p->error!=0)
      sprintf(err,"E%04d",p->error);
    else
      sprintf(err,"     ");
    if(p->src&&p->src->id>maxsrc)
      maxsrc=p->src->id;
    fprintf(f,"F%02d:%04d %s %s",(int)(p->src?p->src->id:0),p->line,err,p->txt);
    a=p->atom;
    pc=p->pc;
    while(a){
      if(a->type==DATA){
        unsigned long size=a->content.db->size;
        for(i=0;i<size&&i<32;i++){
          if((i&15)==0)
            fprintf(f,"\n               S%02d:%08lX: ",(int)(p->sec?p->sec->idx+1:0),(unsigned long)(pc));
          fprintf(f," %0*llX",bytew,
                  (unsigned long long)readbyte(a->content.db->data+OCTETS(i)));
          pc++;
        }
        if(a->content.db->relocs)
          fprintf(f," [R]");
      }
      if(a->next&&a->next->list==a->list){
        a=a->next;
        pc=pcalign(a,pc);
      }else
        a=0;
    }
    fprintf(f,"\n");
  }
  fprintf(f,"\n\nSections:\n");
  for(secp=first_section;secp;secp=secp->next)
    fprintf(f,"S%02d  %s\n",(int)secp->idx+1,secp->name);
  fprintf(f,"\n\nSources:\n");
  for(i=0;i<=maxsrc;i++){
    for(p=first_listing;p;p=p->next){
      if(p->src&&p->src->id==i){
        fprintf(f,"F%02lu  %s\n",i,p->src->name);
        break;
      }
    }
  }
  fprintf(f,"\n\nSymbols:\n");
  for(sym=first_symbol;sym;sym=sym->next){
    print_symbol(f,sym);
    fprintf(f,"\n");
  }
  if(errors==0)
    fprintf(f,"\nThere have been no errors.\n");
  else
    fprintf(f,"\nThere have been %d errors!\n",errors);
  fclose(f);
  for(p=first_listing;p;){
    listing *m=p->next;
    myfree(p);
    p=m;
  }
}
#endif

static void write_listing_wide(char *listname,section *first_section)
{
  int addrw = (bytespertaddr*BITSPERBYTE+3)/4;  /* width of address field */
  int bytew = (BITSPERBYTE+3)/4;
  source *lastsrc = NULL;
  FILE *f;
  section *secp;
  listing *l;
  int i;

  if (!(f = fopen(listname,"w"))) {
    general_error(13,listname);
    return;
  }

  fprintf(f,"Sections:\n");
  for (secp=first_section; secp; secp=secp->next)
    fprintf(f,"%02X: \"%s\" (%llX-%llX)\n",(unsigned)secp->idx,secp->name,
            MADDR(secp->org),MADDR(secp->pc));
  fputc('\n',f);

  for (l=first_listing; l; l=l->next) {
    atom *a = l->atom;
    taddr pc = l->pc;
    int flag = 0;
    char stype = ':';
    size_t spc;

    if (l->src) {
      if (l->src->num_params >= 0)
        stype = 'M';
      else if (!strncmp(l->src->name,"REPEAT:",7))
        stype = 'R';
      else {
        if (listnoinc && l->src->parent!=NULL)
          continue;
        if (l->src != lastsrc) {
          fprintf(f,"\nSource: \"%s\"\n",l->src->name);
          lastsrc = l->src;
        }
      }
    }
    i = 0;

    while (a) {
      size_t dlen;

      spc = 0;
      if (a->type == DATA) {
        dlen = a->content.db->size;
        for (; i<dlen; i++,pc++) {
          if (!(i % listbpl)) {
            if (i) {
              if (!flag) {
                fprintf(f,"\t%6d%c %s\n",l->line,stype,l->txt);
                flag = 1;
              }
              else
                fputc('\n',f);
            }
            fprintf(f,"%02X:%0*llX ",
                    (unsigned)(l->sec?l->sec->idx:0),
                    addrw,MADDR(pc));
          }
          fprintf(f,"%0*llX",bytew,
                  (unsigned long long)readbyte(a->content.db->data+OCTETS(i)));
        }
      }
      else if (a->type == SPACE) {
        dlen = a->content.sb->size;
        if (spc = a->content.sb->space * dlen) {
          for (; i<dlen; i++,pc++) {
            if (!(i % listbpl)) {
              if (i) {
                if (!flag) {
                  fprintf(f,"\t%6d%c %s\n",l->line,stype,l->txt);
                  flag = 1;
                }
                else
                  fputc('\n',f);
              }
              fprintf(f,"%02X:%0*llX ",
                      (unsigned)(l->sec?l->sec->idx:0),
                      addrw,MADDR(pc));
            }
            fprintf(f,"%0*llX",bytew,
                    (unsigned long long)readbyte(a->content.sb->fill+OCTETS(i)));
          }
          spc -= dlen;
        }
      }
      if (i) {
        if (!flag) {
          fprintf(f,"%*c%6d%c %s",
                  bytew*(listbpl-i)+1,'\t',l->line,stype,l->txt);
          if (spc) {
            fprintf(f,"\n%02X:%0*llX *",
                    (unsigned)(l->sec?l->sec->idx:0),
                    addrw,MADDR(pc));
            pc += spc;
          }
          flag = 1;
        }
        fputc('\n',f);
        i = 0;
      }

      if (a->next!=NULL && a->next->list==a->list) {
        a = a->next;
        pc = pcalign(a,pc);
      }
      else
        a = NULL;
    }
    if (!flag)  /* no data generated for this source line */
      fprintf(f,"%*c%6d%c %s\n",
              4+addrw+bytew*listbpl+1,'\t',l->line,stype,l->txt);
    if (l->error)
      fprintf(f,"%*c     ^-ERROR:%04d\n",4+addrw+bytew*listbpl+1,'\t',l->error);
  }

  if (!listnosyms) {
    size_t nsyms = get_symbols(NULL,USEDMASK);
    symbol *sym,**symlist;

    fprintf(f,"\n\nSymbols by name:\n");
    symlist = (symbol **)mymalloc(nsyms*sizeof(symbol *));
    get_symbols(symlist,USEDMASK);
    qsort(symlist,nsyms,sizeof(symbol *),namecmp);  /* sort by name */
    for (i=0; i<nsyms; i++) {
      sym = symlist[i];
      fprintf(f,"%-31s ",sym->name);

      /* remember symbol value in sym->pc for all cases */
      if (sym->flags & COMMON) {
        sym->pc = sym->align;
        fprintf(f,"COM %lld bytes, alignment %lld",
                (long long)get_sym_size(sym),(long long)sym->align);
      }
      else {
        char type;

        switch (sym->type) {
          case LABSYM:
            fprintf(f,"%02X:%0*llX",
                    (unsigned)(sym->sec?sym->sec->idx:0),
                    addrw,MADDR(sym->pc));
            break;
          case IMPORT:
            fprintf(f,"external");
            break;
          default:
            if (sym->flags & EQUATE)
              type = 'E';
            else if (sym->flags & REGLIST)
              type = 'R';
            else if (sym->flags & ABSLABEL)
              type = 'A';
            else
              type = 'S';
            sym->pc = get_sym_value(sym);
            fprintf(f," %c:%0*llX",type,addrw,MADDR(sym->pc));
            break;
        }
      }
      if (sym->flags & EXPORT)
        fprintf(f," EXP");
      if (sym->flags & LOCAL)
        fprintf(f," LOC");
      if (sym->flags & WEAK)
        fprintf(f," WEAK");
      fputc('\n',f);
    }

    if (listlabelsonly)
      fprintf(f,"\nLabels by address:\n");
    else
      fprintf(f,"\nSymbols by value:\n");
    qsort(symlist,nsyms,sizeof(symbol *),valcmp);  /* sort by value/address */
    for (i=0; i<nsyms; i++) {
      sym = symlist[i];
      if (sym->type==IMPORT || (sym->flags & (COMMON|REGLIST)))
        continue;
      if (listlabelsonly) {
        if (sym->type!=LABSYM &&
            (sym->type!=EXPRESSION || !(sym->flags & ABSLABEL)))
          continue;
      }
      fprintf(f,"%0*llX %s\n",addrw,MADDR(sym->pc),sym->name);
    }

    myfree(symlist);
  }
}

static const list_formats list_format_table[] = {
  { "wide", write_listing_wide },
  { "old", write_listing_old }
};

void set_listformat(const char *fmtname)
{
  int i;

  for (i=0; i<sizeof(list_format_table)/sizeof(list_format_table[0]); i++) {
    if (!cistrcmp(fmtname,list_format_table[i].fmtname)) {
      listformat = i;
      return;
    }
  }
  general_error(80,fmtname);  /* format selection ignored */
}

void write_listing(char *listname,section *first_section)
{
  list_format_table[listformat].fmtfunction(listname,first_section);
}
