#include "../../src/tomlc17.c"
#include <inttypes.h>

const char **g_argv = 0;
int g_argc = 0;

static void usage() {
  fprintf(stderr, "Usage: %s [fname]\n", g_argv[0]);
  exit(1);
}

// Print a string in json. Properly escape special chars.
static void print_string(const char *s, int len) {
  printf("{\"type\": \"string\", \"value\": \"");
  for (int i = 0; i < len; i++) {
    int ch = s[i];
    if (isprint(ch) && ch != '\"' && ch != '\\') {
      putchar(ch);
      continue;
    }
    switch (ch) {
    case '\b':
      putchar('\\');
      putchar('b');
      break; // Escape backspace
    case '\t':
      putchar('\\');
      putchar('t');
      break; // Escape tab
    case '\n':
      putchar('\\');
      putchar('n');
      break; // Escape newline
    case '\f':
      putchar('\\');
      putchar('f');
      break; // Escape formfeed
    case '\r':
      putchar('\\');
      putchar('r');
      break; // Escape carriage return
    case '"':
      putchar('\\');
      putchar('"');
      break; // Escape double quotes
    case '\\':
      putchar('\\');
      putchar('\\');
      break; // Escape backslash
    default:
      if (0 <= ch && ch < ' ') {
        printf("\\u%04x", ch);
      } else {
        putchar(ch);
      }
      break;
    }
  }
  printf("\"}");
}

// Print a key
static void print_key(const char *s, int len) {
  putchar('"');
  for (int i = 0; i < len; i++) {
    int ch = s[i];
    if (isprint(ch) && ch != '\"' && ch != '\\') {
      putchar(ch);
      continue;
    }
    switch (ch) {
    case '"':
      putchar('\\');
      putchar('"');
      break; // Escape double quotes
    case '\\':
      putchar('\\');
      putchar('\\');
      break; // Escape backslash
    case '\b':
      putchar('\\');
      putchar('b');
      break; // Escape backspace
    case '\f':
      putchar('\\');
      putchar('f');
      break; // Escape formfeed
    case '\n':
      putchar('\\');
      putchar('n');
      break; // Escape newline
    case '\r':
      putchar('\\');
      putchar('r');
      break; // Escape carriage return
    case '\t':
      putchar('\\');
      putchar('t');
      break; // Escape tab
    default:
      if (0 <= ch && ch < ' ') {
        printf("\\u%04x", ch);
      } else {
        putchar(ch);
      }
      break;
    }
  }
  putchar('"');
}

// Print a DATE datum
static void print_date(toml_datum_t datum) {
  printf("{\"type\": \"date-local\", \"value\": \"%04d-%02d-%02d\"}",
         datum.u.ts.year, datum.u.ts.month, datum.u.ts.day);
}

// Print a TIME datum
static void print_time(toml_datum_t datum) {
  char fracstr[20];
  fracstr[0] = fracstr[1] = '\0';
  if (datum.u.ts.usec) {
    double f = datum.u.ts.usec / 1000000.0;
    snprintf(fracstr, sizeof(fracstr), "%.6f", f);
    fracstr[5] = '\0'; // millisec precision
  }
  printf("{\"type\": \"time-local\", \"value\": \"%02d:%02d:%02d%s\"}",
         datum.u.ts.hour, datum.u.ts.minute, datum.u.ts.second, fracstr + 1);
}

// Print a DATETIME datum
static void print_datetime(toml_datum_t datum) {
  char fracstr[20];
  fracstr[0] = fracstr[1] = '\0';
  if (datum.u.ts.usec) {
    double f = datum.u.ts.usec / 1000000.0;
    snprintf(fracstr, sizeof(fracstr), "%.6f", f);
    fracstr[5] = '\0'; // millisec precision
  }
  printf("{\"type\": \"datetime-local\", \"value\": \"%04d-%02d-%02d "
         "%02d:%02d:%02d%s\"}",
         datum.u.ts.year, datum.u.ts.month, datum.u.ts.day, datum.u.ts.hour,
         datum.u.ts.minute, datum.u.ts.second, fracstr + 1);
}

// Print a DATETIMETZ datum
static void print_datetimetz(toml_datum_t datum) {
  char fracstr[20];
  char tzstr[20];
  fracstr[0] = fracstr[1] = tzstr[0] = '\0';
  if (datum.u.ts.usec) {
    double f = datum.u.ts.usec / 1000000.0;
    snprintf(fracstr, sizeof(fracstr), "%.6f", f);
    fracstr[5] = '\0'; // millisec precision
  }
  int tz = datum.u.ts.tz;
  char sign = tz < 0 ? '-' : '+';
  tz = tz < 0 ? -tz : tz;
  snprintf(tzstr, sizeof(tzstr), "%c%02d:%02d", sign, tz / 60, tz % 60);

  printf("{\"type\": \"datetime\", \"value\": \"%04d-%02d-%02d "
         "%02d:%02d:%02d%s%s\"}",
         datum.u.ts.year, datum.u.ts.month, datum.u.ts.day, datum.u.ts.hour,
         datum.u.ts.minute, datum.u.ts.second, fracstr + 1, tzstr);
}

// Print indent spaces
int indent_level = 0;
static int indent() {
  for (int i = 0; i < indent_level * 2; i++) {
    putchar(' ');
  }
  return 0;
}

static const char *fmt_double(double f, char *buf, int buflen) {
  snprintf(buf, buflen, "%.16g", f);
  if (!strchr(buf, 'e') && !strchr(buf, '.') && !strchr(buf, 'n')) {
    // add a .0 if the number is an int, and not inf or nan.
    snprintf(buf, buflen, "%.16g%s", f, ".0");
  }
  if (isnan(f) && signbit(f)) {
    snprintf(buf, buflen, "%s", "-nan");
  }
  return buf;
}

// Print datum tree recursively
static void print_datum(toml_datum_t datum) {
  char buf[50];

  switch (datum.type) {
  case TOML_STRING:
    print_string(datum.u.str.ptr, datum.u.str.len);
    break;
  case TOML_INT64:
    printf("{\"type\": \"integer\", \"value\": \"%" PRId64 "\"}",
           datum.u.int64);
    break;
  case TOML_FP64:
    printf("{\"type\": \"float\", \"value\": \"%s\"}",
           fmt_double(datum.u.fp64, buf, sizeof(buf)));
    break;
  case TOML_BOOLEAN:
    printf("{\"type\": \"bool\", \"value\": \"%s\"}",
           datum.u.boolean ? "true" : "false");
    break;
  case TOML_DATE:
    print_date(datum);
    break;
  case TOML_TIME:
    print_time(datum);
    break;
  case TOML_DATETIME:
    print_datetime(datum);
    break;
  case TOML_DATETIMETZ:
    print_datetimetz(datum);
    break;
  case TOML_ARRAY:
    printf("[");
    for (int i = 0; i < datum.u.arr.size; i++) {
      printf("%s", i ? ", " : "");
      print_datum(datum.u.arr.elem[i]);
    }
    printf("]");
    break;
  case TOML_TABLE:
    printf("{\n");
    indent_level++;
    for (int i = 0; i < datum.u.tab.size; i++) {
      printf("%s", i ? ",\n" : "");
      indent();
      print_key(datum.u.tab.key[i], datum.u.tab.len[i]);
      putchar(':');
      putchar(' ');
      print_datum(datum.u.tab.value[i]);
    }
    printf("\n");
    indent_level--;
    indent(), printf("}");
    break;
  default:
    fprintf(stderr, "ERROR: unimplemented datum type %d\n", datum.type);
    abort();
  }
}

int main(int argc, const char *argv[]) {
  g_argc = argc;
  g_argv = argv;
  if (argc > 2) {
    usage();
  }

  toml_option_t opt = toml_default_option();
  opt.check_utf8 = 1;
  toml_set_option(opt);

  toml_result_t result;
  if (argc == 2) {
    result = toml_parse_file_ex(argv[1]);
  } else {
    result = toml_parse_file(stdin);
  }

  if (!result.ok) {
    printf("%s\n", result.errmsg);
    toml_free(result);
    exit(1);
  }

  print_datum(result.toptab);
  printf("\n");

  toml_free(result);
  return 0;
}
