#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "global.h"
#include "manhat-lib/shared_util.h"
#include "manhat-lib/shared_file_util.h"
#include "manhat-lib/shared_survey_string.h"
#include "manhat-lib/shared_cs_util.h"
#include "manhat-lib/shared_server_string.h"
#include "manhat-lib/shared_system_data.h"

#define DEBUG 0     /* if set to 1, you'll see the output of the aspell command in your browser
                    ** This is useful when you are first getting aspell/ispell to work, but 
                    ** should be shut off for normal operation.
                    */

#define NULL_DEV  "/dev/null"  /* If DEBUG is 0, aspell/ispell errors will be redirected here.
                               ** This is the standard Null device for Un*x systems? 
                               */


typedef struct word
{
     char *err_word;
     char *sugg_word_list;
     struct word *next;
}WORD;

typedef struct word_list
{
      WORD *head;
      WORD *current;
}WORD_LIST;



int htoi(char *s)
{       
	int value;
	int c;
	
	c = s[0];
	if (isupper(c))
		c = tolower(c);
	value = (c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10) * 16;

	c = s[1];
	if (isupper(c))
		c = tolower(c);
	value += c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10;

	return (value);
}

int url_decode(char *str, int len)
{
#define ISPELL_SPECIAL_CHARS "*&@+-~#!^"
	char *dest = str;
	char *data = str;
        
	while (len--) {
                if(strchr(ISPELL_SPECIAL_CHARS,*data))
                             *dest = ' ';
		else 
	           if (*data == '%')
                       {
                        if ( (len >= 2) && isxdigit((int) *(data + 1)) && isxdigit((int) *(data + 2))) 
                          {
			   *dest = (char) htoi(data + 1);
	                   if((*dest == '%') || strchr(ISPELL_SPECIAL_CHARS,*dest))
                               *dest = ' ';
			   data += 2;
			   len -= 2;
	                  }
                       else
                          *dest = ' ';
                      }    
	           else
			*dest = *data;

		data++;
		dest++;
	}
     *dest = '\0';
     return dest - str;

#undef ISPELL_SPECIAL_CHARS
}

void
free_word_list(WORD_LIST *list)
{
     WORD *ptr;
     while(list->head)
     {
        ptr = list->head->next;
        if(list->head->err_word)
	    free(list->head->err_word);
        if(list->head->sugg_word_list)
	    free(list->head->sugg_word_list);
        free(list->head);
        list->head = ptr;
      }
}     
WORD_LIST *
initialize_word_list(WORD_LIST *list_words)
{
     list_words = (WORD_LIST *)malloc(sizeof(WORD_LIST));
     list_words->head =0;
     list_words->current =0;
     return list_words;
}

void
write_file(const char *text, char *filename)
{
    int fd;
    FILE *fp;
    char *decode_text;
    
    decode_text = malloc_str(text);
    url_decode(decode_text, strlen(decode_text));
    
    snprintf (filename, MAX_PATH + 1, "%sCGI_DATA_spell_XXXXXX", TMP_PATH);
    fd = mkstemp (filename);
    if(fd == -1)
     cs_critical_error (ERR_MKSTEMP_FAILED, "");
    close(fd);
 
    change_mkstemp_permission(filename);   /*shared_file_util.c */
    fp = fopen (filename, "w");
    if(!fp)
     cs_critical_error (ERR_FOPEN_WRITE_FAILED, filename);
    fprintf(fp, "%s", decode_text);
    fclose(fp);
 
    if(decode_text)
	    free(decode_text);
}


void report_aspell_error(const char *error_path)
{
 FILE *fp;
 char *buf;
 struct stat file_stat;
 
 if(DEBUG)  /* then show the error file */
  {
   if(stat(error_path, &file_stat))
       cs_critical_error(ERR_STAT_FAILED, error_path);
   buf = (char*)malloc(sizeof(char) * file_stat.st_size +1);
   if(!buf)
       cs_critical_error(ERR_MALLOC_FAILED, "report_aspell_error()");

   fp = fopen(error_path,"r");
   if(fp)
    {
      fread( buf, file_stat.st_size ,1, fp);
      fclose(fp);
    unlink(error_path);
    *(buf + file_stat.st_size) = '\0';
    cs_set_value("error_content", buf);
    free(buf);
    }  
  }
 else
    cs_critical_error(ERR_GENERAL_ERROR,"spell checker failed");      
 exit(1);
} 




void
spell_checker(const char *filename, char *result)
{

    char cmd[MAX_PATH +1];
    int fd;
    char error_file[MAX_PATH + 1];
    
    snprintf (result, MAX_PATH + 1, "%sCGI_DATA_result_XXXXXX", TMP_PATH);
    fd = mkstemp (result);
    if(fd == -1)
        cs_critical_error (ERR_MKSTEMP_FAILED, result);
    close(fd);
    change_mkstemp_permission(result);   /* shared_file_util.c */
    
    if(DEBUG)  /* then create an error file in Manhattan's 'tmp' directory */
     {
      snprintf (error_file, MAX_PATH + 1, "%sCGI_DATA_aspellerror_XXXXXX", TMP_PATH);
      fd = mkstemp (error_file);
      if(fd == -1)
          cs_critical_error (ERR_MKSTEMP_FAILED, error_file);
      close(fd);
      change_mkstemp_permission(error_file);   /* shared_file_util.c */
     }
    else
      snprintf(error_file, MAX_PATH + 1, NULL_DEV);  /* otherwise errors are redirected to /dev/null */
    
    
    
    /** ASPELL_PATH and ASPELL_ARGS are #defined in system_conf_data  **/
    snprintf(cmd, MAX_PATH +1, "%s %s < %s > %s 2>%s", system_data_str("ASPELL_PATH"), system_data_str("ASPELL_ARGS"), filename, result, error_file);
    if(system(cmd))   /* something failed */
    {
       unlink(filename);
       unlink(result);
       report_aspell_error(error_file);
    } 
   else
    {
     if(DEBUG)
        unlink(error_file); 
    
     unlink(filename);
    } 

}


char *
read_parameters()
{
    char *text, *return_text;
   
   text = hdf_get_value(global_cgi->hdf, "Query.checktext", " ");
   return_text = malloc_str(text);
   return return_text;
}



void
write_js_text( char *text)
{
     cs_set_value("text", text);
}


int 
count_sugg_words(const char *sugg_word)
{
    int count =0;
    int i;
    int len;
    
    len = strlen(sugg_word);
    for(i =0; i < len; i++)
    {
      if(sugg_word[i] == ',')
	   count++;
    }
    return count;
}

WORD *
process_sugg_words(WORD *new_word, const char *sugg_word)
{
     int i, j=0, count, total, len;

     count =  count_sugg_words(sugg_word);
     total = (count +1)*2 + 2 + strlen(sugg_word);
     new_word->sugg_word_list = (char*)malloc(sizeof(char)*total +1);
     new_word->sugg_word_list[j] = '[';
     j++;
     new_word->sugg_word_list[j] = '\'';
     j++;

     for(i = 0, len = strlen(sugg_word); i < len; i++)
     {
	 if(sugg_word[i] == ',')
	 {
	   new_word->sugg_word_list[j]='\'';
	   j++;
	   new_word->sugg_word_list[j]=sugg_word[i]; /* this is , */
	   j++;
	   i++;
	   new_word->sugg_word_list[j]=sugg_word[i]; /* this is white space */
	   j++;
	   new_word->sugg_word_list[j]='\'';
	   j++;
	 }
	 else
	 {
	   new_word->sugg_word_list[j]=sugg_word[i]; 
	   j++;
	 }
     }
     
     new_word->sugg_word_list[j] = '\'';
     j++;
     new_word->sugg_word_list[j] = ']';
     j++;
     new_word->sugg_word_list[j] = '\0';
     return new_word;
}


WORD_LIST *
insert_word(char *line_buffer, WORD_LIST *list_word)
{
    char *ptr, *ptr2, *ptr3;
    WORD *new_word;
    
    ptr = strchr(line_buffer, ' ');
    if(ptr)   /* if a space was found */
    {
       *ptr = '\0';
       ptr3 = ptr;
       ptr++;
       ptr2 = strchr(ptr, ' ');
       if(ptr2)  /* if second space found */
       {
	   *ptr2 = '\0';
          new_word = (WORD*)malloc(sizeof(WORD));
          new_word->err_word = malloc_str(ptr);
          new_word->sugg_word_list =0;
          new_word->next = 0;
          *ptr3 = ' ';
          *ptr2 = ' ';
          ptr2++;
          ptr = strchr(ptr2, ':');
          if(ptr)  /* colon was found this means there are suggs words*/
          {
	     ptr++;
	     *ptr = '\0';
	     ptr2 = ptr;
	     ptr++;
	     new_word = process_sugg_words(new_word, ptr);
             *ptr2 = ' ';
	   }

	  if(!list_word->head)
		list_word->head = new_word;
	  else
	       list_word->current->next = new_word;
	  list_word->current = new_word;
	   
       }
    }
    return list_word;    
}  



     
WORD_LIST *
read_from_file(const char *filename, WORD_LIST *list_words)
{
    FILE *fp;
    char *buffer,  *line_buffer;
    int i, j, big_buffer = 400, line_flag =0;
    int len;
    struct stat buf;


    if(stat(filename, &buf))
       cs_critical_error(ERR_STAT_FAILED, filename);
       
    /* allocate space for holding spell check data */
    buffer = (char*)malloc(sizeof(char) * (buf.st_size +1));
    fp = fopen(filename, "r");
    if(!fp)
       cs_critical_error(ERR_FOPEN_READ_FAILED, filename);
    if(fread(buffer, buf.st_size, 1, fp) != 1)
       cs_critical_error(ERR_FREAD_FAILED, filename);
    fclose(fp);
    
    unlink(filename);
    
    *(buffer+buf.st_size) = '\0';

    line_buffer = (char*)malloc(sizeof(char) * (big_buffer +1));
    *line_buffer = '\0';

    for(j =0, len=strlen(buffer); j < len; j++)
    {
      i=0;
      line_flag =0;
      while(buffer[j] != '\n') 
      {
	   if( (buffer[j] == '&'  && i ==0) || 
			   (buffer[j] == '#' && i ==0 && buffer[j+1] != ')')) 
	       line_flag = 1;
	   if(line_flag)    /* then this word is spelled wrong */
	   {	
	       if((int)strlen(line_buffer) >= big_buffer)
	       {
                  line_buffer = realloc(line_buffer, big_buffer*2 +1);
	          big_buffer = big_buffer *2;
	       }
              if(buffer[j] == '\'')  /* escape single quote */
              {
	         line_buffer[i] = '\\';
	         i++;
	         line_buffer[i] = '\'';
	         line_buffer[i+1] = '\0';
	         i++;
	      }
	      else
	      {
	         line_buffer[i] = buffer[j];
	         line_buffer[i+1] = '\0';
	         i++;
	      }
	  }
          j++;	
      }  /* done processing one line */
      
      if(line_flag)
      {
         list_words = insert_word(line_buffer, list_words);
	 memset(line_buffer, 0, strlen(line_buffer));
      }
    }
    if(buffer)
	free(buffer);
    if(line_buffer)
       free(line_buffer);
    return list_words;
}



void
print_check_result(WORD_LIST *list_words)
{
#define MAX_TMP_NAME 30
    int i =0;
    char name[MAX_TMP_NAME];

    for(list_words->current=list_words->head; 
		    list_words->current; list_words->current = list_words->current->next)
    {
       snprintf(name, MAX_TMP_NAME, "list.%d.index", i);
       cs_set_int_value(name, i);
       snprintf(name, MAX_TMP_NAME, "list.%d.word", i);
       cs_set_value(name, list_words->current->err_word);
       snprintf(name, MAX_TMP_NAME, "list.%d.suggs", i);
       if(list_words->current->sugg_word_list)
          cs_set_value(name, list_words->current->sugg_word_list);
       else
          cs_set_value(name, "\'\'");

       
       i++;
    }
#undef MAX_TMP_NAME 
}



void
print_page( char *text, WORD_LIST *list_words)
{
    char server_string[MAX_PATH +1];
    get_server_string(server_string, MAX_PATH);  /* shared_server_string.c */
    cs_set_value("server_name", server_string);
    write_js_text(text);
    print_check_result(list_words);
}

    
        
int
main()
{
   char *text;
   char filename[MAX_PATH +1];
   char result[MAX_PATH +1];
   WORD_LIST *list_words =0;

   cs_cgi_init();
   list_words = initialize_word_list(list_words);
   text = read_parameters();

   write_file(text, filename);
   spell_checker(filename, result);
   read_from_file(result, list_words);
   print_page(text, list_words);
   if(text)
      free(text);
   free_word_list(list_words);
   if(list_words)
     free(list_words);
   cs_cgi_display("spellchecker", 1);
   cs_cgi_destroy();
   return 0;
}

   

