/*************************************************************************
 *
 *  Authors:
 *  Sun Microsystems Inc., October, 2000
 *  Pasi Ryhänen, 2002/08/15
 *  Harri Pitkänen (hatapitk@cc.jyu.fi), 2005
 *
 *  The Contents of this file are made available subject to the terms of
 *  GNU Lesser General Public License Version 2.1
 *
 *  GNU Lesser General Public License Version 2.1
 *  =============================================
 *  Copyright 2000 by Sun Microsystems, Inc.
 *  901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License version 2.1, as published by the Free Software Foundation.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *  MA  02110-1301, USA
 *
 ************************************************************************/

/* Correct md5sums of binary files:
 * 366b0d2e7d76fded3fd85cbc9ff70b06  Linux_x86/libsoikko.so
 * c2a0519e15418c60cdbf14f537fc9ae7  Linux_x86/soikko-hy.fi_FI.dic
 * 5498ec3109af4f8c94e4c1b5d7bc873e  Linux_x86/soikko-sp.fi_FI.dic */


#include <lfmacros.hxx>
#include <lfimpl.hxx>
#include <cstdio>
#include <cstring>
#ifdef UNX
#include <dlfcn.h>
#endif
#ifdef WNT
#include <windows.h>
#endif

#include <rtl/tencinfo.h>
#include <rtl/ustrbuf.hxx>

/*
 * TM API definitions for OpenOffice
 */

#ifndef _TM_OPENOFFICE_HXX_
#define _TM_OPENOFFICE_HXX_

#define TM_IGNORE_CAPS          (1<<1)
#define TM_IGNORE_DIGITS        (1<<2)

#ifdef UNX
#define SOIKKO_LIB "/Linux_x86/libsoikko.so"
#define SOIKKO_SPELLD "/Linux_x86/soikko-sp.fi_FI.dic"
#define SOIKKO_HYPHD "/Linux_x86/soikko-hy.fi_FI.dic"
#endif
#ifdef WNT
#define SOIKKO_LIB "/Windows/libsoikko.dll"
#define SOIKKO_SPELLD "/Windows/soikko-sp.fi_FI.dic"
#define SOIKKO_HYPHD "/Windows/soikko-hy.fi_FI.dic"
#endif


namespace LF_NAMESPACE {

struct tm_in_s {
	const unsigned char *buf;
	int buf_len;
};
typedef struct tm_in_s tm_in_t;

struct tm_out_s {
	int status;
	int err_start;
	int err_len;
	unsigned char *change;
};
typedef struct tm_out_s tm_out_t;

typedef int (version_func_type)(void);
typedef int (init_func_type)(void **);
typedef int (terminate_func_type)(void *);
typedef int (open_func_type)(void *, const char *);
typedef int (openhyph_func_type)(void *, const char *);
typedef int (encoding_func_type)(void *, const char *);
typedef int (option_func_type)(void *, const char *, const char *);
typedef int (check_func_type)(void *, const tm_in_t *, tm_out_t *);
typedef int (suggest_func_type) (void *, const char *, int,
				 char *, int, int *);
typedef int (hyphenate_func_type)(void *, const char *, char *);

typedef struct {
	bool init_done;
	void *handle;
	version_func_type   * version_func;
	init_func_type      * init_func;
	open_func_type      * open_func;
	openhyph_func_type  * openhyph_func;
	terminate_func_type * terminate_func;
	hyphenate_func_type * hyphenate_func;
	check_func_type     * check_func;
	suggest_func_type   * suggest_func;
	encoding_func_type  * encoding_func;
	option_func_type    * option_func;
} tm_dl_t;

int loadDl(const char *dl_name, tm_dl_t *dl);
int closeDl(tm_dl_t *dl);
static void * openDl(const char *file);
static void * getFunc(void *handle, const char *name);
void initSpellDl();

OUString instPath;
tm_dl_t spellDl;
void * speller_handle;
bool speller_init_done;
tm_dl_t hypDl;
void * hyphenator_handle;
bool hyp_init_done;

static void * openDl(const char *file_name) {
#ifdef WNT
  return (void *)LoadLibrary(file_name);
#endif
#ifdef UNX
  return dlopen(file_name, RTLD_NOW);
#endif
  return NULL;
}


static void * getFunc(void *handle, const char *name) {
	LF_LOG(("getFunc(%s)\n", name));
#ifdef WNT
 	return (void *)GetProcAddress((HMODULE)handle, name);
#endif
#ifdef UNX
	return dlsym(handle, name);
#endif
	return NULL;
}


#define LOAD_FUNC(name, func_type, func) \
	dl->func = (func_type *)getFunc(dl->handle, (name)); \
	if (dl->func == NULL)  { \
		LF_LOG(("getFunc(%s) failed\n", (name))); \
		closeDl(dl); \
		return -1; \
	}

int loadDl(const char *dl_name, tm_dl_t *dl) {
	int version_num;
	if (dl_name == NULL || dl == NULL)
		return -1;
	LF_LOG(("library %s\n", dl_name));
	dl->handle = openDl(dl_name);
	if (!dl->handle) {
		LF_LOG(("loadDl: soikko.so:n avaaminen ei onnistunut\n"));
		return -1;
	}
	LOAD_FUNC("Tm_version", version_func_type, version_func);
	version_num = dl->version_func();
	if (version_num >= 0x00020000) {
		LF_LOG(("loadDl: Tm_version not compatible: %x\n", version_num));
		closeDl(dl);
		return -1;
	}
	LOAD_FUNC("Tm_init",             init_func_type,      init_func);
	LOAD_FUNC("Tm_terminate",   terminate_func_type, terminate_func);
	LOAD_FUNC("Tm_open",             open_func_type,      open_func);
	LOAD_FUNC("Tm_open_hyph",    openhyph_func_type,  openhyph_func);
	LOAD_FUNC("Tm_hyphenate",   hyphenate_func_type, hyphenate_func);
	LOAD_FUNC("Tm_check_buffer",    check_func_type,     check_func);
	LOAD_FUNC("Tm_suggest",       suggest_func_type,   suggest_func);
	LOAD_FUNC("Tm_set_encoding", encoding_func_type,  encoding_func);
	LOAD_FUNC("Tm_set_option",     option_func_type,    option_func);
	return 0;
}


/*
 * Close the library.
 * Returns 0 on success, non-zero on error.
 */
int closeDl(tm_dl_t *dl) {
	int ret = 0;
	if (dl == NULL || dl->handle == NULL) return 0;
#ifdef WNT
	ret = FreeLibrary((HMODULE)dl->handle) ? 0 : 1;
#endif
#ifdef UNX
	ret =  dlclose(dl->handle);
#endif
	dl->handle = NULL;
	return ret;
}

void initSpellDl() {
	int st;
// FIXME this was originally rtl_TextEncoding aEnc = gsl_getSystemTextEncoding();
	rtl_TextEncoding aEnc = RTL_TEXTENCODING_ISO_8859_15;
	LF_LOG(("SystemTextEncoding %d\n", aEnc));
	OUString dict_path = instPath.concat(A2OU( SOIKKO_SPELLD ));
	OString dict_str(OU2ISO_1(dict_path));
	LF_LOG(("dictionary %s\n", dict_str.getStr()));
	OString dll_name(OU2ISO_1(instPath.concat(A2OU( SOIKKO_LIB ))));
	st = loadDl(dll_name.getStr(), &spellDl);
	if (st) {
		LF_LOG(("initSpellDL(): loadDl failed\n"));
		speller_handle = NULL;
		return;
	}
	st = spellDl.init_func(&speller_handle);
	LF_LOG(("init_func returned %d, handle %p\n", st, speller_handle));
	if (st)	{
		LF_LOG(("initSpellDL(): init_func failed\n"));
		speller_handle = NULL;
		closeDl(&spellDl);
		return;
	}
	st = spellDl.open_func(speller_handle, dict_str.getStr());
	LF_LOG(("open_func returned %d\n", st));
	if (st) {
		LF_LOG(("open_func(%s) failed\n", dict_str.getStr()));
		spellDl.terminate_func(speller_handle);
		speller_handle = NULL;
		closeDl(&spellDl);
		return;
	}
	if (aEnc == RTL_TEXTENCODING_ISO_8859_15) {
		LF_LOG(("Use Latin 9\n"));
		spellDl.encoding_func(speller_handle, "latin9");
	}
	speller_init_done = true;
	return;
}

void initHypDl() {
	int st;
	OUString dict_path = instPath.concat(A2OU( SOIKKO_HYPHD ));
	OString dict_str(OU2ISO_1(dict_path));
	LF_LOG(("dictionary %s\n", dict_str.getStr()));
	OString dll_name(OU2ISO_1(instPath.concat(A2OU( SOIKKO_LIB ))));
	st = loadDl(dll_name.getStr(), &hypDl);
	if (st) goto X_ERROR;
	st = hypDl.init_func(&hyphenator_handle);
    if (st) goto X_ERROR;
	st = hypDl.openhyph_func(hyphenator_handle, dict_str.getStr());
	if (st) {
		hypDl.terminate_func(hyphenator_handle);
		goto X_ERROR;
	}
	hyp_init_done = true;
	return;
	X_ERROR:
		LF_LOG(("initHypDl() failed\n"));
		hyphenator_handle = NULL;
		closeDl(&hypDl);
		return;
}



static sal_Int16 GetSpellFailure(const OUString &rWord) {
	sal_Int16 nRes = -1;

	LF_LOG(("GetSpellFailure()\n"));

	OString word(OU2ISO_15(rWord));
	
	if (word.getLength() > 74) return com::sun::star::linguistic2::SpellFailure::SPELLING_ERROR;

	int st = 0;
	if (spellDl.handle) {
		if (speller_handle) {
			tm_in_t tm_in;
			tm_out_t tm_out;
			tm_in.buf = (const unsigned char *)word.getStr();
			tm_in.buf_len = word.getLength();
      // check_func() returns 0==OK
			st = spellDl.check_func(speller_handle, &tm_in, &tm_out);
			LF_LOG(("  word(%s) st(%d)\n", word.getStr(), st));
		}
	}
	else {
		LF_LOG(("no dl_handle\n"));
	}
	if (st)
		nRes = com::sun::star::linguistic2::SpellFailure::SPELLING_ERROR;

	return nRes;
}

Reference< XSpellAlternatives > lfGetProposals(const OUString &rWord) {
	LF_LOG(("GetProposals()\n"));
	Reference< XSpellAlternatives > xRes;
	rtl_TextEncoding aEnc = rtl_getTextEncodingFromUnixCharset("ISO8859-15");
	OString word(OU2ISO_15(rWord));
	if (word.getLength() && word.getLength() <= 74)
	{
		SpellAlternatives *pAlt = new SpellAlternatives;
		pAlt->word = rWord;
		char sugg_buf[1024];
		int sugg_count = 0;
		int st;
		if (speller_handle) {
			st = spellDl.suggest_func(speller_handle, word.getStr(), word.getLength(),
					sugg_buf, sizeof sugg_buf, &sugg_count);
		}
		if (sugg_count)
		{
			Sequence< OUString > aStr( sugg_count );
			OUString *pStr = aStr.getArray();
			char *p = sugg_buf;
			for (int i = 0; i < sugg_count; i++) 
			{  
				OUString cvtwrd(p, strlen(p), aEnc);
				pStr[i] = cvtwrd;
				p += strlen(p) + 1;
			}
			pAlt->alternatives = aStr;
			xRes = pAlt;
			return xRes;
		}
		Sequence< OUString > aStr( 0 );
		pAlt->alternatives = aStr;
		xRes = pAlt;
		return xRes;
	}
	return xRes;
}

/* Begin function implementations */
void lfInitSpeller() {
	speller_init_done = false;
	spellDl.handle = NULL;
	speller_handle = NULL;
	if (instPath.equalsAscii("")) instPath = getInstallationPath();
}

void lfInitHyphenator() {
	hyp_init_done = false;
	hypDl.handle = NULL;
	hyphenator_handle = NULL;
	if (instPath.equalsAscii("")) instPath = getInstallationPath();
}

void lfDisposeSpeller() {
	if (spellDl.handle) {
		if (speller_handle) {
			spellDl.terminate_func(speller_handle);
		}
		speller_handle = NULL;
		closeDl(&spellDl);
	}
}

void lfDisposeHyphenator() {
	if (hyphenator_handle) {
		hypDl.terminate_func(hyphenator_handle);
		hyphenator_handle = NULL;
	}
	closeDl(&hypDl);
}

sal_Bool SAL_CALL lfIsValid( const OUString& rWord, sal_Bool isSpellWithDigits, sal_Bool isSpellUpperCase) 
		throw(::com::sun::star::lang::IllegalArgumentException,	::com::sun::star::uno::RuntimeException) {

	MutexGuard	aGuard( GetLinguMutex() );

	LF_LOG(("isValid() start\n"));
	if (!speller_init_done) initSpellDl();
	if (!rWord.getLength())	return TRUE;
	if (rWord.getLength() > 74) return TRUE;
	long tm_options = 0;
	if (!isSpellWithDigits) {
		tm_options |= TM_IGNORE_DIGITS;
		LF_LOG(("isSpellWithDigits == FALSE\n"));
	}
	if (!isSpellUpperCase) {
		tm_options |= TM_IGNORE_CAPS;
		LF_LOG(("isSpellUpperCase == FALSE\n"));
	}
	char option_buf[80];
	sprintf (option_buf, "%ld", tm_options);
	if (speller_handle) {
		spellDl.option_func(speller_handle, "options", option_buf);
	}

	sal_Int16 nFailure = GetSpellFailure(rWord);

	if (nFailure == -1) {
		LF_LOG(("isValid(): returning TRUE\n"));
		return TRUE;
	}
	else {
		LF_LOG(("isValid(): returning FALSE, nFailure = %d\n", nFailure));
		return FALSE;
	}
}



Reference< XSpellAlternatives > SAL_CALL lfSpell(const OUString& rWord, sal_Bool isSpellWithDigits, sal_Bool isSpellUpperCase) 
		throw(::com::sun::star::uno::RuntimeException) {

	MutexGuard aGuard(GetLinguMutex());
	LF_LOG(("spell()\n"));
	if (!speller_init_done) initSpellDl();
	if (!rWord.getLength() || rWord.getLength() > 74) return NULL;

	Reference< XSpellAlternatives > xAlt = NULL;
	if (!lfIsValid(rWord, isSpellWithDigits, isSpellUpperCase)) xAlt = lfGetProposals(rWord);
	return xAlt;
}


Reference< XHyphenatedWord > SAL_CALL lfHyphenate(const ::rtl::OUString& aWord, sal_Int16 nMaxLeading)
		throw(::com::sun::star::uno::RuntimeException) { 
	LF_LOG(("Hyphenator::hyphenate()\n"));
	if (!hyp_init_done) initHypDl();

	int nHyphenationPos = -1;
	int wordlen;
	char *hyphens;
	int i;

	OString encWord(OU2ISO_1(aWord));
	if (encWord.getLength() > 74) return NULL;

	OUString tohyphenate;
	Reference< XHyphenatedWord > xRes;

	wordlen = encWord.getLength();

	hyphens = new char[wordlen+2];

	LF_LOG(("  word %s\n", encWord.getStr()));
	LF_LOG(("  max leading characters: %d\n", nMaxLeading));
	if (hyphenator_handle) {
		hypDl.hyphenate_func(hyphenator_handle, encWord.getStr(), hyphens);
	}
	OUStringBuffer hyphenatedWord;
	sal_Int32 Leading = GetPosInWordToCheck( aWord, nMaxLeading );

	for (i = 0; i < wordlen; i++)
	{
		if ((hyphens[i + 1] == '^')  && (i < Leading))
		{
			nHyphenationPos = i;
		}
	}
	LF_LOG(("  pos %d, Leading %d\n", nHyphenationPos, Leading));
	// TODO: removing the apostrophe (vaa'an -> vaa-an) is the only special case

	if (nHyphenationPos == -1)
		xRes = NULL;
	else
	{
		xRes = new HyphenatedWord(aWord, nHyphenationPos);
	}

	delete[] hyphens;
	return xRes;
}


Reference< XPossibleHyphens > SAL_CALL lfCreatePossibleHyphens(const ::rtl::OUString& aWord)
		throw(::com::sun::star::uno::RuntimeException) {

	int i;
	LF_LOG(("Hyphenator::createPossibleHyphens\n"));
	int wordlen;
	char *hyphens;

	OString encWord;
	OUString tohyphenate;
	Reference< XPossibleHyphens > xRes;

	encWord = OU2ISO_15(aWord);
	wordlen = encWord.getLength();

	if (wordlen > 74) return NULL;

	hyphens = new char[wordlen + 2];

	LF_LOG(("  word %s\n", encWord.getStr()));
	if (hyphenator_handle) {
		hypDl.hyphenate_func(hyphenator_handle, encWord.getStr(), hyphens);
		LF_LOG(("  hyphens: '%s'\n", hyphens));
	}
	else {
		LF_LOG(("  Error: hyphenator_handle"));
	}

	/* Count the number of hyphenation points but remove the ones that correspond
	 * to a real hyphen in the word. This is required to prevent adding extra
	 * soft hyphen where it is not needed. */
	sal_Int16 hpcount = 0;
	for ( i = 0; i < encWord.getLength(); i++)
		if (hyphens[i] == '^') {
		if (aWord[i - 1] == '-') hyphens[i] = ' ';
		else hpcount++;
	}
	LF_LOG(("  number of hyphenation points: %d\n", hpcount));

	Sequence< sal_Int16 > aHyphPos( hpcount );
	sal_Int16 *pPos = aHyphPos.getArray();
	OUStringBuffer hyphenatedWordBuffer;
	OUString hyphenatedWord;
	sal_Int16 nHyphCount = 0;

	for (i = 0; i < wordlen; i++)
	{
		hyphenatedWordBuffer.append(aWord[i]);
		if (hyphens[i + 1] == '^')
		{
			pPos[nHyphCount] = i;
			hyphenatedWordBuffer.append(sal_Unicode('='));
			nHyphCount++;
		}
	}

	hyphenatedWord = hyphenatedWordBuffer.makeStringAndClear();
	LF_LOG(("\n  hyphenatedWord: '%s'\n", OU2A(hyphenatedWord)));
  
	xRes = new PossibleHyphens( aWord, hyphenatedWord, aHyphPos );

	delete hyphens;
	return xRes;
}

OUString lfHyphenatorDisplayName(const Locale& rLocale) {
	if (rLocale == Locale( A2OU("fi"), A2OU("FI"), OUString() ))
		return A2OU("Suomen kielen tavutus (Soikko)");
	else
		return A2OU("Finnish hyphenator (Soikko)");
}

OUString lfSpellerDisplayName(const Locale& rLocale) {
	if (rLocale == Locale( A2OU("fi"), A2OU("FI"), OUString() ))
		return A2OU( "Suomen kielen oikoluku (Soikko)" );
	else
		return A2OU( "Finnish spellchecker (Soikko)" );
}

}

#endif
