#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.

// Customise here:
#define TITLE "Juez Lingüístico FISE"
#define Version "1.0"
#define ENTER_WORD "Teclee 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 WRONG_SIZE "La palabra consultada debe tener entre dos y quince fichas"
#define NO_ARG_GIVEN "(vacio)"
#define WORD "palabra"
#define PROGNAME "juez.cgi"

#include "FISE/FISE.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 NODE;

#define ROOT_NODE 1		/* Root of dag */

#define V_END_OF_WORD   23	/* Bit number of W */
#define M_END_OF_WORD   (1L << V_END_OF_WORD)
#define V_END_OF_NODE   22	/* Bit number of N */
#define M_END_OF_NODE   (1L << V_END_OF_NODE)

#define V_LETTER        24
#define M_LETTER        0xFF

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

int dawg_check(NODE * dict, char *word)
{
   /* Simple check of word in dawg - must be exact match */
   /* See http://www.gtoal.com/wordgames/spell for support code */
   NODE *edge = dict + ROOT_NODE;

   if (edge == dict) return (0 != 0);
   for (;;) {
      if (*word == (((*edge >> V_LETTER) & M_LETTER))) {
	 if (*++word == '\0') {
	    return ((*edge & M_END_OF_WORD) != 0);
	 } else {
	    if ((edge = dict + (*edge & M_NODE_POINTER)) == dict) break; else continue;
	 }
      }
      if (((*edge++) & M_END_OF_NODE) != 0) break;
   }
   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 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 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];
      int c = 0;

      if (getenv ("REQUEST_METHOD") != NULL) {	/* Invoked from CGI */

          /* WEB */
	  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, "\r\n");
	  fprintf(stdout, "\r\n");
	  fprintf(stdout, "</form>\r\n");

	  if (strcmp(getenv("REQUEST_METHOD"), "POST") == 0) {
              int i, pairs;
              char **name, **value;
              char *GIVEN_ARG = NULL;

	      // word supplied, check it

	      if (pairs = getpairs(&name, &value) > 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;}

                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 (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_check(_token, word)) {
		       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;
	    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;

	    if (!standardize (line, word)) {
	       fprintf(stdout, "\n%s: %s\n\n", INVALID_ENTRY, line);
	    } else if ((strlen(word) < 2) || (strlen(word) > 15)) {
	       fprintf(stdout, "\n%s: %s\n\n", WRONG_SIZE, reformat(word));
	    } else {
	       fprintf(stdout, "\n%s: %s\n\n", (dawg_check(_token, word) ? GOOD_WORD : 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 fprintf(stdout, "%s: %s\n", (dawg_check(_token, word) ? GOOD_WORD : BAD_WORD), reformat(word));
      }
   }
   exit (0);
}
