#if 0
/*─────────────────────────────────────────────────────────────────╗
│ To the extent possible under law, Justine Tunney has waived      │
│ all copyright and related or neighboring rights to this file,    │
│ as it is written in the following disclaimers:                   │
│   • http://unlicense.org/                                        │
│   • http://creativecommons.org/publicdomain/zero/1.0/            │
╚─────────────────────────────────────────────────────────────────*/
#endif
#include "libc/alg/alg.h"
#include "libc/alg/arraylist.internal.h"
#include "libc/alg/critbit0.h"
#include "libc/bits/bits.h"
#include "libc/calls/calls.h"
#include "libc/fmt/conv.h"
#include "libc/log/check.h"
#include "libc/log/log.h"
#include "libc/macros.internal.h"
#include "libc/mem/mem.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/str/tpdecode.internal.h"
#include "libc/sysv/consts/fileno.h"
#include "libc/x/x.h"

STATIC_YOINK("zip_uri_support");

/**
 * @fileoverview Simple Interactive Spell Checker.
 *
 * This is an attempt to get spell checking to work in Emacs across
 * platforms. While the computer science behind spell checking is very
 * simple, unfortunately Emacs probes all these System Five spell progs
 * similar to how websites have been known to probe User-Agent strings.
 *
 * Here's how we believe the repl works:
 *
 *     $ make -j8 o//examples/ispell.com
 *     $ o//examples/ispell.com
 *     @(#) Cosmopolitan Ispell Version 3.4.00 8 Feb 2015
 *     word: hello
 *     ok
 *     word: héllo
 *     how about: hills, hello, hilly, jello
 *     word: lova
 *     how about: diva, dona, dora, dove, elva, fora, hove, iota
 *     word: hecruhecrue
 *     not found
 *
 * The dictionary for this program is embedded as a text file within the
 * zip structure of the binary. It can be edited after distribution.
 *
 * It's possible to go even fancier than what this code is doing, by
 * using cmudict phonemes, bloom filters, unicode tables e.g. e vs. é,
 * and even doing ML similar to Google's online spell checker.
 *
 * TODO: Figure out why Emacs rejects this interface.
 */

#define MISSING_LETTER_DISTANCE  5
#define MAX_NEARBY_WORD_DISTANCE 6
#define MAX_NEARBY_RESULTS       8

FILE *f;
char *line;
size_t linesize;
const char *query;
struct critbit0 words; /* does O(log 𝑛) fast path lookup */

struct NearbyWords {
  size_t i, n;
  struct WordMatch {
    long dist;
    char *word;
  } * p;
} nearby;

long WordDistance(const char *a, const char *b) {
  long dist;
  int gota, gotb;
  unsigned long h, p;
  wint_t chara, charb;
  dist = p = 0;
  for (;;) {
    gota = abs(tpdecode(a, &chara)); /* parses utf-8 multibyte characters */
    gotb = abs(tpdecode(b, &charb)); /* abs() handles -1, always yields � */
    if (!chara && !charb) break;
    if (!chara || !charb) {
      dist += MISSING_LETTER_DISTANCE;
    } else if ((h = hamming(chara, charb))) {
      dist += h + p++; /* penalize multiple mismatched letters */
    }
    if (chara) a += gota;
    if (charb) b += gotb;
  }
  return dist;
}

intptr_t ConsiderWord(const char *word, void *arg) {
  long dist;
  if ((dist = WordDistance(word, query)) < MAX_NEARBY_WORD_DISTANCE) {
    append(&nearby, &((struct WordMatch){dist, word}));
  }
  return 0;
}

int CompareWords(const struct WordMatch *a, const struct WordMatch *b) {
  return a->dist < b->dist;
}

void FindNearbyWords(void) {
  nearby.i = 0;
  critbit0_allprefixed(&words, "", ConsiderWord, NULL);
  qsort(nearby.p, nearby.i, sizeof(struct WordMatch), (void *)CompareWords);
}

void SpellChecker(void) {
  int i;
  printf("@(#) Cosmopolitan Ispell Version 3.4.00 8 Feb 2015\r\n");
  while (!feof(stdin)) {
    printf("word: ");
    fflush(stdout);
    if (getline(&line, &linesize, stdin) > 0) {
      query = strtolower(chomp(line));
      if (critbit0_contains(&words, query)) {
        printf("ok\r\n");
      } else {
        FindNearbyWords();
        if (nearby.i) {
          printf("how about: ");
          for (i = 0; i < MIN(MAX_NEARBY_RESULTS, nearby.i); ++i) {
            if (i) printf(", ");
            fputs(nearby.p[i].word, stdout);
          }
          printf("\r\n");
        } else {
          printf("not found\r\n");
        }
      }
    }
    printf("\r\n");
  }
  CHECK_NE(-1, fclose(stdin));
  CHECK_NE(-1, fclose(stdout));
}

void LoadWords(void) {
  CHECK_NOTNULL((f = fopen("zip:usr/share/dict/words", "r")));
  while (getline(&line, &linesize, f) > 0) {
    critbit0_insert(&words, strtolower(chomp(line)));
  }
  CHECK_NE(-1, fclose(f));
}

int main(int argc, char *argv[]) {
  showcrashreports();
  LoadWords();
  SpellChecker();
  return 0;
}
