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

// The runtime environment assumes ISO8859/1 (European including Spain)
// CH, LL, and RR are not supported glyphs so we handle them here using 2 letters
// Note that when compiled as a stand-alone .exe under DOS/Windows,
// the default DOS character set is *not* ISO8859/1 and therefore the
// accented characters will not be displayed correctly.

// Cristian wants the first word to automatically link to DRAE (if 'si' checked)
// but that cannot be done with this structure, unless we generate the top-level
// enclosing framework as well, which will be quite a major rewrite.  On hold...

// Customise here:
#define TITLE "Juez Lingüístico FISE"
#define Version "2.0"
#define ENTER_WORD "Introduzca la palabra por la que desea consultar"
#define SUBMIT "Consultar"

#define GOOD_WORD "PALABRA ACEPTADA"
#define BAD_WORD "PALABRA NO ACEPTADA"

#define INVALID_ENTRY "Caracteres no válidos"
#define NO_WORD "Debe introducir una palabra."
#define WRONG_SIZE "La palabra consultada debe tener entre dos y quince fichas"
#define NO_ARG_GIVEN ""
//#define NO_ARG_GIVEN "(vacio)"
#define WORD "palabra"
// CHANGE IT BACK TO juez.cgi ONCE TESTING IS COMPLETE
#define PROGNAME "juez.cgi"

static int showroot = (0==0);
static char linkhack[1024] = { '\0' };

#include "FISE/FISE-ROOT.h" // directory is protected.  File is not available for download

#define LATIN_CAPITAL_LETTER_N_WITH_TILDE 0xD1
#define LATIN_SMALL_LETTER_N_WITH_TILDE 0xF1
#define LATIN_SMALL_LETTER_A_WITH_ACUTE 0xE1
#define LATIN_SMALL_LETTER_E_WITH_ACUTE 0xE9
#define LATIN_SMALL_LETTER_I_WITH_ACUTE 0xED
#define LATIN_SMALL_LETTER_O_WITH_ACUTE 0xF3
#define LATIN_SMALL_LETTER_U_WITH_ACUTE 0xFA
#define LATIN_SMALL_LETTER_U_WITH_DIAERESIS 0xFC
#define LATIN_SMALL_LETTER_E_WITH_GRAVE 0xE8
#define LATIN_SMALL_LETTER_I_WITH_CIRCUMFLEX 0xEE
#define LATIN_CAPITAL_LETTER_A_WITH_ACUTE 0xC1
#define LATIN_CAPITAL_LETTER_E_WITH_ACUTE 0xC9
#define LATIN_CAPITAL_LETTER_I_WITH_ACUTE 0xCD
#define LATIN_CAPITAL_LETTER_O_WITH_ACUTE 0xD3
#define LATIN_CAPITAL_LETTER_U_WITH_ACUTE 0xDA
#define LATIN_CAPITAL_LETTER_U_WITH_DIAERESIS 0xDC
#define LATIN_CAPITAL_LETTER_E_WITH_GRAVE 0xC8
#define LATIN_CAPITAL_LETTER_I_WITH_CIRCUMFLEX 0xCE

/***************************************************************************
* Web param stuff derived directly from code by Harry H. Cheng, 11/24/1995 *
****************************************************************************/

int unescapechar(char *url) {
  char *from = url, *to = from;
  char hex[3], *hexp=hex; /* contains xx of %xx */

  for (;;) {
      int c = *from++;
      if (c == '+') c = ' ';
      if (c == '%') {
        *hexp++ = *from++; *hexp++ = *from++; *hexp = '\0';
        c = strtol(hex, NULL, 16); hexp = hex;
      }
      *to++ = c;
      if (c == '\0') return;
  }
}

int getpairs(char ***name, char ***value)
{
  int cl;                    /* content length */
  int i, l1, l2, num=1;
  char *qs, *clientinput, *token;
  char **nam, **val;

  if (!strcmp("POST", getenv("REQUEST_METHOD"))) {
    /* POST */
    if ((cl = atoi(getenv("CONTENT_LENGTH"))) == 0) return 0;
    if ((clientinput = (char *) malloc(sizeof(char) * (cl+1))) == NULL) return -2;
    fgets(clientinput, cl+1, stdin); /* get the client input */ 
  } else {
    /* GET */
    if ((qs = getenv("QUERY_STRING")) == NULL) return 0; else {
      cl = strlen(qs); 
      if ((clientinput = (char *) malloc(sizeof(char) * (cl+1))) == NULL) return -2;
      strcpy(clientinput, qs);
    }
  }

  token = strchr(clientinput, '&'); /* & is the name/value pair separator */
  while (token != NULL) {token = strchr(++token, '&'); num++;} /* obtain the total number of pairs */

  nam = (char **) malloc(sizeof(char*)*num);
  val = (char **) malloc(sizeof(char*)*num);
  if (nam == NULL || val == NULL) return -2;

  for (i = 0, token = strtok(clientinput, "&"); token != NULL; i++) {
    l1 = strlen(token); l2 = strcspn(token,"=");
    nam[i] = (char *) malloc(sizeof(char) * (l2+1));
    if (nam[i] == NULL) return -2;
    strncpy(nam[i], token, l2); 
    nam[i][l2] = '\0';
    unescapechar(nam[i]);
    if (l1 != l2+1) { /* name=value& */
      val[i] = (char *)malloc(sizeof(char) * (l1-l2));
      if (val[i] == NULL) return -2;
      strcpy(val[i], token+l2+1);
      unescapechar(val[i]);
    } else val[i] = NULL; /* special case of name=& */
    token = strtok(NULL, "&");
  }
  free(clientinput);
  *name = nam; *value = val;
  return num; /* return the number of entries */
}

/*********************************************************************
 * Dictionary lookup code.  See also 'token.c'                       *
 *********************************************************************/

typedef long long NODE;

#define ROOT_NODE 1		/* Root of dag */

#define V_END_OF_WORD   33	/* Bit number of W */
#define M_END_OF_WORD   (1LL << V_END_OF_WORD)
#define V_END_OF_NODE   32	/* Bit number of N */
#define M_END_OF_NODE   (1LL << V_END_OF_NODE)

#define V_LETTER        24
#define M_LETTER        0xFF

#define M_NODE_POINTER  0xFFFFFFL	/* Bit mask for node pointer */

typedef int INDEX;
#define MAX_WORD_LEN 128


INDEX dawg_locate_prefix(NODE *dawg, char *word, INDEX edge)
{
  /* Finds the sub-trie in the dictionary whose prefix is word */
  for (;;) {
    if (*word == (((dawg[edge] >> V_LETTER) & M_LETTER))) {
      if (*++word == '\0') {
        return(dawg[edge]&M_NODE_POINTER);
      } else {
        if ((edge = (dawg[edge] & M_NODE_POINTER)) == ROOT_NODE) break;
        continue;
      }
    }
    if (((dawg[edge++]) & M_END_OF_NODE) != 0) break;
  }
  /* What to do when none found? -- fail-safe, or some error code...? */
  return(ROOT_NODE);
}

static char *sup(char *s)
{
  static char *p, q[128], fancy[128];
  sprintf(q, "%s", s);
  p = strchr(q, ' ');
  if (p == NULL) return(s);
  *p++ = '\0';
  sprintf(fancy, "%s<sup>%s</sup>", q, p);
  return fancy;
}

static char *anchor(char *s, char *alt)
{
  static char fancy[128];
  if ((alt == NULL) || (strcmp(alt, "1") == 0)) return("");
  sprintf(fancy, "#%s_1",  alt);
  return fancy;
}

static void dawg_pr2(NODE *dawg, INDEX node, int len)
{
  static char word[MAX_WORD_LEN];
  static int printed1 = 0;
  NODE *edge;

  for (edge = (NODE *)&dawg[node]; ; edge++) {
    long c;
    c = *edge;           /* Don't rewrite this - its avoiding a MSC bug */
    c = c >> V_LETTER;
    c = c & M_LETTER;
    word[len] = (char)c;
    if ((*edge & M_END_OF_WORD) != 0) {
      char ws2[MAX_WORD_LEN], *ws, *s, *s2;
      word[len+1] = '\0';
      s = word;
      for (;;) {
        if ((s == NULL) || (*s == '\0')) break;
        ws = s;
        s = strchr(ws, ';');
        if (s != NULL) *s++ = '\0';

        strcpy(ws2, ws);
        s2 = strchr(ws2, ' ');
        if (s2 != NULL) *s2++ = '\0';
        if (printed1) {
	  fprintf(stdout, "/&nbsp;", sup(ws));
        } else {
          printed1 = 1;
	}
	fprintf(stdout, "%s&nbsp;", sup(ws));

	//	fprintf(stdout, "<FONT color=green><A HREF=\"http://buscon.rae.es/draeI/SrvltGUIBusUsual?LEMA=%s&origen=RAE&TIPO_BUS=3%s\" target=\"dynamic\">%s</A></FONT>&nbsp;\n", ws2, anchor(ws2, s2), sup(ws));
      }
    }
    c = *edge & M_NODE_POINTER;
    if ((*edge & M_NODE_POINTER) != 0)
      dawg_pr2 (dawg, c, len + 1);
    if ((*edge & M_END_OF_NODE) != 0) break; /* End of node */
  }
}

static void dawg_pr(NODE *dawg, INDEX node, int len)
{
  static char word[MAX_WORD_LEN];
  NODE *edge;

  for (edge = (NODE *)&dawg[node]; ; edge++) {
    long c;
    c = *edge;           /* Don't rewrite this - its avoiding a MSC bug */
    c = c >> V_LETTER;
    c = c & M_LETTER;
    word[len] = (char)c;
    if ((*edge & M_END_OF_WORD) != 0) {
      char ws2[MAX_WORD_LEN], *ws, *s, *s2, *bugfix1, *bugfix2;
      word[len+1] = '\0';
      s = word;
      for (;;) {
        if ((s == NULL) || (*s == '\0')) break;
        ws = s;
        bugfix1 = s = strchr(ws, ';');
        if (s != NULL) *s++ = '\0';

        strcpy(ws2, ws);
        bugfix2 = s2 = strchr(ws2, ' ');
        if (s2 != NULL) *s2++ = '\0';

	fprintf(stdout, "<FONT color=blue><A HREF=\"http://buscon.rae.es/draeI/SrvltGUIBusUsual?LEMA=%s&origen=RAE&TIPO_BUS=3%s\" target=\"dynamic\">%s</A></FONT>&nbsp;\n", ws2, anchor(ws2, s2), sup(ws));

	if (*linkhack != '\0') sprintf(linkhack, "<FONT color=blue><A HREF=\"http://buscon.rae.es/draeI/SrvltGUIBusUsual?LEMA=%s&origen=RAE&TIPO_BUS=3%s\" target=\"dynamic\">%s</A></FONT>&nbsp;\n", ws2, anchor(ws2, s2), sup(ws));

        if (bugfix1) {*bugfix1 = ';'; bugfix1 = NULL;}
        if (bugfix2) {*bugfix2 = ' '; bugfix2 = NULL;}
      }
      fprintf(stdout, "<BR>");
    }
    c = *edge & M_NODE_POINTER;
    if ((*edge & M_NODE_POINTER) != 0)
      dawg_pr (dawg, c, len + 1);
    if ((*edge & M_END_OF_NODE) != 0) break; /* End of node */
  }
}

static char *reformat(char *tiles)
{
   static char word[32];
   char *wordp = word;

   for (;;) {
      int c = *tiles++;

      if (c == '4') c = LATIN_SMALL_LETTER_N_WITH_TILDE;

      *wordp = c; if (c == '\0') return word;

      if      (c == '1') {*wordp++ = 'c'; *wordp = 'h';}
      else if (c == '2') {*wordp++ = 'l'; *wordp = 'l';}
      else if (c == '3') {*wordp++ = 'r'; *wordp = 'r';}
      wordp++;
   }
}

static char *global_word_hack = "";
void dawg_print(NODE *dawg, INDEX node)
{
  static int printed = 0;
  if (printed == 0) {
    printed = 1;
    if (showroot) {
      static char debugging_hack[1024];
      INDEX expanded;
      // if showroot, use dawg_pr ~word to convert word to list / of / words
      fprintf(stdout, "<font color=\"green\">%s: ", GOOD_WORD);
      sprintf(debugging_hack, "~%s=", global_word_hack);
      expanded = dawg_locate_prefix(_token, debugging_hack, ROOT_NODE);
      dawg_pr2(dawg, expanded, 0);
      fprintf(stdout, "</font><BR><BR>\nLa palabra proviene de:<BR>");
      dawg_pr(dawg, node, 0);
      fprintf(stdout, "<BR>");
      fprintf(stdout, "Haga clic en la palabra origen si desea ver la definición.<BR>");
    } else {
      fprintf(stdout, "<font color=\"green\">%s: %s</font><BR><BR>\n", GOOD_WORD, reformat(global_word_hack));
    }
  }
}

int dawg_checkeq(NODE *dict, unsigned char *word)
{
  int ch;
  NODE *edge = dict+ROOT_NODE;

  if (edge == dict) return(0!=0);
  for (;;) {
    ch = (int)(((*edge >> V_LETTER) & M_LETTER));
    ch = ch&255;
    if (ch == '=') {
      if (*word == '\0') {
	dawg_print(dict, (*edge & M_NODE_POINTER));
        return(0==0);
      }
    }
    if (ch == *word) {
      word += 1;
      if ((edge = dict+(*edge & M_NODE_POINTER)) == dict) break; /* End of dict */
      continue; /* edge moved down a level */
    }
    if (((*edge++) & M_END_OF_NODE) != 0) break; /* End of options */
  }
  return(0!=0);
}

/* Support for typographical conventions used (see document entitled 'COMMENT') */

static char convert[256];	// canonicalize ISO Latin1

#define GOOD (0==0)
#define BAD (0!=0)

static int standardize(char *inword, char *outword)
{
   unsigned int c;

   for (;;) {
      c = (unsigned int) (*inword++) & 255U;
      if (c == '\0') break;

      c = convert[c];		// remove accents, convert to lower, leave
				// tilde on n.
      if (c == '?') return (BAD);

      if        ((c == 'c') && (convert[(unsigned int) (*inword) & 255U] == 'h')) {	// ch
	 c = '1'; inword++;
      } else if ((c == 'l') && (convert[(unsigned int) (*inword) & 255U] == 'l')) {	// ll
	 c = '2'; inword++;
      } else if ((c == 'r') && (convert[(unsigned int) (*inword) & 255U] == 'r')) {	// rr
	 c = '3'; inword++;
      }

      *outword++ = (char) c;
   }
   *outword = '\0';
   return (GOOD);
}

/* Main program works as both command-line (parameterised or interactive) and
   web-based (standard cgi mechanism).  Portable between unix (linux) and windows (dos) */

int main(int argc, char **argv)
{
   static char word[32] = { '\0' };
   int i;

   for (i = 0; i < 256; i++) convert[i] = '?'; // rejected unless explicitly specified
   for (i = 'a'; i <= 'z'; i++) convert[i] = i;
   for (i = 'A'; i <= 'Z'; i++) convert[i] = tolower (i);

   // K and W not present in Spanish Scrabble
   convert['k'] = '?'; convert['w'] = '?';
   convert['K'] = '?'; convert['W'] = '?';

   // capital tilde N => lower tilde n, encoded here in 7-bit ascii as '4'
   // because the dawg code may not be 8-bit clean (signed char problem? - debug later...)

   convert[LATIN_CAPITAL_LETTER_N_WITH_TILDE] = '4';
   convert[LATIN_SMALL_LETTER_N_WITH_TILDE] = '4';

   // remove all accents and convert to lowercase

   convert[LATIN_SMALL_LETTER_A_WITH_ACUTE] = 'a';
   convert[LATIN_SMALL_LETTER_E_WITH_ACUTE] = 'e';
   convert[LATIN_SMALL_LETTER_I_WITH_ACUTE] = 'i';
   convert[LATIN_SMALL_LETTER_O_WITH_ACUTE] = 'o';
   convert[LATIN_SMALL_LETTER_U_WITH_ACUTE] = 'u';

   convert[LATIN_SMALL_LETTER_U_WITH_DIAERESIS] = 'u';
   convert[LATIN_SMALL_LETTER_E_WITH_GRAVE] = 'e';
   convert[LATIN_SMALL_LETTER_I_WITH_CIRCUMFLEX] = 'i';

   convert[LATIN_CAPITAL_LETTER_A_WITH_ACUTE] = 'a';
   convert[LATIN_CAPITAL_LETTER_E_WITH_ACUTE] = 'e';
   convert[LATIN_CAPITAL_LETTER_I_WITH_ACUTE] = 'i';
   convert[LATIN_CAPITAL_LETTER_O_WITH_ACUTE] = 'o';
   convert[LATIN_CAPITAL_LETTER_U_WITH_ACUTE] = 'u';

   convert[LATIN_CAPITAL_LETTER_U_WITH_DIAERESIS] = 'u';
   convert[LATIN_CAPITAL_LETTER_E_WITH_GRAVE] = 'e';
   convert[LATIN_CAPITAL_LETTER_I_WITH_CIRCUMFLEX] = 'i';


   if (argc > 1) {
      fprintf(stdout, "%s %s\n\n", TITLE, Version);

      if (strcmp(argv[1], "-h") == 0) {
	  fprintf(stdout, "%s -h\n", PROGNAME);
	  fprintf(stdout, "%s\n", PROGNAME);
	  fprintf(stdout, "%s %s\n", PROGNAME, WORD);
	  fprintf(stdout, "%s %s1 %s2 ...\n", PROGNAME, WORD, WORD);
          exit(0);
      }
   }

   if (argc == 1) { // WEB or interactive command line
      static char *s, line[32];
      char **name, **value;
      int c = 0;

      if (getenv ("REQUEST_METHOD") != NULL) {	/* Invoked from CGI */
	  int i, pairs = 0;

          /* WEB */

          *linkhack = '\0';

	  fprintf(stdout, "Content-type: text/html\r\n");
	  fprintf(stdout, "\r\n");

	  fprintf(stdout, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\r\n");
	  fprintf(stdout, "    \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\r\n");
	  fprintf(stdout, "<html xmlns=\"http://www.w3.org/1999/xhtml\">\r\n");
	  fprintf(stdout, "<head>\r\n");
	  fprintf(stdout, "<meta http-equiv=\"Content-Type\" content=\"text/html;charset=iso-8859-1\" />\r\n");
	  fprintf(stdout, "<title>\r\n");
	  fprintf(stdout, "%s %s\r\n", TITLE, Version);
	  fprintf(stdout, "</title>\r\n");
	  fprintf(stdout, "</head>\r\n");
	  fprintf(stdout, "<body>\r\n");
	  fprintf(stdout, "<h1>%s %s</h1>\r\n", TITLE, Version);

          // display form for user to enter word
	  fprintf(stdout, "<form action=\"/cgi-bin/%s\" method=\"post\">\r\n", PROGNAME);
          fprintf(stdout, "%s: \r\n", ENTER_WORD);
	  fprintf(stdout, "<input type=\"text\" name=\"%s\" />\r\n", WORD);
	  fprintf(stdout, "<input type=\"submit\" value=\"%s\" />\r\n", SUBMIT);
	  fprintf(stdout, "<BR>\r\n");
	  fprintf(stdout, "<BR>&iquest;Mostrar el origen de la palabra? ");
	  if (strcmp(getenv("REQUEST_METHOD"), "POST") == 0) pairs = getpairs(&name, &value);
	  if (pairs > 0) {
	    for (i = 0; i < pairs; i++) if (strcmp(name[i], "showroot") == 0) {showroot = (*value[i] == 'Y'); break;}
	  }
	  fprintf(stdout, "<input type=\"radio\" name=\"showroot\" value=\"Y\"%s> SÍ", showroot ? " checked" : "");
	  fprintf(stdout, "<input type=\"radio\" name=\"showroot\" value=\"N\"%s> NO<BR>\r\n", showroot ? "" : " checked");
	  fprintf(stdout, "</form>\r\n");

	  if (strcmp(getenv("REQUEST_METHOD"), "POST") == 0) {
              char *GIVEN_ARG = NULL;

	      // word supplied, check it

	      if (pairs > 0) {

// TO DO: conversions into and out of quoted web format; hex entity refs for accented chars

		for (i = 0; i < pairs; i++) if (strcmp(name[i], WORD) == 0) {GIVEN_ARG = value[i]; break;}
		for (i = 0; i < pairs; i++) if (strcmp(name[i], "showroot") == 0) {showroot = *value[i] == 'Y'; break;}

                if ((GIVEN_ARG != NULL) && (!standardize (GIVEN_ARG, word))) {
		    fprintf(stdout, "<font color=\"red\">%s: %s</font>\n\n", INVALID_ENTRY, GIVEN_ARG);
	        } else if ((GIVEN_ARG == NULL) || (strlen (word) == 0)) {
	            fprintf(stdout, "<font color=\"red\">%s</font>\n", NO_WORD);
	        } else if ((strlen (GIVEN_ARG) > 30) || (strlen (word) < 2) || (strlen (word) > 15)) {
	            fprintf(stdout, "<font color=\"red\">%s: %s</font>\n", WRONG_SIZE,
		            (GIVEN_ARG == NULL ? NO_ARG_GIVEN : GIVEN_ARG));
	        } else {
	           if (dawg_checkeq(_token, global_word_hack = word)) {
		       // already printed: fprintf(stdout, "<font color=\"green\">%s: %s</font>\n", GOOD_WORD, reformat(word));
	           } else {
		       fprintf(stdout, "<font color=\"red\">%s: %s</font>\n", BAD_WORD, reformat(word));
	           }
	        }
		for (i = 0; i < pairs; i++) {
		    if (name[i] != NULL) free(name[i]);
		    if (value[i] != NULL) free(value[i]);
		}
		if (name != NULL) free(name);
		if (value != NULL) free(value);
	      } else {
	          fprintf(stdout, "Form does not seem to include the required field '%s'.\r\n", WORD);
	      }
          }

	  fprintf(stdout, "</body>\r\n");
	  fprintf(stdout, "</html>\r\n");

      } else {

	 /* Console/Interactive */

	 fprintf(stdout, "%s %s\n\n", TITLE, Version);

	 for (;;) {
	    fprintf(stderr, "%s: ", ENTER_WORD); fflush(stderr);

	    s = line; i = 0; *linkhack = '\0';
	    for (;;) {
               int c = fgetc(stdin);

	       if ((c == ('Z' & 31)) || (c == ('D' & 31)) || ferror(stdin)) c = EOF;
	       if (c == EOF) {
		  fprintf(stdout, "\n\n");
		  exit(0);
	       }
	       if (c == '\n') break;
	       if ((c == '\r') || ((c == ' ') || (c == '\t'))) continue;
	       // allow 31 so we catch 'words' > 15 like chllrrchllrrchllrrchllrrchllrrx
	       if (i++ < 31) *s++ = c;
	    }
	    *s = '\0'; if (*line == '\0') continue;

	    fprintf(stdout, "\n");
	    if (!standardize (line, word)) {
	       fprintf(stdout, "%s: %s\n\n", INVALID_ENTRY, line);
	    } else if ((strlen(word) < 2) || (strlen(word) > 15)) {
	       fprintf(stdout, "%s: %s\n\n", WRONG_SIZE, reformat(word));
	    } else {
	       if (dawg_checkeq(_token, global_word_hack = word)) {
                 // already printed
	       } else {
	         fprintf(stdout, "%s: %s\n\n", BAD_WORD, reformat(word));
	       }
	    }
	    fflush(stdout);
	    if (c == EOF) exit (1);
	 }
	 fprintf(stdout, "\n");
      }
   } else {

       /* Console/command-line */

      for (i = 1; i < argc; i++) {
	 if ((strlen (argv[i]) <= 30) && (!standardize (argv[i], word))) {
	    // bad input (length test is so assignment to 'word' is not exceeded)
	    fprintf (stdout, "%s: %s\n", INVALID_ENTRY, argv[i]);
	 } else if ((strlen(argv[i]) > 30) || (strlen(word) < 2) || (strlen(word) > 15)) {
	    fprintf(stdout, "%s: %s\n", WRONG_SIZE, argv[i]);
	 }  else {
            if (dawg_checkeq(_token, global_word_hack = word)) {
	       // printed already: fprintf(stdout, "%s: %s\n", GOOD_WORD, reformat(word));
               // now print root.
            } else {
               fprintf(stdout, "%s: %s\n", BAD_WORD, reformat(word));
            }
	 }
      }
   }
   exit (0);
}
