
//  ------------------------------------------------------------------
//  The Goldware Library
//  Copyright (C) 1990-1999 Odinn Sorensen
//  Copyright (C) 1999-2000 Alexander S. Aganichev
//  Copyright (C) 2005-2006 Ianos Gnatius
//  Copyright (C) 2011 Max Vasilyev
//  Copyright (C) 2011 Stas Degteff
//  ------------------------------------------------------------------
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Library General Public
//  License as published by the Free Software Foundation; either
//  version 2 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Library General Public License for more details.
//
//  You should have received a copy of the GNU Library General Public
//  License along with this program; if not, write to the Free
//  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
//  MA 02111-1307, USA
//  ------------------------------------------------------------------
//  $Id$
//  ------------------------------------------------------------------
//  SpellChecker classes functions implementation.
//  ------------------------------------------------------------------

#if defined(_MSC_VER)
    /* C4786: 'identifier' : identifier was truncated to 'number'
          characters in the debug information
    */
  #pragma warning(disable: 4786)
#endif

#include <vector>
#include <string>

#include <gdirposx.h>
#include <gstrall.h>
#include <glog.h>
#include <gdbgerr.h>
#if !defined(GCFG_NO_MYSPELL)
#include <hunspell/hunspell.hxx>
#endif
#include <gespell.h>

typedef char XlatName[17];
typedef byte ChsTab[4];

struct Chs
{
  long   id;
  int    version;
  int    level;
  XlatName imp;   // From Charset
  XlatName exp;   // To Charset
  ChsTab t[256];  // The Translation Table
};

int   LoadCharset(const char* imp, const char* exp, int query = 0);
char* XlatStr(char* dest, const char* src, int level, Chs* chrtbl, int qpencoded=false, bool i51=false);

extern Chs* CharTable;


//  ------------------------------------------------------------------

#if defined(GCFG_SPELL_INCLUDED)


//  ------------------------------------------------------------------

#if !defined(GCFG_NO_MSSPELL)


//  ------------------------------------------------------------------

#define CHECK_ERROR(jump)     if (error != ERROR_SUCCESS) goto jump
#define CHECK_NULL(ptr, jump) if (ptr == NULL) goto jump
#define CHECK_SEC(jump)       if ((sec & 0xFF) != SC_SEC_NoErrors) goto jump

//  ------------------------------------------------------------------

const char SC_RKEY_Prooftools[]   = "Software\\Microsoft\\Shared Tools\\Proofing Tools";
const char SC_RKEY_Grammar[]      = "Grammar";
const char SC_RKEY_Spelling[]     = "Spelling";
const char SC_RKEY_Hyphenation[]  = "Hyphenation";
const char SC_RKEY_Thesaurus[]    = "Thesaurus";

//  Spell Check Command Code
const SCCC SC_SCCC_VerifyWord     = 1;
const SCCC SC_SCCC_VerifyBuffer   = 2;
const SCCC SC_SCCC_Suggest        = 3;
const SCCC SC_SCCC_SuggestMore    = 4;
const SCCC SC_SCCC_HyphInfo       = 5;
const SCCC SC_SCCC_Wildcard       = 6;
const SCCC SC_SCCC_Anagram        = 7;

//  Spell Check Return Status
const SCRS SC_SRCS_NoErrors                     = 0;
const SCRS SC_SRCS_UnknownInputWord             = 1;
const SCRS SC_SRCS_ReturningChangeAlways        = 2;
const SCRS SC_SRCS_ReturningChangeOnce          = 3;
const SCRS SC_SRCS_InvalidHyphenation           = 4;
const SCRS SC_SRCS_ErrorCapitalization          = 5;
const SCRS SC_SRCS_WordConsideredAbbreviation   = 6;
const SCRS SC_SRCS_HyphChangesSpelling          = 7;
const SCRS SC_SRCS_NoMoreSuggestions            = 8;
const SCRS SC_SRCS_MoreInfoThanBufferCouldHold  = 9;
const SCRS SC_SRCS_NoSentenceStartCap           = 10;
const SCRS SC_SRCS_RepeatWord                   = 11;
const SCRS SC_SRCS_ExtraSpaces                  = 12;
const SCRS SC_SRCS_MissingSpace                 = 13;
const SCRS SC_SRCS_InitialNumeral               = 14;

//  Major Error Codes (Low Byte of SEC)
const SEC  SC_SEC_NoErrors        = 0;
const SEC  SC_SEC_OOM             = 1;
const SEC  SC_SEC_ModuleError     = 2;
const SEC  SC_SEC_IOErrorMdr      = 3;
const SEC  SC_SEC_IOErrorUdr      = 4;

//  Minor Error Codes (High Byte of SEC)
const SEC  SC_SEC_ModuleAlreadyBusy       = 128 << 8;
const SEC  SC_SEC_InvalidID               = 129 << 8;
const SEC  SC_SEC_InvalidWsc              = 130 << 8;
const SEC  SC_SEC_InvalidMdr              = 131 << 8;
const SEC  SC_SEC_InvalidUdr              = 132 << 8;
const SEC  SC_SEC_InvalidSCC              = 133 << 8;
const SEC  SC_SEC_InvalidMainDict         = 134 << 8;
const SEC  SC_SEC_OperNotMatchedUserDict  = 135 << 8;
const SEC  SC_SEC_FileReadError           = 136 << 8;
const SEC  SC_SEC_FileWriteError          = 137 << 8;
const SEC  SC_SEC_FileCreateError         = 138 << 8;
const SEC  SC_SEC_FileShareError          = 139 << 8;
const SEC  SC_SEC_ModuleNotTerminated     = 140 << 8;
const SEC  SC_SEC_UserDictFull            = 141 << 8;
const SEC  SC_SEC_InvalidUdrEntry         = 142 << 8;
const SEC  SC_SEC_UdrEntryTooLong         = 143 << 8;
const SEC  SC_SEC_MdrCountExceeded        = 144 << 8;
const SEC  SC_SEC_UdrCountExceeded        = 145 << 8;
const SEC  SC_SEC_FileOpenError           = 146 << 8;
const SEC  SC_SEC_FileTooLargeError       = 147 << 8;
const SEC  SC_SEC_UdrReadOnly             = 148 << 8;

//  Spell Options bitfield definitions
const dword SC_SO_SuggestFromUserDict     = 0x0001; // Scan UDR as well as MDR
const dword SC_SO_IgnoreAllCaps           = 0x0002; // Ignore a word if all upppercase
const dword SC_SO_IgnoreMixedDigits       = 0x0004; // Ignore word if has any numbers in it
const dword SC_SO_IgnoreRomanNumerals     = 0x0008; // Ignore word composed of all roman numerals
const dword SC_SO_FindUncappedSentences   = 0x0010; // Flag sentences which don't start with a cap
const dword SC_SO_FindMissingSpaces       = 0x0020; // Find missing spaces between words/sentences
const dword SC_SO_FindRepeatWord          = 0x0040; // CSAPI to flag repeated words
const dword SC_SO_FindExtraSpaces         = 0x0080; // CSAPI to flag extra spaces between words
const dword SC_SO_FindSpacesBeforePunc    = 0x0100; // CSAPI to flag space preceeding certain punc
const dword SC_SO_FindSpacesAfterPunc     = 0x0200; // CSAPI to flag space after certain punc
const dword SC_SO_RateSuggestions         = 0x0400;
  // All suggestions returned should be given some scaled value
  // corresponding to liklihood of being correct alternative.
  // Scale is 1..255, 255 most likely correction and 1 least likely
const dword SC_SO_FindInitialNumerals     = 0x0800; // Flag words starting with number(s)
const dword SC_SO_ReportUDHits            = 0x1000;
  // Report (via scrsNoErrorsUDHit) where user dict was used during verification
const dword SC_SO_QuickSuggest            = 0x2000; // Don't use typo suggest code (Soft-Art only)
const dword SC_SO_UseAllOpenUdr           = 0x4000;
  // Automatically use all udr's opened after this option is set,
  // or all opened udr's with mdr's opened after this option is set.
  // This option does not allow exclusion dicts to be edited. (HM only)
const dword SC_SO_SwapMdr                 = 0x8000;
  // Keep the most recent 2 mdr's around. swap between them instead of actually
  // closing and reopening mdr's. (HM only)
const dword SC_SO_SglStepSugg             = 0x10000;
  // Break after each suggestion task for faster return of control to the
  // application. (HM only)

const dword SC_SO_LangMode                    = 0xF0000000; // Language Mode mask
// Hebrew Language Modes -- (CT only)
const dword SC_SO_HebrewFullScript            = 0x00000000;
const dword SC_SO_HebrewPartialScript         = 0x10000000;
const dword SC_SO_HebrewMixedScript           = 0x20000000;
const dword SC_SO_HebrewMixedAuthorizedScript = 0x30000000;
// French Language Modes -- (HM only)
const dword SC_SO_FrenchDialectDefault        = 0x00000000;
const dword SC_SO_FrenchUnaccentedUppercase   = 0x10000000;
const dword SC_SO_FrenchAccentedUppercase     = 0x20000000;
// Russian Language Modes -- (HM only)
const dword SC_SO_RussianDialectDefault       = 0x00000000;
const dword SC_SO_RussianIE                   = 0x10000000;
const dword SC_SO_RussianIO                   = 0x20000000;


//  ------------------------------------------------------------------

bool CMSSpellLang::Init(HKEY hKey, const char *name)
{
  bool result = false;
  int  error;
  HKEY hKeyLang;
  if (!name)
  {
    LOG.errpointer(__FILE__,__LINE__);
    LOG.printf( "! Parameter is NULL pointer: CMSSpellLang::Init(hkey,NULL). "
        "This is bug in program, please make report to developers." );
    return false;
  }

  byte engine[_MAX_PATH];
  byte dictionary[_MAX_PATH];
  unsigned long esize = sizeof(engine);
  unsigned long dsize = sizeof(dictionary);

  mLIDC  = atoi(name);
  strcpy(mLangCode, name);

  char *name2 = new char[strlen(name)+8];
  strcpy(name2, name);
  strcat(name2, "\\Normal");

  error = RegOpenKeyEx(hKey, name2, 0, KEY_READ, &hKeyLang);
  CHECK_ERROR(cleanup0);
  delete[] name2;

  error = RegQueryValueEx(hKeyLang, "Engine", NULL, NULL, engine, &esize);
  CHECK_ERROR(cleanup1);
  error = RegQueryValueEx(hKeyLang, "Dictionary", NULL, NULL, dictionary, &dsize);
  CHECK_ERROR(cleanup1);

  strcpy(mEngine, (char*)engine);
  strcpy(mDictionary, (char*)dictionary);

  result = true;

cleanup1:
  RegCloseKey(hKeyLang);
cleanup0:
  return result;
}


//  ------------------------------------------------------------------

bool CMSSpellLang::Load(const char *codeset, const char *userdic)
{
  bool result = false;
  if (!codeset)
  {
    LOG.errpointer(__FILE__,__LINE__);
    LOG.printf( "! Parameter is NULL pointer: CMSSpellLang::Load(NULL,userdic). "
                "This is bug in program, please make report to developers." );
    return false;
  }

  if( mLibrary != NULL ) return true;  // Already loaded
  mLibrary = LoadLibrary(mEngine);
  CHECK_NULL(mLibrary, cleanup0);

  mSpellVer          = (SpellVer_fn         ) GetProcAddress(mLibrary, "SpellVer"         ); CHECK_NULL(mSpellVer, cleanup1);
  mSpellInit         = (SpellInit_fn        ) GetProcAddress(mLibrary, "SpellInit"        ); CHECK_NULL(mSpellInit, cleanup1);
  mSpellOptions      = (SpellOptions_fn     ) GetProcAddress(mLibrary, "SpellOptions"     ); CHECK_NULL(mSpellOptions, cleanup1);
  mSpellCheck        = (SpellCheck_fn       ) GetProcAddress(mLibrary, "SpellCheck"       ); CHECK_NULL(mSpellCheck, cleanup1);
  mSpellTerminate    = (SpellTerminate_fn   ) GetProcAddress(mLibrary, "SpellTerminate"   ); CHECK_NULL(mSpellTerminate, cleanup1);
  mSpellOpenMdr      = (SpellOpenMdr_fn     ) GetProcAddress(mLibrary, "SpellOpenMdr"     ); CHECK_NULL(mSpellOpenMdr, cleanup1);
  mSpellCloseMdr     = (SpellCloseMdr_fn    ) GetProcAddress(mLibrary, "SpellCloseMdr"    ); CHECK_NULL(mSpellCloseMdr, cleanup1);
  mSpellOpenUdr      = (SpellOpenUdr_fn     ) GetProcAddress(mLibrary, "SpellOpenUdr"     ); CHECK_NULL(mSpellOpenUdr, cleanup1);
  mSpellAddUdr       = (SpellAddUdr_fn      ) GetProcAddress(mLibrary, "SpellAddUdr"      ); CHECK_NULL(mSpellAddUdr, cleanup1);
  mSpellCloseUdr     = (SpellCloseUdr_fn    ) GetProcAddress(mLibrary, "SpellCloseUdr"    ); CHECK_NULL(mSpellCloseUdr, cleanup1);
  mSpellAddChangeUdr = (SpellAddChangeUdr_fn) GetProcAddress(mLibrary, "SpellAddChangeUdr"); CHECK_NULL(mSpellAddChangeUdr, cleanup1);
  mSpellDelUdr       = (SpellDelUdr_fn      ) GetProcAddress(mLibrary, "SpellDelUdr"      ); CHECK_NULL(mSpellDelUdr, cleanup1);
  mSpellClearUdr     = (SpellClearUdr_fn    ) GetProcAddress(mLibrary, "SpellClearUdr"    ); CHECK_NULL(mSpellClearUdr, cleanup1);
  mSpellGetSizeUdr   = (SpellGetSizeUdr_fn  ) GetProcAddress(mLibrary, "SpellGetSizeUdr"  ); CHECK_NULL(mSpellGetSizeUdr, cleanup1);
  mSpellGetListUdr   = (SpellGetListUdr_fn  ) GetProcAddress(mLibrary, "SpellGetListUdr"  ); CHECK_NULL(mSpellGetListUdr, cleanup1);
  mSpellVerifyMdr    = (SpellVerifyMdr_fn   ) GetProcAddress(mLibrary, "SpellVerifyMdr"   ); CHECK_NULL(mSpellVerifyMdr, cleanup1);

  memset(&mSIB, 0, sizeof(mSIB));
  memset(&mSRB, 0, sizeof(mSRB));
  memset(&mMDRS, 0, sizeof(mMDRS));
  mSLID = mUDR = 0;

  SEC sec;

//  word w1, w2, w3;
//  mSpellVer(&w1, &w2, &w3);

  WSC wsc;
  memset(&wsc, 0, sizeof(wsc));
  sec = mSpellInit(&mSLID, &wsc);
  CHECK_SEC(cleanup1);

  sec = mSpellOpenMdr(mSLID, mDictionary, 0, 0, 1, mLIDC, &mMDRS);
  CHECK_SEC(cleanup2);

  mSIB.cMdr = 1;
  mSIB.lrgMdr = &mMDRS.mdr;

  sec = mSpellOptions(mSLID, SC_SO_SuggestFromUserDict|SC_SO_IgnoreAllCaps|SC_SO_IgnoreMixedDigits|SC_SO_IgnoreRomanNumerals|SC_SO_RateSuggestions);
  CHECK_SEC(cleanup2);

  if (userdic)
  {
    BOOL ronly = FALSE;
    sec = mSpellOpenUdr(mSLID, (char*)userdic, TRUE, 0xfffe, &mUDR, &ronly);

    if ((sec & 0xFF) == SC_SEC_NoErrors)
    {
      mSIB.cUdr = 1;
      mSIB.lrgUdr = &mUDR;
    }
  }

  result = true;
  goto cleanup0;

cleanup2:
  mSpellTerminate(mSLID, TRUE);
cleanup1:
  FreeLibrary(mLibrary);
  mLibrary = NULL;
cleanup0:
  if (result)
  {
    BuildRTable(codeset);
    mIsMdrLoaded = (mSIB.cMdr != 0);
    mIsUdrLoaded = (mSIB.cUdr != 0);
  }
  return result;
}


//  ------------------------------------------------------------------

void CMSSpellLang::UnLoad()
{
  if (!mLibrary) return;

  if (mSIB.cUdr) mSpellCloseUdr(mSLID, mUDR, TRUE);
  if (mSIB.cMdr) mSpellCloseMdr(mSLID, &mMDRS);
  if (mToDicTable) delete mToDicTable;
  if (mToLocTable) delete mToLocTable;
  mToDicTable = mToLocTable = NULL;

  FreeLibrary(mLibrary);
  mLibrary = NULL;
}


//  ------------------------------------------------------------------

void CMSSpellLang::BuildRTable(const char *codeset)
{
  char codeset2[20];
  if (!codeset)
  {
    LOG.errpointer(__FILE__,__LINE__);
    LOG.printf( "! Parameter is NULL pointer: CMSSpellLang::BuildRTable(NULL). "
        "This is bug in program, please make report to developers." );
    return;
  }
  strcpy(codeset2, "CP");
  GetLocaleInfo(mLIDC, LOCALE_IDEFAULTANSICODEPAGE, &codeset2[2], sizeof(codeset2)-2);
/*
  int save_chartableno = LoadCharset(NULL,NULL,1);
*/
  LoadCharset(codeset, codeset2);
  mToDicTable = new Chs;
  memset(mToDicTable, 0, sizeof(Chs));
  if (CharTable ) *mToDicTable = *CharTable;

  LoadCharset(codeset2, codeset);
  mToLocTable = new Chs;
  memset(mToLocTable, 0, sizeof(Chs));
  if (CharTable ) *mToLocTable = *CharTable;
/*
  if(save_chartableno != -1) // restore value of the default chaset table
    LoadCharset(CFG->xlatcharset[save_chartableno].imp, CFG->xlatcharset[save_chartableno].exp);
  else
    LoadCharset("N/A","N/A");
*/
}


//  ------------------------------------------------------------------

void CMSSpellLang::BuildSuggest(const char *text, CSpellSuggestV &suggest)
{
  if (!text)
  {
    LOG.errpointer(__FILE__,__LINE__);
    LOG.printf( "! Parameter is NULL pointer: CMSSpellLang::BuildSuggest(NULL,suggest). "
        "This is bug in program, please make report to developers." );
    return;
  }
  if (!SpellSuggest(text, false)) return;

  bool flag = true;
  bool more = false;

  for (int idx = 0; idx < mSRB.cChrMac; idx++)
  {
    if (mSZ[idx] == 0) { idx++; flag = true; }

    if (flag && mSZ[idx])
    {
      flag = false;
      RecodeText(&mSZ[idx], &mSZ[idx], false);
      suggest.push_back(std::pair<byte, std::string>(0, "  " + std::string(&mSZ[idx]) + char(' ')));
    }
    else if (!more && !mSZ[idx])
    {
      more = true;

      if (!SpellSuggest(text, more = true))
        return;
      else
      {
        flag = true;
        idx = -1;
      }
    }
  }
}


//  ------------------------------------------------------------------

bool CMSSpellLang::SpellCheck(const char *text)
{
  if (!text)
  {
    LOG.errpointer(__FILE__,__LINE__);
    LOG.printf( "! Parameter is NULL pointer: CMSSpellLang::SpellCheck(NULL). "
                "This is bug in program, please make report to developers." );
    return false;
  }
  if (!IsMdrLoaded()) return true;

  mSIB.wSpellState = 0;
  mSIB.lrgChr = strdup(text);
  mSIB.cChr = strlen(text);

  mSRB.cChr = sizeof(mSZ);
  mSRB.cbRate = sizeof(mRate);
  mSRB.lrgSZ = mSZ;
  mSRB.lrgbRate = mRate;

  SEC error = mSpellCheck(mSLID, SC_SCCC_VerifyBuffer, &mSIB, &mSRB);
  free( mSIB.lrgChr ); mSIB.lrgChr = NULL;
  if (error & 0xFF) return false;

  return mSRB.scrs == SC_SRCS_NoErrors;
}


//  ------------------------------------------------------------------

bool CMSSpellLang::SpellSuggest(const char *text, bool more)
{
  if (!text)
  {
    LOG.errpointer(__FILE__,__LINE__);
    LOG.printf( "! Parameter is NULL pointer: CMSSpellLang::SpellSuggest(NULL). "
        "This is bug in program, please make report to developers." );
    return false;
  }
  if (!IsMdrLoaded()) return false;

  mSIB.wSpellState = 0;
  mSIB.lrgChr = strdup(text);
  mSIB.cChr = strlen(text);

  memset(mSZ, 0, sizeof(mSZ));

  mSRB.cChr = sizeof(mSZ);
  mSRB.cbRate = sizeof(mRate);
  mSRB.lrgSZ = mSZ;
  mSRB.lrgbRate = mRate;

  SEC error = mSpellCheck(mSLID, more ? SC_SCCC_SuggestMore : SC_SCCC_Suggest, &mSIB, &mSRB);
  free( mSIB.lrgChr ); mSIB.lrgChr = NULL;
  if (error & 0xFF) return false;

  return mSRB.scrs == SC_SRCS_NoErrors;
}


//  ------------------------------------------------------------------

bool CMSSpellLang::AddWord(const char *text)
{
  if (!text)
  {
    LOG.errpointer(__FILE__,__LINE__);
    LOG.printf( "! Parameter is NULL pointer: CMSSpellLang::AddWord(NULL). "
        "This is bug in program, please make report to developers." );
    return false;
  }
  if (!IsMdrLoaded()) return false;
  char *t = strdup(text);
  SEC error = mSpellAddUdr(mSLID, mUDR, t);
  free(t);
  return (error & 0xFF) == 0;
}


//  ------------------------------------------------------------------

#endif  //#if !defined(GCFG_NO_MSSPELL)


//  ------------------------------------------------------------------

#if !defined(GCFG_NO_MYSPELL)


//  ------------------------------------------------------------------

bool CMYSpellLang::Init(const gdirentry *entry)
{
  if (!entry)
  {
    LOG.errpointer(__FILE__, __LINE__);
    LOG.printf( "! Parameter is NULL pointer: CMYSpellLang::Init(NULL). "
        "This is bug in program, please make report to developers." );
    return false;
  }
  gposixdir dir(entry->dirname);

  std::string affname = entry->name.substr(0, entry->name.length()-4);
  strcpy(mLangCode, affname.c_str());

  const gdirentry *entry2 = dir.nextentry((affname+".aff").c_str(), true);
  if (entry2)
  {
    strcpy(mEngine, entry2->dirname);
    strcat(mEngine, GOLD_SLASH_STR);
    strcat(mEngine, entry2->name.c_str());
    strcpy(mDictionary, entry->dirname);
    strcat(mDictionary, GOLD_SLASH_STR);
    strcat(mDictionary, entry->name.c_str());

    return true;
  }

  return false;
}


//  ------------------------------------------------------------------

bool CMYSpellLang::Load(const char *codeset, const char *)
{
  if (!codeset)
  {
    LOG.errpointer(__FILE__,__LINE__);
    LOG.printf( "! Parameter is NULL pointer: CMYSpellLang::Load(NULL,). "
        "This is bug in program, please make report to developers." );
    return false;
  }

  if (mMSpell) return mIsMdrLoaded;
  mMSpell = new Hunspell(mEngine, mDictionary);

  if (mMSpell)
  {
    BuildRTable(codeset);
    return (mIsMdrLoaded = true);
  }

  return false;
}


//  ------------------------------------------------------------------

void CMYSpellLang::UnLoad()
{
  if (!mMSpell) return;
  delete mMSpell;
  mMSpell = NULL;
  mIsMdrLoaded = false;
  if (mToDicTable) delete mToDicTable;
  if (mToLocTable) delete mToLocTable;
  mToDicTable = mToLocTable = NULL;
}


//  ------------------------------------------------------------------

void CMYSpellLang::BuildRTable(const char *codeset)
{
  if (!codeset)
  {
    LOG.errpointer(__FILE__,__LINE__);
    LOG.printf( "! Parameter is NULL pointer: CMYSpellLang::BuildRTable(NULL). "
        "This is bug in program, please make report to developers." );
    return;
  }
/*
  int save_chartableno = LoadCharset(NULL,NULL,1);
*/
  LoadCharset(codeset, mMSpell->get_dic_encoding());
  mToDicTable = new Chs;
  memset(mToDicTable, 0, sizeof(Chs));
  if (CharTable ) *mToDicTable = *CharTable;

  LoadCharset(mMSpell->get_dic_encoding(), codeset);
  mToLocTable = new Chs;
  memset(mToLocTable, 0, sizeof(Chs));
  if (CharTable ) *mToLocTable = *CharTable;
/*
  if(save_chartableno != -1) // restore value of the default chaset table
    LoadCharset(CFG->xlatcharset[save_chartableno].imp, CFG->xlatcharset[save_chartableno].exp);
  else
    LoadCharset("N/A","N/A");
*/
}


//  ------------------------------------------------------------------

void CMYSpellLang::BuildSuggest(const char *text, CSpellSuggestV &suggest)
{
  if (!text)
  {
    LOG.errpointer(__FILE__,__LINE__);
    LOG.printf( "! Parameter is NULL pointer: CMYSpellLang::BuildSuggest(NULL,suggest). "
        "This is bug in program, please make report to developers." );
    return;
  }
  char ** wlst = NULL;
  int ns = mMSpell->suggest(&wlst, text);

  for (int i=0; i < ns; i++)
  {
    char *buff = new char[strlen(wlst[i])+1];
    RecodeText(wlst[i], buff, false);
    suggest.push_back(std::pair<byte, std::string>(0, "  " + std::string(buff) + char(' ')));
    free(wlst[i]);
    delete[] buff;
  }

  free(wlst);
}


//  ------------------------------------------------------------------

bool CMYSpellLang::SpellCheck(const char *text)
{
  if (!text)
  {
    LOG.errpointer(__FILE__,__LINE__);
    LOG.printf( "! Parameter is NULL pointer: CMYSpellLang::SpellCheck(NULL). "
        "This is bug in program, please make report to developers." );
    return false;
  }
  if (!IsMdrLoaded()) return true;

  if (mMSpell->spell(text))
    return true;

  return false;
}


//  ------------------------------------------------------------------

bool CMYSpellLang::AddWord(const char *text)
{
  if (!text)
  {
    LOG.errpointer(__FILE__,__LINE__);
    LOG.printf( "! Parameter is NULL pointer: CMYSpellLang::AddWord(NULL). "
        "This is bug in program, please make report to developers." );
  }
  return false;
}


//  ------------------------------------------------------------------

#endif  //#if !defined(GCFG_NO_MYSPELL)


//  ------------------------------------------------------------------

void CSpellLang::RecodeText(const char *srcText, char *dstText, bool flag)
{
  if (!srcText || !dstText)
  {
    LOG.errpointer(__FILE__,__LINE__);
    LOG.printf( "! Parameter is NULL pointer in CSpellLang::RecodeText. "
        "This is bug in program, please make report to developers." );
    return;
  }
  if (flag)
    XlatStr(dstText, srcText, mToDicTable->level, mToDicTable);
  else
    XlatStr(dstText, srcText, mToLocTable->level, mToLocTable);
}


void CSpellLang::RecodeText(const char *srcText, std::string &dstText, bool flag)
{
  if (!srcText)
  {
    LOG.errpointer(__FILE__,__LINE__);
    LOG.printf( "! Parameter is NULL pointer: CSpellLang::RecodeText(NULL,dstText,flag). "
        "This is bug in program, please make report to developers." );
    return;
  }
  size_t srcLen=strlen(srcText);
  char *dstbuffer = new char[srcLen+1];
  if (dstbuffer) {
  *dstbuffer = '\0';
  if (flag)
    XlatStr(dstbuffer, srcText, mToDicTable->level, mToDicTable);
  else
    XlatStr(dstbuffer, srcText, mToLocTable->level, mToLocTable);
  dstText.assign(dstbuffer);
  delete[] dstbuffer;
  }
}

//  ------------------------------------------------------------------

CSpellChecker::CSpellChecker()
{
  mInited = false;
}


//  ------------------------------------------------------------------

bool CSpellChecker::Init(const char *codeset, const char *dicPath)
{
  if (!codeset)
  {
    LOG.errpointer(__FILE__,__LINE__);
    LOG.printf( "! Parameter is NULL pointer: CSpellChecker::Init(NULL,dicPath). "
                "This is bug in program, please make report to developers." );
    return false;
  }

#if !defined(GCFG_NO_MSSPELL)

  int  error;
  unsigned long index = 0;

  HKEY hKeyPTools;
  HKEY hKeySpelling;

  error = RegOpenKeyEx(HKEY_LOCAL_MACHINE, SC_RKEY_Prooftools, 0, KEY_READ, &hKeyPTools);
  CHECK_ERROR(cleanup0);

  error = RegOpenKeyEx(hKeyPTools, SC_RKEY_Spelling, 0, KEY_READ, &hKeySpelling);
  CHECK_ERROR(cleanup1);

  do
  {
    char name[1024];
    unsigned long nsize = sizeof(name);

    error = RegEnumKeyEx(hKeySpelling, index, name, &nsize, NULL, NULL, NULL, NULL);
    if (error == ERROR_SUCCESS)
    {
      CMSSpellLang *lang = new CMSSpellLang;
      if (lang->Init(hKeySpelling, name))
        mLangs.push_back(lang);
      else
        delete lang;

      index++;
    }
  }
  while (error != ERROR_NO_MORE_ITEMS);

  RegCloseKey(hKeySpelling);
cleanup1:
  RegCloseKey(hKeyPTools);
cleanup0:

#endif  //#if !defined(GCFG_NO_MSSPELL)

#if !defined(GCFG_NO_MYSPELL)

  gposixdir d(dicPath);
  const gdirentry *de;

  while ((de = d.nextentry("*.dic", true)) != NULL)
  {
    CMYSpellLang *lang = new CMYSpellLang;
    if (lang->Init(de))
      mLangs.push_back(lang);
    else
      delete lang;
  }

#endif  //#if !defined(GCFG_NO_MSSPELL)

  strcpy(mXlatLocalset, codeset);

  return mInited = (mLangs.size() > 0);
}


//  ------------------------------------------------------------------

void CSpellChecker::Close()
{
  if (!IsInited()) return;
  UnLoad();
  mLangs.clear();
  mInited = false;
}


//  ------------------------------------------------------------------

bool CSpellChecker::Load(const char *langId, const char *userDic)
{
  if (!langId)
  {
    LOG.errpointer(__FILE__,__LINE__);
    LOG.printf( "! Parameter is NULL pointer: CSpellChecker::Load(NULL,userDic). "
                "This is bug in program, please make report to developers." );
    return false;
  }

  if (!IsInited()) return false;
  if (IsLoaded(langId))
  {
    return true;
  }

  CSpellLangV::iterator it;
  for (it = mLangs.begin(); it != mLangs.end(); it++)
  {
    if (streql((*it)->GetLangCode(), langId) && (*it)->Load(mXlatLocalset, userDic))
    {
      mLangsLoaded.push_back(*it);
      return true;
    }
  }

  return false;
}


//  ------------------------------------------------------------------

void CSpellChecker::UnLoad()
{
  if (!IsLoaded()) return;

  CSpellLangV::iterator it;
  for (it = mLangsLoaded.begin(); it != mLangsLoaded.end(); it++)
  {
    (*it)->UnLoad();
  }
  mLangsLoaded.clear();
}


//  ------------------------------------------------------------------

void CSpellChecker::UnLoad(const char *langId)
{
  if (!langId)
  {
    LOG.errpointer(__FILE__,__LINE__);
    LOG.printf( "! Parameter is NULL pointer: CSpellChecker::UnLoad(NULL). "
                "This is bug in program, please make report to developers." );
    return;
  }
  if (!IsLoaded()) return;

  CSpellLangV::iterator it;
  for (it = mLangsLoaded.begin(); it != mLangsLoaded.end(); it++)
  {
    if (streql((*it)->GetLangCode(), langId))
    {
      (*it)->UnLoad();
      mLangsLoaded.erase(it);
      return;
    }
  }
}


//  ------------------------------------------------------------------

bool CSpellChecker::Check(const char *text)
{
  if (!IsLoaded()) return true;
  if (!text)
  {
    LOG.errpointer(__FILE__,__LINE__);
    LOG.printf( "! Parameter is NULL pointer: CSpellChecker::Check(NULL). "
                "This is bug in program, please make report to developers." );
    return false;
  }

  CSpellLangV::iterator it;
  for (it = mLangsLoaded.begin(); it != mLangsLoaded.end(); it++)
  {
    (*it)->RecodeText(text, mText, true);
    if ((*it)->SpellCheck(mText.c_str()))
    {
      return true;
    }
  }
  return false;
}


//  ------------------------------------------------------------------

CSpellSuggestV &CSpellChecker::Suggest()
{
  mSuggest.clear();
  if (!IsLoaded()) return mSuggest;

  CSpellSuggestV allSuggests;

  CSpellLangV::iterator it;
  for (it = mLangsLoaded.begin(); it != mLangsLoaded.end(); it++)
  {
    (*it)->BuildSuggest(mText.c_str(), allSuggests);
  }

  CSpellSuggestV::iterator all;
  for (all = allSuggests.begin(); all != allSuggests.end(); all++)
  {
    CSpellSuggestV::iterator distinct;
    bool exists = false;
    for (distinct = mSuggest.begin(); distinct != mSuggest.end(); distinct++)
    {
      if ((*distinct).second.compare((*all).second) == 0)
      {
        exists = true;
        break;
      }
    }

    if (!exists)
    {
      mSuggest.push_back(*all);
    }
  }

  return mSuggest;
}


//  ------------------------------------------------------------------

bool CSpellChecker::AddWord()
{
  if (IsLoaded())
  {
    CSpellLangV::iterator it;
    for (it = mLangsLoaded.begin(); it != mLangsLoaded.end(); it++)
    {
      if ((*it)->GetSpellType() == SCHECKET_TYPE_MSSPELL)
      {
        return (*it)->AddWord(mText.c_str());
      }
    }
  }

  return false;
}


//  ------------------------------------------------------------------

const std::vector<const char*> CSpellChecker::GetLangCodes()
{
  std::vector<const char*> codes;

  CSpellLangV::iterator it;
  for (it = mLangsLoaded.begin(); it != mLangsLoaded.end(); it++)
  {
    codes.push_back((*it)->GetLangCode());
  }
  return codes;
}


//  ------------------------------------------------------------------

bool CSpellChecker::IsUdrLoaded()
{
  if (IsLoaded())
  {
    CSpellLangV::iterator it;
    for (it = mLangsLoaded.begin(); it != mLangsLoaded.end(); it++)
    {
      if ((*it)->GetSpellType() == SCHECKET_TYPE_MSSPELL)
      {
        return (*it)->IsUdrLoaded();
      }
    }
  }

  return false;
}


//  ------------------------------------------------------------------

bool CSpellChecker::IsLoaded(const char *langId)
{
  CSpellLangV::iterator it;
  for (it = mLangsLoaded.begin(); it != mLangsLoaded.end(); it++)
  {
    if (streql((*it)->GetLangCode(), langId))
    {
      return true;
    }
  }

  return false;
}


//  ------------------------------------------------------------------

#if !defined(GCFG_NO_MSSPELL)
#undef CHECK_ERROR
#undef CHECK_NULL
#undef CHECK_SEC
#endif


//  ------------------------------------------------------------------

#endif  //#if defined(GCFG_SPELL_INCLUDED)

//  ------------------------------------------------------------------
