/*************************************************************************
 *
 *  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
 *
 ************************************************************************/

/*
 * This file is originally taken from linguistic/workben/sspellimp.cxx.
 * Modifications by Pasi Ryhänen <pry@iki.fi> 2002
 *
 * Disclaimer: the changes/additions are made without much knowledge
 * about the existing OpenOffice source code and practices, so there
 * may be stupid errors. All feedback is welcome.
 *
 * TODO:
 * - Handle different character sets (Latin 1, Latin 9, Windows?) correctly.
 *   initialize() calls gsl_getSystemTextEncoding(), but do we need
 *   to do anything else?
 * - Should we check (and use) the Locale parameter in every function?
 */

 
#include <cstdio>
#include <cppuhelper/factory.hxx>
#include <osl/file.hxx>
#include <cppuhelper/bootstrap.hxx>

#include <com/sun/star/uno/Reference.h>
#include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp>
#include <com/sun/star/linguistic2/SpellFailure.hpp>
#include <com/sun/star/registry/XRegistryKey.hpp>

#include "sspellimp.hxx"
#include "commonfi.hxx"

using namespace osl;
using namespace rtl;
using namespace cppu;
using namespace com::sun::star;
using namespace com::sun::star::beans;
using namespace com::sun::star::lang;
using namespace com::sun::star::uno;
using namespace com::sun::star::linguistic2;


///////////////////////////////////////////////////////////////////////////

namespace soikko {

SpellChecker::SpellChecker() :
  aEvtListeners	( GetLinguMutex() )
{
  SPDLOG(("SpellChecker()\n"));
  init_done = false;
  dl.handle = NULL;
  speller_handle = NULL;
  instPath = getInstallationPath();
  bDisposing = FALSE;
  pPropHelper = NULL;
}


SpellChecker::~SpellChecker()
{
  SPDLOG(("~SpellChecker()\n"));
  if (pPropHelper)
    pPropHelper->RemoveAsPropListener();
}


PropertyHelper_Spell & 
SpellChecker::GetPropHelper_Impl()
{
  SPDLOG(("GetProperHelper_Impl()\n"));
  if (!pPropHelper)
    {
      Reference< XComponentContext > compContext = defaultBootstrap_InitialComponentContext();
      Reference< XMultiComponentFactory > servManager = compContext->getServiceManager();
      Reference< XInterface > iFace = servManager->createInstanceWithContext(
           A2OU("com.sun.star.configuration.ConfigurationProvider"), compContext);
      Reference< XMultiServiceFactory > provider(iFace, UNO_QUERY);
      Reference< XInterface > iFace2 = provider->createInstance(A2OU("com.sun.star.linguistic2.LinguProperties"));
      Reference< XPropertySet >	xPropSet( iFace2, UNO_QUERY );

      pPropHelper = new PropertyHelper_Spell( (XSpellChecker *) this, xPropSet );
      xPropHelper = pPropHelper;
      pPropHelper->AddAsPropListener();	//! after a reference is established
    }
  return *pPropHelper;
}


Sequence< Locale > SAL_CALL 
SpellChecker::getLocales()
  throw(RuntimeException)
{
  MutexGuard	aGuard( GetLinguMutex() );

  SPDLOG(("getLocales()\n"));
  if (!aSuppLocales.getLength())
    {
      aSuppLocales.realloc( 1 );
      Locale *pLocale = aSuppLocales.getArray();
      pLocale[0] = Locale( A2OU("fi"), A2OU("FI"), OUString() );
    }

  return aSuppLocales;
}


sal_Bool SAL_CALL 
SpellChecker::hasLocale(const Locale& rLocale)
  throw(RuntimeException)
{
  MutexGuard	aGuard( GetLinguMutex() );

  sal_Bool bRes = FALSE;
  if (!aSuppLocales.getLength())
    getLocales();
  sal_Int32 nLen = aSuppLocales.getLength();
  for (sal_Int32 i = 0;  i < nLen;  ++i)
    {
      const Locale *pLocale = aSuppLocales.getConstArray();
      if (rLocale == pLocale[i])
	{
	  bRes = TRUE;
	  break;
	}
    }
  return bRes;
}


// Checks wether a word is OK in a given language (Locale) or not, and
// provides a failure type for the incorrect ones.
sal_Int16 
SpellChecker::GetSpellFailure( const OUString &rWord, const Locale &rLocale )
{
  sal_Int16 nRes = -1;

  SPDLOG(("GetSpellFailure()\n"));

  OString word(OU2ISO_15(rWord));
  int st = 0;
  if (dl.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 = dl.check_func(speller_handle, &tm_in, &tm_out);
      SPDLOG(("  word(%s) st(%d)\n", word.getStr(), st));
    }
  }
  else {
    SPDLOG(("no dl_handle\n"));
  }
  if (st)
    nRes = SpellFailure::SPELLING_ERROR;

  return nRes;
}


sal_Bool SAL_CALL 
SpellChecker::isValid( const OUString& rWord, const Locale& rLocale, 
		       const PropertyValues& rProperties ) 
  throw(IllegalArgumentException, RuntimeException)
{
  MutexGuard	aGuard( GetLinguMutex() );

  SPDLOG(("isValid() start\n"));
  if (!init_done)
    init_dl();
  if (rLocale == Locale()  ||  !rWord.getLength())
    return TRUE;

  if (!hasLocale( rLocale ))
#ifdef LINGU_EXCEPTIONS
    throw( IllegalArgumentException() );
#else
  return TRUE;
#endif

  PropertyHelper_Spell &rHelper = GetPropHelper();
  sal_Bool isSpellWithDigits = rHelper.IsSpellWithDigits();
  sal_Bool isSpellUpperCase = rHelper.IsSpellUpperCase();

  long tm_options = 0;
  if (!isSpellWithDigits) {  //!
    tm_options |= TM_IGNORE_DIGITS;
    SPDLOG(("isSpellWithDigits == FALSE\n"));
  }
  if (!isSpellUpperCase) { //!
    tm_options |= TM_IGNORE_CAPS;
    SPDLOG(("isSpellUpperCase == FALSE\n"));
  }
  char option_buf[80];
  sprintf (option_buf, "%ld", tm_options);
  if (speller_handle) {
    dl.option_func(speller_handle, "options", option_buf);
  }

  sal_Int16 nFailure = GetSpellFailure( rWord, rLocale );
  /* TODO is this necessary?
  if (nFailure != -1)
    {
      sal_Int16 nLang = LocaleToLanguage( rLocale );
      // Postprocess result for errors that should be ignored.
      // These should be already ignored because of option_func() above,
      // but keep this here anyway to be sure.
      // The internal ignore system in Soikko might be a bit inconsistent 
      // with OpenOffice's IsUpper() and HasDigits().
      if ( (!isSpellUpperCase && IsUpper( rWord, nLang ))
	   || (!isSpellWithDigits && HasDigits( rWord ))
	   )
	nFailure = -1;
    } */
  if (nFailure == -1) {
    SPDLOG(("isValid(): returning TRUE\n"));
    return TRUE;
  }
  else {
    SPDLOG(("isValid(): returning FALSE, nFailure = %d\n", nFailure));
    return FALSE;
  }
}


// Retrieves the return values for the 'spell' function call in case
// of a misspelled word.
// Especially it may give a list of suggested (correct) words.
Reference< XSpellAlternatives >
SpellChecker::GetProposals( const OUString &rWord, const Locale &rLocale )
{
  SPDLOG(("GetProposals()\n"));
  Reference< XSpellAlternatives > xRes;
  rtl_TextEncoding aEnc = rtl_getTextEncodingFromUnixCharset("ISO8859-15");
  OString word(OU2ISO_15(rWord));
  if (word.getLength())
  {
    SpellAlternatives *pAlt = new SpellAlternatives;
    pAlt->word = rWord;
    char sugg_buf[1024];
    int sugg_count = 0;
    int st;
    if (speller_handle) {
    st = dl.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;
}


Reference< XSpellAlternatives > SAL_CALL 
SpellChecker::spell( const OUString& rWord, const Locale& rLocale, 
		     const PropertyValues& rProperties ) 
  throw(IllegalArgumentException, RuntimeException)
{
  MutexGuard	aGuard( GetLinguMutex() );

  SPDLOG(("spell()\n"));
  if (!init_done)
    init_dl();
  if (rLocale == Locale()  ||  !rWord.getLength())
    return NULL;

  if (!hasLocale( rLocale ))
#ifdef LINGU_EXCEPTIONS
    throw( IllegalArgumentException() );
#else
  return NULL;
#endif

  Reference< XSpellAlternatives > xAlt;
  if (!isValid( rWord, rLocale, rProperties ))
    {
      xAlt = GetProposals( rWord, rLocale );
    }
  return xAlt;
}


    
	
sal_Bool SAL_CALL 
SpellChecker::addLinguServiceEventListener( 
					   const Reference< XLinguServiceEventListener >& rxLstnr ) 
  throw(RuntimeException)
{
  MutexGuard	aGuard( GetLinguMutex() );
	
  sal_Bool bRes = FALSE;   
  if (!bDisposing && rxLstnr.is())
    {
      bRes = GetPropHelper().addLinguServiceEventListener( rxLstnr );
    }
  return bRes;
}


sal_Bool SAL_CALL 
SpellChecker::removeLinguServiceEventListener( 
					      const Reference< XLinguServiceEventListener >& rxLstnr ) 
  throw(RuntimeException)
{
  MutexGuard	aGuard( GetLinguMutex() );
	
  sal_Bool bRes = FALSE;   
  if (!bDisposing && rxLstnr.is())
    {
      DBG_ASSERT( xPropHelper.is(), "xPropHelper non existent" );
      bRes = GetPropHelper().removeLinguServiceEventListener( rxLstnr );
    }
  return bRes;
}


OUString SAL_CALL 
SpellChecker::getServiceDisplayName( const Locale& rLocale ) 
  throw(RuntimeException)
{
  MutexGuard	aGuard( GetLinguMutex() );
  SPDLOG(("%s", OU2A(rLocale.Language)));
  if (rLocale == Locale( A2OU("fi"), A2OU("FI"), OUString() ))
    return A2OU( "Suomen kielen oikoluku (Soikko)" );
  else
    return A2OU( "Finnish spellchecker (Soikko)" );
}


void SAL_CALL 
SpellChecker::initialize( const Sequence< Any >& rArguments ) 
  throw(Exception, RuntimeException)
{
  MutexGuard	aGuard( GetLinguMutex() );
	
  SPDLOG(("initialize()\n"));
  if (!pPropHelper)
    {
      sal_Int32 nLen = rArguments.getLength();
      if (2 == nLen)
	{
	  Reference< XPropertySet > xPropSet;
	  rArguments.getConstArray()[0] >>= xPropSet;
	  //rArguments.getConstArray()[1] >>= xDicList;

	  //! Pointer allows for access of the non-UNO functions.
	  //! And the reference to the UNO-functions while increasing
	  //! the ref-count and will implicitly free the memory
	  //! when the object is not longer used.
	  pPropHelper = new PropertyHelper_Spell( (XSpellChecker *) this, xPropSet );
	  xPropHelper = pPropHelper;
	  pPropHelper->AddAsPropListener();	//! after a reference is established
	}
      else
	DBG_ERROR( "wrong number of arguments in sequence" );
    }
  init_dl();
}


void
SpellChecker::init_dl()
{
  int st;
  init_done = true;
  // FIXME this was originally rtl_TextEncoding aEnc = gsl_getSystemTextEncoding();
  rtl_TextEncoding aEnc = RTL_TEXTENCODING_ISO_8859_15;
  
  SPDLOG(("SystemTextEncoding %d\n", aEnc));
  
  OUString dict_path = instPath.concat(A2OU( SOIKKO_SPELLD ));
  
  OString dict_str(OU2ISO_1(dict_path));
  SPDLOG(("dictionary %s\n", dict_str.getStr()));
  
  OString dll_name(OU2ISO_1(instPath.concat(A2OU( SOIKKO_LIB ))));

  st = load_dl(dll_name.getStr(), &dl);
  if (st)
    goto ERROR;
  st = dl.init_func(&speller_handle);
  SPDLOG(("init_func returned %d, handle %p\n", st, speller_handle));
  if (st)
    goto ERROR;
  st = dl.open_func(speller_handle, dict_str.getStr());
  SPDLOG(("open_func returned %d\n", st));
  if (st) {
    SPDLOG(("open_func(%s) failed\n", dict_str.getStr()));
    dl.terminate_func(speller_handle);
    goto ERROR;
  }
  if (aEnc == RTL_TEXTENCODING_ISO_8859_15) {
    SPDLOG(("Use Latin 9\n"));
    dl.encoding_func(speller_handle, "latin9");
  }
  return;

 ERROR:
  SPDLOG(("initialize() failed\n"));
  speller_handle = NULL;
  close_dl(&dl);
  return;
}


void SAL_CALL 
SpellChecker::dispose() 
  throw(RuntimeException)
{
  MutexGuard	aGuard( GetLinguMutex() );
	
  SPDLOG(("dispose()\n"));
  if (!bDisposing)
    {
      bDisposing = TRUE;
      EventObject	aEvtObj( (XSpellChecker *) this );
      aEvtListeners.disposeAndClear( aEvtObj );
    }
  if (dl.handle) {
    if (speller_handle) {
      dl.terminate_func(speller_handle);
    }
    speller_handle = NULL;
    close_dl(&dl);
  }

}


void SAL_CALL 
SpellChecker::addEventListener( const Reference< XEventListener >& rxListener ) 
  throw(RuntimeException)
{
  MutexGuard	aGuard( GetLinguMutex() );
	
  SPDLOG(("addEventListener()\n"));
  if (!bDisposing && rxListener.is())
    aEvtListeners.addInterface( rxListener );
}


void SAL_CALL 
SpellChecker::removeEventListener( const Reference< XEventListener >& rxListener ) 
  throw(RuntimeException)
{
  MutexGuard	aGuard( GetLinguMutex() );
	
  SPDLOG(("removeEventListener()\n"));
  if (!bDisposing && rxListener.is())
    aEvtListeners.removeInterface( rxListener );
}


///////////////////////////////////////////////////////////////////////////
// Service specific part
//

OUString SAL_CALL 
SpellChecker::getImplementationName() 
  throw(RuntimeException)
{
  MutexGuard	aGuard( GetLinguMutex() );
  SPDLOG(("getImplementationName()\n"));
  return getImplementationName_Static();
}


sal_Bool SAL_CALL 
SpellChecker::supportsService( const OUString& ServiceName )
  throw(RuntimeException)
{
  MutexGuard	aGuard( GetLinguMutex() );

  SPDLOG(("supportsService()\n"));
  Sequence< OUString > aSNL = getSupportedServiceNames();
  const OUString * pArray = aSNL.getConstArray();
  for( sal_Int32 i = 0; i < aSNL.getLength(); i++ )
    if( pArray[i] == ServiceName )
      return TRUE;
  return FALSE;
}


Sequence< OUString > SAL_CALL 
SpellChecker::getSupportedServiceNames()
  throw(RuntimeException)
{
  MutexGuard	aGuard( GetLinguMutex() );
  SPDLOG(("getSupportedServiceNames()\n"));
  return getSupportedServiceNames_Static();
}


Sequence< OUString > 
SpellChecker::getSupportedServiceNames_Static() 
{
  SPDLOG(("getSupportedServiceNames_Static()\n"));
  Sequence< OUString > aSNS( 1 );
  aSNS.getArray()[0] = A2OU("com.sun.star.linguistic2.SpellChecker");
  return aSNS;
}

}


