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

#include "defines.h"
#include "dbgmodel.h"
#include "dbgexp.h"
#include "write.h"
#include "main.h"

static int _is_label_exportable(struct debug_label *label) {

  if (label->status != LABEL_STATUS_LABEL && label->status != LABEL_STATUS_SYMBOL && label->status != LABEL_STATUS_BREAKPOINT)
    return NO;
  if (label->is_anonymous == YES)
    return NO;

  return YES;
}


static int _str_eq(const char *a, const char *b) {

  while (*a != 0 && *b != 0) {
    if (tolower((unsigned char)*a) != tolower((unsigned char)*b))
      return NO;
    a++;
    b++;
  }

  if (*a == 0 && *b == 0)
    return YES;

  return NO;
}


static int _append_format_flag(const char *format, int *flags) {

  if (_str_eq(format, "vice") == YES)
    *flags |= DEBUG_EXPORT_VICE;
  else if (_str_eq(format, "rgbds") == YES || _str_eq(format, "bgb") == YES || _str_eq(format, "sameboy") == YES)
    *flags |= DEBUG_EXPORT_RGBDS;
  else if (_str_eq(format, "mesen") == YES)
    *flags |= DEBUG_EXPORT_MESEN;
  else if (_str_eq(format, "emulicious") == YES)
    *flags |= DEBUG_EXPORT_EMULICIOUS;
  else if (_str_eq(format, "cspect") == YES || _str_eq(format, "sld") == YES)
    *flags |= DEBUG_EXPORT_CSPECT;
  else if (_str_eq(format, "nocash") == YES || _str_eq(format, "no$gmb") == YES || _str_eq(format, "no$sns") == YES)
    *flags |= DEBUG_EXPORT_NOCASH;
  else if (_str_eq(format, "mame") == YES || _str_eq(format, "mamecmd") == YES || _str_eq(format, "mame-cmd") == YES)
    *flags |= DEBUG_EXPORT_MAME;
  else if (_str_eq(format, "json") == YES)
    *flags |= DEBUG_EXPORT_JSON;
  else {
    print_text(NO, "DEBUG_EXPORT: Unknown debug export format \"%s\".\n", format);
    return FAILED;
  }

  return SUCCEEDED;
}


int debug_export_parse_flags(const char *formats, int *flags) {

  char tmp[128];
  int i, j;

  if (formats == NULL || flags == NULL)
    return FAILED;

  i = 0;
  while (formats[i] != 0) {
    while (formats[i] == ',' || isspace((unsigned char)formats[i]) != 0)
      i++;
    if (formats[i] == 0)
      break;
    j = 0;
    while (formats[i] != 0 && formats[i] != ',' && isspace((unsigned char)formats[i]) == 0) {
      if (j >= (int)sizeof(tmp)-1) {
        print_text(NO, "DEBUG_EXPORT: Format name is too long.\n");
        return FAILED;
      }
      tmp[j++] = formats[i++];
    }
    tmp[j] = 0;
    if (_append_format_flag(tmp, flags) == FAILED)
      return FAILED;
  }

  return SUCCEEDED;
}


static int _make_output_name(char *dst, int dst_size, char *outname, const char *extension) {

  int len, i;

  if (dst == NULL || outname == NULL || extension == NULL)
    return FAILED;

  len = (int)strlen(outname);
  if (len + (int)strlen(extension) + 1 >= dst_size) {
    print_text(NO, "DEBUG_EXPORT: File name too long.\n");
    return FAILED;
  }

  strcpy(dst, outname);
  i = len - 1;
  while (i >= 0 && dst[i] != '.' && dst[i] != '/' && dst[i] != '\\')
    i--;
  if (i < 0 || dst[i] == '/' || dst[i] == '\\')
    i = len;
  strcpy(&dst[i], extension);

  return SUCCEEDED;
}


static FILE *_open_output(char *outname, const char *extension) {

  char name[MAX_NAME_LENGTH + 1];
  FILE *f;

  if (_make_output_name(name, sizeof(name), outname, extension) == FAILED)
    return NULL;

  f = fopen(name, "wb");
  if (f == NULL)
    print_text(NO, "DEBUG_EXPORT: Error opening file \"%s\" for writing.\n", name);

  return f;
}


static int _write_vice(struct debug_model *model, char *outname) {

  FILE *f;
  int i;

  f = _open_output(outname, ".vic");
  if (f == NULL)
    return FAILED;

  fprintf(f, "; VICE labels generated by WLALINK\n");
  for (i = 0; i < model->label_count; i++) {
    struct debug_label *label;

    label = &model->labels[i];
    if (_is_label_exportable(label) == YES)
      fprintf(f, "al %04x .%s\n", label->address & 0xffff, label->name);
  }

  fclose(f);
  return SUCCEEDED;
}


static int _write_rgbds_like(struct debug_model *model, char *outname, const char *extension, const char *title) {

  FILE *f;
  int i;

  f = _open_output(outname, extension);
  if (f == NULL)
    return FAILED;

  fprintf(f, "; %s symbols generated by WLALINK\n", title);
  for (i = 0; i < model->label_count; i++) {
    struct debug_label *label;

    label = &model->labels[i];
    if (_is_label_exportable(label) == YES)
      fprintf(f, "%02x:%04x %s\n", (label->bank + label->base) & 0xff, label->address & 0xffff, label->name);
  }

  fclose(f);
  return SUCCEEDED;
}


static int _write_mesen(struct debug_model *model, char *outname) {

  FILE *f;
  int i;

  f = _open_output(outname, ".msn");
  if (f == NULL)
    return FAILED;

  fprintf(f, "# Mesen labels generated by WLALINK\n");
  for (i = 0; i < model->label_count; i++) {
    struct debug_label *label;

    label = &model->labels[i];
    if (_is_label_exportable(label) == YES)
      fprintf(f, "$%04x#%s#\n", label->address & 0xffff, label->name);
  }

  fclose(f);
  return SUCCEEDED;
}


static int _write_cspect(struct debug_model *model, char *outname) {

  FILE *f;
  int i;

  f = _open_output(outname, ".sld");
  if (f == NULL)
    return FAILED;

  fprintf(f, "; CSpect/SjASMPlus-style labels generated by WLALINK\n");
  for (i = 0; i < model->label_count; i++) {
    struct debug_label *label;

    label = &model->labels[i];
    if (_is_label_exportable(label) == YES)
      fprintf(f, "%s = $%04x\n", label->name, label->address & 0xffff);
  }

  fclose(f);
  return SUCCEEDED;
}


static int _is_mame_symbol_name(const char *name) {

  int i;

  if (name == NULL || name[0] == 0)
    return NO;
  if (isalpha((unsigned char)name[0]) == 0 && name[0] != '_')
    return NO;

  for (i = 1; name[i] != 0; i++) {
    if (isalnum((unsigned char)name[i]) == 0 && name[i] != '_')
      return NO;
  }

  return YES;
}


static int _write_mame(struct debug_model *model, char *outname) {

  FILE *f;
  int i;

  f = _open_output(outname, ".mam");
  if (f == NULL)
    return FAILED;

  for (i = 0; i < model->label_count; i++) {
    struct debug_label *label;

    label = &model->labels[i];
    if (_is_label_exportable(label) == YES && _is_mame_symbol_name(label->name) == YES)
      fprintf(f, "do %s = 0x%x\n", label->name, label->address);
  }

  fclose(f);
  return SUCCEEDED;
}


static int _write_json_string(FILE *f, const char *s) {

  fputc('"', f);
  if (s != NULL) {
    while (*s != 0) {
      unsigned char c;

      c = (unsigned char)*s;
      if (c == '"' || c == '\\')
        fprintf(f, "\\%c", c);
      else if (c == '\n')
        fprintf(f, "\\n");
      else if (c == '\r')
        fprintf(f, "\\r");
      else if (c == '\t')
        fprintf(f, "\\t");
      else if (c < 32)
        fprintf(f, "\\u%04x", c);
      else
        fputc(c, f);
      s++;
    }
  }
  fputc('"', f);

  return SUCCEEDED;
}


static int _write_json(struct debug_model *model, char *outname) {

  FILE *f;
  int i;

  f = _open_output(outname, ".jsn");
  if (f == NULL)
    return FAILED;

  fprintf(f, "{\n  \"format\": \"wla-dx-debug\",\n  \"version\": 1,\n");
  fprintf(f, "  \"output\": ");
  _write_json_string(f, model->output_name);
  fprintf(f, ",\n  \"romSize\": %d,\n  \"romBanks\": %d,\n", model->rom_size, model->rom_banks);

  fprintf(f, "  \"sources\": [\n");
  for (i = 0; i < model->source_count; i++) {
    struct debug_source *source;

    source = &model->sources[i];
    fprintf(f, "    {\"fileId\": %d, \"sourceId\": %d, \"checksum\": %lu, \"name\": ", source->file_id, source->source_id, source->checksum & 0xffffffffUL);
    _write_json_string(f, source->name);
    fprintf(f, "}%s\n", (i + 1 < model->source_count) ? "," : "");
  }
  fprintf(f, "  ],\n");

  fprintf(f, "  \"sections\": [\n");
  for (i = 0; i < model->section_count; i++) {
    struct debug_section *section;

    section = &model->sections[i];
    fprintf(f, "    {\"id\": %d, \"name\": ", section->id);
    _write_json_string(f, section->name);
    fprintf(f, ", \"status\": ");
    _write_json_string(f, debug_model_section_status_name(section->status));
    fprintf(f, ", \"bank\": %d, \"base\": %d, \"slot\": %d, \"address\": %d, \"pcAddress\": %d, \"outputAddress\": %d, \"size\": %d, \"isRam\": %s}%s\n",
            section->bank, section->base, section->slot, section->address, section->pc_address, section->output_address, section->size, section->is_ram == YES ? "true" : "false", (i + 1 < model->section_count) ? "," : "");
  }
  fprintf(f, "  ],\n");

  fprintf(f, "  \"labels\": [\n");
  for (i = 0; i < model->label_count; i++) {
    struct debug_label *label;

    label = &model->labels[i];
    fprintf(f, "    {\"name\": ");
    _write_json_string(f, label->name);
    fprintf(f, ", \"kind\": ");
    _write_json_string(f, debug_model_label_status_name(label->status));
    fprintf(f, ", \"fileId\": %d, \"sourceId\": %d, \"line\": %d, \"section\": %d, \"bank\": %d, \"base\": %d, \"slot\": %d, \"address\": %d, \"romAddress\": %d, \"anonymous\": %s}%s\n",
            label->file_id, label->file_id_source, label->linenumber, label->section, label->bank, label->base, label->slot, label->address, label->rom_address, label->is_anonymous == YES ? "true" : "false", (i + 1 < model->label_count) ? "," : "");
  }
  fprintf(f, "  ],\n");

  fprintf(f, "  \"mappings\": [\n");
  for (i = 0; i < model->mapping_count; i++) {
    struct debug_mapping *mapping;

    mapping = &model->mappings[i];
    fprintf(f, "    {\"outputAddress\": %d, \"pcAddress\": %d, \"length\": %d, \"section\": %d, \"fileId\": %d, \"sourceId\": %d, \"line\": %d, \"realLine\": %d, \"bank\": %d, \"base\": %d, \"slot\": %d, \"source\": ",
            mapping->output_address, mapping->pc_address, mapping->length, mapping->section, mapping->file_id, mapping->source_id, mapping->linenumber, mapping->real_linenumber, mapping->bank, mapping->base, mapping->slot);
    _write_json_string(f, mapping->source_name);
    fprintf(f, "}%s\n", (i + 1 < model->mapping_count) ? "," : "");
  }
  fprintf(f, "  ]\n}\n");

  fclose(f);
  return SUCCEEDED;
}


int debug_export_all(struct debug_model *model, int flags, char *outname) {

  if (model == NULL || flags == 0)
    return SUCCEEDED;

  if ((flags & DEBUG_EXPORT_VICE) != 0 && _write_vice(model, outname) == FAILED)
    return FAILED;
  if ((flags & DEBUG_EXPORT_RGBDS) != 0 && _write_rgbds_like(model, outname, ".rgb", "RGBDS/BGB/SameBoy") == FAILED)
    return FAILED;
  if ((flags & DEBUG_EXPORT_MESEN) != 0 && _write_mesen(model, outname) == FAILED)
    return FAILED;
  if ((flags & DEBUG_EXPORT_EMULICIOUS) != 0 && _write_rgbds_like(model, outname, ".emu", "Emulicious") == FAILED)
    return FAILED;
  if ((flags & DEBUG_EXPORT_CSPECT) != 0 && _write_cspect(model, outname) == FAILED)
    return FAILED;
  if ((flags & DEBUG_EXPORT_NOCASH) != 0 && _write_rgbds_like(model, outname, ".noi", "no$") == FAILED)
    return FAILED;
  if ((flags & DEBUG_EXPORT_MAME) != 0 && _write_mame(model, outname) == FAILED)
    return FAILED;
  if ((flags & DEBUG_EXPORT_JSON) != 0 && _write_json(model, outname) == FAILED)
    return FAILED;

  return SUCCEEDED;
}
