// -----------------------------------------------------------------------------
// File GRAMMAR_ENGINE_API.CPP
//
// (c) by Koziev Elijah www.wordysoft.com all rights reserved 
//
// Manual: http://www.solarix.ru/for_developers/grammar-engine-api.shtml
//
// You must not eliminate, delete or supress these copyright strings
// from the file!
//
// Content:
//
// This file contains some basic API functions which can be used by other 
// programs under Win32 to perform operations with words and sentences (analysis
// and synthesys).
//
// API для доступа к грамматическому движку в виде обычной DLL Win32. Функции
// используются для выполнения операций со словами и предложениями.
//
// Описание API: http://www.solarix.ru/for_developers/api/grammar-engine-api.shtml
//
//
// 24.05.2007 - добавлена sol_TranslateToBases.
//
// 19.06.2007 - добавлено использование загружаемого внешнего стеммера
//              в sol_Stemmer.
//
// 17.09.2007 - исправлена ошибка в sol_GetAdjectiveForm с некорректной
//              перекодировкой результата.
//
// 30.09.2007 - добавлена sol_SeekThesaurus для работы с тезаурусом.
// 01.10.2007 - добавлена sol_ProjectMisspelledWord для нечеткой проекции.
// 06.12.2007 - добавлен API генератора фраз, включается макросом GM_PHRASE_GENERATOR
// 30.06.2008 - добавлен API OCR
// 11.07.2008 - поправлен учет флага Allow_Dynforms в sol_SyntaxAnalysis, в эту
//              же процедуру добавлен аргумент Allow_Unknown
// 17.07.2008 - перевод API call convention на stdcall
// 17.07.2008 - изменена сигнатура sol_GeneratePhrase2, она возвращает число
//              символов, записанных в буфер.
// 25.07.2008 - добавлены sol_FindClass, sol_FindEnum, sol_FindEnumState, sol_EnumInClass
// 27.07.2008 - добавлена sol_SeekNGrams
// 29.07.2008 - в процедуре sol_SeekThesaurus убран оверхед для типичных случаев
//              за счет предварительного формирования списков допустимых связок
//              и сохранения их в объекте движка 
// 29.07.2008 - переработ алгоритм работы sol_SeekThesaurus для глаголов, теперь
//              он автоматически делает поиск также для инфинитива.
// 01.08.2008 - исправлен тип возвращаемого значения в sol_LoadDictionary...,
//              все возвращаемые bool'ы переделаны на int'ы
// 02.08.2008 - код sol_SeekThesaurus перенесен в LexiconAutomat, чтобы
//              был унифицированный способ получать связанные статьи и в
//              поисковой системе.
// 20.11.2008 - добавлена sol_MatchNGrams
// 02.12.2008 - добавлена sol_Syllabs - разбивка слова на слоги
// 14.02.2009 - добавлены API токенизатора и SentenceBroker'а
// 25.02.2009 - добавлена sol_PreloadNGramsCaches
// 11.03.2009 - добавлены sol_FindLanguage и sol_HasLanguage
// 08.04.2009 - из API удалена sol_PreloadCaches
// 20.07.2009 - переработка API доступа к базе N-грамм: убрана sol_SeekNGrams,
//              добавлены sol_Seek2Grams, sol_Seek3Grams, sol_Seek4Grams,
//              sol_Seek5Grams
// 31.07.2010 - добавлена sol_ProcessPhraseEntry
// 06.08.2010 - добавлены sol_GetPhraseLanguage и sol_GetPhraseClass
// 21.08.2010 - добавлена sol_SetLinkFlags
// 29.08.2010 - добавлена sol_AddWord
// 04.09.2010 - в ряд функций добвлена поддержка сохранения подробностей ошибки
//              в переменной, содержимое которой возвращается через sol_GetError.
// 29.09.2010 - добавлены sol_SyntaxAnalysis8, sol_MorphologyAnalysis8,
//              sol_LoadDictionary8, sol_CreateGrammarEngine8
// 23.12.2010 - добавлены utf8-версии функций sol_Seek?Grams
// 27.03.2011 - добавлена critical section для Errors API
// 03.04.2011 - в вызовы ProjectWord добавлен параметр - ID текущего языка,
//              так как теперь правила проекции могут быть привязаны к языку.
// 19.04.2011 - функции sol_ListLinksTxt и sol_ListEntries в демо-версии отключены
// 22.06.2011 - добавлена sol_GetLeafLinkType
// 21.08.2011 - добавлена sol_ListPartsOfSpeech
// 26.09.2011 - изменения в сигнатуре функций sol_MorphologyAnalysis и 
//              sol_SyntaxAnalysis, по-другому задаются управляющие флаги.
// 27.09.2011 - добавлен режим ленивой загрузки лексикона.
// 09.01.2012 - переработка sol_Syllabs в связи с переделкой исполняющего движка,
//              в частности в сигнатуру добавлен аргумент LanguageID.
// -----------------------------------------------------------------------------
//
// CD->29.04.2007
// LC->10.01.2012
// --------------

#include <lem/config.h>

#if defined FAIND_IENGINES || defined DLL_EXPORTS

#include <lem/startup.h>
#include <lem/xml_parser.h>
#include <lem/solarix/res_pack.h>
#include <lem/solarix/dictionary.h>
#include <lem/solarix/sg_autom.h>
#include <lem/solarix/la_autom.h>
#include <lem/solarix/gg_autom.h>
#include <lem/solarix/aa_autom.h>
#include <lem/solarix/version.h>
#include <lem/solarix/tree_node.h>
#include <lem/solarix/sentencebroker.h>
#include <lem/system_config.h>
#include <lem/solarix/faind_internal.h>
#include <lem/solarix/text_processor.h>
#include <lem/solarix/sg_only_main_translations_tagfilter.h>
#include <lem/solarix/sg_tag_or_null_tagfilter.h>
#include <lem/solarix/print_variator.h>
#include <lem/solarix/ThesaurusNotesProcessor.h>
#include <lem/solarix/ThesaurusTagDefs.h>
#include <lem/solarix/LexiconStorage.h>
#include <lem/solarix/ThesaurusStorage.h>
#include <lem/solarix/WordLinkEnumerator.h>
#include <lem/solarix/PhraseEntries.h>
#include <lem/solarix/PhraseEnumerator.h>
#include <lem/solarix/WordEntries.h>
#include <lem/solarix/Thesaurus.h>
#include <lem/solarix/WordEntry.h>
#include <lem/solarix/ClassList.h>
#include <lem/solarix/ClassEnumerator.h>
#include <lem/solarix/PartOfSpeech.h>
#include <lem/solarix/CoordList.h>
#include <lem/solarix/TransactionGuard.h>
#include <lem/solarix/WordEntryEnumerator.h>
#include <lem/solarix/Paradigma.h>
#include <lem/solarix/dsa_main.h>
#include <lem/solarix/casing_coder.h>
#include <lem/logfile.h>
#include "ApiTranslationTracer.h"

#define HFLEXIONTABLE void*
#define HFLEXIONS void*
typedef HFAIND HGREN;


#if defined LEM_THREADING
#define CATCH_API(hengine) \
 catch( const lem::E_BaseException &x ) \
 { \
  lem::Process::CritSecLocker locker( & HandleEngine(hengine)->cs_error ); \
  HandleEngine(hengine)->error = x.what(); \
 } \
 catch( const std::exception &y ) \
 { \
  lem::Process::CritSecLocker locker( & HandleEngine(hengine)->cs_error ); \
  HandleEngine(hengine)->error = lem::to_unicode(y.what()); \
 } \
 catch(...) \
 { \
  lem::Process::CritSecLocker locker( & HandleEngine(hengine)->cs_error ); \
  HandleEngine(hengine)->error = L"Error"; \
 }
#else
#define CATCH_API(hengine) \
 catch( const lem::E_BaseException &x ) \
 { \
  HandleEngine(hengine)->error = x.what(); \
 } \
 catch( const std::exception &y ) \
 { \
  HandleEngine(hengine)->error = lem::to_unicode(y.what()); \
 } \
 catch(...) \
 { \
  HandleEngine(hengine)->error = L"Unhandled exception"; \
 }
#endif

// Returns the number of items in array of integers
// http://www.solarix.ru/api/en/sol_CountInts.shtml
// http://www.solarix.ru/api/ru/sol_CountInts.shtml
FAIND_API(int) sol_CountInts( HGREN_INTARRAY h )
{
 if( h==NULL )
  return -1;

 return CastSizeToInt(((lem::MCollect<int>*)h)->size());
}


// http://www.solarix.ru/api/en/sol_GetInt.shtml
// http://www.solarix.ru/api/ru/sol_GetInt.shtml
FAIND_API(int) sol_GetInt( HGREN_INTARRAY h, int i )
{
 if( h==NULL )
  return -1;

 try
  {
   return ((lem::MCollect<int>*)h)->get(i);
  }
 catch(...)
  {
   return -1;
  }
}


// http://www.solarix.ru/api/en/sol_DeleteInts.shtml
// http://www.solarix.ru/api/ru/sol_DeleteInts.shtml
FAIND_API(void) sol_DeleteInts( HGREN_INTARRAY h )
{
 delete h;
}





// ******************************************************************************
// Returns the max lexem length.
// http://www.solarix.ru/api/en/sol_MaxLexemLen.shtml
// Entry names and wordforms can be this length maximum (including C-string '\0')
// 
// Возвращает максимальную длину лексемы.
// Имена словарных статей и словоформ могут иметь такую длину максимум (с учетом
// завершающего 0).
// ******************************************************************************
FAIND_API(int) sol_MaxLexemLen( HGREN hEngine )
{
 return LEM_CSTRING_LEN+1;
}

// Maximum length of lexemes, part of speech names and so on, in utf-8.
FAIND_API(int) sol_MaxLexemLen8( HGREN hEngine )
{
 return LEM_CSTRING_LEN*6+1;
}



// ************************************************************************ 
// Return version info
// Если Major, Minor и Build не NULL, то через них возвращаются компоненты
// полной версии релиза.
//
// Возвращает 0 для DEMO, 1 для PRO, 2 для PREMIUM
// ************************************************************************ 
FAIND_API(int) sol_GetVersion( HGREN hEngine, int *Major, int *Minor, int *Build )
{
 Solarix::Version_Info ver;

 if( Major!=NULL ) *Major = ver.major;
 if( Minor!=NULL ) *Minor = ver.minor;
 if( Build!=NULL ) *Build = ver.build;

 #if defined SOLARIX_PREMIUM
 return 2;
 #elif defined SOLARIX_PRO
 return 1;
 #else
 return 0;
 #endif
}


// ****************************************************************************
// Быстрый поиск слова. Возвращается некий числовой код, одинаковый для всех
// словоформ в рамках одной словарной статьи, либо -1 если поиск не удался.
// ****************************************************************************
FAIND_API(int) sol_SeekWord( HGREN hEngine, const wchar_t *word, int Allow_Dynforms )
{
 if( !hEngine || word==NULL )
  return -1;

 try
  {
   if( HandleEngine(hEngine)->seeker==NULL )
    {
     MCollect<Word_Coord> found_list;
     lem::UCString w(word);
     HandleEngine(hEngine)->dict->GetLexAuto().ProjectWord( w, found_list, UNKNOWN );
     if( !found_list.empty() )
      return found_list.front().GetEntry();
     else
      return UNKNOWN;
    }
   else
    {
     // Создание объекта на стеке. Плохо.
     lem::UCString w(word);
     return HandleEngine(hEngine)->seeker->Find( w, Allow_Dynforms==1 );
    }
  }
 CATCH_API(hEngine)

 return -2;
}




FAIND_API(int) sol_OCR_RecognizeDIB( HGREN hEngine, const void* hDib, wchar_t **Buffer )
{
 #if defined SOLARIX_PREMIUM
 if( hDib==NULL || Buffer==NULL || hEngine==NULL || !HandleEngine(hEngine)->dict )
  return -2;

 *Buffer=NULL;

 if( !HandleEngine(hEngine)->dict->OCRAvailable() )
  {
   return -1;
  }
 
 try
  {
   Solarix::Ocr::OCREngine& ocr = HandleEngine(hEngine)->dict->GetOCR();
   lem::UFString buffer;
   buffer.reserve(4000);
   ocr.RecognizeDIB( (HBITMAP)hDib, buffer, NULL );

   *Buffer = (wchar_t*)malloc( (buffer.length()+1)*sizeof(wchar_t) );
   if( *Buffer!=NULL )
    {
     lem::lem_strcpy( *Buffer, buffer.c_str() );
     return 1;
    }
   else
    {
     return -3;
    }
  }
 catch(...)
  {
   return -1;
  }
 
 #endif
 return -2;
}

FAIND_API(int) sol_OCR_RecognizeFileW( HGREN hEngine, const wchar_t *filename, wchar_t **Buffer )
{
 #if defined SOLARIX_PREMIUM
 if( Buffer==NULL || hEngine==NULL || lem::lem_is_empty(filename) || !HandleEngine(hEngine)->dict )
  return -2;

 *Buffer=NULL;

 if( !HandleEngine(hEngine)->dict->OCRAvailable() )
  {
   return -1;
  }
 
 Solarix::Ocr::OCREngine& ocr = HandleEngine(hEngine)->dict->GetOCR();
 lem::UFString buffer;
 buffer.reserve(4000);
 ocr.RecognizeFile( lem::Path(filename), buffer, NULL );

 *Buffer = (wchar_t*)malloc( (buffer.length()+1)*sizeof(wchar_t) );
 if( *Buffer!=NULL )
  {
   lem::lem_strcpy( *Buffer, buffer.c_str() );
   return buffer.length();
  }
 else
  {
   return -3;
  }
 

 #endif
 return -2;
}


//////////////////////////////////////////////////////////////////////
///
/// \brief Загружена ли подсистема OCR
///
//////////////////////////////////////////////////////////////////////
FAIND_API(int) sol_OCR_Available( HGREN hEngine )
{
 #if defined SOLARIX_PREMIUM
 if( hEngine==NULL || !HandleEngine(hEngine)->dict )
  return 0;

 return HandleEngine(hEngine)->dict->OCRAvailable() ? 1 : 0;
 
 #endif
 return 0;
}


// Releases the memory allocated by the engine and returned to the client code.
FAIND_API(int) sol_Free( HGREN hEngine, void *ptr )
{
 try
  {
   free(ptr);
   return 0;
  }
 CATCH_API(hEngine)

 return -1;
}


FAIND_API(int) sol_CountStrings( HGREN_STR hStrings )
{
 if( hStrings==NULL )
  return 0;

 try
  {
   return CastSizeToInt(HandleStrList(hStrings)->list.size());
  }
 catch(...)
  {
   return -2;
  }
}


FAIND_API(int) sol_GetStrings( HGREN_STR hStrings, wchar_t **Res )
{
 if( hStrings==NULL || Res==NULL )
  return -1;

 try
  {
   for( lem::Container::size_type i=0; i<HandleStrList(hStrings)->list.size(); i++ )
    wcscpy( Res[i], HandleStrList(hStrings)->list[i].c_str() );

   return 0;
  }
 catch(...)
  {
   return -2;
  }
}



FAIND_API(int) sol_GetStringLen( HGREN_STR hStrings, int i )
{
 if( hStrings==NULL )
  return -1;

 try
  {
   return HandleStrList(hStrings)->list[i].length();
  }
 catch(...)
  {
   return -2;
  }
}

FAIND_API(int) sol_GetStringW( HGREN_STR hStrings, int i, wchar_t *Res )
{
 if( hStrings==NULL || Res==NULL )
  return -1;

 try
  {
   wcscpy( Res, HandleStrList(hStrings)->list[i].c_str() );
   return 0;
  }
 catch(...)
  {
   return -2;
  }
}


FAIND_API(int) sol_GetStringA( HGREN_STR hStrings, int i, char *Res )
{
 if( hStrings==NULL || Res==NULL )
  return -1;

 try
  {
   strcpy( Res, lem::to_ascii(HandleStrList(hStrings)->list[i].c_str()).c_str() );
   return 0;
  }
 catch(...)
  {
   return -2;
  }
}

FAIND_API(int) sol_GetString8( HGREN_STR hStrings, int i, char *ResUtf8 )
{
 if( hStrings==NULL || ResUtf8==NULL )
  return -1;

 try
  {
   strcpy( ResUtf8, lem::to_utf8(HandleStrList(hStrings)->list[i].c_str()).c_str() );
   return 0;
  }
 catch(...)
  {
   return -2;
  }
}



FAIND_API(int) sol_DeleteStrings( HGREN_STR hStrings )
{
 try
  {
   if( hStrings!=NULL )
    delete HandleStrList(hStrings);

   return 0;
  }
 catch(...)
  {
   return -2;
  }
}


#if defined SOLARIX_SEARCH_ENGINE && !defined SOLARIX_SYNONYMIZER_ENGINE
 #undef FAIND_API
 #define FAIND_API(x) static x
#endif


#define ENGINE ((Faind_Engine*)hEngine)

using namespace lem;
using namespace Solarix;



#if defined SOLARIX_GRAMMAR_ENGINE || defined SOLARIX_SYNONYMIZER_ENGINE
// ***********************************************************************
// Grammar engine is completely removed from memory.
// Словарь полностью удаляется из оперативной памяти.
//
// http://www.solarix.ru/api/en/sol_DeleteGrammarEngine.shtml
// ***********************************************************************
FAIND_API(int) sol_DeleteGrammarEngine( HGREN hEngine )
{
 try
  {
   delete hEngine;
   return 1;
  }
 CATCH_API(hEngine)

 return 0;
}
#endif


#if defined SOLARIX_GRAMMAR_ENGINE || defined SOLARIX_SYNONYMIZER_ENGINE


FAIND_API(int) sol_LoadDictionaryExW( HGREN hEngine, const wchar_t *Filename, int Flags );

// Initializes a new instance of the grammatical dictionary in memory.
// http://www.solarix.ru/api/en/sol_CreateGrammarEngine.shtml



FAIND_API(HFAIND) sol_CreateGrammarEngineExW( const wchar_t *DictionaryXml, int Flags )
{
 lem::Init();

 int rc=0;

 #if defined SOLARIX_GRAMMAR_ENGINE || defined SOLARIX_SYNONYMIZER_ENGINE
 HGREN hEngine = new Faind_Engine;
 #else
 HGREN hEngine = sol_CreateSearchEngine();

 rc = sol_ReadIniW(hEngine,NULL);
 if( rc!=0 )
  goto Failed;
 #endif

 if( !lem::lem_is_empty(DictionaryXml) )
  {
   rc = sol_LoadDictionaryExW(hEngine,DictionaryXml,Flags);
   if( rc!=1 )
    goto Failed;
  }
 else
  {
   rc=0;
  } 

 #if !defined SOLARIX_GRAMMAR_ENGINE
// rc = sol_LoadGrammarEngine(hEngine);
// if( rc!=0 )
//  goto Failed;
 #endif

 return hEngine;

 Failed:;
  #if defined SOLARIX_GRAMMAR_ENGINE || defined SOLARIX_SYNONYMIZER_ENGINE
  sol_DeleteGrammarEngine(hEngine);
  #else
  sol_DeleteSearchEngine(hEngine);
  #endif
 
 return NULL;
}

FAIND_API(HFAIND) sol_CreateGrammarEngineW( const wchar_t *DictionaryXml )
{
 return sol_CreateGrammarEngineExW( DictionaryXml, 0 );
}

#endif


#if defined SOLARIX_GRAMMAR_ENGINE || defined SOLARIX_SYNONYMIZER_ENGINE
// http://www.solarix.ru/api/en/sol_CreateGrammarEngine.shtml

FAIND_API(HFAIND) sol_CreateGrammarEngineExA( const char *DictionaryXml, int Flags )
{
 lem::Init();

 int rc=0;


 #if defined SOLARIX_GRAMMAR_ENGINE || defined SOLARIX_SYNONYMIZER_ENGINE
 HGREN hEngine = new Faind_Engine;
 #else
 HGREN hEngine = sol_CreateSearchEngine();
 rc = sol_ReadIniW(hEngine,NULL);
 if( rc!=0 )
  goto Failed;
 #endif

 if( !lem::lem_is_empty(DictionaryXml) )
  {
   rc = sol_LoadDictionaryA(hEngine,DictionaryXml);
   if( rc!=1 )
    goto Failed;
  }
 else
  {
   rc=0;
  } 
 
 #if !defined SOLARIX_GRAMMAR_ENGINE
// rc = sol_LoadGrammarEngine(hEngine);
// if( rc!=0 )
//  goto Failed;
 #endif

 return hEngine;

 Failed:;
  #if defined SOLARIX_GRAMMAR_ENGINE || defined SOLARIX_SYNONYMIZER_ENGINE
  sol_DeleteGrammarEngine(hEngine);
  #else
  sol_DeleteSearchEngine(hEngine);
  #endif

 return NULL;
}

FAIND_API(HFAIND) sol_CreateGrammarEngineA( const char *DictionaryXml )
{
 return sol_CreateGrammarEngineExA( DictionaryXml, 0 );
}

#endif


#if defined SOLARIX_GRAMMAR_ENGINE || defined SOLARIX_SYNONYMIZER_ENGINE
// http://www.solarix.ru/api/en/sol_CreateGrammarEngine.shtml
FAIND_API(HFAIND) sol_CreateGrammarEngine8( const char *DictionaryXmlUtf8 )
{
 lem::Init();
 return sol_CreateGrammarEngineW(lem::from_utf8(DictionaryXmlUtf8).c_str());
}

FAIND_API(HFAIND) sol_CreateGrammarEngineEx8( const char *DictionaryXmlUtf8, int Flags )
{
 lem::Init();
 return sol_CreateGrammarEngineExW(lem::from_utf8(DictionaryXmlUtf8).c_str(),Flags);
}
#endif



#if defined SOLARIX_GRAMMAR_ENGINE || defined SOLARIX_SYNONYMIZER_ENGINE

// http://www.solarix.ru/api/en/sol_LoadDictionary.shtml
FAIND_API(int) sol_LoadDictionaryA( HGREN hEngine, const char *Filename )
{
 return sol_LoadDictionaryW( hEngine, to_unicode(Filename).c_str() );
}


// ***********************************************************************
// Load the grammatical dictionary engine in memory.
// Загрузка словаря в оперативную память из указанного дискового файла.
// http://www.solarix.ru/api/en/sol_LoadDictionary.shtml
//
// Returns: 
//  0 - dictionary was not loaded
//  1 - dictionary was successfuly loaded
//  2 - dictionary is already loaded
// ***********************************************************************
FAIND_API(int) sol_LoadDictionaryW( HGREN hEngine, const wchar_t *Filename )
{
 if( lem::lem_is_empty(Filename) || hEngine==NULL )
  return 0;

 #if defined LEM_THREADS
 lem::Process::CritSecLocker guard(&ENGINE->cs);
 #endif

 if( !!ENGINE->dict )
  // Dictionary is already loaded.
  return 2;

 int loaded_ok=0;

 try
  {
   loaded_ok = ENGINE->Load(Filename,false) ? 1 : 0;
  }
 CATCH_API(hEngine)

 return loaded_ok;
}


// http://www.solarix.ru/api/en/sol_LoadDictionary.shtml
FAIND_API(int) sol_LoadDictionary8( HGREN hEngine, const char *FilenameUtf8 )
{
 return sol_LoadDictionaryW( hEngine, lem::from_utf8(FilenameUtf8).c_str() );
}


FAIND_API(int) sol_LoadDictionaryEx8( HGREN hEngine, const char *FilenameUtf8, int Flags )
{
 return sol_LoadDictionaryExW( hEngine, lem::from_utf8(FilenameUtf8).c_str(), Flags );
}


FAIND_API(int) sol_LoadDictionaryExA( HGREN hEngine, const char *Filename, int Flags )
{
 return sol_LoadDictionaryExW( hEngine, to_unicode(Filename).c_str(), Flags );
}

FAIND_API(int) sol_LoadDictionaryExW( HGREN hEngine, const wchar_t *Filename, int Flags )
{
 if( lem::lem_is_empty(Filename) || hEngine==NULL )
  return 0;

 #if defined LEM_THREADS
 lem::Process::CritSecLocker guard(&ENGINE->cs);
 #endif

 if( !!ENGINE->dict )
  // Dictionary is already loaded.
  return 2;

 int loaded_ok=0;

 try
  {
   const bool LazyLexicon = (Flags & 0x00000001) == 0x00000001;
   loaded_ok = ENGINE->Load(Filename,LazyLexicon) ? 1 : 0;
  }
 CATCH_API(hEngine)

 return loaded_ok;
}



#endif


// Returns the value indicating whether the dictionary has been initialized and loaded
// http://www.solarix.ru/api/en/sol_IsDictionaryLoaded.shtml
FAIND_API(int) sol_IsDictionaryLoaded( HGREN hEngine )
{
 #if defined LEM_THREADS
 lem::Process::CritSecLocker guard(&ENGINE->cs);
 #endif
 return (hEngine!=NULL && !!HandleEngine(hEngine)->dict) ? 1 : 0;
}



#if defined SOLARIX_GRAMMAR_ENGINE
// Drops the grammatical dictionary in-memory structures, frees the allocated resources.
// http://www.solarix.ru/api/en/sol_UnloadDictionary.shtml
FAIND_API(void) sol_UnloadDictionary( HGREN hEngine )
{
 if( hEngine==NULL )
  return;

 try
  {
   #if defined LEM_THREADS
   lem::Process::CritSecLocker guard(&ENGINE->cs);
   #endif
   
   ENGINE->dict.reset();
   ENGINE->seeker.reset();
   ENGINE->fuzzy.reset();
  }
 CATCH_API(hEngine)
 
 return;
}
#endif



// Check the availability of lexicon and thesaurus for specified language
// http://www.solarix.ru/api/en/sol_HasLanguage.shtml
FAIND_API(int) sol_HasLanguage( HGREN hEngine, int LanguageID )
{
 if( hEngine==NULL || LanguageID==UNKNOWN || HandleEngine(hEngine)->dict.IsNull() )
  return 0;

 int ret=0;
 try
  {
   #if defined SOL_CAA && !defined SOL_NO_AA
   lem::MCollect<int> langs;
   HandleEngine(hEngine)->dict->GetLanguages(langs);
   return langs.find(LanguageID)!=UNKNOWN;
   #endif
  }
 CATCH_API(hEngine)

 return 0;
}


// ***********************************************************************
// Count the total number of word and phrase entries in dictionary.
// Сколько словарных статей в словаре
//
// http://www.solarix.ru/api/en/sol_CountEntries.shtml
// http://www.solarix.ru/api/ru/sol_CountEntries.shtml
// ***********************************************************************
FAIND_API(int) sol_CountEntries( HGREN hEngine )
{
 if( hEngine==NULL || !HandleEngine(hEngine)->dict )
  return -1;

 int ret=-1;
 try
  {
   const int n_words = HandleEngine(hEngine)->dict->GetSynGram().GetnEntry(ANY_STATE);
   const int n_phrases = HandleEngine(hEngine)->dict->GetSynGram().GetStorage().CountPhrases();
   return n_words+n_phrases;
  }
 CATCH_API(hEngine)

 return ret;
}


// ***********************************************************************
// Count the total number of wordforms in dictionary.
// Сколько словоформ (явно определенных) в словаре.
// ***********************************************************************
FAIND_API(int) sol_CountForms( HGREN h )
{
 if( h==NULL || !HandleEngine(h)->dict )
  return -1;

 try
  {
   return HandleEngine(h)->dict->GetSynGram().Count_Forms();
  }
 CATCH_API(h)

 return -1;
}


// ****************************************************************************
// Returns the total number of links (word and phrase relations) in thesaurus.
// Статистика тезауруса: сколько записей всего.
//
// http://www.solarix.ru/api/en/sol_CountLinks.shtml
// ****************************************************************************
FAIND_API(int) sol_CountLinks( HGREN hEngine )
{
 if( hEngine==NULL || !HandleEngine(hEngine)->dict )
  return -1;

 try
  {
   return CastSizeToInt(HandleEngine(hEngine)->dict->GetSynGram().Get_Net().CountAllLinks());
  }
 CATCH_API(hEngine)

 return -1;
}






// ********************************************************
// Возвращает номер версии словаря.
// http://www.solarix.ru/api/ru/sol_DictionaryVersion.shtml
// ********************************************************
FAIND_API(int) sol_DictionaryVersion( HGREN hEngine )
{
 if( hEngine==NULL || !HandleEngine(hEngine)->dict )
  return -1;

 return HandleEngine(hEngine)->dict->version.code;
}






// Set default language ID. This is vital parameter for correct normalization of characters.
FAIND_API(int) sol_SetLanguage( HGREN hEngine, int LanguageID )
{
 if( hEngine==NULL || !HandleEngine(hEngine)->dict )
  return -1;

 #if defined LEM_THREADS
 lem::Process::CritSecLocker guard(&HandleEngine(hEngine)->cs);
 #endif
 
 HandleEngine(hEngine)->DefaultLanguage = LanguageID;
 HandleEngine(hEngine)->dict->SetDefaultLanguage(LanguageID);

 return 0;
}


// ***********************************************************************
// Поиск словоформы в Лексиконе.
// В случае успеха возвращается число успешных проекций, и индексы статьи
// и формы в полях ientry для ПЕРВОЙ проекции.
// и iform соответственно.
// Через поле iclass возвращается индекс грамматического класса.
//
// Если найти не удалось - возвращается -1.
//
// ***********************************************************************
FAIND_API(int) sol_FindWord(
                            HGREN h, 
                            const wchar_t *Word,
                            int *EntryIndex,
                            int *Form,
                            int *Class 
                           )
{
 if( h==NULL || !HandleEngine(h)->dict )
  return -1;

 if( EntryIndex!=NULL ) *EntryIndex = UNKNOWN;
 if( Form!=NULL ) *Form  = UNKNOWN;
 if( Class!=NULL ) *Class  = UNKNOWN;

 try
  {
   UCString uWord(Word);
   uWord.to_upper();

   // Look for it in dictionary
   Lexem ml(uWord);
   HandleEngine(h)->dict->GetLexAuto().TranslateLexem(ml,true);

   RC_Lexem rc(&ml,null_deleter());

   MCollect<Word_Coord> found_list;
   MCollect<Real1> val_list;
   PtrCollect<LA_ProjectInfo> inf_list;

   HandleEngine(h)->dict->GetLexAuto().ProjectWord( rc, found_list, val_list, inf_list, LexicalAutomat/*::DynformsMode*/::Wordforms, 0, HandleEngine(h)->DefaultLanguage, NULL ); 

   if( !found_list.empty() )
    {
     const int ientry0 = found_list.front().GetEntry();
     const SG_Entry &e = HandleEngine(h)->dict->GetSynGram().GetEntry(ientry0);

     if( EntryIndex ) *EntryIndex = ientry0;
     if( Form )  *Form  = found_list.front().GetForm();         
     if( Class ) *Class = e.GetClass();
    }

   // Returns number of projections
   return CastSizeToInt(found_list.size());
  }
 CATCH_API(h)
 
 // Missing in dictionary
 return UNKNOWN;
}


static void getForms(
                     HGREN h,
                     const wchar_t *Word,
                     bool Allow_Dynforms,
                     bool Synonyms,
                     bool Grammar_Links,
                     bool Translations,
                     bool Semantics,  
                     int nJumps,
                     lem::MCollect<UCString> &res
                    )
{
 const bool Thesaurus = (Synonyms || Grammar_Links) && nJumps>0;
 const LexicalAutomat::DynformsMode morphology = Allow_Dynforms ? LexicalAutomat::Dynforms_Last_Chance : LexicalAutomat::Wordforms;

 const bool all_links = Synonyms && Grammar_Links && Translations && Semantics;

 // Тут конечно возникает серьезный оверхед, так как этот список допустимых связок
 // заполняется каждый раз при работе процедуры. 
 lem::MCollect< Tree_Link > allowed_links;
 if( !all_links )
  {
   if( Synonyms )
    allowed_links.push_back( SYNONYM_link );

   if( Grammar_Links )
    {
     allowed_links.push_back( TO_VERB_link );
     allowed_links.push_back( TO_INF_link );
     allowed_links.push_back( TO_PERFECT );
     allowed_links.push_back( TO_UNPERFECT );
     allowed_links.push_back( TO_NOUN_link );
     allowed_links.push_back( TO_ADJ_link );
     allowed_links.push_back( TO_ADV_link );
     allowed_links.push_back( TO_RETVERB );
     allowed_links.push_back( WOUT_RETVERB );
    }

   if( Translations )
    {
     allowed_links.push_back( TO_ENGLISH_link );
     allowed_links.push_back( TO_RUSSIAN_link );
     allowed_links.push_back( TO_FRENCH_link );
     allowed_links.push_back( TO_SPANISH_link );
     allowed_links.push_back( TO_GERMAN_link );
     allowed_links.push_back( TO_CHINESE_link );
     allowed_links.push_back( TO_POLAND_link );
     allowed_links.push_back( TO_ITALIAN_link );
     allowed_links.push_back( TO_PORTUGUAL_link );
     allowed_links.push_back( TO_JAPANESE_link );
    }

   if( Semantics )
    {
     allowed_links.push_back( CLASS_link );
     allowed_links.push_back( ACTION_link );
     allowed_links.push_back( ACTOR_link );
     allowed_links.push_back( TOOL_link );
     allowed_links.push_back( RESULT_link );
    }
  }


 LexicalAutomat &la = HandleEngine(h)->dict->GetLexAuto();
 SynGram &sg = HandleEngine(h)->dict->GetSynGram();

 std::set<int> ientry_set;

 Lexem *lex = new Lexem(Word);
 la.TranslateLexem( *lex, true );
 RC_Lexem rc_ml( lex );

 MCollect<Word_Coord> found_list;
 MCollect<Real1> val_list;
 PtrCollect<LA_ProjectInfo> inf_list;

 la.ProjectWord( rc_ml, found_list, val_list, inf_list, morphology, 0, HandleEngine(h)->DefaultLanguage, NULL );

 // Накапливаем неповторяющийся список индексов статей
 for( Container::size_type i=0; i<found_list.size(); i++ )
  {
   int ie = found_list[i].GetEntry();

   // Лексическое содержимое всех словоформ статьи
   const SG_Entry &e = sg.GetEntry( ie );
   const Container::size_type nf = e.forms().size();
   for( Container::size_type k=0; k<nf; k++ )
    {
     const UCString &str = e.forms()[k].name();

     bool f=false;
     for( Container::size_type j=0; j<res.size(); j++ )
      if( res[j] == str )
       {
        f=true;
        break;
       }

     if( !f )
      res.push_back( str );
    }

   // Если требуется искать связанные словарные статьи по тезаурусу
   if( Thesaurus )
    ientry_set.insert(ie); 
  }

 if( Thesaurus )
  {
   std::set<int> links;

   for( std::set<int>::const_iterator l=ientry_set.begin(); l!=ientry_set.end(); l++ )
    {
     links.clear();

     // Связки
     if( all_links )
      sg.Get_Net().Find_Linked_Entries( sg.GetEntry( *l ).GetKey(), nJumps, links );
     else
      sg.Get_Net().Find_Linked_Entries( sg.GetEntry( *l ).GetKey(), nJumps, links, true, true, &allowed_links );

     for( std::set<int>::const_iterator k=links.begin(); k!=links.end(); k++ )
      {
       const SG_Entry &e = sg.GetEntry( *k );

       const Container::size_type nf = e.forms().size();
       for( Container::size_type k=0; k<nf; k++ )
        {
         const UCString &str = e.forms()[k].name();

         bool f=false;
         for( Container::size_type j=0; j<res.size(); j++ )
          if( res[j] == str )
           {
            f=true;
            break;
           }

         if( !f )
          res.push_back( str );
        }
      }
    }
  }

 return;
}






FAIND_API(HGREN_STR) sol_FindStringsEx(
                                       HGREN h,
                                       const wchar_t *Word,
                                       int Allow_Dynforms,
                                       int Synonyms,
                                       int Grammar_Links,
                                       int Translations,
                                       int Semantics,
                                       int nJumps
                                      )
{
 if( h==NULL || !HandleEngine(h)->dict )
  return NULL;

 GREN_Strings *res = new GREN_Strings;

 lem::MCollect<UCString> list;
 list.reserve(32);

 try
  {
   getForms( h, Word, Allow_Dynforms==1, Synonyms==1, Grammar_Links==1, Translations==1, Semantics==1, nJumps, list );

   for( MCollect<UCString>::const_iterator i=list.begin(); i!=list.end(); i++ )
    res->list.push_back( *i );
  }
 catch(...) 
  {
   delete res;
   return NULL;
  } 

 return res; 
}




static int Decline(
                   HGREN h, 
                   int ientry, int icase, int number, int anim_form );

static int Correlate_Nom(
                         HGREN h, 
                         int ientry,
                         int factor,
                         int Anim
                        );
static int Correlate_Instr(
                           HGREN h, 
                           int ientry,
                           int factor,
                           int Anim
                          );
static int Correlate_Accus(
                           HGREN h, 
                           int ientry,
                           int factor,
                           int Anim
                          );
static int Correlate_All( 
                         HGREN h, 
                         int ientry, 
                         int factor,
                         int Case,
                         int Anim
                        );


// ***********************************************************************
// Согласование существительного и числительного. Отдельно задается падеж
// и (опционально) - одушевленность/неодушевленность.
// ***********************************************************************
FAIND_API(int) sol_CorrNounNumber(
                                 HGREN h, 
                                 int EntryIndex,
                                 int Value,
                                 int Case,         /*NOMINATIVE_CASE*/
                                 int Anim,         /*INANIMATIVE_FORM*/
                                 wchar_t *Result
                                )
{
 if( h==NULL || !HandleEngine(h)->dict || Result==NULL )
  return -1;

 *Result = 0;

 if( !h || EntryIndex==-1 )
  return -1;

 try
  {
   const SG_Entry &e = HandleEngine(h)->dict->GetSynGram().GetEntry(EntryIndex);

//UCString aaa = e.GetName();

   if( e.GetClass() == NOUN_ru )
    {
     // Нашли среди проекций существительное русского языка
       
     int factor = Value % 100;
     factor = factor<20l ? factor%20l : factor%10l;

     int iform=UNKNOWN;
     switch(Case)
     {
      case NOMINATIVE_CASE_ru:
       iform = Correlate_Nom( h, EntryIndex, factor, Anim );
       break;

      case ACCUSATIVE_CASE_ru:
       iform = Correlate_Accus( h, EntryIndex, factor, Anim );
       break;

      case INSTRUMENTAL_CASE_ru:
       iform = Correlate_Instr( h, EntryIndex, factor, Anim );
       break;

      default:
       iform = Correlate_All( h, EntryIndex, factor, Case, Anim );
     }

     if( iform==UNKNOWN )
      // Не найдена подходящая форма слова 
      return -3;

     // Нашли нужную форму слова.
     // Извлекаем лексическое содержание формы
     lem_strcpy( Result, e.forms()[iform].name().c_str() );
     return 0;
    }
  }
 catch(...)
  {
   return -1;
  } 

 // Слово не найдено в словаре
 return -2;
}




static int Correlate_Nom(
                         HGREN h, 
                         int ientry,
                         int factor,
                         int Anim
                        )
{
 if( factor==0 )
  return Decline(
                 h, 
                 ientry,
                 GENITIVE_CASE_ru,
                 PLURAL_NUMBER_ru,
                 Anim
                );

 if( factor==1 )
  return Decline(
                 h, 
                 ientry,
                 NOMINATIVE_CASE_ru,
                 SINGULAR_NUMBER_ru,
                 Anim
                );

 if( factor>=2 && factor<=4 )
  {
   return Decline(
                  h, 
                  ientry,
                  GENITIVE_CASE_ru,
                  SINGULAR_NUMBER_ru,
                  Anim
                 );
  }

 return Decline(
                h,
                ientry,
                GENITIVE_CASE_ru,
                PLURAL_NUMBER_ru,
                Anim
               );
}



static int Correlate_Instr(
                           HGREN h, 
                           int ientry,
                           int factor,
                           int Anim
                          )
{ 
 if( factor==0 )
  return Decline(
                 h,
                 ientry,
                 GENITIVE_CASE_ru,
                 PLURAL_NUMBER_ru,
                 Anim
                );

 if( factor==1 )
  return Decline(
                 h,
                 ientry,
                 INSTRUMENTAL_CASE_ru,
                 SINGULAR_NUMBER_ru,
                 Anim
                );

 return Decline(
                h, 
                ientry,
                INSTRUMENTAL_CASE_ru,
                PLURAL_NUMBER_ru,
                Anim
               );
}


static int Correlate_Accus(
                           HGREN h, 
                           int ientry,
                           int factor,
                           int Anim
                          )
{
 if( factor==0 )
  return Decline(
                 h, 
                 ientry,
                 GENITIVE_CASE_ru,
                 PLURAL_NUMBER_ru,
                 Anim
                );

 if( factor==1 )
  return Decline(
                 h, 
                 ientry,
                 ACCUSATIVE_CASE_ru,
                 SINGULAR_NUMBER_ru,
                 Anim
                );

 if( factor>=2 && factor<=4 )
  {
   return Decline(
                  h, 
                  ientry,
                  GENITIVE_CASE_ru,
                  SINGULAR_NUMBER_ru,
                  Anim
                 );
  }

 return Decline(
                h,
                ientry,
                GENITIVE_CASE_ru,
                PLURAL_NUMBER_ru,
                Anim
               );
}


static int Correlate_All( 
                         HGREN h, 
                         int ientry, 
                         int factor,
                         int Case,
                         int Anim
                        )
{
 if( factor==0 )
  return Decline(
                 h,
                 ientry,
                 GENITIVE_CASE_ru,
                 PLURAL_NUMBER_ru,
                 Anim
                );

 if( factor==1 )
  return Decline(
                 h,
                 ientry,
                 Case,
                 SINGULAR_NUMBER_ru,
                 Anim
                );

 return Decline(
                h,
                ientry,
                Case,
                PLURAL_NUMBER_ru,
                Anim
               );
}


// ********************************************************************
// Поиск словоформы в указанной статье.
// ********************************************************************
static int Decline(
                   HGREN h, 
                   int ientry,
                   int icase,
                   int number,
                   int anim_form
                  )
{
 try
  {
   const SG_Entry &e = HandleEngine(h)->dict->GetSynGram().GetEntry(ientry);

   CP_Array dim;

   dim.push_back( GramCoordPair( GramCoordAdr(CASE_ru,0), icase ) );
   dim.push_back( GramCoordPair( GramCoordAdr(NUMBER_ru,0), number ) );

   return e.FindFormIndex( dim );
  }
 catch(...)
  {
   return UNKNOWN;
  }

 return UNKNOWN; 
}

// ****************************************************************
// Число, заданное целочисленным аргументом value, преобразуем в
// текстовое представление.
// ****************************************************************
FAIND_API(int) sol_Value2Text(
                             HGREN h, 
                             wchar_t *Result,
                             int Value,
                             int Gender
                            )
{
 if( h==NULL || !HandleEngine(h)->dict || Result==NULL )
  return -1;

 #if defined LEM_OFMT_MICROSOL
 try
  {
   wstring res = to_text( Value, LEM_RUS, Gender!=FEMININE_GENDER_ru );
   lem_strcpy( Result, res.c_str() );
  }
 catch(...)
  {
   return -1;
  }

 return 0;
 #else
 return -1;
 #endif
}


// ***************************************************************************
// Find the entry ID.
//
// http://www.solarix.ru/api/en/sol_FindEntry.shtml
// http://www.solarix.ru/api/ru/sol_FindEntry.shtml
//
// Other API functions use this index to perform operations on words without
// repeated search in dictionary.
//
// Returns: 
//         -1  word entry not found
//         -2  internal error  
//
// Поиск индекса словарной статьи во внутреннем списке. Другие функции
// грамматической машины могут выполнять операции со словами с использованием
// этого индекса (чтобы не повторять поиск в словаре).
//
// Возвращает:
//            -1  словарная статья не найдена 
//            -2  внутренний сбой
// ***************************************************************************
FAIND_API(int) sol_FindEntry(
                             HGREN h,
                             const wchar_t *Word, // Entry name
                             int Class,           // Required grammatical class
                             int Language         // Language ID (if ambiguos)
                            )
{
 if( h==NULL || !HandleEngine(h)->dict )
  return -2;

 try
  {
   // Convert to UNICODE lexem
   UCString uWord(Word);
   uWord.to_upper();

   // Ищем словарную статью с таким именем
   return HandleEngine(h)->dict->GetSynGram().FindEntry( uWord, Class, false );
  } 
 catch(...)
  {
   // Internal error
   return -2;
  }
}


FAIND_API(int) sol_FindEntry8( HGREN hEngine, const char *Word, int Class, int Language )
{
 return sol_FindEntry( hEngine, lem::from_utf8(Word).c_str(), Class, Language );
}


// *************************************************************
// Returns the gender of noun (unapplayable for English) 
//
// http://www.solarix.ru/api/en/sol_GetNounGender.shtml
// http://www.solarix.ru/api/ru/sol_GetNounGender.shtml
//
// Возвращает род существительного (неприменимо для английского)
// *************************************************************
FAIND_API(int) sol_GetNounGender(
                                 HGREN h,
                                 int EntryIndex  // Entry index (see sol_Find_Entry API functions)
                                )
{
 if( !h || !HandleEngine(h)->dict || EntryIndex==-1 )
  return -2;

 try
  {
   // The language that the entry belongs to.
   int Lang = static_cast<const SG_Class&>(
                                HandleEngine(h)->dict->GetSynGram().classes()[ HandleEngine(h)->dict->GetSynGram().GetEntry(EntryIndex).GetClass() ]
                               ).GetLanguage();

   if( Lang==RUSSIAN_LANGUAGE )
    return HandleEngine(h)->dict->GetSynGram().GetEntry( EntryIndex ).GetAttrState( GramCoordAdr( GENDER_ru, 0 ) );
  }
 catch(...)
  {
  }

 return UNKNOWN;
}

// ***********************************************************************
// Get the noun form for given case and number. Result will contain the 
// lexical content of the wordform (or empty string if not found). 
// 
// Возвращает в Result форму существительного с заданным падежом и числом.
// ***********************************************************************
FAIND_API(int) sol_GetNounForm(
                               HGREN h,
                               int EntryIndex,
                               int Number,
                               int Case,
                               wchar_t *Result
                              )
{
 *Result = 0;

 if( !h || !HandleEngine(h)->dict || EntryIndex==-1 || Result==NULL )
  return -2;

 try
  {
   const SG_Entry &e = HandleEngine(h)->dict->GetSynGram().GetEntry(EntryIndex);

   // The language that the entry belongs to.
   int Lang = static_cast<const SG_Class&>(
                                HandleEngine(h)->dict->GetSynGram().classes()[ e.GetClass() ]
                               ).GetLanguage();

   CP_Array dim;

   switch( Lang )
   {
    case RUSSIAN_LANGUAGE:
     {
      dim.push_back( GramCoordPair( GramCoordAdr(CASE_ru,0), Case ) );
      dim.push_back( GramCoordPair( GramCoordAdr(NUMBER_ru,0), Number ) );
      break;
     }

    #if defined SOL_GM_ENGLISH
    case ENGLISH_LANGUAGE:
     {
      dim.push_back( GramCoordPair( GramCoordAdr(NUMBER_xx,0), Number ) );

      if( Case!=-1 )
       dim.push_back( GramCoordPair( GramCoordAdr(CASE_en,0), Case ) );
      else
       dim.push_back( GramCoordPair( GramCoordAdr(CASE_en,0), NOMINATIVE_CASE_en ) );

      break;
     }
    #endif 
   }

   int iform = e.FindFormIndex( dim );
   if( iform==UNKNOWN )
    // Не найдена подходящая форма слова 
    return -3;

   // Нашли нужную форму слова.
   // Извлекаем лексическое содержание формы
   const SG_EntryForm &f = e.forms()[iform];
   lem_strcpy( Result, e.forms()[iform].name().c_str() );
   return 0;
  }
 catch(...)
  {
  }

 return UNKNOWN;
}


// ************************************************************
// Get the proper verb form.
//
// Ищет подходящую форму глагола. 
// ************************************************************
FAIND_API(int) sol_GetVerbForm(
                               HGREN h,
                               int EntryIndex,
                               int Number,
                               int Gender,
                               int Tense,
                               int Person,
                               wchar_t *Result
                              )
{
 if( !h || !HandleEngine(h)->dict || EntryIndex==-1 || Result==NULL )
  return -2;

 *Result = 0;

 try
  {
   const SG_Entry &e = HandleEngine(h)->dict->GetSynGram().GetEntry(EntryIndex);

   // The language that the entry belongs to.
   int Lang = static_cast<const SG_Class&>(
                                HandleEngine(h)->dict->GetSynGram().classes()[ e.GetClass() ]
                               ).GetLanguage();

   CP_Array dim;

   switch(Lang)
   {
    case RUSSIAN_LANGUAGE:
     {
      dim.push_back( GramCoordPair( GramCoordAdr(NUMBER_ru,0), Number ) );

      if(
         Number == SINGULAR_NUMBER_ru &&
         Tense == PAST_ru
        )
       dim.push_back( GramCoordPair( GramCoordAdr(GENDER_ru,0), Gender ) );

      dim.push_back( GramCoordPair( GramCoordAdr(TENSE_ru,0), Tense ) );

      if( Tense == PRESENT_ru || Tense==FUTURE_ru )
       dim.push_back( GramCoordPair( GramCoordAdr(PERSON_ru,0), Person ) );

      break;
     } 

    #if defined SOL_GM_ENGLISH
    case ENGLISH_LANGUAGE:
     {
      dim.push_back( GramCoordPair( GramCoordAdr(NUMBER_xx,0), Number ) );
      dim.push_back( GramCoordPair( GramCoordAdr(TENSE_en,0), Tense ) );
      dim.push_back( GramCoordPair( GramCoordAdr(PERSON_xx,0), Person ) );
      break;
     }
    #endif
   }

   int iform = e.FindFormIndex( dim );;
   if( iform==UNKNOWN )
    // Не найдена подходящая форма слова 
    return -3;

   // Нашли нужную форму слова.
   // Извлекаем лексическое содержание формы
   lem_strcpy( Result, e.forms()[iform].name().c_str() );
   return 0;
  }
 catch(...)
  {
  }

 return UNKNOWN;
}



FAIND_API(int) sol_CorrVerbNumber(
                                 HGREN h,
                                 int EntryIndex,
                                 int Value,
                                 int Gender, 
                                 int Tense,
                                 wchar_t *Result
                                )
{
 if( !h || !HandleEngine(h)->dict || EntryIndex==-1 || Result==NULL )
  return -2;

 *Result = 0;

 int f100 = Value % 100;
 int f10  = Value % 10;

 if(
    (f100==1 ||
    f100>20) &&
    f10 == 1
   )
  return sol_GetVerbForm( 
                         h,
                         EntryIndex,
                         SINGULAR_NUMBER_ru,
                         Gender,
                         Tense,
                         PERSON_3_ru,
                         Result  
                        ); 

 return sol_GetVerbForm( 
                        h,
                        EntryIndex,
                        PLURAL_NUMBER_ru,
                        Gender,
                        Tense,
                        PERSON_3_ru, 
                        Result  
                       ); 
}



FAIND_API(int) sol_GetAdjectiveForm(
                                    HGREN h,
                                    int EntryIndex,
                                    int Number,
                                    int Gender,
                                    int Case,
                                    int Anim,
                                    int Shortness,
                                    int Compar_Form, 
                                    wchar_t *Result
                                   )
{
 if( !h || !HandleEngine(h)->dict || EntryIndex==-1 || Result==NULL )
  return -2;

 *Result = 0;

 try
  {
   const SG_Entry &e = HandleEngine(h)->dict->GetSynGram().GetEntry(EntryIndex);

   // The language that the entry belongs to.
   int Lang = static_cast<const SG_Class&>(
                                HandleEngine(h)->dict->GetSynGram().classes()[ e.GetClass() ]
                               ).GetLanguage();

   CP_Array dim;

   switch(Lang)
   {
    case RUSSIAN_LANGUAGE:
     {
      dim.push_back( GramCoordPair( GramCoordAdr(CASE_ru,0), Case ) );
      dim.push_back( GramCoordPair( GramCoordAdr(NUMBER_ru,0), Number ) );

      if( Number==SINGULAR_NUMBER_ru )
       {
        dim.push_back( GramCoordPair( GramCoordAdr(GENDER_ru,0), Gender ) );

        if( Gender==MASCULINE_GENDER_ru && Case==ACCUSATIVE_CASE_ru )
         dim.push_back( GramCoordPair( GramCoordAdr(FORM_ru,0), Anim ) );
       }
      else if( Number==PLURAL_NUMBER_ru && Case==ACCUSATIVE_CASE_ru )
       {
        dim.push_back( GramCoordPair( GramCoordAdr(FORM_ru,0), Anim ) );
       }
       
      if( Shortness==UNKNOWN )
       dim.push_back( GramCoordPair( GramCoordAdr(SHORTNESS_ru,0), 0 ) );
      else
       dim.push_back( GramCoordPair( GramCoordAdr(SHORTNESS_ru,0), Shortness ) );

      if( Compar_Form==UNKNOWN )
       dim.push_back( GramCoordPair( GramCoordAdr(COMPAR_FORM_ru,0), ATTRIBUTIVE_FORM_ru ) );
      else
       dim.push_back( GramCoordPair( GramCoordAdr(COMPAR_FORM_ru,0), Compar_Form ) );

      break;
     }

    #if defined SOL_GM_ENGLISH
    case ENGLISH_LANGUAGE:
     {
      dim.push_back( GramCoordPair( GramCoordAdr(ADJ_FORM_en,0), Compar_Form ) );
      break;
     }
    #endif
   }

   int iform = e.FindFormIndex( dim );;
   if( iform==UNKNOWN )
    // Не найдена подходящая форма слова 
    return -3;

   // Нашли нужную форму слова.
   // Извлекаем лексическое содержание формы
   const SG_EntryForm &f = e.forms()[iform];
   lem_strcpy( Result, f.name().c_str() );
   return 0;
  }
 catch(...)
  {
  }

 return UNKNOWN;
}


static int Decline_Adj( HGREN h, int ientry, int icase, int number, int anim_form, int gender );
static int Correlate_Nom_Adj(
                             HGREN h,
                             int ientry,
                             int factor,
                             int Gender,
                             int Anim
                            );
static int Correlate_Instr_Adj(
                               HGREN h,
                               int ientry,
                               int factor,
                               int Gender,
                               int Anim
                              );
static int Correlate_Accus_Adj(
                               HGREN h,
                               int ientry,
                               int factor,
                               int Gender,
                               int Anim
                              );
static int Correlate_All_Adj( 
                             HGREN h,
                             int ientry, 
                             int factor,
                             int Case,
                             int Gender,
                             int Anim
                            );



FAIND_API(int) sol_CorrAdjNumber(
                                HGREN h,
                                int EntryIndex,
                                int Value,
                                int Case, 
                                int Gender,
                                int Anim,  
                                wchar_t *Result
                               )
{
 if( !h || !HandleEngine(h)->dict || EntryIndex==-1 || Result==NULL )
  return -2;

 *Result = 0;

 try
  {
   const SG_Entry &e = HandleEngine(h)->dict->GetSynGram().GetEntry(EntryIndex);

//UCString aaa = e.GetName();

   if( e.GetClass() == ADJ_ru )
    {
     // Нашли среди проекций существительное русского языка
       
     int factor = Value % 100;
     factor = factor<20l ? factor%20l : factor%10l;

     int iform=UNKNOWN;
     switch(Case)
     {
      case NOMINATIVE_CASE_ru:
       iform = Correlate_Nom_Adj( h, EntryIndex, factor, Gender, Anim );
       break;

      case ACCUSATIVE_CASE_ru:
       iform = Correlate_Accus_Adj( h, EntryIndex, factor, Gender, Anim );
       break;

      case INSTRUMENTAL_CASE_ru:
       iform = Correlate_Instr_Adj( h, EntryIndex, factor, Gender, Anim );
       break;

      default:
       iform = Correlate_All_Adj( h, EntryIndex, factor, Case, Gender, Anim );
     }

     if( iform==UNKNOWN )
      // Не найдена подходящая форма слова 
      return -3;

     // Нашли нужную форму слова.
     // Извлекаем лексическое содержание формы
     lem_strcpy( Result, e.forms()[iform].name().c_str() );
     return 0;
    }
  }
 catch(...)
  {
   return -1;
  } 

 return -2;
}


static int Correlate_Nom_Adj(
                             HGREN h,
                             int ientry,
                             int factor,
                             int Gender,
                             int Anim
                            )
{
 if( factor==0 )
  return Decline_Adj(
                     h, 
                     ientry,
                     GENITIVE_CASE_ru,
                     PLURAL_NUMBER_ru,
                     Anim,
                     Gender 
                    );

 if( factor==1 )
  return Decline_Adj(
                     h, 
                     ientry,
                     NOMINATIVE_CASE_ru,
                     SINGULAR_NUMBER_ru,
                     Anim,
                     Gender 
                    );
/*
 if( factor>=2 && factor<=4 )
  {
   return Decline_Adj(
                      ientry,
                      GENITIVE_CASE_ru,
                      SINGULAR_NUMBER_ru,
                      Anim,
                      Gender 
                     );
  }
*/
 return Decline_Adj(
                    h, 
                    ientry,
                    GENITIVE_CASE_ru,
                    PLURAL_NUMBER_ru,
                    Anim,
                    Gender 
                   );
}



static int Correlate_Instr_Adj(
                               HGREN h,
                               int ientry,
                               int factor,
                               int Gender,
                               int Anim
                              )
{ 
 if( factor==0 )
  return Decline_Adj(
                     h, 
                     ientry,
                     GENITIVE_CASE_ru,
                     PLURAL_NUMBER_ru,
                     Anim,
                     Gender
                    );

 if( factor==1 )
  return Decline_Adj(
                     h, 
                     ientry,
                     INSTRUMENTAL_CASE_ru,
                     SINGULAR_NUMBER_ru,
                     Anim,
                     Gender
                    );

 return Decline_Adj(
                    h, 
                    ientry,
                    INSTRUMENTAL_CASE_ru,
                    PLURAL_NUMBER_ru,
                    Anim,
                    Gender
                   );
}



static int Correlate_Accus_Adj(
                               HGREN h,
                               int ientry,
                               int factor,
                               int Gender,
                               int Anim
                              )
{
 if( factor==0 )
  return Decline_Adj(
                     h, 
                     ientry,
                     GENITIVE_CASE_ru,
                     PLURAL_NUMBER_ru,
                     Anim,
                     Gender 
                    );

 // вижу одну белую розу
 // вижу один большой камень
 // вижу одного большого пса
 if( factor==1 )
  return Decline_Adj(
                     h, 
                     ientry,
                     ACCUSATIVE_CASE_ru,
                     SINGULAR_NUMBER_ru,
                     Anim,
                     Gender 
                    );

 // вижу 2 круглых камня
 // вижу 2 пушистых кошки
 // вижу 2 белых розы
 if( factor>=2 && factor<=4 )
  {
   return Decline_Adj(
                      h,
                      ientry,
                      GENITIVE_CASE_ru,
                      PLURAL_NUMBER_ru,
                      Anim,
                      Gender
                     );
  }

 // вижу 5 круглых камней
 // вижу 5 пушистых кошек
 return Decline_Adj(
                    h, 
                    ientry,
                    GENITIVE_CASE_ru,
                    PLURAL_NUMBER_ru,
                    Anim,
                    Gender 
                   );
}


static int Correlate_All_Adj( 
                             HGREN h,
                             int ientry, 
                             int factor,
                             int Case,
                             int Gender,
                             int Anim
                            )
{
 if( factor==0 )
  return Decline_Adj(
                     h, 
                     ientry,
                     GENITIVE_CASE_ru,
                     PLURAL_NUMBER_ru,
                     Anim,
                     Gender
                    );

 if( factor==1 )
  return Decline_Adj(
                     h, 
                     ientry,
                     Case,
                     SINGULAR_NUMBER_ru,
                     Anim,
                     Gender
                    );

 return Decline_Adj(
                    h, 
                    ientry,
                    Case,
                    PLURAL_NUMBER_ru,
                    Anim,
                    Gender
                   );
}


// ********************************************************************
// Поиск словоформы в указанной статье.
// ********************************************************************
static int Decline_Adj( HGREN h, int ientry, int Case, int Number, int Anim, int Gender )
{
 try                                    
  {
   const SG_Entry &e = HandleEngine(h)->dict->GetSynGram().GetEntry(ientry);

   CP_Array dim;

   dim.push_back( GramCoordPair( GramCoordAdr(CASE_ru,0), Case ) );
   dim.push_back( GramCoordPair( GramCoordAdr(NUMBER_ru,0), Number ) );

   if( Case==ACCUSATIVE_CASE_ru )
    {
     if( Number==SINGULAR_NUMBER_ru && Gender==MASCULINE_GENDER_ru )
      dim.push_back( GramCoordPair( GramCoordAdr(FORM_ru,0), Anim ) );
     else if( Number==PLURAL_NUMBER_ru )
      dim.push_back( GramCoordPair( GramCoordAdr(FORM_ru,0), Anim ) );
    }

   if( Number==SINGULAR_NUMBER_ru )
    {
     dim.push_back( GramCoordPair( GramCoordAdr(GENDER_ru,0), Gender ) );
    }

   return e.FindFormIndex( dim );
  }
 catch(...)
  {
   return UNKNOWN;
  }

 return UNKNOWN; 
}




// **********************************************************
// Простая лемматизация слова - приведение к базовой форме
// **********************************************************
FAIND_API(int) sol_LemmatizeWord( HGREN h, wchar_t *word, int Allow_Dynforms )
{
 if( !h || HandleEngine(h)->seeker==NULL || word==NULL )
  return 0;

 // Создание объекта на стеке. Плохо.
 UCString w(word);
 const int ientry = HandleEngine(h)->seeker->Find( w, Allow_Dynforms==1 );

 if( ientry!=UNKNOWN )
  {
   // Копируем имя статьи - почти всегда это базовая форма
   lem_strcpy( word, HandleEngine(h)->sg->GetEntry(ientry).GetName().c_str() );
   return 1;
  }

 return 0;
}





// ***************************************************************************
//
// Словарная статья приводится к семантически эквивалентному
// существительному, например "ИСКАТЬ->ПОИСК". Для работы необходим
// тезаурус!
// Входной аргумент EntryIndex - результат предыдущего вызова sol_Find_Entry.
// Возвращается ID словарной статьи (существительное) для использования,
// например, в sol_GetNounForm, либо -1 если привести к существительному
// не представляется возможным.
//
// http://www.solarix.ru/api/ru/sol_TranslateToNoun.shtml
//
// ***************************************************************************
FAIND_API(int) sol_TranslateToNoun( HGREN hEngine, int EntryID )
{
 if( EntryID==-1 || !hEngine || !HandleEngine(hEngine)->dict )
  return -1;

 try
 {
  const SG_Entry &e = HandleEngine(hEngine)->dict->GetSynGram().GetEntry(EntryID);
  if(
     e.GetClass()==NOUN_ru ||
     e.GetClass()==NOUN_en ||
     e.GetClass()==NOUN_fr ||
     e.GetClass()==NOUN_es
    )
   // Преобразование не требуется, так как исходное слово уже является
   // существительным.
   return EntryID;

  lem::MCollect<int> key_list; // список связанных существительных
  if( HandleEngine(hEngine)->dict->GetLexAuto().Translate_To_Nouns( EntryID, key_list ) )
   {
    // НАШЛИ ФОРМУ СУЩЕСТВИТЕЛЬНОГО
    // Может быть несколько альтернатив, выбираем просто первую.
    return key_list.front();
   }
 }
 CATCH_API(hEngine);
 
 return -1;
}


// ************************************************************************
//
// Приведение словарной статьи к семантически или грамматически связанной
// неопределенной форме глагола (инфинитиву).
//
// http://www.solarix.ru/api/ru/sol_TranslateToInfinitive.shtml
//
// ************************************************************************
FAIND_API(int) sol_TranslateToInfinitive( HGREN hEngine, int EntryID )
{
 if( EntryID==-1 || !hEngine || !HandleEngine(hEngine)->dict )
  return -1;

 try
 {
  const SG_Entry &e = HandleEngine(hEngine)->dict->GetSynGram().GetEntry(EntryID);
  const int pos_id = e.GetClass();
  if( pos_id==INFINITIVE_ru || pos_id==VERB_en )
   return EntryID;

  lem::MCollect<int> key_list; // список связанных инфинитивов
  if( HandleEngine(hEngine)->dict->GetLexAuto().Translate_To_Infinitives( EntryID, key_list ) )
   {
    // НАШЛИ ФОРМУ ИНФИНИТИВА
    return key_list.front();
   }
 }
 CATCH_API(hEngine);

 return -1;
}



// ***************************************************************
//
// Нахождение всех проекций указанного слова и работа
// с полученным списком.
//
// http://www.solarix.ru/api/ru/sol_ProjectWord.shtml
//
// ***************************************************************
FAIND_API(HGREN_WCOORD) sol_ProjectWord( HGREN hEngine, const wchar_t *Word, int Allow_Dynforms )
{
 if( !hEngine || !HandleEngine(hEngine)->dict || Word==NULL )
  return NULL;

 try
  {
   UCString uWord(Word);
   uWord.to_upper();

   // Look for it in dictionary
   Lexem ml(uWord);
   HandleEngine(hEngine)->dict->GetLexAuto().TranslateLexem(ml,true);

   RC_Lexem rc(&ml,null_deleter());

   MCollect<Real1> val_list;
   PtrCollect<LA_ProjectInfo> inf_list;


   GREN_WordCoords *res = new GREN_WordCoords;
   HandleEngine(hEngine)->dict->GetLexAuto().ProjectWord(
                                                         rc,
                                                         res->list,
                                                         val_list,
                                                         inf_list,
                                                         Allow_Dynforms ? LexicalAutomat::Dynforms_Last_Chance : LexicalAutomat::Wordforms,
                                                         0,
                                                         HandleEngine(hEngine)->DefaultLanguage,
                                                         NULL
                                                        );

   if( !res->list.empty() )
    {
     return res;
    }

   delete res;
   return NULL;
 }
 CATCH_API(hEngine)
 {
  return NULL;
 }
}


FAIND_API(HGREN_WCOORD) sol_ProjectWord8( HGREN hEngine, const char *WordUtf8, int Allow_Dynforms )
{
 return sol_ProjectWord( hEngine, lem::from_utf8(WordUtf8).c_str(), Allow_Dynforms );
}


// ****************************************************************
// Нечеткая проекция - допускается nmissmax несовпадений символов.
// ****************************************************************
FAIND_API(HGREN_WCOORD) sol_ProjectMisspelledWord(
                                                  HGREN hEngine,
                                                  const wchar_t *Word,
                                                  int Allow_Dynforms,
                                                  int nmaxmiss
                                                 )
{
 if( !hEngine || !HandleEngine(hEngine)->dict || Word==NULL )
  return NULL;

 try
 {
  UCString uWord(Word);
  uWord.to_upper();

  // Look for it in dictionary
  Lexem ml(uWord);

  RC_Lexem rc(&ml,null_deleter());

  MCollect<Real1> val_list;
  PtrCollect<LA_ProjectInfo> inf_list;


  GREN_WordCoords *res = new GREN_WordCoords;
  HandleEngine(hEngine)->dict->GetLexAuto().ProjectWord(
                                                        rc,
                                                        res->list,
                                                        val_list,
                                                        inf_list,
                                                        Allow_Dynforms ? LexicalAutomat::Dynforms_Last_Chance : LexicalAutomat::Wordforms,
                                                        nmaxmiss,
                                                        HandleEngine(hEngine)->DefaultLanguage,
                                                        NULL
                                                       );

  if( !res->list.empty() )
   {
    return res;
   }

  delete res;

  return NULL;
 }
 CATCH_API(hEngine)
 {
  return NULL;
 }
}



// *********************************************************
// Возвращает количество элементов (проекций) в списке hList
// *********************************************************
FAIND_API(int) sol_CountProjections( HGREN_WCOORD hList )
{
 if( !hList )
  return 0;

 return CastSizeToInt( ((const Solarix::GREN_WordCoords*)hList)->list.size());
}


FAIND_API(void) sol_DeleteProjections( HGREN_WCOORD hList )
{
 delete (Solarix::GREN_WordCoords*)hList;
}



// *************************************************************
// Возвращается id (primary key) словарной статьи среди проекций
// *************************************************************
FAIND_API(int) sol_GetIEntry( HGREN_WCOORD hList, int Index )
{
 if( !hList || Index<0 || Index>=CastSizeToInt(((const Solarix::GREN_WordCoords*)hList)->list.size()) )
  return -1;

 return ((const Solarix::GREN_WordCoords*)hList)->list[Index].GetEntry();
}


// **********************************************************************
// Возвращается состояние грамматического признака Coord у проекции Index
// **********************************************************************
FAIND_API(int) sol_GetProjCoordState(
                                     HGREN hEngine,
                                     HGREN_WCOORD hList,
                                     int Index,
                                     int Coord
                                    )
{
 if( !hList || Index<0 || Index>=CastSizeToInt(((const Solarix::GREN_WordCoords*)hList)->list.size()) )
  return -1;

 int istate = UNKNOWN;

 try
  {
   const int ientry = ((const Solarix::GREN_WordCoords*)hList)->list[Index].GetEntry();

   if( ientry==UNKNOWN )
    return -1;

   const SG_Entry &e = HandleEngine(hEngine)->dict->GetSynGram().GetEntry(ientry);

   istate = e.GetAttrState(Coord);
   if( istate!=UNKNOWN )
    return istate;

   istate = e.forms()[ ((const Solarix::GREN_WordCoords*)hList)->list[Index].GetForm() ].FindDimState(Coord);
  }
 catch(...)  
  {
   return UNKNOWN;
  } 

 return istate;
}

#define SOL_GREN_ALLOW_FUZZY    0x00000002
#define SOL_GREN_COMPLETE_ONLY  0x00000004
#define SOL_GREN_PRETOKENIZED   0x00000008


// ****************************************************************************
// Выполнение морфологического анализа.
// ****************************************************************************
FAIND_API(HGREN_RESPACK) sol_MorphologyAnalysis(
                                                HGREN h,
                                                const wchar_t *Sentence,
                                                int Flags,
                                                int UnusedFlags,
                                                int max_msec,
                                                int Language
                                               ) 
{
 if( !h || !HandleEngine(h)->dict || Sentence==NULL )
  return NULL;

 #if !defined SOL_NO_AA

 PhrasoBlock *fblo=NULL;
 try
  {
   HandleEngine(h)->GetSyntaxAnalyzer();

   const bool Allow_Fuzzy = (Flags & SOL_GREN_ALLOW_FUZZY) == SOL_GREN_ALLOW_FUZZY;
   const bool CompleteAnalysisOnly = (Flags & SOL_GREN_COMPLETE_ONLY) == SOL_GREN_COMPLETE_ONLY;
   const bool Pretokenized = (Flags & SOL_GREN_PRETOKENIZED) == SOL_GREN_PRETOKENIZED;

   // Parse input string 'str'
   Solarix::Sentence sent;
   sent.Parse(
              Sentence,
              Pretokenized,
              HandleEngine(h)->dict.get(),
              Language==-1 ? HandleEngine(h)->DefaultLanguage : Language,
              NULL
             );

   SyntaxAnalyzerTimeout timeout;
   timeout.max_elapsed_millisecs = max_msec;

   fblo = HandleEngine(h)->tpu->process_morphology(sent,Allow_Fuzzy,CompleteAnalysisOnly,timeout);
  }
 catch( E_AA_Error& )
  {
   return NULL;
  }

 if( fblo!=NULL && fblo->AreAllProjected() )
  return fblo;

 delete fblo;
 return NULL;
 #else
  return NULL;
 #endif
}


FAIND_API(HGREN_RESPACK) sol_MorphologyAnalysis8(
                                                 HGREN hEngine,
                                                 const char *SentenceUtf8,
                                                 int Flags,
                                                 int UnusedFlags,
                                                 int max_msec,
                                                 int Language
                                                ) 
{
 return sol_MorphologyAnalysis(
                               hEngine,
                               lem::from_utf8(SentenceUtf8).c_str(),
                               Flags,
                               UnusedFlags,
                               max_msec,
                               Language
                              );
}


FAIND_API(HGREN_RESPACK) sol_MorphologyAnalysisA(
                                                 HGREN hEngine,
                                                 const char *Sentence,
                                                 int Flags,
                                                 int UnusedFlags,
                                                 int max_msec,
                                                 int Language
                                                ) 
{
 return sol_MorphologyAnalysis(
                               hEngine,
                               lem::to_unicode(Sentence).c_str(),
                               Flags,
                               UnusedFlags,
                               max_msec,
                               Language
                              );
}



// ****************************************************************************
// Выполнение синтаксического анализа - на входе фраза, на выходе получается
// объект, хранящий альтернативные варианты построения синтаксического графа.
// ****************************************************************************
FAIND_API(HGREN_RESPACK) sol_SyntaxAnalysis(
                                            HGREN hEngine,
                                            const wchar_t *Sentence,
                                            int MorphologicalFlags,
                                            int SyntacticFlags,
                                            int MaxMillisecTimeout,
                                            int LanguageID
                                           ) 
{
 if( !hEngine || !HandleEngine(hEngine)->dict || Sentence==NULL )
  return NULL;

 #if !defined SOL_NO_AA

 PhrasoBlock *fblo=NULL;

 try
  {
   HandleEngine(hEngine)->GetSyntaxAnalyzer();

   const bool Allow_Fuzzy = (MorphologicalFlags & SOL_GREN_ALLOW_FUZZY) == SOL_GREN_ALLOW_FUZZY;
   const bool CompleteAnalysisOnly = (MorphologicalFlags & SOL_GREN_COMPLETE_ONLY) == SOL_GREN_COMPLETE_ONLY;
   const bool Pretokenized = (MorphologicalFlags & SOL_GREN_PRETOKENIZED) == SOL_GREN_PRETOKENIZED;

   const bool NewSyntacticAnalysis = (SyntacticFlags & 0x00000002)==0x00000002;

   // Parse and tokenize the input string 'str'
   Solarix::Sentence sent;
   sent.Parse(
              Sentence,
              Pretokenized,
              HandleEngine(hEngine)->dict.get(),
              LanguageID==-1 ? HandleEngine(hEngine)->DefaultLanguage : LanguageID,
              NULL
             );

   SyntaxAnalyzerTimeout timeout;
   timeout.max_elapsed_millisecs = MaxMillisecTimeout;

/*
#if LEM_DEBUGGING==1
if( !lem::LogFile::IsOpen() )
 lem::LogFile::Open( lem::Path("c:\\TMP\\log.txt") );

lem::LogFile::Print( "sol_SyntaxAnalysis #1" );
#endif
*/

   fblo = HandleEngine(hEngine)->tpu->process_syntax(sent,Allow_Fuzzy,CompleteAnalysisOnly,NewSyntacticAnalysis,timeout);
   return fblo;
  }
 catch( E_AA_Error& )
  {
   return NULL;
  }
 CATCH_API(hEngine)

 return NULL;
 #else
 return NULL;
 #endif
}



FAIND_API(HGREN_RESPACK) sol_SyntaxAnalysis8(
                                             HGREN hEngine,
                                             const char *SentenceUtf8,
                                             int MorphologicalFlags,
                                             int SyntacticFlags,
                                             int max_msec,
                                             int Language
                                            ) 
{
 return sol_SyntaxAnalysis(
                           hEngine,
                           lem::from_utf8(SentenceUtf8).c_str(),
                           MorphologicalFlags,
                           SyntacticFlags,
                           max_msec,
                           Language
                          );
}

FAIND_API(HGREN_RESPACK) sol_SyntaxAnalysisA(
                                             HGREN hEngine,
                                             const char *Sentence,
                                            int MorphologicalFlags,
                                            int SyntacticFlags,
                                             int max_msec,
                                             int Language
                                            ) 
{
 return sol_SyntaxAnalysis(
                           hEngine,
                           lem::to_unicode(Sentence).c_str(),
                           MorphologicalFlags,
                           SyntacticFlags,
                           max_msec,
                           Language
                          );
}


FAIND_API(void) sol_DeleteResPack( HGREN_RESPACK hPack )
{
 #if !defined SOL_NO_AA
 delete (Solarix::PhrasoBlock*)hPack;
 #endif
}


// *************************************************************
// Возвращает "имя" словарной статьи - обычно это базовая форма,
// но могут быть особые случаи (например, статьи ЕСТЬ - кушать
// и ЕСТЬ - иметься)
//
// http://www.solarix.ru/api/en/sol_GetEntryName.shtml
// http://www.solarix.ru/api/ru/sol_GetEntryName.shtml
// *************************************************************
FAIND_API(int) sol_GetEntryName(
                                HGREN h,
                                int EntryIndex,
                                wchar_t *Result
                               )
{
 if( !h || EntryIndex==-1 || Result==NULL )
  return -2;

 *Result = 0;

 try
  {
   const SG_Entry &e = HandleEngine(h)->dict->GetSynGram().GetEntry(EntryIndex);

   lem::UFString w( e.GetName().c_str() );

   const int icasing = e.GetAttrState( GramCoordAdr(CharCasing) );

   switch( icasing )
   {
    case UNKNOWN:
    case DECAPITALIZED_CASED:
     {
      w.to_lower();
      break;
     }

     case FIRST_CAPITALIZED_CASED:
     {
      w.to_Aa();
      break;
     }

    case ALL_CAPITALIZED_CASED:
     {
      w.to_upper();
      break;
     }

    case EACH_LEXEM_CAPITALIZED_CASED:
     {
      Solarix::MakeEachLexemAa(w);
      break;
     }
   }

   w.subst_all( L" - ", L"-" );
   w.subst_all( L" ' ", L"'" );

   lem::lem_strcpy( Result, w.c_str() );

   return 0;
  }
 catch(...)
  {
  }

 return UNKNOWN;
}


FAIND_API(int) sol_GetEntryName8(
                                 HGREN h,
                                 int EntryIndex,
                                 char *Result
                                )
{
 try
  {
   wchar_t buf[ lem::UCString::max_len+1 ];
   *buf = 0;
   int rc = sol_GetEntryName(h,EntryIndex,buf);
   lem::FString u8 = lem::to_utf8(buf);
   lem::lem_strcpy( Result, u8.c_str() );
   return rc;
  }
 catch(...)
  {
   return -1;
  }
}



// Возвращает индекс грамматического класса, к которому относится
// словарная статья.
// http://www.solarix.ru/api/en/sol_GetEntryClass.shtml
// http://www.solarix.ru/api/ru/sol_GetEntryClass.shtml
FAIND_API(int) sol_GetEntryClass( HGREN h, int EntryIndex )
{
 if( !h || !HandleEngine(h)->dict || EntryIndex==-1 )
  return -2;

 try
  {
   return HandleEngine(h)->dict->GetSynGram().GetEntry(EntryIndex).GetClass(); 
  }
 catch(...)
  {
  }

 return UNKNOWN;
}


// http://www.solarix.ru/api/en/sol_GetEntryCoordState.shtml
FAIND_API(int) sol_GetEntryCoordState( HGREN hEngine, int EntryID, int CategoryID )
{
 if( !hEngine || !HandleEngine(hEngine)->dict || CategoryID==-1 || EntryID==-1 )
  return -2;

 try
  {
   const Solarix::SG_Entry &e = HandleEngine(hEngine)->dict->GetSynGram().GetEntry(EntryID);
   const int state = e.GetAttrState( GramCoordAdr(CategoryID) );
   return state;
  }
 catch(...)
  {
  }

 return UNKNOWN;
}


// В некоторых случаях статья может иметь несколько состояний одной координаты. Например,
// падежная валентность глагола РИСОВАТЬ включает винительный, дательный и творительный падежи.
// Данная процедура позволяет узнать, присутствует ли координатная пара среди атрибутов.
// http://www.solarix.ru/api/ru/sol_FindEntryCoordPair.shtml
FAIND_API(int) sol_FindEntryCoordPair( HGREN hEngine, int EntryID, int CategoryID, int StateID )
{
 if( !hEngine || !HandleEngine(hEngine)->dict || EntryID==-1 || CategoryID==-1 || StateID==-1 )
  return -2;

 try
  {
   const Solarix::SG_Entry &e = HandleEngine(hEngine)->dict->GetSynGram().GetEntry(EntryID);
   return e.attrs().FindOnce( Solarix::GramCoordPair( CategoryID, StateID ) )!=-1;
  }
 catch(...)
  {
  }

 return UNKNOWN;
}




// **********************************************************************************
// Возвращает имя грамматического класса (части речи) по его ID
// http://www.solarix.ru/api/en/sol_GetClassName.shtml
// http://www.solarix.ru/api/ru/sol_GetClassName.shtml
//
// Результат: 0   имя часчти речи скопировано в Result
//            -1  ошибка, например указан некорректный ClassIndex
// **********************************************************************************
FAIND_API(int) sol_GetClassName( HGREN hEngine, int ClassID, wchar_t *Result )
{
 if( !hEngine || !HandleEngine(hEngine)->dict || ClassID==-1 )
  return -2;

 *Result = 0;

 try
  {
   const Solarix::GramClass &c = HandleEngine(hEngine)->dict->GetSynGram().classes()[ClassID];
   wcscpy( Result, c.GetName().c_str() );
   return 0;
  }
 catch(...)
  {
  }

 return UNKNOWN;
}


// http://www.solarix.ru/api/en/sol_GetClassName.shtml
FAIND_API(int) sol_GetClassName8( HGREN h, int ClassID, char *ResultUtf8 )
{
 if( !h || !HandleEngine(h)->dict || ClassID==-1 )
  return -2;

 *ResultUtf8 = 0;

 try
  {
   const Solarix::GramClass &c = HandleEngine(h)->dict->GetSynGram().classes()[ClassID];
   strcpy( ResultUtf8, lem::to_utf8(c.GetName().c_str()).c_str() );
   return 0;
  }
 catch(...)
  {
  }

 return UNKNOWN;
}


// http://www.solarix.ru/api/en/sol_GetCoordName.shtml
FAIND_API(int) sol_GetCoordName( HGREN hEngine, int CoordID, wchar_t *Result )
{
 if( !hEngine || !HandleEngine(hEngine)->dict || CoordID==-1 )
  return -2;

 *Result = 0;

 try
  {
   const Solarix::GramCoord & c = HandleEngine(hEngine)->dict->GetSynGram().coords()[CoordID];
   wcscpy( Result, c.GetName().string().c_str() );
   return 0;
  }
 catch(...)
  {
  }

 return UNKNOWN;
}




// http://www.solarix.ru/api/en/sol_GetCoordType.shtml
// http://www.solarix.ru/api/ru/sol_GetCoordType.shtml
FAIND_API(int) sol_GetCoordType( HGREN hEngine, int CoordId, int ClassId )
{
 if( !hEngine || !HandleEngine(hEngine)->dict || CoordId==-1 || ClassId==-1 )
  return -2;

 try
  {
   const Solarix::SG_Class &cls = HandleEngine(hEngine)->dict->GetSynGram().GetClass(ClassId);

   if( cls.attrs().find(CoordId)!=UNKNOWN )
    return 0;

   if( cls.dims().find(CoordId)!=UNKNOWN )
    return 1;

   if( cls.tags().find(CoordId)!=UNKNOWN )
    return 2;
   
   return UNKNOWN;
  }
 catch(...)
  {
  }

 return UNKNOWN;
}



// http://www.solarix.ru/api/en/sol_GetCoordName.shtml
FAIND_API(int) sol_GetCoordName8( HGREN hEngine, int CoordID, char *ResultUtf8 )
{
 if( !hEngine || !HandleEngine(hEngine)->dict || CoordID==-1 )
  return -2;

 *ResultUtf8 = 0;

 try
  {
   const Solarix::GramCoord & c = HandleEngine(hEngine)->dict->GetSynGram().coords()[CoordID];
   strcpy( ResultUtf8, lem::to_utf8(c.GetName().string().c_str()).c_str() );
   return 0;
  }
 catch(...)
  {
  }

 return UNKNOWN;
}


// Return: name of the grammatical coordinate state
// http://www.solarix.ru/api/en/sol_GetCoordStateName.shtml
FAIND_API(int) sol_GetCoordStateName( HGREN hEngine, int CoordID, int StateID, wchar_t *Result )
{
 if( !hEngine || !HandleEngine(hEngine)->dict || CoordID==-1 )
  return -2;

 *Result = 0;

 try
  {
   const Solarix::GramCoord & c = HandleEngine(hEngine)->dict->GetSynGram().coords()[CoordID];

   if( !c.IsBistable() )
    wcscpy( Result, c.GetStateName(StateID).c_str() );

   return 0;
  }
 catch(...)
  {
  }

 return UNKNOWN;
}


// http://www.solarix.ru/api/en/sol_GetCoordStateName.shtml
FAIND_API(int) sol_GetCoordStateName8( HGREN hEngine, int CoordID, int StateID, char *ResultUtf8 )
{
 if( !hEngine || !HandleEngine(hEngine)->dict || CoordID==-1 )
  return -2;

 *ResultUtf8 = 0;

 try
  {
   const Solarix::GramCoord & c = HandleEngine(hEngine)->dict->GetSynGram().coords()[CoordID];

   if( !c.IsBistable() )
    strcpy( ResultUtf8, lem::to_utf8(c.GetStateName(StateID).c_str()).c_str() );

   return 0;
  }
 catch(...)
  {
  }

 return UNKNOWN;
}


// Return the number of enumeration items (coordinate states).
// It returns 0 for bistable coordinates without explicit states
// http://www.solarix.ru/api/en/sol_CountCoordStates.shtml
FAIND_API(int) sol_CountCoordStates( HGREN hEngine, int CoordID )
{
 if( !hEngine || !HandleEngine(hEngine)->dict || CoordID==-1 )
  return -2;

 try
  {
   const Solarix::GramCoord & c = HandleEngine(hEngine)->dict->GetSynGram().coords()[CoordID];

   return c.GetTotalStates();
  }
 catch(...)
  {
  }

 return UNKNOWN;
}


// ***************************************************
// Сколько альтернативных вариантов анализа фразы?  
// ***************************************************
FAIND_API(int) sol_CountGrafs( HGREN_RESPACK hPack )
{
 #if !defined SOL_NO_AA
 if( hPack==NULL )
  return 0;

 return CastSizeToInt(HandlePack(hPack)->GetPack().vars().size());
 #else
 return 0;
 #endif
}


// *************************************************************
// Сколько деревьев (узлов верхнего уровня) в заданном графе?
// *************************************************************
FAIND_API(int) sol_CountRoots( HGREN_RESPACK hPack, int iGraf )
{
 try
  {
   #if !defined SOL_NO_AA
   if( hPack==NULL )
    return 0;

   if( iGraf<0 || iGraf>=CastSizeToInt(HandlePack(hPack)->GetPack().vars().size()) )
    return -1;

   return HandlePack(hPack)->GetPack().vars()[iGraf]->size();
   #else
   return 0;
   #endif
  }
 catch(...)
  {
   return -1;
  }
}


// *****************************************************
// Получение указателя на заданный узел верхнего уровня 
// *****************************************************
FAIND_API(HGREN_TREENODE) sol_GetRoot( HGREN_RESPACK hPack, int iGraf, int iRoot )
{
 #if !defined SOL_NO_AA
 if( hPack==NULL )
  return NULL;

 try
  {
   return &(HandlePack(hPack)->GetPack().vars()[iGraf]->get(iRoot));
  }
 catch(...)
  {
   return NULL;
  }
 #else
 return NULL;
 #endif
}


// *****************************************************
// Количество прикрепленных к данному узлу веток
// *****************************************************
FAIND_API(int) sol_CountLeafs( HGREN_TREENODE hNode )
{
 #if !defined SOL_NO_AA
 if( hNode==NULL )
  return 0;

 return CastSizeToInt( HandleNode(hNode)->leafs().size());
 #else
 return 0;
 #endif
}


// *****************************************************
// Возвращает указатель на заданную ветку
// http://www.solarix.ru/api/ru/sol_GetLeaf.shtml
// *****************************************************
FAIND_API(HGREN_TREENODE) sol_GetLeaf( HGREN_TREENODE hNode, int iLeaf )
{
 #if !defined SOL_NO_AA
 if( hNode==NULL )
  return 0;

 try
  {
   return &HandleNode(hNode)->leafs()[iLeaf];
  }
 catch(...)
  {
   return NULL;
  }

 #else
 return NULL;
 #endif
}



// http://www.solarix.ru/api/ru/sol_GetLeafLinkType.shtml
FAIND_API(int) sol_GetLeafLinkType( HGREN_TREENODE hNode, int iLeaf )
{
 #if !defined SOL_NO_AA
 if( hNode==NULL )
  return 0;

 try
  {
   return HandleNode(hNode)->leafs()[iLeaf].GetLink().GetState();
  }
 catch(...)
  {
   return -2;
  } 
 #else
 return UNKNOWN;
 #endif
}



// *****************************************************
// Возвращает индекс словарной статьи в узле.
// *****************************************************
FAIND_API(int) sol_GetNodeIEntry( HGREN hEngine, HGREN_TREENODE hNode )
{
 if( hEngine==NULL || HandleEngine(hEngine)->dict==NULL )
  return UNKNOWN;

 #if !defined SOL_NO_AA
 if( hNode==NULL )
  return 0;

 int ekey = HandleNode(hNode)->GetNode().GetEntryKey();
 return ekey;//HandleEngine(hEngine)->dict->GetSynGram().FindEntryIndexByKey(ekey);
 #else
 return UNKNOWN;
 #endif  
}


FAIND_API(int) sol_GetNodeVerIEntry( HGREN hEngine, HGREN_TREENODE hNode, int iver )
{
 if( hEngine==NULL || HandleEngine(hEngine)->dict==NULL )
  return UNKNOWN;

 #if !defined SOL_NO_AA
 if( hNode==NULL )
  return 0;

 int ekey = UNKNOWN;
 if( iver==0 )
  ekey = HandleNode(hNode)->GetNode().GetEntryKey();
 else
  ekey = HandleNode(hNode)->GetNode().GetAlts()[iver-1]->GetEntryKey();

 return ekey;//HandleEngine(hEngine)->dict->GetSynGram().FindEntryIndexByKey(ekey);
 #else
 return UNKNOWN;
 #endif  
}


FAIND_API(int) sol_GetNodeVersionCount( HGREN hEngine, HGREN_TREENODE hNode )
{
 #if !defined SOL_NO_AA
 if( hNode==NULL )
  return 0;

 return CastSizeToInt(HandleNode(hNode)->GetNode().GetAlts().size())+1;
 #else
 return 0;
 #endif  
}


FAIND_API(int) sol_GetNodePosition( HGREN_TREENODE hNode )
{
 if( hNode==NULL )
  return -1;

 #if !defined SOL_NO_AA
 const Word_Form &wf = HandleNode(hNode)->GetNode();
 return wf.GetOriginPos();
 #else
 return -1;
 #endif
}




// *****************************************************
// Текстовое содержимое узла.
//
// *****************************************************
FAIND_API(void) sol_GetNodeContents( HGREN_TREENODE hNode, wchar_t *Buffer )
{
 if( Buffer==NULL )
  return;

 try
  {
   *Buffer = 0;

   #if !defined SOL_NO_AA
   if( hNode==NULL )
    return;

   const Word_Form &wf = HandleNode(hNode)->GetNode();
   UCString s = wf.GetName()->ToWord();
   wcscpy( Buffer, s.c_str() ); 
   #endif
  }
 catch(...)
  {
  }
  
 return; 
}


FAIND_API(void) sol_GetNodeContents8( HGREN_TREENODE hNode, char *BufferUtf8 )
{
 if( BufferUtf8==NULL )
  return;

 try
  {
   *BufferUtf8 = 0;

   #if !defined SOL_NO_AA
   if( hNode==NULL )
    return;

   const Word_Form &wf = HandleNode(hNode)->GetNode();
   UCString s = wf.GetName()->ToWord();
   strcpy( BufferUtf8, lem::to_utf8(s.c_str()).c_str() );
   #endif
  }
 catch(...)
  {
  }
  
 return; 
}



FAIND_API(int) sol_GetNodeContentsLen( HGREN_TREENODE hNode )
{
 try
  {
   #if !defined SOL_NO_AA
   if( hNode==NULL )
    return -1;

   const int len = HandleNode(hNode)->GetNode().GetName()->ToWord().length();
   return len;
   #endif
  }
 catch(...)
  {
  }
  
 return -1;
}




FAIND_API(int) sol_RestoreCasing( HGREN hEngine, wchar_t *Word, int EntryIndex )
{
 if( !hEngine || EntryIndex==-1 || lem::lem_is_empty(Word) )
  return -2;

 try
  {
   lem::UFString w(Word);
   CasingCoder& cc = HandleEngine(hEngine)->dict->GetLexAuto().GetCasingCoder();
   cc.RestoreCasing(w,EntryIndex);

   w.subst_all( L" - ", L"-" );
   w.subst_all( L" ' ", L"'" );

   lem::lem_strcpy( Word, w.c_str() );

   return 0;
  }
 CATCH_API(hEngine);

 return 3;
}

FAIND_API(int) sol_RestoreCasing8( HGREN hEngine, char *WordUtf8, int EntryIndex )
{
 if( WordUtf8==NULL || EntryIndex==UNKNOWN )
  return -1;

 wchar_t buf[lem::UCString::max_len+1];
 lem::lem_strcpy( buf, lem::from_utf8(WordUtf8).c_str() );
 int rc = sol_RestoreCasing( hEngine, buf, EntryIndex );
 if( rc==0 )
  {
   strcpy( WordUtf8, lem::to_utf8(buf).c_str() );
  }

 return rc;
}



// ***********************************************************************
// Приведение к базовой форме. Вернет 1, если преобразование имело место.
//
// Аргумент AllowDynforms позволяет включить сложную морфологию.
// ***********************************************************************
FAIND_API(int) sol_TranslateToBase( HGREN hEngine, wchar_t *Word, int AllowDynforms )
{
 if( hEngine==NULL || HandleEngine(hEngine)->dict==NULL || Word==NULL )
  return UNKNOWN;

 try
  {
   // Быстрый поиск словоформы.
   UCString w(Word);

   int ientry=UNKNOWN;
   if( HandleEngine(hEngine)->seeker==NULL )
    {
     MCollect<Word_Coord> found_list;
     HandleEngine(hEngine)->dict->GetLexAuto().ProjectWord( w, found_list, UNKNOWN );
     if( !found_list.empty() )
      ientry = found_list.front().GetEntry();
    }
   else
    {
     w.to_upper();
     ientry = HandleEngine(hEngine)->seeker->Find( w, AllowDynforms==1 );
    }

   if( ientry==UNKNOWN )
    return 0;

   // Зная индекс словарной статьи, можем преобразовать к названию статьи.
   const SG_Entry &e = HandleEngine(hEngine)->dict->GetSynGram().GetEntry(ientry);
   wcscpy( Word, e.GetName().c_str() );
   sol_RestoreCasing( hEngine, Word, e.GetKey() );
   return 1;
  }
 CATCH_API(hEngine)

 return UNKNOWN;
}


// *****************************************************************
// Возвращает список базовых форм для слова. 
// *****************************************************************
FAIND_API(HGREN_STR) sol_TranslateToBases(
                                          HGREN hEngine,
                                          const wchar_t *Word,
                                          int AllowDynforms
                                         )
{
 if( !hEngine || !HandleEngine(hEngine)->dict || Word==NULL )
  return NULL;

 try
  {
   UCString uWord(Word);
   uWord.to_upper();

   // Look for it in dictionary
   Lexem ml(uWord);

   RC_Lexem rc(&ml,null_deleter());

   lem::MCollect<Real1> val_list;
   lem::MCollect<Solarix::Word_Coord> coords;
   PtrCollect<LA_ProjectInfo> inf_list;

   HandleEngine(hEngine)->dict->GetLexAuto().ProjectWord(
                                                         rc,
                                                         coords,
                                                         val_list,
                                                         inf_list,
                                                         AllowDynforms ? LexicalAutomat::Dynforms_Last_Chance : LexicalAutomat::Wordforms,
                                                         0,
                                                         HandleEngine(hEngine)->DefaultLanguage,
                                                         NULL
                                                        );

   if( coords.empty() )
    return NULL;

   SynGram &sg = HandleEngine(hEngine)->dict->GetSynGram();
   GREN_Strings *res = new GREN_Strings;

   wchar_t buf[ lem::UCString::max_len+1 ];

   for( lem::Container::size_type i=0; i<coords.size(); i++ )
    {
     const int ekey = coords[i].GetEntry();
     const SG_Entry &e = sg.GetEntry(ekey);
     if( !e.IsQuantor() )
      {
       const UCString &s = e.GetName();
       lem::lem_strcpy( buf, s.c_str() );
       sol_RestoreCasing( hEngine, buf, ekey ); 
       lem::UCString u(buf);
      
       if( res->list.find(u)==UNKNOWN )
        res->list.push_back(u);
      }
    }

   if( res->list.empty() )
    {
     res->list.push_back( lem::UCString(Word) );
    }

   return res;
  }
 CATCH_API(hEngine)

 return NULL;
}

// *****************************************************************************
// Стеммер - возвращает число символов в слове, которые составляют 
// "корень", неизменный для всех форм этого слова. В случае невозможности
// выделить корень, вернет 0.
// *****************************************************************************
FAIND_API(int) sol_Stemmer( HGREN hEngine, const wchar_t *Word )
{
 #if defined GM_STEMMER
 if( !hEngine || !HandleEngine(hEngine)->dict || Word==NULL )
  return -1;

 try
  {
   // Быстрый поиск словоформы по лексикону
   UCString w(Word);
   int ientry = HandleEngine(hEngine)->seeker==NULL ? UNKNOWN : HandleEngine(hEngine)->seeker->Find( w, true );
   if( ientry==UNKNOWN )
    {
     // Это слово отсутствует в лексиконе.
     // Попробуем применить известные правила аффиксов.
   
     LEM_CHECKIT_Z( HandleEngine(hEngine)->fuzzy!=NULL );

     if( HandleEngine(hEngine)->fuzzy==NULL )
      // Отсутствует нужный модуль.
      return -1;

     lem::MCollect<UCString> roots;
     lem::MCollect<float> roots_val;
   
     HandleEngine(hEngine)->fuzzy->GetAffixTable().GenerateRoots( w, 0.0, roots, roots_val );

     if( !roots.empty() )
      {
       // Вообще говоря, тут есть выбор - возвращать самый длинный корень или самый короткий.
       int best_len=roots.front().length();

       for( lem::Container::size_type j=1; j<roots.size(); j++ )
        {
         best_len = std::min( best_len, roots[j].length() );
        }
       
       return best_len;
      }

     if( !!HandleEngine(hEngine)->dict->stemmer )
      {
       // Попробуем использовать загруженный стеммер.
       UCString stem;
       if( HandleEngine(hEngine)->dict->stemmer->Stem( w, stem ) )
        return stem.length();
      }

     return 0;
    }
   else
    {
     // Так как словарная статья найдена, то используем известный для нее корень.
     return HandleEngine(hEngine)->dict->GetSynGram().GetEntry(ientry).GetRoot().length();
    } 

   return 0;
  }
 CATCH_API(hEngine)

 return -2;
 
 #else
 return -1;
 #endif
}



// **********************************************************
// Поиск в тезаурусе связанных статей для указанной iEntry.
// Возвращается список индексов статей.
// **********************************************************
FAIND_API(HGREN_INTARRAY) sol_SeekThesaurus(
                                            HGREN hEngine,
                                            int iEntry,
                                            int Synonyms,
                                            int Grammar_Links,
                                            int Translations,
                                            int Semantics,
                                            int nJumps
                                           )
{
 if( hEngine==NULL || !HandleEngine(hEngine)->dict )
  return NULL;

 #if defined GM_THESAURUS
 lem::MCollect<int> *list = new lem::MCollect<int>();
 list->reserve(32);

 try
  {
   HandleEngine(hEngine)->dict->GetLexAuto().SeekThesaurus( HandleEngine(hEngine)->dict->GetSynGram().GetEntry(iEntry).GetKey(), Synonyms==1, Grammar_Links==1, Translations==1, Semantics==1, nJumps, *list );

   for( lem::Container::size_type i=0; i<list->size(); ++i )
    {
     #if LEM_DEBUGGING==1
     int key = list->get(i);
     int ie =  key;
     #endif
     (*list)[i] = list->get(i);
    }

   return list;
  }
 CATCH_API(hEngine)

 delete list;
 return NULL;
 #endif

 return NULL; 
}


FAIND_API(HGREN_INTARRAY) sol_Thesaurus(
                                        HGREN hEngine,
                                        int iEntry,
                                        int Link
                                       )
{
 if( hEngine==NULL || !HandleEngine(hEngine)->dict || iEntry==UNKNOWN || Link==UNKNOWN )
  return NULL;

 #if defined SOLARIX_PRO && defined GM_THESAURUS
 lem::MCollect<int> *list = new lem::MCollect<int>();
 list->reserve(32);

 try
  {
   const int ekey = HandleEngine(hEngine)->dict->GetSynGram().GetEntry(iEntry).GetKey();

   Tree_Link l(Link);

   IntCollect links;
   HandleEngine(hEngine)->dict->GetSynGram().Get_Net().Find_Linked_Entries( ekey, l, links, NULL );

   for( lem::Container::size_type i=0; i<links.size(); ++i )
    {
     const int key = links[i];
     list->push_back( key );//HandleEngine(hEngine)->dict->GetSynGram().FindEntryIndexByKey( key ) );
    }

   return list;
  }
 catch(...) 
  {
   delete list;
   return NULL;
  } 
 #endif

 return NULL; 
}


// ***********************************************************************
// http://www.solarix.ru/for_developers/api/ngrams-api.shtml
//
// Возвращается количество N-грамм заданного порядка (Order=1...5) и
// типа (0-буквальные,1-нормализованные).
//
// Так как записей может быть>4 млрд, то 64-битный результат возвращается
// в виде двух 32-битных частей.
//
// Возвращаемое значение: 1 - успех, 0 - нет записей или какая-то ошибка.
// ************************************************************************
FAIND_API(int) sol_CountNGrams( HGREN hEngine, int type, int Order, unsigned int *Hi, unsigned int *Lo )
{
 if( hEngine==NULL || !HandleEngine(hEngine)->dict || type<0 || type>1 || Order<1 || Order>5 || Hi==NULL || Lo==NULL )
  return 0;

 #if !defined SOL_NO_NGRAMS

 try
  {
   *Hi = *Lo = 0;

   lem::Ptr<Ngrams> ngrams = HandleEngine(hEngine)->dict->GetNgrams();

   if( ngrams.NotNull() )
    { 
     lem::int64_t c=0;
     
     switch( Order )
     {
      case 1: c = type==1 ? ngrams->CountRaw1() : ngrams->CountLiteral1(); break;
      case 2: c = type==1 ? ngrams->CountRaw2() : ngrams->CountLiteral2(); break;
      case 3: c = type==1 ? ngrams->CountRaw3() : ngrams->CountLiteral3(); break;
      case 4: c = type==1 ? ngrams->CountRaw4() : ngrams->CountLiteral4(); break;
      case 5: c = type==1 ? ngrams->CountRaw5() : ngrams->CountLiteral5(); break;
     }
 
     *Hi = (lem::uint32_t)(c>>32);
     *Lo = (lem::uint32_t)(c);
     
     return c>0;
    }

   return 0;
  }
 catch(...) 
  {
   return 0;
  } 
 #endif

 return 0; 
}


// http://www.solarix.ru/for_developers/api/ngrams-api.shtml
FAIND_API(int) sol_Seek1Grams( HGREN hEngine, int type, const wchar_t *word1 )
{
 if( hEngine==NULL || !HandleEngine(hEngine)->dict || word1==NULL )
  return 0;

 #if !defined SOL_NO_NGRAMS

 try
  {
   lem::Ptr<Ngrams> ngrams = HandleEngine(hEngine)->dict->GetNgrams();

   if( ngrams.NotNull() )
    { 
     float wf=0;
     int iw=0;
 
     if( ngrams->FindNGrams( type==1, word1, wf, iw ) )
      return iw;
     
     return 0;
    }

   return 0;
  }
 catch(...) 
  {
   return 0;
  } 
 #endif

 return 0; 
}

FAIND_API(int) sol_Seek1Grams8( HGREN hEngine, int type, const char *Word1Utf8 )
{
 return sol_Seek1Grams( hEngine, type, lem::from_utf8(Word1Utf8).c_str() );
}


// http://www.solarix.ru/for_developers/api/ngrams-api.shtml
FAIND_API(int) sol_Seek2Grams(
                              HGREN hEngine,
                              int type,
                              const wchar_t *word1,
                              const wchar_t *word2
                             )
{
 if( hEngine==NULL || !HandleEngine(hEngine)->dict || word1==NULL || word2==NULL )
  return 0;

 #if !defined SOL_NO_NGRAMS

 try
  {
   lem::Ptr<Ngrams> ngrams = HandleEngine(hEngine)->dict->GetNgrams();

   if( ngrams.NotNull() )
    { 
     float wf=0;
     int iw=0;
 
     if( ngrams->FindNGrams( type==1, word1, word2, wf, iw ) )
      return iw;
     
     return 0;
    }

   return 0;
  }
 catch(...) 
  {
   return 0;
  } 
 #endif

 return 0; 
}

FAIND_API(int) sol_Seek2Grams8(
                               HGREN hEngine,
                               int type,
                               const char *Word1Utf8,
                               const char *Word2Utf8
                              )
{
 return sol_Seek2Grams(
                       hEngine,
                       type,
                       lem::from_utf8(Word1Utf8).c_str(),
                       lem::from_utf8(Word2Utf8).c_str()
                      );
}


// http://www.solarix.ru/for_developers/api/ngrams-api.shtml
FAIND_API(int) sol_Seek3Grams(
                              HGREN hEngine,
                              int type,
                              const wchar_t *word1,
                              const wchar_t *word2,
                              const wchar_t *word3
                             )
{
 if( hEngine==NULL || !HandleEngine(hEngine)->dict || word1==NULL || word2==NULL || word3==NULL )
  return 0;

 #if !defined SOL_NO_NGRAMS

 try
  {
   lem::Ptr<Ngrams> ngrams = HandleEngine(hEngine)->dict->GetNgrams();

   if( ngrams.NotNull() )
    { 
     float wf=0;
     int iw=0;
 
     if( ngrams->FindNGrams( type==1, word1, word2, word3, wf, iw ) )
      return iw;
     
     return 0;
    }

   return 0;
  }
 catch(...) 
  {
   return 0;
  } 
 #endif

 return 0; 
}

FAIND_API(int) sol_Seek3Grams8(
                               HGREN hEngine,
                               int type,
                               const char *Word1Utf8,
                               const char *Word2Utf8,
                               const char *Word3Utf8
                              )
{
 return sol_Seek3Grams(
                       hEngine,
                       type,
                       lem::from_utf8(Word1Utf8).c_str(),
                       lem::from_utf8(Word2Utf8).c_str(),
                       lem::from_utf8(Word3Utf8).c_str()
                      );
}


// http://www.solarix.ru/for_developers/api/ngrams-api.shtml
FAIND_API(int) sol_Seek4Grams(
                              HGREN hEngine,
                              int type,
                              const wchar_t *word1,
                              const wchar_t *word2,
                              const wchar_t *word3,
                              const wchar_t *word4
                             )
{
 if( hEngine==NULL || !HandleEngine(hEngine)->dict ||
     word1==NULL || word2==NULL || word3==NULL || word4==NULL )
  return 0;

 #if !defined SOL_NO_NGRAMS

 try
  {
   lem::Ptr<Ngrams> ngrams = HandleEngine(hEngine)->dict->GetNgrams();

   if( ngrams.NotNull() )
    { 
     float wf=0;
     int iw=0;
 
     if( ngrams->FindNGrams( type==1, word1, word2, word3, word4, wf, iw ) )
      return iw;
     
     return 0;
    }

   return 0;
  }
 catch(...) 
  {
   return 0;
  } 
 #endif

 return 0; 
}

FAIND_API(int) sol_Seek4Grams8(
                               HGREN hEngine,
                               int type,
                               const char *Word1Utf8,
                               const char *Word2Utf8,
                               const char *Word3Utf8,
                               const char *Word4Utf8
                              )
{
 return sol_Seek4Grams(
                       hEngine,
                       type,
                       lem::from_utf8(Word1Utf8).c_str(),
                       lem::from_utf8(Word2Utf8).c_str(),
                       lem::from_utf8(Word3Utf8).c_str(),
                       lem::from_utf8(Word4Utf8).c_str()
                      );
}

// http://www.solarix.ru/for_developers/api/ngrams-api.shtml
FAIND_API(int) sol_Seek5Grams(
                              HGREN hEngine,
                              int type,
                              const wchar_t *word1,
                              const wchar_t *word2,
                              const wchar_t *word3,
                              const wchar_t *word4,
                              const wchar_t *word5
                             )
{
 if( hEngine==NULL || !HandleEngine(hEngine)->dict ||
     word1==NULL || word2==NULL || word3==NULL || word4==NULL || word5==NULL )
  return 0;

 #if !defined SOL_NO_NGRAMS

 try
  {
   lem::Ptr<Ngrams> ngrams = HandleEngine(hEngine)->dict->GetNgrams();

   if( ngrams.NotNull() )
    { 
     float wf=0;
     int iw=0;
 
     if( ngrams->FindNGrams( type==1, word1, word2, word3, word4, word5, wf, iw ) )
      return iw;
     
     return 0;
    }

   return 0;
  }
 catch(...) 
  {
   return 0;
  } 
 #endif

 return 0; 
}



FAIND_API(int) sol_Seek5Grams8(
                               HGREN hEngine,
                               int type,
                               const char *Word1Utf8,
                               const char *Word2Utf8,
                               const char *Word3Utf8,
                               const char *Word4Utf8,
                               const char *Word5Utf8
                              )
{
 return sol_Seek5Grams(
                       hEngine,
                       type,
                       lem::from_utf8(Word1Utf8).c_str(),
                       lem::from_utf8(Word2Utf8).c_str(),
                       lem::from_utf8(Word3Utf8).c_str(),
                       lem::from_utf8(Word4Utf8).c_str(),
                       lem::from_utf8(Word5Utf8).c_str()
                      );
}





//////////////////////////////////////////////////////////////////////
///
/// \brief Является ли строка фразой на указанном языке
///
//////////////////////////////////////////////////////////////////////
FAIND_API(int) sol_IsLanguagePhrase( HGREN hEngine, const wchar_t *Phrase, int Language )
{
 #if defined SOLARIX_PRO
 if( hEngine==NULL || lem::lem_is_empty(Phrase) || !HandleEngine(hEngine)->dict )
  return 0;

 #if defined SOL_CAA
 lem::UFString uphrase(Phrase);
 lem::MCollect<UCString> words;
 lem::parse( uphrase, words, true );
 int iLang = HandleEngine(hEngine)->dict->GetLexAuto().GuessLanguage(words);
 return iLang==Language ? 1 : 0;
 #else
 return 0;
 #endif
 
 #endif
 return 0;
}

//////////////////////////////////////////////////////////////////////
///
/// \brief  Определяет язык для фразы
///
/// @param Phrase null terminated строка с текстом
/// @return Код языка, или -1 (не удалось подобрать), или -2 (ошибка)
//////////////////////////////////////////////////////////////////////
FAIND_API(int) sol_GuessPhraseLanguage( HGREN hEngine, const wchar_t *Phrase )
{
 #if defined SOLARIX_PRO
 if( hEngine==NULL || lem::lem_is_empty(Phrase) || !HandleEngine(hEngine)->dict )
  return -2;

 #if defined SOL_CAA
 lem::UFString uphrase(Phrase);
 lem::MCollect<UCString> words;
 lem::parse( uphrase, words, true );
 int iLang = HandleEngine(hEngine)->dict->GetLexAuto().GuessLanguage(words);
 return iLang;
 #else
 return -1;
 #endif
 
 #else
 return -1;
 #endif
}

namespace
{
 struct TranslationResult
 {
  lem::UFString text;
  lem::FString text8;
  lem::Ptr<Solarix::PhrasoBlock> fblo; 
  lem::PtrCollect<lem::UFString> ustrings;
  lem::PtrCollect<lem::FString> astrings;

  lem::UFString trace;
  lem::FString trace8;
 };
}


//////////////////////////////////////////////////////////////////////
///
/// \brief Перевод фразы с одного языка на другой
///
//////////////////////////////////////////////////////////////////////
FAIND_API(void*) sol_TranslatePhrase(
                                     HGREN hEngine,
                                     const wchar_t *Phrase,
                                     int FromLanguage,
                                     int ToLanguage,
                                     int CommonFlags,
                                     const wchar_t * Tags,
                                     const wchar_t * EnvParams,
                                     const wchar_t * Scenario
                                    )
{
 if( hEngine==NULL || lem::lem_is_empty(Phrase) || !HandleEngine(hEngine)->dict )
  return NULL;

 try
  {
   UCString scenario(Scenario);
   if( scenario.empty() )
    scenario=L"translate";

   Solarix::Dictionary * sol_id = &*(HandleEngine(hEngine)->dict);

   lem::Ptr<Solarix::SG_TagFilter> tags_ptr; // скомпилированные тэги
   lem::Ptr<Solarix::SG_TagFilter> transl0_tag; // специальный фильтр для отбора только переводов с transl_order=0
   lem::Ptr<Solarix::SG_TagFilter> transl1_tag; // специальный фильтр для отбора только основных переводов

   transl0_tag = new Solarix::TF_TranslOrderZero(*sol_id);
   transl1_tag = new Solarix::TF_OnlyMainTransl(*sol_id);

   ApiTranslationTracer* trace = NULL;
   lem::Ptr<ApiTranslationTracer> trace_ptr;

   if( (CommonFlags&0x00000001)==0x00000001 ||
       (CommonFlags&0x00000002)==0x00000002 )
    {
     bool xml = (CommonFlags&0x00000002)==0x00000002;
     trace = new ApiTranslationTracer( sol_id, xml );
     trace_ptr = trace;
    }


   // Разберем строку с описанием тегов.
   if( !lem::lem_is_empty(Tags) )
    {
     lem::Collect<lem::UFString> toks0;
     lem::parse( UFString(Tags), toks0, L",; \n" );

     lem::MCollect<UCString> tag_names;
     lem::MCollect<UCString> tag_values;

     for( lem::Container::size_type i=0; i<toks0.size(); ++i )
      {
       const lem::UFString & tok = toks0[i];
       
       lem::Collect<lem::UFString> toks;
       lem::parse( tok, toks, L"=" );
       UCString tag_name, tag_value;

       if( toks.size()>0 )
        tag_name = toks[0].c_str();

       if( toks.size()>1 )
        tag_value = toks[1].c_str();

       tag_name.trim();
       tag_value.trim();

       const int itag = sol_id->GetSynGram().Get_Net().FindTag(tag_name);
       if( itag==UNKNOWN )
        {
         lem::UFString msg = lem::format_str( L"Tag [%vfE%s%vn] not found\n", tag_name.c_str() );
         HandleEngine(hEngine)->error = msg;
         return NULL;
        }

       const ThesaurusTag &tt = sol_id->GetSynGram().Get_Net().GetTagDefs()[itag];

       if( tt.CountValues()>0 )
        { 
         int ivalue=UNKNOWN;
         ivalue = tt[tag_value];
         if( ivalue==UNKNOWN )
          {
           lem::UFString msg = lem::format_str( L"Tag value [%vfE%s%vn] not found\n", tag_value.c_str() );
           HandleEngine(hEngine)->error = msg;
           return NULL;
          }
        }

       tag_names.push_back( tag_name );
       tag_values.push_back( tag_value );
      }
   
     if( tag_names.size()==1 )
      {  
       tags_ptr = new TF_TagOrNullFilter( *sol_id, tag_names.front(), tag_values.front() );
      }
     else if( !tag_names.empty() )
      {
       lem::UFString msg = lem::format_str( L"Multiple tags filter is not supported" );
       HandleEngine(hEngine)->error = msg;
       return NULL;
      }
    }

   // Разбираем строку с глобальными параметрами
   lem::Collect< std::pair<UCString /*param*/, UCString /*value*/> > params;
   if( !lem::lem_is_empty(EnvParams) )
    {
     lem::Collect<lem::UFString> toks0;
     lem::parse( UFString(EnvParams), toks0, L",; \n" );

     for( lem::Container::size_type i=0; i<toks0.size(); ++i )
      {
       const UFString & tok = toks0[i];

       lem::Collect<lem::UFString> toks;
       lem::parse( tok, toks, L"=" );
       UCString param_name, param_value;

       if( toks.size()>0 )
        param_name = toks[0].c_str();

       if( toks.size()>1 )
        param_value = toks[1].c_str();

       param_name.trim();
       param_value.trim();

       params.push_back( std::make_pair( param_name, param_value ) );
      }
    }

   lem::Ptr<Solarix::Text_Processor> tpu = new Solarix::Text_Processor( sol_id );
   tpu->PerformSyntacticAnalysis(true);

   // Сегментируем исходный текст на предложения.
   lem::Ptr<SentenceBroker> broker = new SentenceBroker( Phrase, sol_id, FromLanguage );

   UFString sentence;

   TranslationResult *yield = new TranslationResult();

   TrEnvironment global(L"global");
   Solarix::TrWideContext env(&global);
            
   // Проинициализируем глобальные параметры, если они заданы пользователем
   for( lem::Container::size_type k=0; k<params.size(); ++k )
    {
     const UCString & param_name = params[k].first;
     const UCString & param_value = params[k].second;

     if( param_value.eqi(L"true") || param_value.eqi(L"false") )
      {
       TrBoolValue value( param_value.eqi(L"true") ); 
       env.SetParamValue( L"global", param_name, value );
      } 
     else if( lem::is_int(param_value.c_str()) )
      {
       TrIntValue value( lem::to_int(param_value) );
       env.SetParamValue( L"global", param_name, value );
      } 
     else
      {
       TrValue value( param_value );
       env.SetParamValue( L"global", param_name, value );
      } 
    }

   if( tags_ptr.NotNull() ) 
    {
     env.tags0_ptr = new SG_TF_AndFilter( tags_ptr, transl0_tag );
     env.tags_ptr = new SG_TF_AndFilter( tags_ptr, transl1_tag );
    } 
   else
    {
     env.tags0_ptr = transl0_tag;
     env.tags_ptr = transl1_tag;
    }

   while(true) 
    {
     sentence.clear();
     if( !broker->Fetch(sentence) )
      break;

     sentence.trim();
     if( !sentence.empty() )
      {
       // Перевод предложения.
       SyntaxAnalyzerTimeout timeout;
       timeout.max_elapsed_millisecs = 600000; // 10 минут

       Sentence sent;

       if( trace==NULL )
        sent.Parse(sentence,false,sol_id,FromLanguage,NULL);
       else
        sent.Parse(sentence,false,sol_id,FromLanguage,trace->PreprocessorTrace());

       yield->fblo = tpu->process_morphology(sent,false,false,timeout,trace);

       if( yield->fblo.NotNull() )
        { 
         yield->fblo->GetPackPtr()->Save_Shortest_Vars();
 
         Res_Pack &pack = yield->fblo->GetPack();

         if( !pack.vars().empty() )
          {
           Solarix::Variator *var = pack.vars().front();
           sol_id->GetAlephAuto().Transform( scenario, *var, env, trace );

           UFString str_res = VarToStr(*sol_id,*var);
           if( yield->text.length()>0 ) yield->text.Add_Dirty( L"\r\n" );
           yield->text.Add_Dirty(str_res);
          }
        }
      }
    }

   yield->text.calc_hash();

   if( trace!=NULL )
    trace->GetString(yield->trace);

   return yield;
  }
 catch( const lem::E_BaseException &e )
  {
   HandleEngine(hEngine)->error = e.what();
  }
 catch( const std::exception &e )
  {
   HandleEngine(hEngine)->error = e.what();
  }
 catch(...)
  {
   return NULL;
  }
 
 return NULL;
}


FAIND_API(void*) sol_TranslatePhrase8(
                                      HGREN hEngine,
                                      const char *Phrase,
                                      int FromLanguage,
                                      int ToLanguage,
                                      int CommonFlags,
                                      const char * Tags,
                                      const char * EnvParams,
                                      const char * Scenario
                                     )
{
 void* rc = sol_TranslatePhrase( hEngine, from_utf8(Phrase).c_str(), FromLanguage,
                               ToLanguage, CommonFlags, 
                               from_utf8(Tags).c_str(), from_utf8(EnvParams).c_str(),
                               from_utf8(Scenario).c_str() );

 
 return rc;
}




FAIND_API(const wchar_t*) sol_GetTranslationText( void* hTran )
{
 try
  {
   TranslationResult *ptr = (TranslationResult*)hTran;
   return ptr->text.c_str();
  }
 catch(...)
  {
   return NULL;
  }
}


FAIND_API(const char*) sol_GetTranslationText8( void* hTran )
{
 try
  {
   TranslationResult *ptr = (TranslationResult*)hTran;
   if( ptr->text8.empty() && !ptr->text.empty() )
    {
     ptr->text8 = lem::to_utf8(ptr->text);
    }

   return ptr->text8.c_str();
  }
 catch(...)
  {
   return NULL;
  }
}


FAIND_API(const wchar_t*) sol_GetTranslationLog( void* hTran )
{
 try
  {
   TranslationResult *ptr = (TranslationResult*)hTran;
   return ptr->trace.c_str();
  }
 catch(...)
  {
   return NULL;
  }
}


FAIND_API(const char*) sol_GetTranslationLog8( void* hTran )
{
 try
  {
   TranslationResult *ptr = (TranslationResult*)hTran;
   if( ptr->trace8.empty() && !ptr->trace.empty() )
    {
     ptr->trace8 = lem::to_utf8(ptr->trace);
    }

   return ptr->trace8.c_str();
  }
 catch(...)
  {
   return NULL;
  }
}






FAIND_API(int) sol_CountTranslationMarkItems( void* hTran, const wchar_t *MarkName )
{
 try
  {
   TranslationResult *ptr = (TranslationResult*)hTran;
   const Variator *var = ptr->fblo->GetPack().vars().front();
   const TreeMarks* marks = var->FindMarks(MarkName);
   if( marks==NULL )
    return 0;
   else
    return marks->Size(); 
  }
 catch(...)
  {
   return -1;
  }
}

FAIND_API(int) sol_CountTranslationMarkItems8( void* hTran, const char *MarkName8 )
{
 try
  {
   UFString MarkName = lem::from_utf8(MarkName8);

   TranslationResult *ptr = (TranslationResult*)hTran;
   const Variator *var = ptr->fblo->GetPack().vars().front();

   const TreeMarks* marks = var->FindMarks( lem::UCString(MarkName.c_str()) );
   if( marks==NULL )
    return 0;
   else
    return marks->Size(); 
  }
 catch(...)
  {
   return -1;
  }
}



class PrinterDecorator1 : public TrValuePrinterDecorator
{
 public:
  PrinterDecorator1(void):TrValuePrinterDecorator(){}

  virtual void PrintString( const wchar_t *str, lem::OFormatter &out ) const
  {
   out.printf( "%us", str );
   return;
  }

 virtual void OpenTuple( const TrTuple &t, lem::OFormatter &out ) const
 {
  out.printf( "{" );
 }
 
 virtual void CloseTuple( const TrTuple &t, lem::OFormatter &out ) const
 {
  out.printf( "}" );
 }

 virtual void NextTupleItem( const TrTuple &t, lem::OFormatter &out ) const
 {
  out.printf( "|" );
 }

};


FAIND_API(const wchar_t*) sol_GetTranslationMarkItem(
                                                     HGREN hEngine,
                                                     void* hTran,
                                                     const wchar_t *MarkName,
                                                     int ItemIndex
                                                    )
{
 try
  {
   Solarix::Dictionary * sol_id = &*(HandleEngine(hEngine)->dict);

   TranslationResult *ptr = (TranslationResult*)hTran;
   const Variator *var = ptr->fblo->GetPack().vars().front();

   const TreeMarks* marks = var->FindMarks(MarkName);
   if( marks==NULL )
    return L"";
   else
    {
     lem::Char_Stream::UTF16_MemWriter wrt;
     lem::OUFormatter frm(&wrt,false);
     PrinterDecorator1 decorator;
     (*marks)[ItemIndex].Print( *sol_id, frm, &decorator );
     lem::UFString *str = new lem::UFString(wrt.string());
     ptr->ustrings.push_back(str);
     return str->c_str(); 
    }
  }
 catch(...)
  {
   return NULL;
  }
}


FAIND_API(const char*) sol_GetTranslationMarkItem8( HGREN hEngine, void* hTran, const char *MarkName8, int ItemIndex )
{
 try
  {
   UFString MarkName = lem::from_utf8(MarkName8);

   Solarix::Dictionary * sol_id = &*(HandleEngine(hEngine)->dict);

   TranslationResult *ptr = (TranslationResult*)hTran;
   const Variator *var = ptr->fblo->GetPack().vars().front();

   const TreeMarks* marks = var->FindMarks( lem::UCString(MarkName.c_str()) );
   if( marks==NULL )
    return "";
   else
    {
     lem::Char_Stream::UTF16_MemWriter wrt;
     lem::OUFormatter frm(&wrt,false);
     PrinterDecorator1 decorator;
     (*marks)[ItemIndex].Print( *sol_id, frm, &decorator );
     lem::FString *str = new lem::FString( lem::to_utf8(wrt.string()) );
     ptr->astrings.push_back(str);
     return str->c_str(); 
    }
  }
 catch(...)
  {
   return NULL;
  }
}



// Search for ID of part of speech
// http://www.solarix.ru/api/en/sol_FindClass.shtml
// http://www.solarix.ru/api/ru/sol_FindClass.shtml
FAIND_API(int) sol_FindClass( HGREN hEngine, const wchar_t *ClassName )
{
 if( hEngine==NULL || !HandleEngine(hEngine)->dict || lem::lem_is_empty(ClassName) )
  return -2;

 try
  {
   return HandleEngine(hEngine)->dict->GetSynGram().FindClass( lem::UCString(ClassName) );
  } 
 catch(...)
  {
   // Internal error
   return -2;
  }
}


// Search for ID of part of speech - utf8 version
// http://www.solarix.ru/api/en/sol_FindClass.shtml
// http://www.solarix.ru/api/ru/sol_FindClass.shtml
FAIND_API(int) sol_FindClass8( HGREN hEngine, const char *ClassNameUtf8 )
{
 lem::UFString NameW = lem::from_utf8(ClassNameUtf8);
 return sol_FindClass( hEngine, NameW.c_str() );
}


// http://www.solarix.ru/api/en/sol_FindEnum.shtml
// http://www.solarix.ru/api/ru/sol_FindEnum.shtml
FAIND_API(int) sol_FindEnum( HGREN hEngine, const wchar_t *EnumName )
{
 if( hEngine==NULL || !HandleEngine(hEngine)->dict || lem::lem_is_empty(EnumName) )
  return -2;

 try
  {
   const Solarix::GramCoordAdr ca = HandleEngine(hEngine)->dict->GetSynGram().FindCoord( lem::UCString(EnumName) );
   return ca.GetIndex();
  } 
 catch(...)
  {
   // Internal error
   return -2;
  }
}

FAIND_API(int) sol_FindEnum8( HGREN hEngine, const char *EnumNameUtf8 )
{
 lem::UFString NameW = lem::from_utf8(EnumNameUtf8);
 return sol_FindEnum( hEngine, NameW.c_str() );
}


// http://www.solarix.ru/api/en/sol_FindEnumState.shtml
// http://www.solarix.ru/api/ru/sol_FindEnumState.shtml
FAIND_API(int) sol_FindEnumState( HGREN hEngine, int CoordID, const wchar_t *StateName )
{
 if( hEngine==NULL || !HandleEngine(hEngine)->dict || CoordID<0 || lem::lem_is_empty(StateName) )
  return -2;

 try
  {
   return HandleEngine(hEngine)->dict->GetSynGram().coords()[CoordID].FindState(StateName);
  } 
 catch(...)
  {
   // Internal error
   return -2;
  }
}

// http://www.solarix.ru/api/en/sol_FindEnumState.shtml
// http://www.solarix.ru/api/ru/sol_FindEnumState.shtml
FAIND_API(int) sol_FindEnumState8( HGREN hEngine, int CoordID, const char *StateNameUtf8 )
{
 lem::UFString NameW = lem::from_utf8(StateNameUtf8);
 return sol_FindEnumState( hEngine, CoordID, NameW.c_str() );
}


FAIND_API(int) sol_MatchNGrams(
                               HGREN hEngine,
                               const wchar_t *Text,
                               int * unmatched_2_ngrams,
                               int *n2,
                               int *n3
                              )
{
 #if defined SOLARIX_PRO && !defined SOL_NO_NGRAMS && !defined SOL_NO_AA
 if( hEngine==NULL || !HandleEngine(hEngine)->ok || Text==NULL )
  return -1;

 try
  {
   Solarix::Sentence sent;
   sent.Parse(Text,false,HandleEngine(hEngine)->dict.get(),HandleEngine(hEngine)->DefaultLanguage,NULL);
   sent.to_upper();
   HandleEngine(hEngine)->dict->MatchNGrams( sent, unmatched_2_ngrams, n2, n3 );
  }
 catch(...)
  {
   return -1;
  }

 return 0;
 #else
 return -1;
 #endif
}






FAIND_API(int) sol_Syllabs(
                           HGREN hEngine,
                           const wchar_t *OrgWord,
                           wchar_t SyllabDelimiter, 
                           wchar_t *Result,
                           int LanguageID
                          )
{
 #if !defined SOL_NO_AA
 if( hEngine==NULL || !HandleEngine(hEngine)->ok || OrgWord==NULL )
  return -1;

 try
  {
   *Result = 0;

   wchar_t delim[2] = { SyllabDelimiter, 0 };

   lem::UCString word(OrgWord);
   lem::MCollect<lem::UCString> slb_list;
   
   HandleEngine(hEngine)->dict->GetGraphGram().FindSyllabs( word, LanguageID, false, slb_list, NULL );
      
   for( lem::Container::size_type i=0; i<slb_list.size(); ++i )
    {
     if( i>0 )
      wcscat( Result, delim );

     wcscat( Result, slb_list[i].c_str() );
    }
  }
 catch(...)
  {
   return -1;
  }

 return 0;
 #else
 return -1;
 #endif
}


FAIND_API(int) sol_Syllabs8(
                            HGREN hEngine,
                            const char *OrgWord,
                            char SyllabDelimiter, 
                            char *Result,
                            int LanguageID
                           )
{
 try
  {
   wchar_t *buffer = new wchar_t[ strlen(OrgWord)*8 ];
   *buffer=0;
   const int rc = sol_Syllabs( hEngine, lem::from_utf8(OrgWord).c_str(), SyllabDelimiter, buffer, LanguageID );
   
   lem::lem_strcpy( Result, lem::to_utf8(buffer).c_str() );
   
   delete[] buffer;
   return rc;
  }
 CATCH_API(hEngine);

 return -2;

}


// **********************************************************
// Токенизация строки - разбивка на лексемы.
//
// API Help:
// http://www.solarix.ru/api/en/sol_Tokenize.shtml
// http://www.solarix.ru/api/ru/sol_Tokenize.shtml
//
// Detailed description of underlying algorithms:
// http://www.solarix.ru/for_developers/docs/tokenizer.shtml
//
// **********************************************************
FAIND_API(HGREN_STR) sol_TokenizeW( HGREN hEngine, const wchar_t *Sentence, int LanguageID )
{
 if( hEngine==NULL || !HandleEngine(hEngine)->ok || Sentence==NULL )
  return NULL;

 #if defined SOL_CAA && !defined SOL_NO_AA
 std::auto_ptr<GREN_Strings> list( new GREN_Strings );

 try
  {
   lem::UFString sentence(Sentence);
   Solarix::Sentence sent;
   sent.Parse( sentence, false, HandleEngine(hEngine)->dict.get(), LanguageID, NULL );
   std::copy( sent.begin(), sent.end(), std::back_inserter(list->list) );
   return list.release();
  }
 CATCH_API(hEngine)
 #endif

 return NULL;
}


FAIND_API(HGREN_STR) sol_TokenizeA( HGREN hEngine, const char *Sentence, int LanguageID )
{
 return sol_TokenizeW( hEngine, to_unicode(Sentence).c_str(), LanguageID );
}

FAIND_API(HGREN_STR) sol_Tokenize8( HGREN hEngine, const char *SentenceUtf8, int LanguageID )
{
 return sol_TokenizeW( hEngine, lem::from_utf8(SentenceUtf8).c_str(), LanguageID );
}



FAIND_API(HGREN_SBROKER) sol_CreateSentenceBroker(
                                                  HGREN hEngine,
                                                  const wchar_t *Filename,
                                                  const wchar_t *DefaultCodepage,
                                                  int language
                                                 )
{
 if( hEngine==NULL || lem::lem_is_empty(Filename) )
  return NULL;

 try 
  {
   lem::Path filename(Filename);

   #if defined SOLARIX_SEARCH_ENGINE
   lem::StreamPtr file( new BinaryReader(filename) );

   const lem::CodeConverter *cp = &lem::UI::get_UI().GetSessionCp();

   if( !lem::lem_is_empty(DefaultCodepage) )
    {
     try
      {
       cp = lem::CodeConverter::getConverter( to_ascii(DefaultCodepage) );
      }
     catch(...)
      {
       return NULL;
      }
    } 

   HandleEngine(hEngine)->scanning.prefer_cp_list.clear();
   HandleEngine(hEngine)->scanning.prefer_cp_list.push_back( cp );

   lem::Ptr<Solarix::Search_Engine::Base_File_Reader> reader = HandleEngine(hEngine)->detector->FindReader(
                                                  HandleEngine(hEngine)->scanning,
                                                  filename.GetUnicode(),
                                                  to_upper(filename.GetExtension()),
                                                  file 
                                                 );

   if( reader.NotNull() )
    {
     lem::Ptr<UFString> text = new UFString;
     reader->read_whole_text(*text);
     lem::Ptr<lem::Char_Stream::WideStream> reader2 = new lem::Char_Stream::UTF16_MemReader(text);
     SentenceBroker *broker = new SentenceBroker( reader2, HandleEngine(hEngine)->dict.get(), language );
     return broker;
    }

   #else

    lem::Ptr<lem::Char_Stream::WideStream> reader = lem::Char_Stream::WideStream::GetReader(filename);
    SentenceBroker *broker = new SentenceBroker( reader, HandleEngine(hEngine)->dict.get(), language );
    return broker;

   #endif
  }
 CATCH_API(hEngine)

 return NULL;
}


FAIND_API(HGREN_SBROKER) sol_CreateSentenceBrokerMemW( HGREN hEngine, const wchar_t *Text, int LanguageID )
{
 if( hEngine==NULL || lem::lem_is_empty(Text) )
  return NULL;

 try 
  {
   lem::Ptr<lem::Char_Stream::WideStream> reader2 = new lem::Char_Stream::UTF16_MemReader(Text);
   SentenceBroker *broker = new SentenceBroker( reader2, HandleEngine(hEngine)->dict.get(), LanguageID );
   return broker;
  }
 CATCH_API(hEngine)

 return NULL;
}


FAIND_API(HGREN_SBROKER) sol_CreateSentenceBrokerMemA( HGREN hEngine, const char *Text, int LanguageID )
{
 return sol_CreateSentenceBrokerMemW( hEngine, lem::to_unicode(Text).c_str(), LanguageID );
}


FAIND_API(HGREN_SBROKER) sol_CreateSentenceBrokerMem8( HGREN hEngine, const char *TextUtf8, int LanguageID )
{
 if( hEngine==NULL || lem::lem_is_empty(TextUtf8) )
  return NULL;

 try 
  {
   lem::Ptr<lem::Char_Stream::WideStream> reader2 = new lem::Char_Stream::UTF8_MemReader(TextUtf8);
   SentenceBroker *broker = new SentenceBroker( reader2, HandleEngine(hEngine)->dict.get(), LanguageID );
   return broker;
  }
 CATCH_API(hEngine)

 return NULL;
}




FAIND_API(int) sol_FetchSentence( HGREN_SBROKER hBroker )
{
 try
  {
   if( hBroker!=NULL )
    {
     if( !((SentenceBroker*)hBroker)->Fetch() )
      return -1; // no more sentences is available

     const UFString &s = ((SentenceBroker*)hBroker)->GetFetchedSentence();
     return s.length();
    }
  }
 catch(...)
  {
  }

 return -2;
}


FAIND_API(int) sol_GetFetchedSentenceLen( HGREN_SBROKER hBroker )
{
 try
  {
   if( hBroker!=NULL )
    {
     return ((SentenceBroker*)hBroker)->GetFetchedSentence().length();
    }
  }
 catch(...)
  {
  }

 return -1;
}


FAIND_API(int) sol_GetFetchedSentenceW( HGREN_SBROKER hBroker, wchar_t *Buffer )
{
 try
  {
   if( hBroker!=NULL && Buffer!=NULL )
    {
     lem::lem_strcpy( Buffer, ((SentenceBroker*)hBroker)->GetFetchedSentence().c_str() );
     return ((SentenceBroker*)hBroker)->GetFetchedSentence().length();
    }
  }
 catch(...)
  {
  }

 return -1; 
}


FAIND_API(int) sol_GetFetchedSentenceA( HGREN_SBROKER hBroker, char *Buffer )
{
 try
  {
   if( hBroker!=NULL && Buffer!=NULL )
    {
     lem::lem_strcpy( Buffer, lem::to_ascii(((SentenceBroker*)hBroker)->GetFetchedSentence()).c_str() );
     return lem::lem_strlen(Buffer);
    }
  }
 catch(...)
  {
  }

 return -1; 
}


FAIND_API(int) sol_GetFetchedSentence8( HGREN_SBROKER hBroker, char *BufferUtf8 )
{
 try
  {
   if( hBroker!=NULL && BufferUtf8!=NULL )
    {
     lem::lem_strcpy( BufferUtf8, lem::to_utf8(((SentenceBroker*)hBroker)->GetFetchedSentence()).c_str() );
     return lem::lem_strlen(BufferUtf8);
    }
  }
 catch(...)
  {
  }

 return -1; 
}




FAIND_API(void) sol_DeleteSentenceBroker( HGREN_SBROKER hBroker )
{
 try
  {
   delete ((SentenceBroker*)hBroker);
  }
 catch(...)
  {
  }

 return;
}




FAIND_API(int) sol_Bits(void)
{
 if( sizeof(int*)==8 )
  return 64;
 else if( sizeof(int*)==4 )
  return 32;
 else -1;
}


// Get the ID of the language
// http://www.solarix.ru/api/en/sol_FindLanguage.shtml
FAIND_API(int) sol_FindLanguage( HGREN hEngine, const wchar_t *LanguageName )
{
 if( hEngine==NULL || lem::lem_is_empty(LanguageName) )
  return -1;

 try
  {
   return HandleEngine(hEngine)->dict->GetSynGram().Find_Language(LanguageName);
  }
 catch(...)
  { 
   return -1;
  }

 return -1;
}


// http://www.solarix.ru/api/en/sol_FindLanguage.shtml
FAIND_API(int) sol_FindLanguage8( HGREN hEngine, const char *LanguageNameUtf8 )
{
 lem::UFString w( lem::from_utf8(LanguageNameUtf8) );
 return sol_FindLanguage( hEngine, w.c_str() );
}


// **********************************
// Состояние координаты для узла
// **********************************
FAIND_API(int) sol_GetNodeCoordState( HGREN_TREENODE hNode, int CoordID )
{
 if( hNode==NULL || CoordID==-1 )
  return -1;

 try
  {
   const int istate = HandleNode(hNode)->GetNode().GetState( Solarix::GramCoordAdr(CoordID) );
   return istate;
  }
 catch(...)
  {
  }

 return -1;
}


// *******************************************
// Состояние координаты для версии словоформы
// *******************************************
FAIND_API(int) sol_GetNodeVerCoordState( HGREN_TREENODE hNode, int iver, int CoordID )
{
 if( hNode==NULL || CoordID==-1 )
  return -1;

 try
  {
   int istate=-1;
   if( iver==0 )
    istate = HandleNode(hNode)->GetNode().GetState( Solarix::GramCoordAdr(CoordID) );
   else
    istate = HandleNode(hNode)->GetNode().GetAlts()[iver-1]->GetState( Solarix::GramCoordAdr(CoordID) );

   return istate;
  }
 catch(...)
  {
  }

 return -1;
}



// Detect the presence of icoord:istate in wordform.
// Return value: 0 - not found, 1 - found.
//
// http://www.solarix.ru/api/ru/sol_GetNodeCoordPair.shtml
FAIND_API(int) sol_GetNodeCoordPair( HGREN_TREENODE hNode, int CoordID, int StateID )
{
 if( hNode==NULL || CoordID==-1 || StateID==-1 )
  return 0;

 bool res = HandleNode(hNode)->GetNode().GetPairs().FindOnce( Solarix::GramCoordPair(CoordID,StateID) )!=UNKNOWN;
 return res ? 1 : 0;
}


// http://www.solarix.ru/api/ru/sol_GetNodeVerCoordPair.shtml
FAIND_API(int) sol_GetNodeVerCoordPair( HGREN_TREENODE hNode, int iver, int CoordID, int StateID )
{
 if( hNode==NULL || CoordID==-1 || StateID==-1 )
  return 0;

 bool res;

 if( iver==0 )
  res = HandleNode(hNode)->GetNode().GetPairs().FindOnce( Solarix::GramCoordPair(CoordID,StateID) )!=UNKNOWN;
 else
  res = HandleNode(hNode)->GetNode().GetAlts()[iver-1]->GetPairs().FindOnce( Solarix::GramCoordPair(CoordID,StateID) )!=UNKNOWN;

 return res ? 1 : 0;
}


// http://www.solarix.ru/api/ru/sol_GetNodePairsCount.shtml
FAIND_API(int) sol_GetNodePairsCount( HGREN_TREENODE hNode )
{
 if( hNode==NULL )
  return -1;

 return CastSizeToInt(HandleNode(hNode)->GetNode().GetPairs().size());
}


// http://www.solarix.ru/api/ru/sol_GetNodeVerPairsCount.shtml
FAIND_API(int) sol_GetNodeVerPairsCount( HGREN_TREENODE hNode, int iver )
{
 if( hNode==NULL )
  return -1;

 if( iver==0 )
  return CastSizeToInt(HandleNode(hNode)->GetNode().GetPairs().size());
 else
  return CastSizeToInt(HandleNode(hNode)->GetNode().GetAlts()[iver-1]->GetPairs().size());
}


// http://www.solarix.ru/api/ru/sol_GetNodePairCoord.shtml
FAIND_API(int) sol_GetNodePairCoord( HGREN_TREENODE hNode, int ipair )
{
 if( hNode==NULL || ipair<0 || ipair>=CastSizeToInt(HandleNode(hNode)->GetNode().GetPairs().size()) )
  return -1;

 return HandleNode(hNode)->GetNode().GetPairs()[ipair].GetCoord().GetIndex();
}


// http://www.solarix.ru/api/ru/sol_GetNodeVerPairCoord.shtml
FAIND_API(int) sol_GetNodeVerPairCoord( HGREN_TREENODE hNode, int iver, int ipair )
{
 if( hNode==NULL || ipair<0 )
  return -1;

 if( iver==0 )
  {
   if( ipair>=CastSizeToInt(HandleNode(hNode)->GetNode().GetPairs().size()) )
    return -1;

   return HandleNode(hNode)->GetNode().GetPairs()[ipair].GetCoord().GetIndex();
  }
 else
  {
   if( ipair>=CastSizeToInt(HandleNode(hNode)->GetNode().GetAlts()[iver-1]->GetPairs().size()) )
    return -1;

   return HandleNode(hNode)->GetNode().GetAlts()[iver-1]->GetPairs()[ipair].GetCoord().GetIndex();
  }
}


// http://www.solarix.ru/api/ru/sol_GetNodePairState.shtml
FAIND_API(int) sol_GetNodePairState( HGREN_TREENODE hNode, int ipair )
{
 if( hNode==NULL || ipair<0 || ipair>=CastSizeToInt(HandleNode(hNode)->GetNode().GetPairs().size()) )
  return -1;

 return HandleNode(hNode)->GetNode().GetPairs()[ipair].GetState();
}


// http://www.solarix.ru/api/ru/sol_GetNodeVerPairState.shtml
FAIND_API(int) sol_GetNodeVerPairState( HGREN_TREENODE hNode, int iver, int ipair )
{
 if( hNode==NULL || ipair<0 )
  return -1;

 if( iver==0 )
  {
   if( ipair>=CastSizeToInt(HandleNode(hNode)->GetNode().GetPairs().size()) )
    return -1;

   return HandleNode(hNode)->GetNode().GetPairs()[ipair].GetState();
  }
 else
  {
   if( ipair>=CastSizeToInt(HandleNode(hNode)->GetNode().GetAlts()[iver-1]->GetPairs().size()) )
    return -1;

   return HandleNode(hNode)->GetNode().GetAlts()[iver-1]->GetPairs()[ipair].GetState();
  }
}


FAIND_API(int) sol_GenerateWordform(
                                    HGREN hEng,
                                    int ie,
                                    int npairs,
                                    const int *pairs,
                                    wchar_t *Result
                                   )
{
 if( hEng==NULL || ie==UNKNOWN ) 
  {
   return -1;
  }

 try
  {
   const SG_Entry &e = HandleEngine(hEng)->dict->GetSynGram().GetEntry(ie);

   CP_Array apairs;
   for( int i=0; i<npairs; ++i )
    apairs.push_back( Solarix::GramCoordPair( pairs[i*2], pairs[i*2+1] ) ); 

//   const SG_EntryForm &f = e.FindFormAny( apairs );
   for( lem::Container::size_type i=0; i<e.forms().size(); ++i )
    {
     const SG_EntryForm &f = e.forms()[i];
     if( f.does_match(apairs) )
      {
       wcscpy( Result, f.name().c_str() );
       return 1;
      } 
    }

   *Result = 0;
   return 0;
  }
 catch(...)
  {
   return -1;
  }
}



// http://www.solarix.ru/api/ru/sol_GenerateWordforms.shtml
FAIND_API(HGREN_STR) sol_GenerateWordforms(
                                           HGREN hEngine, 
                                           int EntryID,
                                           int npairs,
                                           const int *pairs
                                          )
{
 if( hEngine==NULL || EntryID==UNKNOWN ) 
  {
   return NULL;
  }

 try
  {
   GREN_Strings *res = new GREN_Strings;

   const SG_Entry &e = HandleEngine(hEngine)->dict->GetSynGram().GetEntry(EntryID);

   CP_Array apairs;
   for( int i=0; i<npairs; ++i )
    apairs.push_back( Solarix::GramCoordPair( pairs[i*2], pairs[i*2+1] ) );

   for( lem::Container::size_type i=0; i<e.forms().size(); ++i )
    {
     const SG_EntryForm &f = e.forms()[i];
     if( f.does_match(apairs) )
      {
       res->list.push_back( f.name().c_str() );
      } 
    }

   return res;
  }
 catch(...)
  {
   return NULL;
  }
}






FAIND_API(int) sol_CountLexems( HGREN hEng )
{
 if( hEng==NULL ) 
  {
   return -1;
  }

 try
  {
   SynGram &sg = HandleEngine(hEng)->dict->GetSynGram();
   const int n = sg.GetEntries().CountLexemes();
   return n;
  }
 catch(...)
  {
   return -1;
  }
}


/*
FAIND_API(int) sol_GetLexem( HGREN hEng, int ilexem, wchar_t *buffer )
{
 if( hEng==NULL && ilexem<0 ) 
  {
   return -1;
  }

 try
  {
   const SynGram &sg = HandleEngine(hEng)->dict->GetSynGram();
   const lem::UCString & lexem = sg.Get_ML_ref().get_list()[ilexem];
   lem::lem_strcpy( buffer, lexem.c_str() );
   return lexem.length();
  }
 catch(...)
  {
   return -1;
  }
}
*/

struct MatchingParadigma : lem::NonCopyable
{
 Solarix::Lexem entry_name;
 lem::MCollect<int> ies; // если найдены точные словарные статьи
 lem::MCollect<lem::UCString> paradigmas; // имена подходящих парадигм
 lem::MCollect<int> iparadigmas; // индексы подходящих парадигм во внутреннем списке
};


FAIND_API(HFLEXIONS) sol_FindFlexionHandlers( HGREN hEngine, const wchar_t *WordBasicForm, int Flags )
{
 if( hEngine==NULL && lem::lem_is_empty(WordBasicForm) ) 
  {
   return NULL;
  }

 try
  {
   SynGram &sg = HandleEngine(hEngine)->dict->GetSynGram();

   MatchingParadigma *res = new MatchingParadigma;

   res->entry_name = WordBasicForm;
   res->entry_name.to_upper();
   HandleEngine(hEngine)->dict->GetLexAuto().TranslateLexem(res->entry_name,true);

   if( Flags==1 )
    {
     // Во-первых, ищем подходящие словарные статьи.
     sg.FindEntries( res->entry_name, res->ies );
    }

   // Во-вторых, ищем автопарадигмы
   sg.Get_DSA().FindDecl( res->entry_name, res->iparadigmas );

   // Заполняем список их именами  
   for( lem::Container::size_type i=0; i<res->iparadigmas.size(); ++i )
    res->paradigmas.push_back( sg.Get_DSA().GetDecl( res->iparadigmas[i] ).GetName() );

   return res;
  }
 catch(...)
  {
   return NULL;
  }
}

// Flexion engine: return the number of matching entries
FAIND_API(int) sol_CountEntriesInFlexionHandlers( HGREN hEng, HFLEXIONS hFlexs )
{
 if( hEng==NULL && hFlexs==NULL ) 
  {
   return NULL;
  }

 try
  {
   return CastSizeToInt( ((const MatchingParadigma*)hFlexs)->ies.size());
  }
 catch(...)
  {
   return -1;
  }
}


// Flexion engine: return the number of matching paradigmas
FAIND_API(int) sol_CountParadigmasInFlexionHandlers( HGREN hEng, HFLEXIONS hFlexs )
{
 if( hEng==NULL && hFlexs==NULL ) 
  {
   return NULL;
  }

 try
  {
   const MatchingParadigma *x = (const MatchingParadigma*)hFlexs;
   return CastSizeToInt(x->iparadigmas.size());
  }
 catch(...)
  {
   return -1;
  }
}


// Flexion engine: return the entry ID
FAIND_API(int) sol_GetEntryInFlexionHandlers( HGREN hEng, HFLEXIONS hFlexs, int Index )
{
 if( hEng==NULL && hFlexs==NULL ) 
  {
   return NULL;
  }

 try
  {
   const MatchingParadigma *x = (const MatchingParadigma*)hFlexs;
   return x->ies[Index];
  }
 catch(...)
  {
   return -1;
  }
}


// Flexion engine: return the paradigma internal index and human-friendly name
FAIND_API(int) sol_GetParadigmaInFlexionHandlers( HGREN hEng, HFLEXIONS hFlexs, int Index, wchar_t *ParadigmaName )
{
 if( hEng==NULL && hFlexs==NULL ) 
  {
   return NULL;
  }

 try
  {
   const MatchingParadigma *x = (const MatchingParadigma*)hFlexs;

   if( ParadigmaName!=NULL )
    lem::lem_strcpy( ParadigmaName, x->paradigmas[Index].c_str() );

   return x->iparadigmas[Index];
  }
 catch(...)
  {
   return -1;
  }
}


struct FlexionTable : lem::NonCopyable
{
 lem::MCollect<Solarix::Lexem> form_names;
 lem::PtrCollect< CP_Array > form_dims;
};



FAIND_API(HFLEXIONTABLE) sol_BuildFlexionHandler(
                                                 HGREN hEng,
                                                 HFLEXIONS hFlexs,
                                                 const wchar_t *ParadigmaName,
                                                 int EntryIndex
                                                )
{
 if( hEng==NULL && (hFlexs==NULL && !lem::lem_is_empty(ParadigmaName) ) ) 
  {
   return NULL;
  }

 try
  {
   FlexionTable *res = new FlexionTable;

   SynGram &sg = HandleEngine(hEng)->dict->GetSynGram();

   if( !lem::lem_is_empty(ParadigmaName) )
    {
     const MatchingParadigma *xpar = (const MatchingParadigma*)hFlexs;

     // Отыскиваем нужную парадигму.
     const int ipar = sg.Get_DSA().FindDecl( ParadigmaName );
     if( ipar!=UNKNOWN )
      {
       const SG_DeclensionTable &par = sg.Get_DSA().GetDecl(ipar);

       par.GenerateForms( 
                         xpar->entry_name,
                         res->form_names,
                         res->form_dims,
                         sg,
                         sg.Get_DSA() 
                        );
      }
    }
   else if ( EntryIndex!=UNKNOWN )
    {
     const Solarix::SG_Entry &e = sg.GetEntry(EntryIndex);
     for( lem::Container::size_type i=0; i<e.forms().size(); ++i )
      {
       const Solarix::SG_EntryForm &f = e.forms()[i];
       res->form_names.push_back( f.name() );
       res->form_dims.push_back( new CP_Array( f.coords() ) );
      }
    }

   return res;
  }
 catch(...)
  {
   return NULL;
  }
}

FAIND_API(int) sol_CountFlexionHandlerWordform( HGREN hEngine, HFLEXIONTABLE hFlex )
{
 try
  {
   if( hFlex==NULL )
    return 0;

   const FlexionTable *table = (const FlexionTable*)hFlex;
   return CastSizeToInt(table->form_names.size());
  }
 CATCH_API(hEngine);

 return -1;
} 

FAIND_API(const wchar_t*) sol_GetFlexionHandlerWordformText( HGREN hEngine, HFLEXIONTABLE hFlex, int FormIndex )
{
 try
  {
   if( hFlex==NULL )
    return 0;

   const FlexionTable *table = (const FlexionTable*)hFlex;
   return table->form_names[FormIndex].c_str();
  }
 CATCH_API(hEngine);

 return NULL;
}


FAIND_API(const wchar_t*) sol_GetFlexionHandlerWordform(
                                                        HGREN hEng,
                                                        HFLEXIONTABLE hFlex,
                                                        const wchar_t *dims
                                                       )
{
 try
  {
   const FlexionTable *table = (const FlexionTable*)hFlex;

   // Сначала надо распарсить строку координат dims на координатные пары
   lem::UFString sdims(dims);
   lem::Collect< lem::UFString > spairs;
   lem::parse( sdims, spairs, L" ,;" );

   const SynGram &sg = HandleEngine(hEng)->dict->GetSynGram();

   lem::MCollect< Solarix::GramCoordPair > req_dims;
   for( lem::Container::size_type i=0; i<spairs.size(); ++i )
    {
     const UFString &s = spairs[i];
     lem::MCollect<UCString> s2;
     lem::parse( s, s2, false );

     const int icoord = sg.FindCoord( s2[0] ).GetIndex();
     if( icoord!=UNKNOWN )
      {
       int istate=UNKNOWN;

       // Для бистабильных координат формат задания состояния немного другой:
       // ~КРАТКИЙ = КРАТКИЙ:0
       // КРАТКИЙ  = КРАТКИЙ:1
 
       const Solarix::GramCoord &c = sg.coords()[icoord];

       if( c.IsBistable() )
        {
         if( s2[0].front()==L'~' )
          istate=0;
         else
          istate=1; 
        }
       else
        {
         istate = c.FindState(s2[1]);
        } 

       if( istate!=UNKNOWN )
        { 
         req_dims.push_back( Solarix::GramCoordPair(GramCoordAdr(icoord),istate) );
        }
      }
    }

   // Получили список координатных пар, которые должны присутствовать в искомой форме.
   // Ищем форму. Подойдет первая, у которой все необходимые координаты есть и минимум
   // прочих координат.
   int ibest_form=UNKNOWN, best_form_dims=lem::int_max;

   for( lem::Container::size_type i=0; i<table->form_dims.size(); ++i )
    {
     const lem::MCollect<GramCoordPair> &form_dims = * table->form_dims[i];
     if( CastSizeToInt(form_dims.size()) > best_form_dims )
      continue;

     bool all_match=true;
     for( lem::Container::size_type j=0; j<req_dims.size(); ++j )
      if( form_dims.find( req_dims[j] )==UNKNOWN )
       {
        all_match=false;
        break;
       }

     if( all_match && CastSizeToInt(form_dims.size())<best_form_dims )
      {
       best_form_dims = CastSizeToInt(form_dims.size());
       ibest_form = CastSizeToInt(i);
      }
    }

   if( ibest_form!=UNKNOWN )
    {
     return table->form_names[ibest_form].c_str();
    }
   else
    {
     return L"";
    }
  }
 CATCH_API(hEng);

 return NULL;
}




FAIND_API(int) sol_DeleteFlexionHandlers( HGREN hEngine, HFLEXIONS hFlexs )
{
 try
  {
   delete (MatchingParadigma*)hFlexs;
   return 0;
  }
 CATCH_API(hEngine);

 return -1;
}



FAIND_API(int) sol_DeleteFlexionHandler( HGREN hEng, HFLEXIONTABLE hFlex )
{
 try
  {
   delete (FlexionTable*)hFlex;
   return 0;
  }
 catch(...) 
  {
   return -1;
  }
}




// ******************************************************************
// Поиск фразовой статьи. Возвращается первичный ключ записи.
// http://www.solarix.ru/api/en/sol_FindPhrase.shtml
//
// Flags=0 - ignore case
// Flags=1 - case sensitive lookup
// ******************************************************************
FAIND_API(int) sol_FindPhrase( HGREN hEngine, const wchar_t *Phrase, int Flags )
{
 if( hEngine==NULL || lem::lem_is_empty(Phrase) )
  return -2;

 try
  {
   SynGram &sg = HandleEngine(hEngine)->dict->GetSynGram();
   const int ekey = sg.GetStorage().FindPhrase( Phrase, Flags==0 );
   return ekey;   
  }
 CATCH_API(hEngine);

 return -1;
}


// http://www.solarix.ru/api/en/sol_FindPhrase.shtml
FAIND_API(int) sol_FindPhrase8( HGREN hEngine, const char *PhraseUtf8, int Flags )
{
 return sol_FindPhrase( hEngine, lem::from_utf8(PhraseUtf8).c_str(), Flags );
}



struct LinkInfo : lem::NonCopyable
{
 int type; // 0 - для словарных статей, 1 - для фразовых
 int id; // первичный ключ записи в соответствующей таблице связей.
 int code; // код типа связи
 int ekey1, ekey2; // ключи словарных или фразовых статей
 lem::UFString tags_txt; // список тегов
 lem::FString tags_txt8;
 lem::UFString flags; // список флагов
 lem::FString flags8;
};

typedef lem::PtrCollect<LinkInfo> LinksInfo;

typedef void* HLINKSINFO;



// Ищем все связи в тезаурусе для словарной или фразовой статьи.
// Key1 - id словарной (ключ) или фразовой статьи
// LinkCode - константа типа связи
// Flags - дополнительные управляющие флаги: 0-для словарных статей, 1 - для фразовых.

// http://www.solarix.ru/api/ru/sol_ListLinksTxt.shtml

FAIND_API(HLINKSINFO) sol_ListLinksTxt(
                                       HGREN hEngine,
                                       int ie1,
                                       int LinkCode,
                                       int Flags
                                      )
{
 LinksInfo * res = NULL;

 #if !defined SOLARIX_DEMO
 try
  {
   res = new LinksInfo();

   SynGram &sg = HandleEngine(hEngine)->dict->GetSynGram();
   SG_Net &th = sg.Get_Net();

   if( Flags==0 )
    {
     const int Key1 = sg.GetEntry(ie1).GetKey();

     // Ищем связи для словарных статей.
     lem::Ptr<WordLinkEnumerator> links( sg.Get_Net().EnumerateWordLinks( Key1, LinkCode ) ); 
     while( links->Fetch() )
      {     
       LinkInfo *li = new LinkInfo;
       li->type = 0;
       li->id = links->GetLinkId();
       li->code = links->GetLinkType();
       li->ekey1 = ie1;
       li->ekey2 = links->GetEntryKey2();//sg.FindEntryIndexByKey( links->GetEntryKey2() );

       #if defined SOL_SAVETXT
       Solarix::PrintTags( links->GetTags(), li->tags_txt, sg );
       #endif

       res->push_back(li);
      }
    }
   else if( Flags==1 )
    {
     // Ищем связи для фразовых статей.
     lem::MCollect<int> tl_id, te_id;

     const int Key1 = ie1;

     lem::PtrCollect<SG_LinkFlag> flist; 

     th.FindComplexLinks( Key1, LinkCode, tl_id, te_id );

     for( lem::Container::size_type i=0; i<tl_id.size(); ++i )
      {
       lem::Ptr<SG_ComplexLink> lnk = th.GetComplexLink( tl_id[i] );

       SG_Phrase frz;
       sg.GetStorage().GetPhrase( te_id[i], frz );
       
       LinkInfo *li = new LinkInfo;
       li->type = 0;
       li->id = tl_id[i];
       li->code = LinkCode;
       li->ekey1 = Key1;
       li->ekey2 = te_id[i];

       Solarix::PrintTags( lnk->GetTags(), li->tags_txt, sg );

       flist.clear();
       th.GetLinkFlags( tl_id[i], flist );
       for( lem::Container::size_type k=0; k<flist.size(); ++k )
        {
         if( !li->flags.empty() )
          li->flags += L' ';

         li->flags += lem::format_str( L"%s=%s", flist[k]->GetFlag().c_str(), flist[k]->GetValue().c_str() );
        }

       res->push_back(li);
      }
    }

   return res;
  }
 CATCH_API(hEngine);
 #endif
 
 return NULL;
}


// http://www.solarix.ru/api/ru/sol_DeleteLinksInfo.shtml
FAIND_API(int) sol_DeleteLinksInfo( HGREN hEng, HLINKSINFO hList )
{
 try
  {
   if( hList!=NULL )
    {
     delete (LinksInfo*)hList;
    }

   return 0;
  }
 catch(...)
  {
   return -2;
  }
}

// Возвращается число найденных связей в списке.
// http://www.solarix.ru/api/ru/sol_LinksInfoCount.shtml
FAIND_API(int) sol_LinksInfoCount( HGREN hEngine, HLINKSINFO hList )
{
 try
  {
   if( hList==NULL )
    return 0;

   return CastSizeToInt(((LinksInfo*)hList)->size());
  }
 CATCH_API(hEngine);

 return -2;
}



// Возвращается тип связи в списке: 0 - для словарных статей, 1 - для фразовых
// http://www.solarix.ru/api/ru/sol_LinksInfoType.shtml
FAIND_API(int) sol_LinksInfoType( HGREN hEngine, HLINKSINFO hList, int Index )
{
 try
  {
   if( hList==NULL )
    return -1;

   const LinksInfo& list = *(LinksInfo*)hList;
   return list[Index]->type;
  }
 CATCH_API(hEngine);

 return -2;
}


// Возвращается числовая константа для типа связи (по справочнику типов).
// http://www.solarix.ru/api/ru/sol_LinksInfoCode.shtml
FAIND_API(int) sol_LinksInfoCode( HGREN hEngine, HLINKSINFO hList, int Index )
{
 try
  {
   if( hList==NULL )
    return -1;

   const LinksInfo& list = *(LinksInfo*)hList;
   return list[Index]->code;
  }
 CATCH_API(hEngine);

 return -2;
}


// *************************************************************************
// Возвращается первичный ключ записи для связи в списке.
// *************************************************************************
FAIND_API(int) sol_LinksInfoID( HGREN hEngine, HLINKSINFO hList, int Index )
{
 try
  {
   if( hList==NULL )
    return -1;

   const LinksInfo& list = *(LinksInfo*)hList;
   return list[Index]->id;
  }
 CATCH_API(hEngine);

 return -2;
}


// *************************************************************************
// Возращается ключ словарной или фразовой статьи слева в связи.
// *************************************************************************
FAIND_API(int) sol_LinksInfoEKey1( HGREN hEngine, HLINKSINFO hList, int Index )
{
 try
  {
   if( hList==NULL )
    return -1;

   const LinksInfo& list = *(LinksInfo*)hList;
   return list[Index]->ekey1;
  }
 CATCH_API(hEngine);

 return -2;
}


// *************************************************************************
// Возращается ключ словарной или фразовой статьи справа в связи.
// *************************************************************************
FAIND_API(int) sol_LinksInfoEKey2( HGREN hEngine, HLINKSINFO hList, int Index )
{
 try
  {
   if( hList==NULL )
    return -1;

   const LinksInfo& list = *(LinksInfo*)hList;
   return list[Index]->ekey2;
  }
 CATCH_API(hEngine);

 return -2;
}



// ********************************************************************
// Возвращается текстовое представление тегов для связи в списке.
// Если тегов у связи нет, то вернет NULL.
// ********************************************************************
FAIND_API(const wchar_t*) sol_LinksInfoTagsTxt(
                                               HGREN hEngine,
                                               HLINKSINFO hList,
                                               int Index
                                              )
{
 try
  {
   if( hList==NULL )
    return NULL;

   const LinksInfo& list = *(LinksInfo*)hList;
   return list[Index]->tags_txt.c_str();
  }
 CATCH_API(hEngine);

 return NULL;
}

FAIND_API(const char*) sol_LinksInfoTagsTxt8(
                                             HGREN hEngine,
                                             HLINKSINFO hList,
                                             int Index
                                            )
{
 try
  {
   if( hList==NULL )
    return NULL;

   LinksInfo& list = *(LinksInfo*)hList;

   #if defined LEM_THREADS
   lem::Process::CritSecLocker guard(&ENGINE->cs);
   #endif
    
   if( list[Index]->tags_txt8.empty() && !list[Index]->tags_txt.empty() )
    {
     list[Index]->tags_txt8 = lem::to_utf8(list[Index]->tags_txt);
    }

   return list[Index]->tags_txt8.c_str();
  }
 CATCH_API(hEngine);

 return NULL;
}

// ********************************************************************
// Возвращается текстовое представление списка флагов для связи.
// Если тегов у связи нет, то вернет NULL.
// ********************************************************************
FAIND_API(const char*) sol_LinksInfoFlagsTxt8(
                                              HGREN hEngine,
                                              HLINKSINFO hList,
                                              int Index
                                             )
{
 try
  {
   if( hList==NULL )
    return NULL;

   LinksInfo& list = *(LinksInfo*)hList;

   #if defined LEM_THREADS
   lem::Process::CritSecLocker guard(&ENGINE->cs);
   #endif
   if( list[Index]->flags8.empty() && !list[Index]->flags.empty() )
    {
     list[Index]->flags8 = lem::to_utf8(list[Index]->flags);
    }

   return list[Index]->flags8.c_str();
  }
 CATCH_API(hEngine);
 return NULL;
}

FAIND_API(const wchar_t*) sol_LinksInfoFlagsTxt(
                                                HGREN hEngine,
                                                HLINKSINFO hList,
                                                int Index
                                               )
{
 try
  {
   if( hList==NULL )
    return NULL;

   const LinksInfo& list = *(LinksInfo*)hList;
   return list[Index]->flags.c_str();
  }
 CATCH_API(hEngine);
 return NULL;
}



// *******************************************************************
// Из соответствующих таблиц удаляем запись о связи.
// LinkID - первичный ключ удаляемой записи.
// LinkType - 0 для словарных статей, 1 для фразовых
//
// ВАЖНО: после добавления связи для словарных статей становятся невалидными
// все ранее полученные id связей.
// *******************************************************************
FAIND_API(int) sol_DeleteLink( HGREN hEngine, int LinkID, int LinkType )
{
 try
  {
   if( LinkID==-1 )
    return -1;

   SynGram &sg = HandleEngine(hEngine)->dict->GetSynGram();
   SG_Net &th = sg.Get_Net();

   lem::Ptr<Solarix::TransactionGuard> tx( th.GetStorage().GetTxGuard() );
   tx->Begin();

   if( LinkType==0 )
    {
     th.RemoveWordsLink(LinkID);
    }
   else if( LinkType==1 )
    {
     th.RemoveComplexLink(LinkID);
    }

   tx->Commit();

   return 0;
  }
 CATCH_API(hEngine);

 return -1;
}


// ***************************************************************************
// Добавляем новую запись о связи между словарными или фразовыми статьями.
// LinkType=0 для словарных статей, 1 для фразовых.
// Возвращается ID добавленной записи.
//
// ВАЖНО: после добавления связи для словарных статей становятся невалидными
// все ранее полученные id связей.
// ***************************************************************************
FAIND_API(int) sol_AddLink(
                           HGREN hEngine,
                           int LinkType,
                           int IE1,
                           int LinkCode,
                           int IE2,
                           const wchar_t *Tags
                          )
{
 if( hEngine==NULL || IE1==UNKNOWN || IE2==UNKNOWN )
  return -2;

 try
  {
   SynGram &sg = HandleEngine(hEngine)->dict->GetSynGram();
   SG_Net &th = sg.Get_Net();

   lem::Ptr<Solarix::TransactionGuard> tx( th.GetStorage().GetTxGuard() );
   tx->Begin();

   int id_tags = th.LoadTags( Tags );
   int id_link=UNKNOWN;

   if( LinkType==0 )
    {
     id_link = th.AddWordsLink(IE1,LinkCode,IE2,id_tags);
    }
   else
    {
     id_link = th.AddComplexLink(IE1,LinkCode,IE2,id_tags);
    }

   tx->Commit();

   return id_link;
  }
 CATCH_API(hEngine);

 return -1;
}

FAIND_API(int) sol_AddLink8(
                            HGREN hEngine,
                            int LinkType,
                            int EntryID1,
                            int LinkCode,
                            int EntryID2,
                            const char *TagsUtf8
                           )
{
 lem::UFString w( lem::from_utf8(TagsUtf8) );
 return sol_AddLink( hEngine, LinkType, EntryID1, LinkCode, EntryID2, w.c_str() );
}



// Manual page: http://www.solarix.ru/api/en/sol_SetLinkFlags.shtml
// Описание:    http://www.solarix.ru/api/ru/sol_SetLinkFlags.shtml
FAIND_API(int) sol_SetLinkFlags(
                                HGREN hEngine,
                                int id_link,
                                const wchar_t *Flags
                               )
{
 if( hEngine==NULL || id_link==UNKNOWN )
  return -2;

 try
  {
   SynGram &sg = HandleEngine(hEngine)->dict->GetSynGram();
   SG_Net &th = sg.Get_Net();

   lem::Ptr<Solarix::TransactionGuard> tx( th.GetStorage().GetTxGuard() );
   tx->Begin();

   if( lem::lem_is_empty(Flags) )
    {
     th.ClearLinkFlags(id_link); 
    }
   else
    {
     lem::UFString str(Flags);
     lem::Collect< lem::UFString > pairs;
     lem::parse( str, pairs, L" " );
   
     for( lem::Container::size_type i=0; i<pairs.size(); ++i )
      {
       lem::Collect<lem::UFString> toks;
       lem::parse( pairs[i], toks, L"=" );
       const lem::UFString &flag = toks[0];
       const lem::UFString &val = toks[1];
       SG_LinkFlag x( UNKNOWN, flag, val );
       th.StoreLinkFlag( id_link, x );
      }
    }

   tx->Commit();

   return 0;
  }
 CATCH_API(hEngine);

 return -1;
}

FAIND_API(int) sol_SetLinkFlags8(
                                 HGREN hEngine,
                                 int id_link,
                                 const char *FlagsUtf8
                                )
{
 lem::UFString w( lem::from_utf8(FlagsUtf8) );
 return sol_SetLinkFlags( hEngine, id_link, w.c_str() );
}



// Manual page: http://www.solarix.ru/api/ru/sol_SetLinkTags.shtml
// Описание:    http://www.solarix.ru/api/en/sol_SetLinkTags.shtml
FAIND_API(int) sol_SetLinkTags(
                               HGREN hEngine,
                               int LinkType,
                               int link_id,
                               const wchar_t *Tags
                              )
{
 if( hEngine==NULL || link_id==-1 )
  return -2;

 try
  {
   SynGram &sg = HandleEngine(hEngine)->dict->GetSynGram();
   SG_Net &th = sg.Get_Net();

   lem::Ptr<Solarix::TransactionGuard> tx( th.GetStorage().GetTxGuard() );
   tx->Begin();

   int tags = th.LoadTags( Tags );

   if( LinkType==0 )
    {
     th.SetLinkTags(link_id,tags);
    }
   else
    {
     th.SetComplexLinkTags(link_id,tags);
    }

   tx->Commit();

   return 0;
  }
 CATCH_API(hEngine);

 return -1;
}


FAIND_API(int) sol_SetLinkTags8(
                                HGREN hEngine,
                                int LinkType,
                                int LinkID,
                                const char *TagsUtf8
                               )
{
 lem::UFString w( lem::from_utf8(TagsUtf8) );
 return sol_SetLinkTags( hEngine, LinkType, LinkID, w.c_str() ); 
}


// *********************************************************
//
// http://www.solarix.ru/api/en/sol_GetPhraseText.shtml
//
// Возвращается текст фразовой статьи по ее ключу.
// Возвращенный указатель нужно освободить вызовом sol_Free;
// *********************************************************
FAIND_API(wchar_t*) sol_GetPhraseText( HGREN hEngine, int PhraseId )
{
 if( hEngine==NULL || PhraseId==UNKNOWN )
  return NULL;

 try
  {
   SG_Phrase f;
   SynGram &sg = HandleEngine(hEngine)->dict->GetSynGram();
   if( sg.GetStorage().GetPhrase(PhraseId,f) )
    {
     const int len = f.GetText().length();
     wchar_t *str = (wchar_t*)malloc( sizeof(wchar_t)*(len+1) );
     lem::lem_strcpy( str, f.GetText().c_str() );
     return str;
    }  
   else
    {
     return NULL;
    }
  }
 catch(...)
  {
   return NULL;
  }
}



// http://www.solarix.ru/api/en/sol_GetPhraseText.shtml
FAIND_API(char*) sol_GetPhraseText8( HGREN hEngine, int PhraseId )
{
 if( hEngine==NULL || PhraseId==UNKNOWN )
  return NULL;

 try
  {
   SG_Phrase f;
   SynGram &sg = HandleEngine(hEngine)->dict->GetSynGram();
   if( sg.GetStorage().GetPhrase(PhraseId,f) )
    {
     lem::FString utf8 = to_utf8(f.GetText()); 
     const int len = utf8.length();
     char *str = (char*)malloc( len+1 );
     lem::lem_strcpy( str, utf8.c_str() );
     return str;
    }  
   else
    {
     return NULL;
    }
  }
 catch(...)
  {
   return NULL;
  }
}


// http://www.solarix.ru/api/en/sol_GetPhraseLanguage.shtml
FAIND_API(int) sol_GetPhraseLanguage( HGREN hEngine, int PhraseId )
{
 if( hEngine==NULL || PhraseId==UNKNOWN )
  return NULL;

 try
  {
   SG_Phrase f;
   SynGram &sg = HandleEngine(hEngine)->dict->GetSynGram();
   if( sg.GetStorage().GetPhrase(PhraseId,f) )
    {
     return f.GetLanguage();
    }  
   else
    {
     return -1;
    }
  }
 CATCH_API(hEngine);

 return -2;
}


// http://www.solarix.ru/api/en/sol_GetPhraseClass.shtml
FAIND_API(int) sol_GetPhraseClass( HGREN hEngine, int PhraseId )
{
 if( hEngine==NULL || PhraseId==UNKNOWN )
  return NULL;

 try
  {
   SG_Phrase f;
   SynGram &sg = HandleEngine(hEngine)->dict->GetSynGram();
   if( sg.GetStorage().GetPhrase(PhraseId,f) )
    {
     return f.GetClass();
    }  
   else
    {
     return -1;
    }
  }
 CATCH_API(hEngine);

 return -2;
}


// ****************************************************
// Create new phrasal entry in lexicon.
// ****************************************************
// http://www.solarix.ru/api/en/sol_AddPhrase.shtml
FAIND_API(int) sol_AddPhrase( HGREN hEngine, const wchar_t *Phrase, int LanguageID, int ClassID, int Flags )
{
 if( hEngine==NULL || lem::lem_is_empty(Phrase) )
  return -2;

 try
  {
   SynGram &sg = HandleEngine(hEngine)->dict->GetSynGram();

   lem::Ptr<Solarix::TransactionGuard> tx( sg.GetStorage().GetTxGuard() );
   tx->Begin();

   SG_Phrase frz( Phrase, LanguageID, ClassID, 0 );
   int id = sg.GetStorage().AddPhrase(frz);
   
   tx->Commit();

   return frz.GetId();
  }
 CATCH_API(hEngine);

 return -1;
}


// http://www.solarix.ru/api/en/sol_AddPhrase.shtml
FAIND_API(int) sol_AddPhrase8( HGREN hEngine, const char *Phrase, int Language, int Class, int Flags )
{
 return sol_AddPhrase( hEngine, lem::from_utf8(Phrase).c_str(), Language, Class, Flags );
}


// http://www.solarix.ru/api/en/sol_DeletePhrase.shtml
FAIND_API(int) sol_DeletePhrase( HGREN hEngine, int PhraseId )
{
 if( hEngine==NULL || PhraseId==UNKNOWN )
  return -2;

 int res=-1;
 try
  {
   SynGram &sg = HandleEngine(hEngine)->dict->GetSynGram();

   lem::Ptr<Solarix::TransactionGuard> tx( sg.GetStorage().GetTxGuard() );
   tx->Begin();

   sg.GetStorage().DeletePhrase(PhraseId);

   tx->Commit();

   res=0;
  }
 CATCH_API(hEngine);

 return -1;
}


FAIND_API(int) sol_SetPhraseNote(
                                 HGREN hEngine,
                                 int PhraseId,
                                 const wchar_t *Name,
                                 const wchar_t *Value
                                )
{
 if( hEngine==NULL || PhraseId==UNKNOWN || lem::lem_is_empty(Name) )
  return -2;

 try
  {
   SynGram &sg = HandleEngine(hEngine)->dict->GetSynGram();

   int tn_type=-1;
   if( lem::lem_eqi(Name,L"pivot") )
    {
     tn_type=Solarix::SynGram::PivotNote;
    }

   if( tn_type==-1 )
    return -3;

   lem::Ptr<TransactionGuard> tx( sg.GetStorage().GetTxGuard() );

   SG_PhraseNote note;
   int tn_id = sg.GetStorage().GetPhraseNote( PhraseId, tn_type, note );

   tx->Begin();

   if( lem::lem_is_empty(Value) )
    {
     if( tn_id!=-1 )
      sg.GetStorage().DeletePhraseNote(tn_id);
    }
   else
    {
     if( tn_id!=-1 )
      sg.GetStorage().DeletePhraseNote(tn_id);

     tn_id = sg.GetStorage().AddPhraseNote( PhraseId, tn_type, Value );
    }

   tx->Commit();

   return tn_id;
  }
 CATCH_API(hEngine);

 return 0;
}




FAIND_API(int) sol_ProcessPhraseEntry(
                                      HGREN hEngine,
                                      int PhraseId,
                                      const wchar_t *Scenario,
                                      int Language,
                                      wchar_t DelimiterChar
                                     )
{
 if( hEngine==NULL || PhraseId==UNKNOWN || lem::lem_is_empty(Scenario) )
  return -2;

 try
  {
   Solarix::Dictionary * dict = HandleEngine(hEngine)->dict.get();
  
   ThesaurusNotesProcessor proc(dict);

   proc.SetLanguage( Language );
   proc.SetScenario( Scenario );
   proc.SetSegmentationDelimiter(DelimiterChar);

   lem::Ptr<TransactionGuard> tx( dict->GetSynGram().GetStorage().GetTxGuard() );
   tx->Begin();

   std::pair<bool,bool> res = proc.ProcessPhrase(PhraseId);

   tx->Commit();

   if( res.second==true )
    return -1; // была ошибка

   if( res.first==true )
    return 1; // обработана

   return 0; // скорее всего необходимые записи в TNOTES уже внесены.
  }
 CATCH_API(hEngine);

 return -1;
}



// Добавление новой словарной статьи по ее текстовому описанию.
// Возвращается ключ созданной статьи, либо -1 при возникновении ошибки
FAIND_API(int) sol_AddWord( HGREN hEngine, const wchar_t *Txt )
{
 try
  {
   int id=UNKNOWN;

   #if defined SOL_LOADTXT
   UFString txt(Txt);
   lem::StrParser<lem::UFString> parser(txt);
   Solarix::Dictionary * dict = HandleEngine(hEngine)->dict.get();
   SynGram & sg = dict->GetSynGram();

   lem::Ptr<Solarix::TransactionGuard> tx( sg.GetStorage().GetTxGuard() );
   tx->Begin();

   int ekey = sg.LoadEntry(parser);

   if( ekey!=UNKNOWN )
    {
     id = ekey;//sg.FindEntryIndexByKey(ekey);

     tx->Commit();
    }
   #endif

   return id;   
  }
 CATCH_API(hEngine);

 return -1;
}


FAIND_API(int) sol_AddWord8( HGREN hEngine, const char *Txt )
{
 return sol_AddWord( hEngine, lem::from_utf8(Txt).c_str() );
}


// ******************************************************************************************
// Возвращается список ключей словарных (EntryType=0) или фразовых (EntryType=1) статей,
// которые удовлетворяют заданным параметрам:
// Flags - это битовая комбинация, задающая способ поиска:
//         0x00000000 поиск по регулярному выражению с точным соответствием регистра, применимо для фразовых статей
//         0x00000001 если нужен нечувствительный к регистру поиск по регулярному выражению
//         0x00010000 если нужен поиск по заданному имени словарной статьи или тексту фразовой
//
// EntryType - 0 для словарных статей, 1 для фразовых
// Mask - регулярное выражение для сопоставления с именем статьи, 
// Language - код языка (-1 означает отсутствие фильтрации)
// Class - грамматический класс, то есть ID части речи (-1 означает любой)
//
// Manual page: http://www.solarix.ru/api/en/sol_ListEntries.shtml
// ******************************************************************************************
FAIND_API(HGREN_INTARRAY) sol_ListEntries(
                                          HGREN hEngine,
                                          int Flags,
                                          int EntryType,
                                          const wchar_t *Mask,
                                          int Language,
                                          int Class
                                         )
{
 if( hEngine==NULL || lem::lem_is_empty(Mask) )
  return NULL;

 #if !defined SOLARIX_DEMO
 try
  {
   lem::MCollect<int> *list = new lem::MCollect<int>();
   list->reserve(32);

   SynGram &sg = HandleEngine(hEngine)->dict->GetSynGram();
   SG_Net &th = sg.Get_Net();

   boost::wregex mask_rx;
   bool use_rx=false;

   if( (Flags & 0x00010000) == 0x00000000 )
    {
     if( !lem::lem_is_empty(Mask) )
      {
       if( Flags==1 )
        mask_rx = boost::wregex( Mask );
       else
        mask_rx = boost::wregex( Mask, boost::regex_constants::icase );

       use_rx = true;
      }
    }

   if( EntryType==0 )
    {
     lem::Ptr<WordEntryEnumerator> wenum;

     if( (Flags & 0x00010000) == 0x00010000 )
      {
       UCString exact(Mask);
       exact.to_upper(); 
       wenum = sg.GetEntries().ListEntries( exact, ANY_STATE );
      }
     else
      {
       wenum = sg.GetEntries().ListEntries();
      }

     while( wenum->Fetch() )
      {
       const Solarix::SG_Entry &e = wenum->GetItem();

       if( !lem::lem_is_empty(Mask) && use_rx )
        {
         if( !boost::regex_match( e.GetName().c_str(), mask_rx ) )
          continue;
        }

       if( Class!=UNKNOWN || Language!=UNKNOWN )
        {
         const int is_class = e.GetClass();

         if( Class!=UNKNOWN && is_class!=Class )
          continue;

         if( Language!=UNKNOWN )
          {
           const SG_Class & c = sg.GetClass(is_class);
           if( c.GetLanguage()!=Language )
            continue;
          }         
        }
     
       list->push_back(e.GetKey());
      }
    }
   else if( EntryType==1 )
    {
     lem::Ptr<Solarix::PhraseEnumerator> phrases;

     phrases = sg.GetComplexEntries();

     while( phrases->Fetch() )
      {
       const int te_id = phrases->GetPhraseId();
       
       SG_Phrase f;
       if( sg.GetStorage().GetPhrase(te_id,f) )
        {
         if( !lem::lem_is_empty(Mask) && use_rx )
          {
           if( !boost::regex_match( f.GetText().c_str(), mask_rx ) )
            continue;
          }

         if( Class!=UNKNOWN && f.GetClass()!=Class )
          continue;

         if( Language!=UNKNOWN && f.GetLanguage()!=Language )
          continue;
     
         list->push_back(te_id); 
        }
      }
    }

   return (HGREN_INTARRAY)list;
  }
 catch(...)
  {
   return NULL;
  }
 #else
  return NULL;
 #endif
}



FAIND_API(HGREN_INTARRAY) sol_ListEntries8(
                                           HGREN hEngine,
                                           int Flags,
                                           int EntryType,
                                           const char *MaskUtf8,
                                           int Language,
                                           int Class
                                          )
{
 return sol_ListEntries( hEngine, Flags, EntryType, lem::from_utf8(MaskUtf8).c_str(), Language, Class );
}


FAIND_API(int) sol_SaveDictionary( HGREN hEngine, int Flags, const wchar_t *Folder )
{
 if( hEngine==NULL )
  return -1;
  
 #if defined SOL_SAVEBIN && !defined SOLARIX_DEMO
 try
  {
   if( (Flags & 0x00000002) )
    {
     SynGram &sg = HandleEngine(hEngine)->dict->GetSynGram();

     lem::Xml::Parser p;
     p.Load(HandleEngine(hEngine)->dict->dictionary_xml);
    
     lem::Path db_file;
     const lem::Xml::Node * nod = p.Find_By_Path( L"dataroot.morphology" );
     if( nod!=NULL && nod->GetBody().empty()==false )
      {
       db_file = lem::lem_is_empty(Folder) ? HandleEngine(hEngine)->dict->xml_base_path : lem::Path(Folder);
       db_file.ConcateLeaf( lem::Path(nod->GetBody()) );
       
       lem::BinaryWriter bin(db_file); 
       HandleEngine(hEngine)->dict->SaveBin(bin);
      }
    }
  }
 catch(...)
  {
    return -2;
  }  
 return 0; 
 #else
 return -1;
 #endif
}


FAIND_API(int) sol_ReserveLexiconSpace( HGREN hEngine, int n )
{
 if( hEngine==NULL || n<1 )
  return -1;

 try
  {
   HandleEngine(hEngine)->n_lexicon_reserve=n;
   return 0;
  }
 catch(...)
  {
   return -2;
  }
}


FAIND_API(int) sol_DeleteTranslation( void* hTran )
{
 try
  {
   TranslationResult *ptr = (TranslationResult*)hTran;
   delete ptr;
   return 0;
  }
 catch(...)
  {
   return -1;
  }
}


// http://www.solarix.ru/api/ru/sol_ListPartsOfSpeech.shtml
FAIND_API(HGREN_INTARRAY) sol_ListPartsOfSpeech( HGREN hEngine, int Language )
{
 try
  {
   lem::MCollect<int> *list = new lem::MCollect<int>();
   list->reserve(32);

   SynGram &sg = HandleEngine(hEngine)->dict->GetSynGram();
   lem::Ptr<ClassEnumerator> e( sg.classes().Enumerate() );

   while( e->Fetch() )
    {
     if( Language<0 )
      list->push_back( e->GetId() );
     else
      {
       const SG_Class &c = sg.GetClass( e->GetId() );
       if( c.GetLanguage()==Language )
        {
         list->push_back(e->GetId());
        }
      }
    }

   return list;
  }
 CATCH_API(hEngine);
 
 return NULL; 
}


// http://www.solarix.ru/api/ru/sol_GetEntryFreq.shtml
FAIND_API(int) sol_GetEntryFreq( HGREN hEngine, int EntryID )
{
 try
  {
   SynGram &sg = HandleEngine(hEngine)->dict->GetSynGram();
   const SG_Entry & e = sg.GetEntry(EntryID);
   int freq = e.GetFreq();
   return freq;   
  }
 CATCH_API(hEngine);
 
 return -1;
}



#endif

