/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8                                :vi│
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2022 Justine Alexandra Roberts Tunney                              │
│                                                                              │
│ Permission to use, copy, modify, and/or distribute this software for         │
│ any purpose with or without fee is hereby granted, provided that the         │
│ above copyright notice and this permission notice appear in all copies.      │
│                                                                              │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL                │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED                │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE             │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL         │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR        │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER               │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR             │
│ PERFORMANCE OF THIS SOFTWARE.                                                │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/fmt/conv.h"
#include "libc/log/check.h"
#include "libc/math.h"
#include "libc/mem/gc.h"
#include "libc/mem/mem.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/str/tab.internal.h"
#include "libc/x/xasprintf.h"

/**
 * @fileoverview Tool for generating rldecode'd character sets, e.g.
 *
 *     # generate http token table
 *     o//tool/build/xlat.com -TiC ' ()<>@,;:\"/[]?={}' -i
 */

int dig;
int xlat[256];
bool identity;
const char *symbol;

static int Bing(int c) {
  if (!c) return L'∅';
  if (c == ' ') return L'␠';
  if (c == '$') return L'§';
  if (c == '\\') return L'⭝';
  return kCp437[c & 255];
}

static void Fill(int f(int)) {
  int i;
  for (i = 0; i < 256; ++i) {
    if (f(i)) {
      xlat[i] = identity ? i : 1;
    }
  }
}

static void Invert(void) {
  int i;
  for (i = 0; i < 256; ++i) {
    xlat[i] = !xlat[i];
  }
}

static void Negate(void) {
  int i;
  for (i = 0; i < 256; ++i) {
    xlat[i] = ~xlat[i] & 255;
  }
}

static void Negative(void) {
  int i;
  for (i = 0; i < 256; ++i) {
    xlat[i] = -xlat[i] & 255;
  }
}

static bool ArgNeedsShellQuotes(const char *s) {
  if (*s) {
    for (;;) {
      switch (*s++ & 255) {
        case 0:
          return false;
        case '-':
        case '.':
        case '/':
        case '_':
        case '=':
        case ':':
        case '0' ... '9':
        case 'A' ... 'Z':
        case 'a' ... 'z':
          break;
        default:
          return true;
      }
    }
  } else {
    return true;
  }
}

static char *AddShellQuotes(const char *s) {
  char *p, *q;
  size_t i, j, n;
  n = strlen(s);
  p = malloc(1 + n * 5 + 1 + 1);
  j = 0;
  p[j++] = '\'';
  for (i = 0; i < n; ++i) {
    if (s[i] != '\'') {
      p[j++] = s[i];
    } else {
      p[j + 0] = '\'';
      p[j + 1] = '"';
      p[j + 2] = '\'';
      p[j + 3] = '"';
      p[j + 4] = '\'';
      j += 5;
    }
  }
  p[j++] = '\'';
  p[j] = 0;
  if ((q = realloc(p, j + 1))) p = q;
  return p;
}

static const char *GetArg(char *argv[], int i, int *k) {
  if (argv[*k][i + 1]) {
    return argv[*k] + i + 1;
  } else {
    return argv[++*k];
  }
}

int main(int argc, char *argv[]) {
  const char *arg;
  int i, j, k, opt;
  dig = 1;
  symbol = "kXlatTab";

  for (k = 1; k < argc; ++k) {
    if (argv[k][0] != '-') {
      for (i = 0; argv[k][i]; ++i) {
        /* xlat[argv[k][i] & 255] = identity ? i : dig; */
        xlat[argv[k][i] & 255] = identity ? (argv[k][i] & 255) : dig;
      }
    } else {
      i = 0;
    moar:
      ++i;
      if ((opt = argv[k][i])) {
        switch (opt) {
          case 's':
            symbol = GetArg(argv, i, &k);
            break;
          case 'x':
            dig = atoi(GetArg(argv, i, &k)) & 255;
            break;
          case 'i':
            Invert();
            goto moar;
          case 'I':
            identity = !identity;
            goto moar;
          case 'n':
            Negative();
            goto moar;
          case 'N':
            Negate();
            goto moar;
          case 'T':
            Fill(isascii);
            goto moar;
          case 'C':
            Fill(iscntrl);
            goto moar;
          case 'A':
            Fill(isalpha);
            goto moar;
          case 'B':
            Fill(isblank);
            goto moar;
          case 'G':
            Fill(isgraph);
            goto moar;
          case 'P':
            Fill(ispunct);
            goto moar;
          case 'D':
            Fill(isdigit);
            goto moar;
          case 'U':
            Fill(isupper);
            goto moar;
          case 'L':
            Fill(islower);
            goto moar;
          default:
            fprintf(stderr, "error: unrecognized option: %c\n", opt);
            return 1;
        }
      }
    }
  }

  ////////////////////////////////////////////////////////////
  printf("#include \"libc/macros.internal.h\"\n");
  printf("\n");

  printf("//\tgenerated by:\n");
  printf("//\t");
  for (i = 0; i < argc; ++i) {
    if (i) printf(" ");
    printf("%s", !ArgNeedsShellQuotes(argv[i]) ? argv[i]
                                               : _gc(AddShellQuotes(argv[i])));
  }
  printf("\n");

  ////////////////////////////////////////////////////////////
  printf("//\n");
  printf("//\t    present            absent\n");
  printf("//\t    ────────────────   ────────────────\n");
  for (i = 0; i < 16; ++i) {
    char16_t absent[16];
    char16_t present[16];
    for (j = 0; j < 16; ++j) {
      if (xlat[i * 16 + j]) {
        absent[j] = L' ';
        present[j] = Bing(i * 16 + j);
      } else {
        absent[j] = Bing(i * 16 + j);
        present[j] = L' ';
      }
    }
    printf("//\t    %.16hs   %.16hs   0x%02x\n", present, absent, i * 16);
  }

  ////////////////////////////////////////////////////////////
  printf("//\n");
  printf("//\tconst char %s[256] = {\n//\t", symbol);
  for (i = 0; i < 16; ++i) {
    printf("  ");
    for (j = 0; j < 16; ++j) {
      printf("%2d,", (char)xlat[i * 16 + j]);
    }
    printf(" // 0x%02x\n//\t", i * 16);
  }
  printf("};\n");
  printf("\n");

  ////////////////////////////////////////////////////////////
  printf("\t.initbss 300,_init_%s\n", symbol);
  printf("%s:\n", symbol);
  printf("\t.zero\t256\n");
  printf("\t.endobj\t%s,globl\n", symbol);
  printf("\t.previous\n");
  printf("\n");

  ////////////////////////////////////////////////////////////
  printf("\t.initro 300,_init_%s\n", symbol);
  printf("%s.rom:\n", symbol);

  int thebloat = 0;
  int thetally = 0;
  int thecount = 0;
  int runstart = 0;
  int runchar = -1;
  int runcount = 0;
  for (i = 0;; ++i) {
    if (i < 256 && xlat[i] == runchar) {
      ++runcount;
    } else {
      if (runcount) {
        printf("\t.byte\t%-24s# %02x-%02x %hc-%hc\n",
               _gc(xasprintf("%3d,%d", runcount, runchar)), runstart,
               runstart + runcount - 1, Bing(runstart),
               Bing(runstart + runcount - 1));
        thetally += 2;
        thecount += runcount;
      }
      if (i < 256) {
        runcount = 1;
        runchar = xlat[i];
        runstart = i;
      }
    }
    if (i == 256) {
      break;
    }
  }
  CHECK_EQ(256, thecount);
  printf("\t.byte\t%-24s# terminator\n", "0,0");
  thetally += 2;
  thebloat = thetally;
  for (i = 0; (thetally + i) % 8; i += 2) {
    printf("\t.byte\t%-24s# padding\n", "0,0");
    thebloat += 2;
  }

  printf("\t.endobj\t%s.rom,globl\n", symbol);
  printf("\n");

  ////////////////////////////////////////////////////////////
  printf("\t.init.start 300,_init_%s\n", symbol);
  printf("\tcall\trldecode\n");
  thebloat += 5;
  int padding = 8 - thetally % 8;
  if (padding < 8) {
    if (padding >= 4) {
      thebloat += 1;
      printf("\tlodsl\n");
      padding -= 4;
    }
    if (padding >= 2) {
      thebloat += 2;
      printf("\tlodsw\n");
    }
  }
  printf("\t.init.end 300,_init_%s\n", symbol);

  ////////////////////////////////////////////////////////////
  printf("\n");
  printf("//\t%d bytes total (%d%% original size)\n", thebloat,
         (int)round((double)thebloat / 256 * 100));
}