/*************************************************************************
*
* BG Office Project
* Copyright (C) 2000-2004 Radostin Radnev <radnev@yahoo.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
*************************************************************************/


#include <string.h>
#include <ctype.h>

#include "database.h"
#include "translator.h"


/*
* This class represents Translator.
* It holds databases and perform some text translation operations
*
* $Id: translator.cpp,v 1.2 2004/06/06 14:03:07 radnev Exp $
*/


// Declaration about max data and word length
const int Translator::MAX_WORD_LEN = 128;
const int Translator::MAX_WORD_NUM = 8;
const int Translator::MAX_DATA_LEN = 1024 * 256;

// Declaration about dictinonary constants
const int Translator::DUAL  = 1;
const int Translator::EN_BG = 2;
const int Translator::BG_EN = 3;

// Declarations about English Suffixs
const char Translator::EN_SUFFIXES[] = "s,ing,ed";

// Declaration about HTML decoration
const char Translator::BOLD_START[]          = "<B>";
const char Translator::BOLD_END[]            = "</B>";
const char Translator::TRANSCRIPTION_START[] = "<FONT color=\"blue\">";
const char Translator::TRANSCRIPTION_END[]   = "</FONT>";
const char Translator::BREAK_LINE[]          = "<BR>";
const char Translator::A_S_HL_HTML_START[]   = "<FONT color=\"red\">";
const char Translator::A_S_HL_HTML_END[]     = "</FONT>";
const char Translator::A_S_HL_NORMAL_START[] = "<----*";
const char Translator::A_S_HL_NORMAL_END[]   = "*---->";

// Declaration about word delimiter test
const char Translator::DELIMITER = ',';

// Declaration about transcription
const char Translator::TRANSCRIPTION_ANSI[] = "\241\242\243\244\245\246\247\250\251\255";
const char Translator::TRANSCRIPTION_UNICODE[10][8] = {
	"&#0230;", "&#0601;", "&#0596;", "&#0331;", "&#0952;",
	"&#0643;", "&#0652;", "&#0949;", "&#0658;", "&#0240;",
};


Translator::Translator(const int type) {
	dictionaryType            = type;
	separateMeanings          = true;
	boldDecoration            = true;
	htmlOutput                = true;
	tryWithoutSuffix          = true;
	advancedSearchState       = false;
	advancedSearchHighlight   = true;
	advancedSearchWholeWord   = true;
	advancedSearchExactPhrase = false;
	testLevel                 = 0;
	dataBuffer  = new char[MAX_DATA_LEN];
	dataBuffer2 = new char[MAX_DATA_LEN];
	wordBuffer  = new char[MAX_WORD_LEN];
	wordBuffer2 = new char[MAX_WORD_LEN];
	tempBuffer  = new char[(MAX_WORD_LEN)*MAX_WORD_NUM];
	wordPlus    = new bool[MAX_WORD_NUM];
	dictEnBg = NULL;
	dictBgEn = NULL;
}


Translator::~Translator() {
	delete [] dataBuffer;
	delete [] dataBuffer2;
	delete [] wordBuffer;
	delete [] wordBuffer2;
	delete [] tempBuffer;
	delete [] wordPlus;
	if (dictEnBg != NULL) {
		delete dictEnBg;
	}
	if (dictBgEn != NULL) {
		delete dictBgEn;
	}
}


bool Translator::init(const int type, const char *fileName, const long fixedLastWordPointer) {
	// Check input data
	if ((type != EN_BG) && (type != BG_EN)) {
		return false;
	}
	if ((dictionaryType != DUAL) && (type != dictionaryType)) {
		return false;
	}
	// Init database
	bool ret = false;
	if (type == EN_BG) {
		dictEnBg = new Database();
		ret = dictEnBg->init(fileName, fixedLastWordPointer);
	} else {
		dictBgEn = new Database();
		ret = dictBgEn->init(fileName, fixedLastWordPointer);
	}
	// Set current dictionary
	// If it is dual, en-bg is set as default
	if (dictionaryType == BG_EN) {
		currentDict = dictBgEn;
	} else {
		currentDict = dictEnBg;
	}
	return ret;
}


char *Translator::toLowerCase(const char *word, char *buf) {
	int i = 0;
	while (word[i] != '\0') {
		if (('A' <= word[i]) && (word[i] <= 'Z')) {
			buf[i] = word[i] - 'A' + 'a';
		} else if (('À' <= word[i]) && (word[i] <= 'ß')) {
			buf[i] = word[i] - 'À' + 'à';
		} else {
			buf[i] = word[i];
		}
		i++;
	}
	buf[i] = '\0';
	return buf;
}


char *Translator::toUpperCase(const char *word, char *buf) {
	int i = 0;
	while (word[i] != '\0') {
		if (('a' <= word[i]) && (word[i] <= 'z')) {
			buf[i] = word[i] + 'A' - 'a';
		} else if (('à' <= word[i]) && (word[i] <= 'ÿ')) {
			buf[i] = word[i] + 'À' - 'à';
		} else {
			buf[i] = word[i];
		}
		i++;
	}
	buf[i] = '\0';
	return buf;
}


char *Translator::toLegalDictionaryWord(const char *word, char *buf) {
	int i = 0;
	int j = 0;
	while (word[i] != '\0') {
		if ((('a' <= word[i]) && (word[i] <= 'z')) || (('à' <= word[i]) && (word[i] <= 'ÿ'))) {
			buf[j++] = word[i];
		} else if (('A' <= word[i]) && (word[i] <= 'Z')) {
			buf[j++] = word[i] - 'A' + 'a';
		} else if (('À' <= word[i]) && (word[i] <= 'ß')) {
			buf[j++] = word[i] - 'À' + 'à';
		}
		i++;
	}
	buf[j] = '\0';
	return buf;
}


char *Translator::trimWord(const char *word, char *buf) {
	bool add = false;
	int i = 0;
	int j = 0;
	while (word[i] != '\0') {
		if ((word[i] != ' ') || (add)) {
			add = true;
			buf[j++] = word[i];
		}
		i++;
	}
	buf[j] = '\0';
	while ((j > 0) && (buf[j - 1] == ' ')) {
		buf[--j] = '\0';
	}
	return buf;
}


bool Translator::isEnglishWordWithoutSuffix(const char *word, char *buf) {
	char *p;
	char *b;
	strcpy(buf, word);
	strcpy(tempBuffer, EN_SUFFIXES);
	p = tempBuffer - 1;
	while (p != NULL) {
		b = p + 1;
		p = strchr(b, ',');
		if (p != NULL) {
			*p = '\0';
		}
		if (strlen(buf) > strlen(b)) {
			char *l = buf + strlen(buf) - strlen(b);
			if (strcmp(l, b) == 0) {
				*l = '\0';
				return true;
			}
		}
	}
	return false;
}


bool Translator::goToNextWord() {
	return currentDict->goToNextWord();
}


char *Translator::countWords() {
	if (dictionaryType == DUAL) {
		dictEnBg->goToFirstWord();
		int i = 0;
		do {
			i++;
		} while (dictEnBg->goToNextWord());
		dictBgEn->goToFirstWord();
		int j = 0;
		do {
			j++;
		} while (dictBgEn->goToNextWord());
		// Put bg first, because the name is dual bg en
		sprintf(tempBuffer, "%d, %d", j, i);
	} else {
		currentDict->goToFirstWord();
		int i = 0;
		do {
			i++;
		} while (currentDict->goToNextWord());
		sprintf(tempBuffer, "%d", i);
	}
	return tempBuffer;
}


char *Translator::getWord(const bool toUpper) {
	char *ret = NULL;
	ret = currentDict->getWord();
	if (toUpper) {
		ret = toUpperCase(ret, wordBuffer);
	}
	return ret;
}


bool Translator::findWord(const char *word, char **result) {
	bool ret = false;
	toLegalDictionaryWord(word, wordBuffer);
	if (strlen(wordBuffer) == 0) {
		dataBuffer[0] = '\0';
		*result = dataBuffer;
	} else {
		setCurrentDictionary(wordBuffer[0]);
		ret = currentDict->findWord(wordBuffer);
		if ((!ret) && (tryWithoutSuffix) && (isEnglishWordWithoutSuffix(wordBuffer, wordBuffer2)) && (strlen(wordBuffer2) > 1)) {
			ret = currentDict->findWord(wordBuffer2);
			if (!ret) {
				ret = currentDict->findWord(wordBuffer);
			}
		}
		*result = transformResult(currentDict->getResult());
	}
	return ret;
}


char *Translator::transformResult(const char *result) {
	int j, i, m, n, u;
	char *b, *pu;
	char c;
	strcpy(dataBuffer2, result);
	if ((advancedSearchState) && (advancedSearchHighlight)) {
		for (i = 0; i < wordNumber; i++) {
			if (wordPlus[i]) {
				toLowerCase(dataBuffer2, dataBuffer);
				j = strlen(dataBuffer2);
				b = dataBuffer;
				n = 0;
				while ((b = search(b, tempBuffer + (MAX_WORD_LEN)*i, advancedSearchWholeWord)) != NULL) {
					m = b - dataBuffer + 2*n;
					memmove(dataBuffer2 + m + 1, dataBuffer2 + m, j - m + 2 + 2*n);
					dataBuffer2[m] = '|';
					m += strlen(tempBuffer + (MAX_WORD_LEN)*i) + 1;
					memmove(dataBuffer2 + m + 1, dataBuffer2 + m, j - m + 2 + 2*n);
					dataBuffer2[m] = '|';
					b += strlen(tempBuffer + (MAX_WORD_LEN)*i);
					n++;
				}
			}
		}
	}
	bool currentBold = false;
	bool currentColor = false;
	bool firstLineBreak = true;
	bool transcription = false;
	bool secondLineBreak = false;
	i = 0;
	j = 0;
	while ((c = dataBuffer2[i]) != '\0') {
		if (c == '\n') {
			if (secondLineBreak) {
				secondLineBreak = false;
			}
			if ((firstLineBreak) && (dataBuffer2[i + 1] != '[')) {
				firstLineBreak = false;
				secondLineBreak = true;
			}
			if (htmlOutput) {
				j += appendString(BREAK_LINE, j);
				if ((separateMeanings) && (!firstLineBreak)) {
					j += appendString(BREAK_LINE, j);
				}
			} else {
				dataBuffer[j++] = '\n';
				if ((separateMeanings) && (!firstLineBreak)) {
					dataBuffer[j++] = '\n';
				}
			}
			if (firstLineBreak) {
				firstLineBreak = false;
				secondLineBreak = true;
			}
		} else if ((advancedSearchState) && (advancedSearchHighlight) && (c == '|')) {
			if (!transcription) {
				if (htmlOutput) {
					// Fix the problem with cross tags
					if (currentBold) {
						j += appendString(BOLD_END, j);
					}
					// Append color end or color start tag
					j += appendString((currentColor ? A_S_HL_HTML_END : A_S_HL_HTML_START), j);
					// Fix the problem with cross tags
					if (currentBold) {
						j += appendString(BOLD_START, j);
					}
				} else {
					j += appendString((currentColor ? A_S_HL_NORMAL_END : A_S_HL_NORMAL_START), j);
				}
				currentColor = !currentColor;
			}
		} else {
			if ((secondLineBreak) && (c == '[')) {
				if (htmlOutput) {
					j += appendString(TRANSCRIPTION_START, j);
				}
				j += appendString("[", j);
				transcription = true;
				i++;
				continue;
			}
			if (transcription) {
				if (c == ']') {
					j += appendString("]", j);
					if (htmlOutput) {
						j += appendString(TRANSCRIPTION_END, j);
					}
					transcription = false;
					i++;
					continue;
				}
			}
			if ((htmlOutput) && (boldDecoration) && (!transcription)) {
				if (currentBold) {
					if (!((('A' <= c) && (c <= 'Z')) || (('a' <= c) && (c <= 'z')))) {
						j += appendString(BOLD_END, j);
						currentBold = false;
					}
				} else {
					if ((('A' <= c) && (c <= 'Z')) || (('a' <= c) && (c <= 'z'))) {
						j += appendString(BOLD_START, j);
						currentBold = true;
					}
				}
			}
			pu = strchr(TRANSCRIPTION_ANSI, c);
			if ((htmlOutput) && (pu != NULL)) {
				u = (pu - TRANSCRIPTION_ANSI);
				j += appendString(TRANSCRIPTION_UNICODE[u], j);
			} else {
				dataBuffer[j++] = c;
			}
		}
		i++;
	}
	if (currentBold) {
		j += appendString(BOLD_END, j);
	}
	if (htmlOutput) {
		j += appendString(BREAK_LINE, j);
	} else {
		dataBuffer[j++] = '\n';
	}
	dataBuffer[j] = '\0';
	return dataBuffer;
}


int Translator::appendString(const char *data, const int currentPointer) {
	int i = 0;
	while (data[i] != '\0') {
		dataBuffer[currentPointer + i] = data[i];
		i++;
	}
	return i;
}


char *Translator::getResult() {
	return transformResult(currentDict->getResult());
}


char *Translator::getNextRandomWord() {
	do {
		currentDict->getNextRandomWord();
	} while ((signed)strlen(currentDict->getResult()) < testLevel);
	return getWord(true);
}


void Translator::setTestParameters(const int level) {
	testLevel = level;
}


bool Translator::testWord(const char *word) {
	char *p;
	char *b;
	int l = 0;
	if (strlen(word) == 0) {
		return false;
	}
	toLowerCase(currentDict->getResult(), dataBuffer);
	// Analize word
	strcpy(tempBuffer, word);
	p = tempBuffer - 1;
	while (p != NULL) {
		b = p + 1;
		p = strchr(b, DELIMITER);
		l = (p == NULL ? strlen(b) : p - b);
		if (l >= MAX_WORD_LEN) {
			l = MAX_WORD_LEN - 1;
		}
		strncpy(wordBuffer2, b, l);
		wordBuffer2[l] = '\0';
		trimWord(wordBuffer2, wordBuffer2);
		toLowerCase(wordBuffer2, wordBuffer);
		if (wordBuffer[0] != '\0') {
			toLowerCase(currentDict->getWord(), wordBuffer2);
			if (strcmp(wordBuffer, wordBuffer2) != 0) {
				if (search(dataBuffer, wordBuffer, true) != NULL) {
					return true;
				}
			}
		}
	}
	return false;
}


bool Translator::setAdvancedSearchText(const char *word) {
	int i = 0;
	int j = 0;
	wordNumber = 0;
	bool string = false;
	bool closeWord = false;
	char c;
	char oldc = ' ';
	bool wPlus = true;
	while ((c = word[i]) != '\0') {
		if (c == '"') {
			string = !string;
			closeWord = true;
		} else if (string) {
			if (!((c == ' ') && (oldc == ' '))) {
				tempBuffer[(MAX_WORD_LEN)*wordNumber + j] = c;
				j++;
			}
		} else if ((c == '+') || (c == '-') || ((c == ' ') && (!advancedSearchExactPhrase))) {
			closeWord = true;
		} else {
			if (!((c == ' ') && (oldc == ' '))) {
				tempBuffer[(MAX_WORD_LEN)*wordNumber + j] = c;
				j++;
			}
		}
		if (closeWord) {
			if ((j > 1) || ((j == 1) && (tempBuffer[(MAX_WORD_LEN)*wordNumber] != ' '))) {
				tempBuffer[(MAX_WORD_LEN)*wordNumber + j] = '\0';
				wordPlus[wordNumber] = wPlus;
				wordNumber++;
				if (wordNumber == MAX_WORD_NUM) {
					break;
				}
			}
			if (c == '-') {
				wPlus = false;
			} else if (c == '+') {
				wPlus = true;
			} else if ((c == ' ') && (j > 0)) {
				wPlus = true;
			}
			j = 0;
		}
		closeWord = false;
		oldc = c;
		i++;
	}
	if (j > 0) {
		if ((j > 1) || ((j == 1) && (tempBuffer[(MAX_WORD_LEN)*wordNumber] != ' '))) {
			tempBuffer[(MAX_WORD_LEN)*wordNumber + j] = '\0';
			wordPlus[wordNumber] = wPlus;
			wordNumber++;
		}
	}
	if (wordNumber > 0) {
		for (i = 0; i < wordNumber; i++) {
			trimWord(tempBuffer + (MAX_WORD_LEN)*i, tempBuffer + (MAX_WORD_LEN)*i);
			toLowerCase(tempBuffer + (MAX_WORD_LEN)*i, tempBuffer + (MAX_WORD_LEN)*i);
		}
		advancedSearchState = true;
		setCurrentDictionary(tempBuffer[0]);
		firstTimeAdvancedSearch = true;
		firstDataBaseAdvancedSearch = true;
		return true;
	}
	return false;
}


bool Translator::searchNextWord() {
	int i;
	if (firstTimeAdvancedSearch) {
		currentDict->goToFirstWord();
		firstTimeAdvancedSearch = false;
	}
	else {
		if (!currentDict->goToNextWord()) {
			return false;
		}
	}
	while (true) {
		do {
			toLowerCase(currentDict->getResult(), dataBuffer);
			for (i = 0; i < wordNumber; i++) {
				if (((wordPlus[i]) && (search(dataBuffer, tempBuffer + (MAX_WORD_LEN)*i, advancedSearchWholeWord) == NULL))
					|| ((!wordPlus[i]) && (search(dataBuffer, tempBuffer + (MAX_WORD_LEN)*i, advancedSearchWholeWord) != NULL))) {
					break;
				}
			}
			if (i == wordNumber) {
				return true;
			}
		} while (currentDict->goToNextWord());
		if (firstDataBaseAdvancedSearch) {
			firstDataBaseAdvancedSearch = false;
			if (!setCurrentDictionary(tempBuffer[0], true)) {
				break;
			}
			currentDict->goToFirstWord();
		} else {
			break;
		}
	}
	return false;
}


bool Translator::setCurrentDictionary(const char c, const bool opposite) {
	if (dictionaryType == DUAL) {
		if ((('À' <= c) && (c <= 'ß')) || (('à' <= c) && (c <= 'ÿ'))) {
			if (opposite) {
				currentDict = dictEnBg;
			} else {
				currentDict = dictBgEn;
			}
		} else {
			if (opposite) {
				currentDict = dictBgEn;
			} else {
				currentDict = dictEnBg;
			}
		}
		return true;
	}
	return false;
}


char *Translator::search(char *text, const char *word, const bool exactSearch) {
	if (exactSearch) {
		char *b = text - 1;
		while (b != NULL) {
			b++;
			b = strstr(b, word);
			if ((b != NULL) &&
				((b == text) || ((b > text) &&
				(!isAlphaChar(text[b - text - 1])))) &&
				(((b - text + strlen(word) < strlen(text)) &&
				(!isAlphaChar(text[b - text + strlen(word)])) ||
				(b - text + strlen(word) == strlen(text))))) {
				return b;
			}
		}
	} else {
		return strstr(text, word);
	}
	return NULL;
}


bool Translator::isAlphaChar(const char c) {
	return (
		(('A' <= c) && (c <= 'Z')) ||
		(('a' <= c) && (c <= 'z')) ||
		(('À' <= c) && (c <= 'ß')) ||
		(('à' <= c) && (c <= 'ÿ'))
	);
}


char *Translator::extractText(const char *text) {
	int j = 0;
	int i = 0;
	bool tag = false;
	char c;
	while (((c = text[i]) != '\0') && (j < MAX_DATA_LEN)) {
		if (c == '<') {
			tag = true;
			if (strncmp(text + i, BREAK_LINE, strlen(BREAK_LINE)) == 0) {
				dataBuffer[j++] = '\n';
			}
		} else if (c == '>') {
			tag = false;
		} else if (!tag) {
			dataBuffer[j++] = c;
		}
		i++;
	}
	dataBuffer[j] = '\0';
	return dataBuffer;
}
