/* trace.c --- tracing output for the RL78 simulator.

   Copyright (C) 2005-2015 Free Software Foundation, Inc.
   Contributed by Red Hat, Inc.

   This file is part of the GNU simulators.

   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 3 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, see <http://www.gnu.org/licenses/>.
*/


#include "config.h"
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>

#include "libiberty.h"
#include "bfd.h"
#include "dis-asm.h"

#include "cpu.h"
#include "mem.h"
#include "load.h"

static disassembler_ftype rl78_disasm_fn = NULL;

static int
sim_dis_read (bfd_vma memaddr, bfd_byte * ptr, unsigned int length,
	      struct disassemble_info *info)
{
  mem_get_blk (memaddr, ptr, length);
  return 0;
}

/* Filter out (in place) symbols that are useless for disassembly.
   COUNT is the number of elements in SYMBOLS.
   Return the number of useful symbols. */

static long
remove_useless_symbols (asymbol ** symbols, long count)
{
  register asymbol **in_ptr = symbols, **out_ptr = symbols;

  while (-- count >= 0)
    {
      asymbol *sym = *in_ptr ++;

      if (strstr (sym->name, "gcc2_compiled"))
	continue;
      if (sym->name == NULL || sym->name[0] == '\0')
	continue;
      if (sym->flags & (BSF_DEBUGGING))
	continue;
      if (bfd_is_und_section (sym->section)
	  || bfd_is_com_section (sym->section))
	continue;

      *out_ptr++ = sym;
    }
  return out_ptr - symbols;
}

static int
compare_symbols (const PTR ap, const PTR bp)
{
  const asymbol *a = *(const asymbol **) ap;
  const asymbol *b = *(const asymbol **) bp;

  if (bfd_asymbol_value (a) > bfd_asymbol_value (b))
    return 1;
  else if (bfd_asymbol_value (a) < bfd_asymbol_value (b))
    return -1;
  return 0;
}

static char opbuf[1000];

static int
op_printf (char *buf, char *fmt, ...)
{
  int ret;
  va_list ap;

  va_start (ap, fmt);
  ret = vsprintf (opbuf + strlen (opbuf), fmt, ap);
  va_end (ap);
  return ret;
}

static bfd *       current_bfd = NULL;
static asymbol **  symtab = NULL;
static int         symcount = 0;
static asection *  code_section = NULL;
static bfd_vma     code_base = 0;
static struct disassemble_info info;

void
sim_disasm_init (bfd *prog)
{
  current_bfd = prog;
  rl78_disasm_fn = NULL;
}

typedef struct Files
{
  struct Files *next;
  char *filename;
  int nlines;
  char **lines;
  char *data;
} Files;
Files *files = 0;

static char *
load_file_and_line (const char *filename, int lineno)
{
  Files *f;
  for (f = files; f; f = f->next)
    if (strcmp (f->filename, filename) == 0)
      break;
  if (!f)
    {
      int i;
      struct stat s;
      const char *found_filename, *slash;

      found_filename = filename;
      while (1)
	{
	  if (stat (found_filename, &s) == 0)
	    break;
	  slash = strchr (found_filename, '/');
	  if (!slash)
	    return "";
	  found_filename = slash + 1;
	}

      f = (Files *) xmalloc (sizeof (Files));
      f->next = files;
      files = f;
      f->filename = xstrdup (filename);
      f->data = (char *) xmalloc (s.st_size + 2);
      FILE *file = fopen (found_filename, "rb");
      fread (f->data, 1, s.st_size, file);
      f->data[s.st_size] = 0;
      fclose (file);

      f->nlines = 1;
      for (i = 0; i < s.st_size; i ++)
	if (f->data[i] == '\n')
	  f->nlines ++;
      f->lines = (char **) xmalloc (f->nlines * sizeof (char *));
      f->lines[0] = f->data;
      f->nlines = 1;
      for (i = 0; i < s.st_size; i ++)
	if (f->data[i] == '\n')
	  {
	    f->lines[f->nlines] = f->data + i + 1;
	    while (*f->lines[f->nlines] == ' '
		   || *f->lines[f->nlines] == '\t')
	      f->lines[f->nlines] ++;
	    f->nlines ++;
	    f->data[i] = 0;
	  }
    }
  if (lineno < 1 || lineno > f->nlines)
    return "";
  return f->lines[lineno - 1];
}

int
sim_get_current_source_location (const char **  pfilename,
				 const char **  pfunctionname,
				 unsigned int * plineno)
{
  static int   initted = 0;
  int          mypc = pc;

  if (current_bfd == NULL)
    return 0;

  if (!initted)
    {
      int storage;
      asection * s;

      initted = 1;
      memset (& info, 0, sizeof (info));
      INIT_DISASSEMBLE_INFO (info, stdout, op_printf);
      info.read_memory_func = sim_dis_read;
      info.arch = bfd_get_arch (current_bfd);
      info.mach = bfd_get_mach (current_bfd);
      if (info.mach == 0)
	info.arch = bfd_arch_rl78;

      disassemble_init_for_target (& info);

      storage = bfd_get_symtab_upper_bound (current_bfd);
      if (storage > 0)
	{
	  symtab = (asymbol **) xmalloc (storage);
	  symcount = bfd_canonicalize_symtab (current_bfd, symtab);
	  symcount = remove_useless_symbols (symtab, symcount);
	  qsort (symtab, symcount, sizeof (asymbol *), compare_symbols);
	}

      for (s = current_bfd->sections; s; s = s->next)
	{
	  if (s->flags & SEC_CODE || code_section == 0)
	    {
	      code_section = s;
	      code_base = bfd_section_lma (current_bfd, s);
	      break;
	    }
	}
    }

  *pfilename = *pfunctionname = NULL;
  *plineno = 0;

  bfd_find_nearest_line
    (current_bfd, code_section, symtab, mypc - code_base,
     pfilename, pfunctionname, plineno);

  return 1;
}

void
sim_disasm_one (void)
{
  static int           last_sym = -1;
  static const char *  prev_filename = "";
  static int           prev_lineno = 0;
  const char *  filename;
  const char *  functionname;
  unsigned int  lineno;
  int           sym, bestaddr;
  int           min, max, i;
  int           save_trace = trace;
  int           mypc = pc;

  if (! sim_get_current_source_location (& filename, & functionname, & lineno))
    return;

  trace = 0;

  if (!rl78_disasm_fn)
    {
      if (rl78_g10_mode)
	rl78_disasm_fn = print_insn_rl78_g10;
      else if (g14_multiply)
	rl78_disasm_fn = print_insn_rl78_g14;
      else if (g13_multiply)
	rl78_disasm_fn = print_insn_rl78_g13;
      else
	rl78_disasm_fn = print_insn_rl78;
    }

  if (filename && functionname && lineno)
    {
      if (lineno != prev_lineno || strcmp (prev_filename, filename))
	{
	  char *       the_line = load_file_and_line (filename, lineno);
	  const char * slash = strrchr (filename, '/');

	  if (!slash)
	    slash = filename;
	  else
	    slash ++;
	  printf
	    ("========================================"
	     "=====================================\n");
	  printf ("\033[37;41m %s:%d: \033[33;40m %s\033[K\033[0m\n",
		  slash, lineno, the_line);
	}
      prev_lineno = lineno;
      prev_filename = filename;
    }

  min = -1;
  max = symcount;
  while (min < max - 1)
    {
      bfd_vma sa;

      sym = (min + max) / 2;
      sa = bfd_asymbol_value (symtab[sym]);
      /*printf ("checking %4d %08x %s\n",
	sym, sa, bfd_asymbol_name (symtab[sym])); */
      if (sa > mypc)
	max = sym;
      else if (sa < mypc)
	min = sym;
      else
	{
	  min = sym;
	  break;
	}
    }

  if (min != -1 && min != last_sym)
    {
      bestaddr = bfd_asymbol_value (symtab[min]);
      printf ("\033[43;30m%s", bfd_asymbol_name (symtab[min]));
      if (bestaddr != mypc)
	printf ("+%d", mypc - bestaddr);
      printf (":\t\t\t\033[0m\n");
      last_sym = min;
#if 0
      if (trace == 1)
	if (strcmp (bfd_asymbol_name (symtab[min]), "abort") == 0
	    || strcmp (bfd_asymbol_name (symtab[min]), "exit") == 0)
	  trace = 0;
#endif
    }

#define TCR0	0xf0180

  opbuf[0] = 0;
#ifdef CYCLE_ACCURATE
  printf ("\033[33m %04u %06x: ", (int)(regs.cycle_count % 10000), mypc);
#else
  printf ("\033[33m %08llx %06x: ", total_clocks, mypc);
#endif

  max = rl78_disasm_fn (mypc, & info);

  for (i = 0; i < max; i ++)
    printf ("%02x", mem_get_qi (mypc + i));

  do
    {
      printf ("  ");
      i ++;
    }
  while (i < 6);

  printf ("%-16s  ", opbuf);

  printf ("\033[0m\n");
  trace = save_trace;
}
