
/*
 Copyright (c)2001 Daniel Mealha Cabrita (dancab@iname.com)
 This code is under the terms of GNU General Public License
 */

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

#include "di_tools.h"

#include "config.h"

#ifdef USE_CUSTOM_STR_FUNCTIONS /* use the (usually) faster str routines instead the ones from string.h */
#include "custom_strf.h"
#define STRCHR custom_strchr
#define STRSTR custom_strstr
#else                           /* stick with the old good string.h */
#define STRCHR strchr
#define STRSTR strstr
#endif

/* will add a new word to the string.
   inserts ',' automatically */
/* returns current pointer of the buffer (because of realloc) */
char *append_word_n(char *write_to, const char *read_from, int howmany_chars)
{
    int needed_mem;
    char *new_buffer;
    const char word_separator[]=", "; /* inserted between words */

    if(!howmany_chars)
        return(write_to);

    /* check if the word already exists (will not add it if so) */
    {
        int this_word_already_exists=0;
        char *my_temp;
        char *current_pos;
        int word_separator_strlen;

        word_separator_strlen=strlen(word_separator);

        if(my_temp=(char *)malloc(sizeof(char) * (howmany_chars+1))){
            strncpy(my_temp, read_from, howmany_chars);
            current_pos=write_to;

            if(!strncmp(current_pos, my_temp, howmany_chars)){
                if((*(current_pos+howmany_chars)==',')||(!*(current_pos+howmany_chars))){
                    this_word_already_exists=1;
                    current_pos=STRCHR(current_pos, '\0');
                }
            }
            while(current_pos=STRSTR(current_pos, word_separator)){
                current_pos+=word_separator_strlen;
                if(!strncmp(current_pos, my_temp, howmany_chars)){
                    if((*(current_pos+howmany_chars)==',')||(!*(current_pos+howmany_chars))){
                        this_word_already_exists=1;
                        current_pos=STRCHR(current_pos, '\0');
                    }
                }
            }
            free((void *)my_temp);
        }

        if(this_word_already_exists)
            return(write_to);
    }

    needed_mem=((sizeof(char) * howmany_chars)+\
                (sizeof(char) * strlen(write_to))+\
                sizeof(char));

    if(*write_to)
        needed_mem+=sizeof(char) * strlen(word_separator);

    if(new_buffer=realloc(write_to, needed_mem)){
        if(*new_buffer)
            strcat(new_buffer, word_separator);
        strncat(new_buffer, read_from, howmany_chars);
        return(new_buffer);
    }
    return(write_to); /* could not reallocate mem, string is untouched */
}

/* will add a new word to the string.
   inserts ',' automatically */
/* returns current pointer of the buffer (because of realloc) */
char *append_word(char *write_to, const char *read_from)
{
    return(append_word_n(write_to, read_from, strlen(read_from)));
}

/* deallocate what was previously allocated with load_txtfile() */
void unload_dictionary(struct t_dictionary *prov_dic)
{
    free((void *)prov_dic->buffer_location);
    free(prov_dic);
}

/* loads dictionary to memory, returns pointer to t_dictionary
   or NULL. buffer must be unload_dictionary()'ed later */
struct t_dictionary *load_dictionary(const char *given_filename)
{
    FILE *my_file;
    int myfilesize;
    char *buffer_location;
    struct t_dictionary *t_dic_location;

    if(my_file=fopen(given_filename, "r")){
        fseek(my_file, 0, SEEK_END);
        myfilesize=ftell(my_file);
        fseek(my_file, 0, SEEK_SET);

        if((buffer_location=(char *)malloc((myfilesize+1+2+3)*sizeof(char)))){
            if(fread(buffer_location+2, myfilesize, 1, my_file)){
                /* for speed purposes, this, instead of a integral mem cleaning */
                *(buffer_location)='\0';
                *(buffer_location+1)='\0';
                *(buffer_location+myfilesize)='\0';
                *(buffer_location+myfilesize+1)='\0';
                *(buffer_location+myfilesize+2)='\0';
            }
        }
        fclose(my_file);

        /* allocate the t_dictionary structure */
        if(t_dic_location=(struct t_dictionary *)malloc(sizeof(struct t_dictionary))){
            t_dic_location->buffer_location=buffer_location;
            t_dic_location->dic_text_location=buffer_location+2;
            return(t_dic_location);
        }else{
            free((void *)buffer_location);
        }
    }
    return(NULL);
}

/* will allocate memory and put the line into
   free() this after usage */
char *create_line_from_this(const char *given_word)
{
    const char *current_pos;
    const char *begins_at;
    int total_chars=0;
    char *line_buff;

    /* finds beginning */
    current_pos=given_word;
    while((*current_pos)&&(*current_pos!='\n'))
        current_pos--;
    begins_at=++current_pos;
    /* finds out how many chars */
    while((*current_pos)&&(*current_pos!='\n')){
        current_pos++;
        total_chars++;
    }
    if(line_buff=(char *)malloc((total_chars+1)*sizeof(char))){
        strncpy(line_buff, begins_at, total_chars);
        *(line_buff+total_chars)='\0';
    }
    return(line_buff);
}

/* !=0 if good match, ==0 if not */
/* if accepts_partial_match!=0 will accept words which match
   their header to given_string */
int good_word_match(const char *given_string, int word_len, int accepts_partial_match)
{
    const char *end_of_word; /* the 1st byte after the end of found word */

    end_of_word=given_string+word_len;
    if(accepts_partial_match){
        int end_reached=0;

        while(!end_reached){
            switch(*end_of_word){
            case ' ':
            case '\0':
            case '\n':
            case ';':
            case ',':
                end_reached=1;
                break;
            default:
                end_of_word++;
                break;
            }
        }
    }

    /* test characters before the word */
    switch(*(given_string-1)){
    case '\0':
    case '\n':
        /* OK! */
        break;
    case ' ':
        switch(*(given_string-2)){
        case '\0':
        case ' ':
        case '\n':
        case ',':
        case ';':
        case '.':
        case '>':
        case ')':
            /* OK! */
            break;
        default:
            return(0);
            break;
        }
        break;
    default:
        return(0);
        break;
    }

    /* test characters after the word */
    switch(*end_of_word){
    case ' ':
//        if(*(end_of_word+1)!=' '){
        switch(*(end_of_word+1)){
        case ' ':
            break;
        case '_':
            break;
        default:
            if(*(end_of_word+2)!='>'){
                if(*(end_of_word+3)!='>'){
                    switch(*(end_of_word+1)){
                    case '0':
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7':
                    case '8':
                    case '9':
                        break;
                    default:
                        return(0);
                    }
                }
            }
            break;
        }
        break;
    case '\0':
    case '\n':
    case ';':
    case ',':
        /* OK! */
        break;
    default:
        return(0);
        break;
    }

    /* checks if the word is not inside parenthesis area */
    {
        const char *my_pos;

        my_pos=given_string;
        while((*my_pos)&&(*my_pos!='\n')){
            switch(*my_pos){
            case ')':
                return(0); /* aha.. forget it, it may be a comment not directly related to the meaning of the word */
            case '(':
                return(1); /* in this case assume it was out (no parenthesis inside parenthesis) */
            default:
                break;
            }
            my_pos++;
        }
    }

    return(1);
}

/* search the entire text */
const char *find_word(const char *where_to_search, const char *word_to_find)
{
    const char *current_point;
	int word_to_find_strlen;

    if(!*word_to_find)
        return(NULL);

    word_to_find_strlen=strlen(word_to_find);

    current_point=where_to_search;;
    while(current_point=(const char *)STRSTR(current_point, word_to_find)){
        if(good_word_match(current_point, word_to_find_strlen, 0))
            return(current_point);
        current_point++;
    }
    return(NULL);
}

/* search only the first words of each line (ignore english words inside definitions) */
const char *find_english_word(struct t_dictionary *prov_dic, const char *word_to_find)
{
    const char *current_point;
    int size_of_word;

    size_of_word=strlen(word_to_find);

    current_point=prov_dic->dic_text_location;
    while(current_point=(const char *)STRSTR(current_point, word_to_find)){
        if(((!*(current_point-1))||(*(current_point-1)=='\n'))&&\
           (*(current_point+size_of_word)==' ')&&\
           (*(current_point+size_of_word+1)==' '))
            return(current_point);
        if(!(current_point=STRCHR(current_point, '\n')))
			return(NULL);
    }
    return(NULL);
}

void free_text(const char *given_string)
{
    free((void *)given_string);
}

/* the word was not found, list the possible words which may be the right query */
/* the returned buffer must be free()'ed later */
/* (for english lang) */
const char *find_possible_words_en(struct t_dictionary *prov_dic, const char *word_to_find)
{
    char *new_buff;

    if(new_buff=(char *)malloc(1*sizeof(char))){ /* this will grow with reallocs */
        const char *current_point;
        int word_to_find_strlen;

        word_to_find_strlen=strlen(word_to_find);
        *new_buff='\0';
        current_point=prov_dic->dic_text_location;

        while(current_point){
            if(!strncmp(current_point, word_to_find, word_to_find_strlen)){
                const char *end_of_word;
                int word_size=0;
                int end_reached=0;

                /* find found-word size */
                end_of_word=current_point;
                while(!end_reached){
                    switch(*end_of_word){
                    case ' ':
                        if(*(end_of_word+1)==' '){
                            end_reached=1;
                            break;
                        }
                    default:
                        end_of_word++;
                        word_size++;
                        break;
                    }
                }
                new_buff=append_word_n(new_buff, current_point, word_size);
            }
            if(current_point=STRCHR(current_point, '\n'))
                current_point++;
        }
    }
    return(new_buff);
}

/* the word was not found, list the possible words which may be the right query */
/* the returned buffer must be free()'ed later */
/* (for russian lang) */
const char *find_possible_words_ru(struct t_dictionary *prov_dic, const char *word_to_find)
{
    char *new_buff;

    if(new_buff=(char *)malloc(1*sizeof(char))){ /* this will grow with reallocs */
        const char *current_point;
        int word_to_find_strlen;

        word_to_find_strlen=strlen(word_to_find);
        *new_buff='\0';

        current_point=prov_dic->dic_text_location;
        while(current_point=(const char *)STRSTR(current_point, word_to_find)){
            if(good_word_match(current_point, word_to_find_strlen, 1)){
                const char *end_of_word;
                int word_size=0;
                int end_reached=0;

                /* find found-word size */
                end_of_word=current_point;
                while(!end_reached){
                    switch(*end_of_word){
                    case ' ':
                    case '\0':
                    case '\n':
                    case ';':
                    case ',':
                        end_reached=1;
                        break;
                    default:
                        end_of_word++;
                        word_size++;
                        break;
                    }
                }
                /* add the found word */
                new_buff=append_word_n(new_buff, current_point, word_size);
            }
            current_point++;
        }
    }
    return(new_buff);
}

/* builds a string with english definitions of the given word */
/* returned buffer must be free()'ed later */
const char *query_english_word(struct t_dictionary *prov_dic, const char *search_this)
{
    const char *word_pos;
    const char *my_line;
    char *content_start;

    if(word_pos=find_english_word(prov_dic, search_this)){
        if(my_line=create_line_from_this(word_pos)){
            if(content_start=STRSTR(my_line, "  ")){
                content_start+=2;
                memmove((void *)my_line, (void *)content_start, (strlen(content_start)+1)*sizeof(char));
            }
            return(my_line);
        }
    }
    return(NULL);
}


/* routines for smart word ordering while querying russian words */

/* IMPORTANT NOTE:
   these routines have a far-from-perfect logic, in words with 2 levels
   of numbering like "tomorrow" the logic is broken.
   more common words with 1 level (or no level at all) work ok. */

/* total of ">" characters in string */
/* will return 1 or more, never 0 */
int meanings_in_line(const char *given_line)
{
	int totmean=0;
	const char *curpos=given_line;

	while(curpos=STRCHR(curpos, '>')){
		curpos++;
		totmean++;
	}

	if(!totmean)
		totmean++;
	return(totmean);
}

/* total of ">" characters before the given_word in string */
/* 1 = first element */
int meaning_number(const char *given_line, const char *given_word)
{
	int codret;

	codret=meanings_in_line(given_line)-
		meanings_in_line(STRSTR(given_line, given_word));
	if(!codret)
		codret++;
	return(codret);
}

/* copy which_word (0=first) from given_line, append to append_here */
/* this is a string with words separated by commas */
/* ATTENTION: the append_here buffer must meet buffer requirements */
void transplant_word(const char *given_line, char *append_here, int which_word)
{
	int to_jump=which_word;
	const char *read_from_here=given_line;
	int chars_to_copy=0;

	if(which_word){
		while(to_jump--){
			if(read_from_here=STRCHR(read_from_here, ','))
				read_from_here++;
		}
		read_from_here=STRCHR(read_from_here, ' ');
	}
	if(!read_from_here)
		return;
	if(which_word)
		read_from_here++;

	{
		const char *tempy=read_from_here;

		while((*tempy)&&(*tempy!=',')){
			chars_to_copy++;
			tempy++;
		}
	}

	if(*append_here)
		strcat(append_here, ", ");
	strncat(append_here, read_from_here, chars_to_copy);
}

/* the main routine, responsible for the words' reordering */
char *reorder_words_by_relevance(char *original_list, int *table_a, int *table_b)
{
	char *new_buff;
	int total_words_found=1;
	int last_a=0, last_b=0; /* 0 is illegal, so there will be never a word with 0,0 */
	int worst_a=0, worst_b=0;

   /* best word is: lowest B, if B==B then lower A is the best one */

	/* find out total of words (assume at least 1 word) */
	{
		const char *current_point=original_list;

		while(current_point=STRCHR(current_point, ',')){
			current_point++;
			total_words_found++;
		}
	}

	/* find out AB combination the given tables */
	{
		int my_loop=total_words_found;

		while(my_loop--){
			if(*(table_a+my_loop)>worst_a)
				worst_a=table_a[my_loop];
			if(*(table_b+my_loop)>worst_b)
				worst_b=table_b[my_loop];
		}

	}

	if(new_buff=(char *)malloc((strlen(original_list)+1)*sizeof(char))){
		int current_a, current_b;
		int temp_a, temp_b;
		int word_count=total_words_found;
		int still_more_to_do=1;

		*new_buff='\0';

		while(still_more_to_do){
			/* search for next priority code */
			current_a=worst_a;
			current_b=worst_b;
			{
				int my_loop=total_words_found;

				while(my_loop--){
					temp_a=*(table_a+my_loop);
					temp_b=*(table_b+my_loop);

					if((temp_b<current_b)&&(temp_b>=last_b)){
						if(!((temp_b==last_b)&&(temp_a<=last_a))){
							current_b=temp_b;
							current_a=temp_a;
						}
					}else if((temp_b==current_b)&&(temp_a<current_a)&&(temp_a>last_a)){
								current_a=temp_a;
					}
				}
			}

			/* append the priority-matching words */
			{
				int my_loop=total_words_found;

				while(my_loop--){
					if((*(table_b+my_loop)==current_b)&&(*(table_a+my_loop)==current_a)){
						transplant_word(original_list, new_buff, my_loop);
					}
				}
			}

			last_a=current_a;
			last_b=current_b;

			if((current_b==worst_b)&&(current_a==worst_a))
				still_more_to_do=0;
		}

		free((void *)original_list);
		return(new_buff);
	}
	return(original_list);
}

/* end of block of smart russian words' querying */


/* builds a string with english definitions of the given word */
/* returned buffer must be free()'ed later */
/* MAX FOUND WORDS = 1024, otherwise fixed-size table overflow and will crash */
const char *query_russian_word(struct t_dictionary *prov_dic, const char *search_this)
{
    char *new_buff;
    int table_a[1024]; /* meanings in line - used for words' sorting */
    int table_b[1024]; /* meaning number   - used for words' sorting */
    int total_words_found=0;

    if(new_buff=(char *)malloc(1*sizeof(char))){ /* this will grow because realloc */
        const char *current_point;
        char *my_line;
        char *zero_here;

        int old_size=0;
        int new_size;

        current_point=prov_dic->dic_text_location;
        *new_buff='\0';

        while(current_point=find_word(current_point, search_this)){
            my_line=create_line_from_this(current_point);
            if(zero_here=STRSTR(my_line, "  "))
                *zero_here='\0';

            /* append found word */
            new_buff=append_word(new_buff, my_line);
				if((new_size=strlen(new_buff))>old_size){
					*zero_here=' '; /* restore line to its original, i'll need it */
					table_a[total_words_found]=meanings_in_line(my_line);
					table_b[total_words_found]=meaning_number(my_line, search_this);
					old_size=new_size;
	            total_words_found++; /* not always a new word is appended */
	         }

            current_point=STRCHR(current_point, '\n');
            free_text(my_line);
        }
        if(!total_words_found){
            free_text(new_buff);
            return(NULL);
        }
    }

    new_buff=reorder_words_by_relevance(new_buff, table_a, table_b);
    return(new_buff);
}

