/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * Copyright (C) 2002-2017 Németh László
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * Hunspell is based on MySpell which is Copyright (C) 2002 Kevin Hendricks.
 *
 * Contributor(s): David Einstein, Davide Prina, Giuseppe Modugno,
 * Gianluca Turconi, Simon Brouwer, Noll János, Bíró Árpád,
 * Goldman Eleonóra, Sarlós Tamás, Bencsáth Boldizsár, Halácsy Péter,
 * Dvornik László, Gefferth András, Nagy Viktor, Varga Dániel, Chris Halls,
 * Rene Engelhard, Bram Moolenaar, Dafydd Jones, Harri Pitkänen
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/* Un-munch a root word list with affix tags
 * to recreate the original word list
 */

#include <ctype.h>
#include <string.h>
#include <string>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <stddef.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits>

#include "unmunch.h"

int main(int argc, char** argv) {
  int i;
  int al;

  FILE* wrdlst;
  FILE* afflst;

  char *wf, *af;
  char ts[MAX_LN_LEN];

  (void)argc;

  /* first parse the command line options */
  /* arg1 - munched wordlist, arg2 - affix file */

  if (argv[1]) {
    wf = mystrdup(argv[1]);
  } else {
    fprintf(stderr, "correct syntax is:\n");
    fprintf(stderr, "unmunch dic_file affix_file\n");
    exit(1);
  }
  if (argv[2]) {
    af = mystrdup(argv[2]);
  } else {
    fprintf(stderr, "correct syntax is:\n");
    fprintf(stderr, "unmunch dic_file affix_file\n");
    exit(1);
  }

  /* open the affix file */
  afflst = fopen(af, "r");
  if (!afflst) {
    fprintf(stderr, "Error - could not open affix description file\n");
    exit(1);
  }

  /* step one is to parse the affix file building up the internal
     affix data structures */

  numpfx = 0;
  numsfx = 0;
  fullstrip = 0;

  if (parse_aff_file(afflst)) {
    fprintf(stderr, "Error - in affix file loading\n");
    exit(1);
  }

  fclose(afflst);

  fprintf(stderr, "parsed in %d prefixes and %d suffixes\n", numpfx, numsfx);

  /* affix file is now parsed so create hash table of wordlist on the fly */

  /* open the wordlist */
  wrdlst = fopen(wf, "r");
  if (!wrdlst) {
    fprintf(stderr, "Error - could not open word list file\n");
    exit(1);
  }

  /* skip over the hash table size */
  if (!fgets(ts, MAX_LN_LEN - 1, wrdlst)) {
    fclose(wrdlst);
    return 2;
  }
  mychomp(ts);

  while (fgets(ts, MAX_LN_LEN - 1, wrdlst)) {
    mychomp(ts);
    /* split each line into word and affix char strings */
    char* ap = strchr(ts, '/');
    if (ap) {
      *ap = '\0';
      ap++;
      al = strlen(ap);
    } else {
      al = 0;
      ap = NULL;
    }

    int wl = strlen(ts);

    numwords = 0;
    wlist[numwords].word = mystrdup(ts);
    wlist[numwords].pallow = 0;
    numwords++;

    if (al)
      expand_rootword(ts, wl, ap);

    for (i = 0; i < numwords; i++) {
      fprintf(stdout, "%s\n", wlist[i].word);
      free(wlist[i].word);
      wlist[i].word = NULL;
      wlist[i].pallow = 0;
    }
  }

  fclose(wrdlst);
  return 0;
}

int parse_aff_file(FILE* afflst) {
  int i, j;
  int numents = 0;
  char achar = '\0';
  short ff = 0;
  struct affent* ptr = NULL;
  struct affent* nptr = NULL;
  char* line = (char*)malloc(MAX_LN_LEN);

  while (fgets(line, MAX_LN_LEN, afflst)) {
    mychomp(line);
    char ft = ' ';
    fprintf(stderr, "parsing line: %s\n", line);
    if (strncmp(line, "FULLSTRIP", 9) == 0)
      fullstrip = 1;
    if (strncmp(line, "PFX", 3) == 0)
      ft = 'P';
    if (strncmp(line, "SFX", 3) == 0)
      ft = 'S';
    if (ft != ' ') {
      char* tp = line;
      char* piece;
      ff = 0;
      i = 0;
      while ((piece = mystrsep(&tp, ' '))) {
        if (*piece != '\0') {
          switch (i) {
            case 0:
              break;
            case 1: {
              achar = *piece;
              break;
            }
            case 2: {
              if (*piece == 'Y')
                ff = XPRODUCT;
              break;
            }
            case 3: {
              numents = atoi(piece);
              if ((numents <= 0) || ((std::numeric_limits<size_t>::max() /
                                      sizeof(struct affent)) < static_cast<size_t>(numents))) {
                fprintf(stderr, "Error: too many entries: %d\n", numents);
                numents = 0;
              } else {
                ptr = (struct affent*)malloc(numents * sizeof(struct affent));
                ptr->achar = achar;
                ptr->xpflg = ff;
                fprintf(stderr, "parsing %c entries %d\n", achar, numents);
              }
              break;
            }
            default:
              break;
          }
          i++;
        }
        free(piece);
      }
      /* now parse all of the sub entries*/
      nptr = ptr;
      for (j = 0; j < numents; j++) {
        if (!fgets(line, MAX_LN_LEN, afflst))
          return 1;
        mychomp(line);
        tp = line;
        i = 0;
        while ((piece = mystrsep(&tp, ' '))) {
          if (*piece != '\0') {
            switch (i) {
              case 0: {
                if (nptr != ptr) {
                  nptr->achar = ptr->achar;
                  nptr->xpflg = ptr->xpflg;
                }
                break;
              }
              case 1:
                break;
              case 2: {
                nptr->strip = mystrdup(piece);
                nptr->stripl = strlen(nptr->strip);
                if (strcmp(nptr->strip, "0") == 0) {
                  free(nptr->strip);
                  nptr->strip = mystrdup("");
                  nptr->stripl = 0;
                }
                break;
              }
              case 3: {
                nptr->appnd = mystrdup(piece);
                nptr->appndl = strlen(nptr->appnd);
                if (strcmp(nptr->appnd, "0") == 0) {
                  free(nptr->appnd);
                  nptr->appnd = mystrdup("");
                  nptr->appndl = 0;
                }
                if (strchr(nptr->appnd, '/')) {
                  char* addseparator =
                      (char*)realloc(nptr->appnd, nptr->appndl + 2);
                  if (addseparator) {
                    nptr->appndl++;
                    addseparator[nptr->appndl - 1] = '|';
                    addseparator[nptr->appndl] = '\0';
                    nptr->appnd = addseparator;
                  }
                }
                break;
              }
              case 4: {
                encodeit(nptr, piece);
              }
                fprintf(stderr, "   affix: %s %d, strip: %s %d\n", nptr->appnd,
                        nptr->appndl, nptr->strip, nptr->stripl);
                // no break
              default:
                break;
            }
            i++;
          }
          free(piece);
        }
        nptr++;
      }
      if (ptr) {
        if (ft == 'P') {
          ptable[numpfx].aep = ptr;
          ptable[numpfx].num = numents;
          fprintf(stderr, "ptable %d num is %d flag %c\n", numpfx,
                  ptable[numpfx].num, ptr->achar);
          numpfx++;
        } else if (ft == 'S') {
          stable[numsfx].aep = ptr;
          stable[numsfx].num = numents;
          fprintf(stderr, "stable %d num is %d flag %c\n", numsfx,
                  stable[numsfx].num, ptr->achar);
          numsfx++;
        }
        ptr = NULL;
      }
      nptr = NULL;
      numents = 0;
      achar = '\0';
    }
  }
  free(line);
  return 0;
}

void encodeit(struct affent* ptr, char* cs) {
  int nc;
  int neg;
  int grp;
  int n;
  int ec;
  int nm;
  int i, j, k;
  unsigned char mbr[MAX_WD_LEN];

  /* now clear the conditions array */
  for (i = 0; i < SET_SIZE; i++)
    ptr->conds[i] = (unsigned char)0;

  /* now parse the string to create the conds array */
  nc = strlen(cs);
  neg = 0; /* complement indicator */
  grp = 0; /* group indicator */
  n = 0;   /* number of conditions */
  ec = 0;  /* end condition indicator */
  nm = 0;  /* number of member in group */
  i = 0;
  if (strcmp(cs, ".") == 0) {
    ptr->numconds = 0;
    return;
  }
  while (i < nc) {
    unsigned char c = *((unsigned char*)(cs + i));
    if (c == '[') {
      grp = 1;
      c = 0;
    }
    if ((grp == 1) && (c == '^')) {
      neg = 1;
      c = 0;
    }
    if (c == ']') {
      ec = 1;
      c = 0;
    }
    if ((grp == 1) && (c != 0)) {
      *(mbr + nm) = c;
      nm++;
      c = 0;
    }
    if (c != 0) {
      ec = 1;
    }
    if (ec) {
      if (grp == 1) {
        if (neg == 0) {
          for (j = 0; j < nm; j++) {
            k = (unsigned int)mbr[j];
            ptr->conds[k] = ptr->conds[k] | (1 << n);
          }
        } else {
          for (j = 0; j < SET_SIZE; j++)
            ptr->conds[j] = ptr->conds[j] | (1 << n);
          for (j = 0; j < nm; j++) {
            k = (unsigned int)mbr[j];
            ptr->conds[k] = ptr->conds[k] & ~(1 << n);
          }
        }
        neg = 0;
        grp = 0;
        nm = 0;
      } else {
        /* not a group so just set the proper bit for this char */
        /* but first handle special case of . inside condition */
        if (c == '.') {
          /* wild card character so set them all */
          for (j = 0; j < SET_SIZE; j++)
            ptr->conds[j] = ptr->conds[j] | (1 << n);
        } else {
          ptr->conds[(unsigned int)c] = ptr->conds[(unsigned int)c] | (1 << n);
        }
      }
      n++;
      ec = 0;
    }
    i++;
  }
  ptr->numconds = n;
  return;
}

/* add a prefix to word */
void pfx_add(const char* word, int len, struct affent* ep, int num) {
  struct affent* aent;
  int cond;
  unsigned char* cp;
  int i;

  for (aent = ep, i = num; i > 0; aent++, i--) {
    /* now make sure all conditions match */
    if ((len + fullstrip > aent->stripl) && (len >= aent->numconds) &&
        ((aent->stripl == 0) ||
         (strncmp(aent->strip, word, aent->stripl) == 0))) {
      cp = (unsigned char*)word;
      for (cond = 0; cond < aent->numconds; cond++) {
        if ((aent->conds[*cp++] & (1 << cond)) == 0)
          break;
      }
      if (cond >= aent->numconds) {
        std::string tword;
        /* we have a match so add prefix */
        if (aent->appndl) {
          tword.append(aent->appnd);
        }
        tword.append(word + aent->stripl);

        if (numwords < MAX_WORDS) {
          wlist[numwords].word = mystrdup(tword.c_str());
          wlist[numwords].pallow = 0;
          numwords++;
        }
      }
    }
  }
}

/* add a suffix to a word */
void suf_add(const char* word, int len, struct affent* ep, int num) {
  struct affent* aent;
  int cond;
  unsigned char* cp;
  int i;

  for (aent = ep, i = num; i > 0; aent++, i--) {
    /* if conditions hold on root word
     * then strip off strip string and add suffix
     */

    if ((len + fullstrip > aent->stripl) && (len >= aent->numconds) &&
        ((aent->stripl == 0) ||
         (strcmp(aent->strip, word + len - aent->stripl) == 0))) {
      cp = (unsigned char*)(word + len);
      for (cond = aent->numconds; --cond >= 0;) {
        if ((aent->conds[*--cp] & (1 << cond)) == 0)
          break;
      }
      if (cond < 0) {
        /* we have a matching condition */
        std::string tword(word);
        tword.resize(len - aent->stripl);
        tword.append(aent->appnd);

        if (numwords < MAX_WORDS) {
          wlist[numwords].word = mystrdup(tword.c_str());
          wlist[numwords].pallow = (aent->xpflg & XPRODUCT);
          numwords++;
        }
      }
    }
  }
}

int expand_rootword(const char* ts, int wl, const char* ap) {
  int i;
  int nh = 0;

  for (i = 0; i < numsfx; i++) {
    if (strchr(ap, (stable[i].aep)->achar)) {
      suf_add(ts, wl, stable[i].aep, stable[i].num);
    }
  }

  nh = numwords;

  if (nh > 1) {
    for (int j = 1; j < nh; j++) {
      if (wlist[j].pallow) {
        for (i = 0; i < numpfx; i++) {
          if (strchr(ap, (ptable[i].aep)->achar)) {
            if ((ptable[i].aep)->xpflg & XPRODUCT) {
              int nwl = strlen(wlist[j].word);
              pfx_add(wlist[j].word, nwl, ptable[i].aep, ptable[i].num);
            }
          }
        }
      }
    }
  }

  for (i = 0; i < numpfx; i++) {
    if (strchr(ap, (ptable[i].aep)->achar)) {
      pfx_add(ts, wl, ptable[i].aep, ptable[i].num);
    }
  }
  return 0;
}

/* strip strings into token based on single char delimiter
 * acts like strsep() but only uses a delim char and not
 * a delim string
 */
char* mystrsep(char** stringp, const char delim) {
  char* rv = NULL;
  char* mp = *stringp;
  int n = strlen(mp);
  if (n > 0) {
    char* dp = (char*)memchr(mp, (int)((unsigned char)delim), n);
    if (dp) {
      ptrdiff_t nc;
      *stringp = dp + 1;
      nc = dp - mp;
      rv = (char*)malloc(nc + 1);
      if (rv) {
        memcpy(rv, mp, nc);
        *(rv + nc) = '\0';
      }
    } else {
      rv = (char*)malloc(n + 1);
      if (rv) {
        memcpy(rv, mp, n);
        *(rv + n) = '\0';
        *stringp = mp + n;
      }
    }
  }
  return rv;
}

char* mystrdup(const char* s) {
  char* d = NULL;
  if (s) {
    int sl = strlen(s) + 1;
    d = (char*)malloc(sl);
    if (d)
      memcpy(d, s, sl);
  }
  return d;
}

void mychomp(char* s) {
  int k = strlen(s);
  if ((k > 0) && (*(s + k - 1) == '\n'))
    *(s + k - 1) = '\0';
  if ((k > 1) && (*(s + k - 2) == '\r'))
    *(s + k - 2) = '\0';
}
