content: /*
   * Copyright (c) 1997 - 2016
   * Actelion Pharmaceuticals Ltd.
   * Gewerbestrasse 16
   * CH-4123 Allschwil, Switzerland
   *
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *
   * 1. Redistributions of source code must retain the above copyright notice, this
   *    list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright notice,
   *    this list of conditions and the following disclaimer in the documentation
   *    and/or other materials provided with the distribution.
   * 3. Neither the name of the the copyright holder nor the
   *    names of its contributors may be used to endorse or promote products
   *    derived from this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   *
   * @author Thomas Sander
   */
  
  package com.actelion.research.chem;
  
  import com.actelion.research.chem.descriptor.AbstractDescriptorHandlerLongFP;
  
  import java.nio.charset.StandardCharsets;
  
  // TODO purge mMoleculeIndexInt,mFragmentIndexInt and related methods from this class. Long versions were introduced Aug 3, 2018
  
  public class SSSearcherWithIndex {
  	public static final String cIndexVersion = "1.2.1";
  
  	// Outcommented and replaced idcodes are fragments, which originally used datatype==2 (wrongly encoded delocalized bond).
  	// They were replaced by fragments with bonds explicitly defined as delocalized, which should be compatible with original FFPs.
  	public static final String[] cKeyIDCode = {
          "QM@HzAmdqjF@",
          "RF@Q``",
          "qC`@ISTAlQE`",
          "`J@H",
          "QM@HzAmdqbF@",
          "qC`@ISTAlQEhqPp@",
          "sJP@DiZhAmQEb",
          "RF@QPvR@",
          "QM@HzA@",
          "qC`@ISTAlQEhpPp@",
          "qC`@Qz`MbHl",
          "sJP@DiZhAmQEcFZF@",
          "RFPDXH",
          "qC`@IVtAlQE`",
          "QM@HvAmdqfF@",
          "sGP@DiVj`FsDVM@",
          "`L@H",
          "sJP@DizhAmQEcFBF@",
          "sJP@DjvhAmQEb",
          "sFp@DiTt@@AlqEcP",
          "sGP@LdbMU@MfHlZ",
          "QMHAIhD",
          "QM@HzAy@",
          "sJP@DkVhAmQEb",
          "sNp@DiUjj@[\\QXu`",
          "sJP@DiZhAmQEcFBF@",
          "sGP@DjVj`FsDVM@",
          "RFPDTH",
          "RG@DXOH@",
          "sGP@Divj`FsDVMcAC@",
          "sGP@Dj}j`FsDVM@",
          "qC`@Qz`MbHmFRF@",
          "sNp@LdbJjj@[\\QXu`",
          "QMHAIhGe@",
          "QM@HzAyd`",
          "QM`AIhD",
          "qC`@ISTA@",
          "sGP@DkUj`FsDVM@",
          "qC`@IVtAlQEhqPp@",
          "sNp@DiUjj@[\\QXuqea`@",
          "KAx@@IRjuUPAlHPfES\\",
          "QM`BN`P",
          "sJP@DjZhAmQEcFJF@",
          "Hid@@DjU^nBBH@FtaBXUMp`",
          "sNp@Diujj@[\\QXuq`a`@",
          "sJP@DjvhAmQEcFZF@",
          "sJP@DjZhAmQEcFFF@",
          "sOp@DjWkB@@FwDVM\\YhX@",
          "sNp@Dj}Zj@[\\QXu`",
          "sNp@DiWjj@[\\QXuq`a`@",
          "sOp@DjWkB@@D",
          "KAx@@ITouUPAlHPfES\\",
          "KAx@@YIDTjjh@vDHSBin@",
          "sNp@DkUZj@[\\QXu`",
          "RFPDXOH@",
          "QM`BN`^L`",
          "qC`@ISTAy@",
          "sGP@LdbMU@MfHl[FVF@",
          "qCb@AIZ`H",
          "KAx@@IRjuUPAlHPfES]FFa`@",
          "KAx@@ITnuUPAlHPfES\\",
          "HiD@@DiUVjj`AmHPfES\\H",
          "sNp@DjUjj@[\\QXu`",
          "sJP@DkVhAmQEcFJF@",
          "sGP@DjVj`FsDVMcCC@",
          "qC`@Qz`MbHmFBF@",
          "sJP@DkfhAmQEb",
          "qC`@IVtAlQEhsPp@",
          "sGP@Djuj`FsDVM@",
          "sGP@Dj}j`FsDVMcMC@",
          "sJP@DiZhA@",
          "KAx@@ISjuUPAlHPfES]F@a`@",
          "sJP@DjZhAmQEcFRF@",
          "KAx@@IRnuUPAlHPfES]F@a`@",
          "HiD@@DjWvjj`AmHPfES\\H",
          "QMHAIhGd@",
          "sNp@DiUjj@[\\QXuq`a`@",
          "KAx@@IVjmUPAlHPfES\\",
          "sGP@DjVj`FsDVMcMC@",
          "QM`AIhGe@",
          "HiD@@LdbJRjjh@[RDIaTwB",
          "qCp@AIZ`H",
          "sGP@LdbMU@MfHl[FFF@",
          "QMDARVA@",
          "sNp@LdbJjj@[\\QXuqba`@",
          "sNp@LdbJjj@[\\QXuqca`@",
          "sGP@Dkej`FsDVM@",
          "qCb@AIZ`OI@",
          "HaD@@DjUZxHH@AlHPfES]FLa`@",
          "sGP@DkYj`FsDVM@",
          "qCb@AIV`H",
          "sNp@LdbJjj@[\\QXuqea`@",
          "sGP@DkUj`FsDVMcEC@",
          "sFp@DiTt@@Axa@",
          "Hmt@@DjU_ZxHHj@AmhPfES\\Lj",
          "QM`BN`^P",
          "qCb@AIZ`OH`",
          "sFp@DiTt@@AxaP",
          "sGP@Djuj`FsDVMcEC@",
          "sGP@Djuj`FsDVMcIC@",
          "sGP@DkUj`FsDVMcKC@",
          "sJP@DkfhAmQEcFRF@",
          "sGP@DjVj`FsDVMcIC@",
          "HaD@@DjUZxHH@AlHPfES]FFa`@",
          "qC`@IRtDVqDV@",
          "sNp@Dj}Zj@[\\QXuqfa`@",
          "KAx@@ITnuUPAlHPfES]FFa`@",
          "HiD@@DkUUjj`AmHPfES\\H",
          "sJQ@@dkU@H",
          "qC`@Qz`H",
          "KAx@@IUkmUPAlHPfES\\",
          "KAx@@ITouUPAlHPfES]FJa`@",
          "sJP@H~j@[TQX`",
          "sGP@DjZj`FsDVM@",
          "sJP@DkVhAmQEcFFF@",
          "sJX@@eKU@H",
          "sJP@DizhAy@",
          "QMHAIhGbP",
          "KAx@@ITouUPAlHPfES]FNa`@",
          "HaD@@DjUZxHD@AlHPfES\\",
          "HaD@@DjUZxHH@A@",
          "sNp@LdbJjj@[\\QXuqaa`@",
          "Hed@@LdbRQUUUP@vTHSBinFP",
          "KAx@@ITouUPAlHPfES]FLa`@",
          "sNp@DkUZj@[\\QXuqba`@",
          "KAx@@ITjuUPAlHPfES]FNa`@",
          "KAx@@YIDTjjh@vDHSBincGPp@",
          "HaD@@DjYvxH`@AlHPfES]FLa`@",
          "RF@QP`",
          "qCb@AIj`H",
          "sNp@DjUjj@[\\QXuqaa`@",
          "sNp@DkVZj@[\\QXu`",
          "KAx@@YIDUJjh@vDHSBin@",
          "sGP@DkYj`FsDVMcIC@",
          "sGP@DjVj`FsDVMcAC@",
          "sGP@DiVj`D",
          "sJP@DkVhAmQEcFZF@",
          "sNp@LdbLjj@[\\QXu`",
          "QM@HvAmdqbF@",
          "HaD@@DjWjXHB@AlHPfES\\",
          "sNp@DjwZj@[\\QXuqba`@",
          "sNp@LdbJjj@[\\QXuqda`@",
          "sFp@DiTt@@Axa`",
          "HiD@@Djuujj`AmHPfES\\H",
          "sNp@DkUZj@[\\QXuqca`@",
          "sJP@DiZhAy@",
          "KAx@@YIDTjjh@vDHSBincCPp@",
          "KAx@@IWNmUPAlHPfES\\",
          "KAx@@IVkMUPAlHPfES\\",
          "sJQ@@dju@H",
          "qCb@AIZ`OH@",
          "qC`@ISTAxa@",
          "sNp@DjyZj@[\\QXu`",
          "Hid@@DjUfaBB`@FtaBXUMp`",
          "HiD@@DiUVjj`AmHPfES\\LXBF@",
          "KAx@@IUjmUPAlHPfES\\",
          "HiD@@DjWvjj`AmHPfES\\LXjF@",
          "sJP@DjVhAmQEb",
          "qCb@AIV`OH`",
          "HiD@@LdbJRjjh@[RDIaTwCFDa`@",
          "KAx@@YIDTjjh@vDHSBinc@Pp@",
          "sNp@DjUjj@[\\QXuqda`@",
          "qC`@Qz`OED",
          "sJP@DkfhAmQEcFZF@",
          "KAx@@YIDbjjh@vDHSBincDPp@",
          "sGP@Djyj`FsDVMcMC@",
          "KAx@@IVrmUPAlHPfES\\",
          "qCp@AIZ`OI@",
          "sJX@@dkU@H",
          "sJQ@@dkU@OH`",
          "sNp@Di]ZjBBvxbqk@",
          "Hkl@@DjU_Uk``bj`@[VDIaTwCJzX",
          "sGP@DjZj`FsDVMcEC@",
          "Hid@@DjU^nBBH@FtaBXUMpqcHX@",
          "sNp@DkeZj@[\\QXu`",
          "sNp@DjYjj@[\\QXuqca`@",
          "sGQ@@djuT@`",
          "HiD@@LdbJTjjh@[RDIaTwB",
          "sOp@DjWkB@@Gd`",
          "HeT@@LdbbRKBDQD@CYPaLJfxY@",
          "qCr@XIKTA@",
          "HiD@@DjW^jj`AmHPfES\\LXJF@",
          "HeT@@DjU]k``b`@[JDIaTwCH",
          "sGP@Djuj`FsDVMcCC@",
          "`IH`B",
          "sOp@DjWkB@@GdX",
          "sJQ@@eKU@H",
          "KAx@@YIDUJjh@vDHSBincBPp@",
          "sJX@@eKU@OH@",
          "KAx@@YIDTjjh@vDHSBincAPp@",
          "sOq@@drm\\@@@`",
          "KAx@@IUkMUPAlHPfES\\",
          "qCp@AIj`H",
          "Hed@@DjUUjjj@FraBXUMpr",
          "sGX@@eJuT@`",
          "sGP@DkUj`FsDVMcCC@",
          "HiD@@Dj}Ujj`AmHPfES\\LXrF@",
          "KAx@@ITouUPAlHPfES]FHa`@",
          "Hed@@DjWujjj@FraBXUMpsFIa`@",
          "sGP@DiUj``mfHlZ",
          "sFp@DiTvjhAlqEcP",
          "Hid@@DjU^nBBH@FtaBXUMpq`XX@",
          "sJP@DkVdAmQEb",
          "qCp@AIZ`OH`",
          "QMhDRVA@",
          "qC`@ISJAlQE`",
          "qCp@BOTAyhl",
          "sJX@@eOU@ODB",
          "sFp@DiTt@@AyaB",
          "sGP@DkUj`FsDVMcMC@",
          "Hid@@DjYUaBH`@FtaBXUMpqcHX@",
          "qC`@Qz`OH@",
          "HiD@@DjUVjj`AmHPfES\\LXZF@",
          "sJP@H~j@[TQXqda`@",
          "sJX@@eKU@OI@",
          "sNp@Djejj@[\\QXu`",
          "sJQ@@dsU@H",
          "sJQ@@dkU@OI`",
          "KAx@@YIMDVjh@vDHSBin@",
          "Hid@@DjU^nBBD@FtaBXUMp`",
          "sNp@DkgZj@[\\QXuqca`@",
          "qC`@IRtDVqDVcEC@",
          "Hed@@LdbRQeUUP@vTHSBinFP",
          "sNp@DiUjj@P",
          "qC`@IRtDT",
          "sNp@DkYZj@[\\QXuqca`@",
          "KAx@@IUkmUPAlHPfES]FDa`@",
          "KAx@@IVjmUPAlHPfES]FNa`@",
          "sOx@@drm\\@@@`",
          "KAx@@ITjuUPAlHPfES]FBa`@",
          "QMDARVAyH",
          "sJP`@dfvhA@",
          "HeT@@DjU_k``b`@[JDIaTwCLXfF@",
          "KAx@@IToUUPAlHPfES]FJa`@",
          "sGP@DkYj`FsDVMcEC@",
          "qCb@AIZ`ODH",
          "`I@`B",
          "KAx@@IUzmUPAlHPfES]FFa`@",
          "sNp@DkfZj@[\\QXu`",
          "KAx@@ITnuUPAlHPfES]F@a`@",
          "HiD@@LddURjjh@[RDIaTwB",
          "sNp@Dj~Zj@[\\QXuqfa`@",
          "Hed@@Dj{uZjj@FraBXUMpr",
          "KAx@@ITsUUPAlHPfES\\",
          "Hid@@LdbRQk``b@AmHPfES\\LXrF@",
          "sOp@DjWkB@@GdH",
          "sJQ@@dkU@OH@",
          "Hid@@DjU^nBBH@FtaBXUMpqahX@",
          "sGP@DiYj``mfHlZ",
          "KAx@@IToUUPAlHPfES]FLa`@",
          "qCp@AJZ`ODH",
          "Hmt@@DjU]ZxHHj@AmhPfES\\Lj",
          "sGP@DkUjPFsDVM@",
          "qC`@IVtA@",
          "Hed@@LdbJReUUP@vTHSBinFP",
          "sNp@DjuZj@[\\QXuqea`@",
          "KAx@@IUkmUPAlHPfES]FNa`@",
          "HiD@@DkVUjj`AmHPfES\\H",
          "Hed@@DkUeZjj@FraBXUMpr",
          "sNp@DkVZj@[\\QXuqea`@",
          "sJP@DiVhHKZbKFLLL@",
          "HiD@@Djuyjj`AmHPfES\\H",
          "sNp@DjUjj@[\\QXuq`a`@",
          "HeT@@DjYUXPbH`@[JDIaTwCH",
          "HiD@@DjwUjj`AmHPfES\\LXRF@",
          "sNq@@djmUPB",
          "KAx@@YIEEZjh@vDHSBincCPp@",
          "sGP@Di^V`dmfHlZ",
          "Hid@@DjYUaBHP@FtaBXUMp`",
          "sNp@DjYjj@[\\QXuqba`@",
          "sGP@Dkej`FsDVMcKC@",
          "HeT@@DjU^k``b`@[JDIaTwCH",
          "qC`@Qv`MbHmFBF@",
          "sGQ@@djmT@`",
          "qCr@XIKTAyH",
          "qC`@IVtAlQEhpPp@",
          "Hid@@LdbbQxXF@@AmHPfES\\LXjF@",
          "sGP@DkYj`FsDVMcCC@",
          "KAx@@IVsMUPAlHPfES\\",
          "qCp@AIj`ODl",
          "HiD@@DkeUjj`AmHPfES\\H",
          "deT@@DjU[k``b`@vTHSBinFP"/* "HeT@@DjU[kjjjh@ZLDXSSYPaLJfxY@" */,
          "sJP@DkVdAmQEcFRF@",
          "HiD@@LdbJTjjh@[RDIaTwCFDa`@",
          "HiD@@DkYyjj`AmHPfES\\H",
          "sJP@DjZhAyH",
          "KAx@@IVkMUPAlHPfES]FDa`@",
          "sJX@@dkU@OI@",
          "Hed@@LdbRQUUUP@vTHSBinFXpLL@",
          "Hed@@DjuUZjj@FraBXUMpr",
          "sGP@Djfj`FsDVMcKC@",
          "sNp@DkVZj@[\\QXuqba`@",
          "sNp@DjyZj@[\\QXuqfa`@",
          "qCb@AIj`OH@",
          "sNp@DjUZj@[\\QXu`",
          "KAx@@IWOMUPAlHPfES\\",
          "Hid@@DjU^nBBH@D",
          "Hed@@DjuvZjj@FraBXUMpr",
          "sJP@DiVhHKZbKFLtL@",
          "dmt@@DjU_ZxHHj@C[PaLJfxYVLKC@" /* "Hmt@@DjU_Zzjjj`AhpQaLmmBDpj[aeXplL@" */,
          "sNp@DjuZj@[\\QXuqca`@",
          "sJP@DkfhAmQEcFJF@",
          "sNp@LdbJZj@[\\QXu`",
          "HeT@@DjU_k``b`@[JDIaTwCLXFF@",
          "KAx@@IVlmUPAlHPfES]FNa`@",
          "HeT@@LdbbRKBDQD@CYPaLJfxYcEPp@",
          "Hid@@DjUZnBBH@FtaBXUMpqcHX@",
          "qCa@CIKTA@",
          "HiD@@Dj~]jj`AmHPfES\\LXFF@",
          "sKP@Di\\Zj@[TQX`",
          "sGP@Djfj`FsDVMcEC@",
          "HiD@@DkgYjj`AmHPfES\\H",
          "sNp@DjuZj@[\\QXuqaa`@",
          "KAx@@YIMDVjh@vDHSBincDPp@",
          "sJP@DjVhHKZbKFLTL@",
          "Hid@@LdbRQk``b@AmHPfES\\LXZF@",
          "HiD@@Dj}Ujj`AmHPfES\\LXzF@",
          "HeT@@DjU_k``bP@[JDIaTwCH",
          "sNp@DkUZi@[\\QXu`",
          "HiD@@DjYfjj`AmHPfES\\H",
          "sGP@DjZj`FsDVMcAC@",
          "Hmt@@DjU_jxHHj@AmhPfES\\Lj",
          "Hid@@LdbRQk``R@AmHPfES\\H",
          "KAx@@YIDUJjh@vDHSBincDPp@",
          "qCr@XIKTAyD",
          "sOq@@drm\\@@@|`@",
          "Hed@@DjW^jjj@FraBXUMpsFBa`@",
          "HeT@@DjY]zXFB@@[JDIaTwCH",
          "Hkl@@DjU_Vk``bj`@[VDIaTwCJzX",
          "Hid@@DjY}nBHH@FtaBXUMpqcHX@",
          "sGX@@eKuT@|d@",
          "sGP@Dj^Y`FsDVM@",
          "HcL@@DjU_ZnBBJh@FqaBXUMprn`",
          "sJP@DkVdAmQEcFJF@",
          "sOq@@drm\\@@@|b@",
          "sNp@DjyZj@[\\QXuqaa`@",
          "HaD@@DjUZxHH@AyD@",
          "qC`@Qv`H",
          "dmt@@DjU_ZxHHj@C[PaLJfxYVLYC@" /* "Hmt@@DjU_Zzjjj`AhpQaLmmBDpj[aeXqdL@" */,
          "sGP@Dkej`FsDVMcMC@",
          "Hed@@DjUUjjj@FraBXUMpsFHa`@",
          "HeT@@LdbbRkBDQD@CYPaLJfxY@",
          "KAx@@IU{MUPAlHPfES]FLa`@",
          "RG@DTH",
          "sJY@DDeVhA@",
          "KAx@@YIDUJjh@vDHSBinc@Pp@",
          "sJX@@dkU@OI`",
          "sJQ@@dju@OI`",
          "HeT@@LdbbRKBDQD@CYPaLJfxYcFPp@",
          "sFp@DiTvjhAlqEcXpPp@",
          "HaD@@DjUZxHH@AyG@",
          "sNx@@eJ}UPB",
          "sNp@LddUjj@[\\QXuqca`@",
          "HaDH@@RVU[j@@@D",
          "sNp@DkgZi@[\\QXu`",
          "sGY@LDeVj`D",
          "sNp@LdbJfZBZvxbqk@",
          "sJP`@dfvhAyL",
          "sGX@AddQjhAxe`",
          "Hmt@@DjU_ZxHHj@AmhPfES\\LkFIa`@",
          "qCh@CIKTA@",
          "sNp@LdbLjj@[\\QXuq`a`@",
          "sOq@@drm\\@@@|a@",
          "KAx@@IUzmUPAlHPfES]FJa`@",
          "sNx@AddQUUPB",
          "sGP@Di]jP`mfHlZ",
          "sJP`@TeZhA@",
          "KAx@@IRjmUPHKXPaLJfx",
          "HeT@@LdbRTM\\DDT@CYPaLJfxY@",
          "HaF@@@Rfu[j@@@D",
          "Hid@@DjYUaBH`@FtaBXUMpqchX@",
          "KAx@@IUjmTpAlHPfES\\",
          "Hid@@DjU^nBBD@FtaBXUMpqcHX@",
          "sGP@DiUj``mfHl[FFF@",
          "KAx@@IUvmUPAlHPfES]FLa`@",
          "Hed@@LdbQTUUUP@vTHSBinFXqDL@",
          "sJP@DkVhA@",
          "sOx@@drm\\@@@|b@",
          "KAx@@IUkMUPAlHPfES]FDa`@",
          "HeT@@LdbRQU\\DDT@CYPaLJfxY@",
          "HiD@@Dj}Yjj`AmHPfES\\LXrF@",
          "HiD@@Dj{ujj`AmHPfES\\LXFF@",
          "KAx@@IWNmUPAlHPfES]FFa`@",
          "KAx@@IRkMUPHKXPaLJfx",
          "sJP@DjYdAmQEcFZF@",
          "sJY@LDeZhAyL",
          "HaDH@@RVU[f@@@D",
          "sJP`@deVhAyB",
          "HaD@@DjWjZjj`AlHPfES\\",
          "sGP@DkYj`FsDVMcMC@",
          "sNp@DkgZj@[\\QXuqea`@",
          "sJQ@@dlu@H",
          "HeT@@DjU]k``b`@[JDIaTwCLXrF@",
          "sJX@@dkU@OH`",
          "RFDDQFCr`",
          "sJP@DiYXIKZbKFLLL@",
          "KAx@@YIHjjjh@vDHSBincGPp@",
          "Hk\\@@DjU^ukmLHH@@@AmXPfES\\Lki`",
          "sGQ@@djmT@|b@",
          "Hid@@DjUfaBB`@FtaBXUMpqahX@",
          "sNx@@eRmUPB",
          "Hmt@@LdbRVak``ah@FvaBXUMprh",
          "qCr@XIJtA@",
          "KAx@@IWMmUPAlHPfES]FNa`@",
          "HeT@@DjYYZPbJ@@[JDIaTwCH",
          "sNp@DkfZj@[\\QXuqea`@",
          "Hid@@DjU^nBAHAEVtaBXUMp`",
          "dmt@@DjUgZDHJJ@C[PaLJfxYT" /* "Hmt@@DjYU^Vjjj`AhtISRmmBDpj[aeP" */,
          "sGP@DkejPFsDVM@",
          "sNx@@eJmUPB",
          "qCb@AIf`H",
          "HcL@@DjU_VnBBJh@FqaBXUMprnqcXX@",
          "Hid@@DjUZnBBH@FtaBXUMpqahX@",
          "sNp@LdbQZjBBvxbqkcGC@",
          "sOx@@drm\\@@@|c@",
          "sJP@H~j@^R@",
          "KAx@@YIDcFjhDElHPfES\\",
          "Hid@@DjUZnBAH@FtaBXUMp`",
          "sNp@LddUji@[\\QXu`",
          "sGP@DjfjPFsDVM@",
          "HeT@@DjYUXPbD`@[JDIaTwCH",
          "KAx@@IUoMUPAlHPfES]FDa`@",
          "sFp@DiTt@@AyaD",
          "Hed@@DjuuZjj@FraBXUMpsFIa`@",
          "HeT@@DjUghP`h`@[JDIaTwCLXfF@",
          "sOp@DjWkjj`FwDVM\\YhX@",
          "sGP@Djfj`FsDVMcIC@",
          "KAx@@IRkmUPHKXPaLJfzL]C@",
          "sNx@@djmUPB",
          "QM`AIdD",
          "sOp@DjWkB@@Gbe@",
          "sNp@DjyZj@[\\QXuqca`@",
          "QM@HuAmd`",
          "sNp@LddUjj@[\\QXuqea`@",
          "daD@@DjUZxHD@CXPaLJfzLYC@" /* "HaD@@DkeVyjj`AhrXUMuaBDpj[hpDL@" */,
          "qCb@AIZPH",
          "HiD@@LdbJTjjh@[RDIaTwCF@a`@",
          "Hmt@@DjU_ZxHHi@AmhPfES\\Lj",
          "HaDH@@RYWih@H@D",
          "HiD@@LdbJTjjh@[RDIaTwCFHa`@",
          "sGX@@djuT@|a@",
          "sNp@DkfZj@[\\QXuqaa`@",
          "Hid@@DjU^nBBH@GdL",
          "KAx@@IVkMUPAlHPfES]FJa`@",
          "qCr@XIKTAy@",
          "HmT@@Dj{uVjjh@[ZDIaTwCJqaXX@",
          "dmt@@DjYWVDHbJ@C[PaLJfxYT" /* "Hmt@@DjYWVFjjj`AhpQe\\mmBDpj[aeP" */,
          "Hif@@@RUe^Fh@@@P",
          "HaDH@@Rfu[j@@@GdH",
          "KAx@@IVsMUPAlHPfES]FDa`@",
          "sKP@Di\\Zj@[TQXq`a`@",
          "sJX@@eMU@OH@",
          "HeT@@DjU^k``b`@[JDIaTwCLXFF@",
          "Hmt@@LdbbRJXPbHh@FvaBXUMprh",
          "sJP@DjvhAmQEcFBF@",
          "dmt@@LdbbRNXPbHh@MmBDpj[aeXplL@" /* "Hmt@@LdbbRNXZjjj@FcAFUrvtHSBinFUcBpp@" */,
          "sJP`@dfvhAyD",
          "sGP@Di^V`dmfHl[FVF@",
          "KAx@@IVsmUPAlHPfES]FBa`@",
          "sOq@@drm\\@@@|PP",
          "sJY@BDeZhA@",
          "HeT@@LdbRbmBDED@CYPaLJfxY@",
          "Hed@@Djy[Zjj@FraBXUMpr",
          "HeT@@DjU]k``b`@[JDIaTwCLXFF@",
          "Hid@@DjUfaBB`@D",
          "qCa@CIJtA@",
          "QMPARVA@",
          "Hid@@DjUfaBB`@FtaBXUMpqcHX@",
          "sJY@BDfZhA@",
          "HeT@@DjUghP`hP@[JDIaTwCH",
          "Hed@@Dj{uZjj@FraBXUMpsFIa`@",
          "dmt@@DjYUvDHbJ@C[PaLJfxYVLUC@" /* "Hmt@@LdbbRUXZjjj@FcAFUrvtHSBinFUcFPp@" */,
          "sNp`@dfuZj@P",
          "sJQ@@dmU@OH@",
          "sJX@@dmU@H",
          "HeT@@DjU]k``b`@[JDIaTwCLXZF@",
          "HiD@@LdfbJZjh@[RDIaTwCFAa`@",
          "sOx@@drm\\@@@|a@",
          "deT@@DjUfhP`h`@vTHSBinFP" /* "HeT@@LdbbQgCUUU@CQhRfz[JDIaTwCH" */,
          "dmt@@DjU]ZxHHj@C[PaLJfxYVLKC@" /* "Hmt@@DjU]Zzjjj`AhpQaLmmBDpj[aeXplL@" */,
          "sOp@DjWkjj`FwDVM\\XHX@",
          "HcL@@LdbbRNSBDQEP@McBDpj[ae]cFpp@",
          "HiD@@Dj}Yji`AmHPfES\\H",
          "HaDH@@RYe[hB@@D",
          "Hid@@DjU^njjj@FtaBXUMpq`XX@",
          "deT@@LdbbRKBDQB@FraBXUMpr" /* "HeT@@DkYeFVjjh@ZMaUpsYPaLJfxY@" */,
          "QMPARZA@",
          "sOq@@drm\\@@@|QX",
          "HaD@@DjYvxH`@A@",
          "HcL@@LdbbRNcBDQEP@McBDpj[ae]@",
          "QMhDRZA@",
          "RG@DXLHmP",
          "QM`BN`XQYd",
          "RG@DTLHmP",
          "QMHAIXFEVd",
          "QMDARVAaH",
          "RFPDXLHmP",
          "RF@Q`vRbdLEC@",
          "RF@QpvR@",
          "QO@HyjAmd`",
          "`II@B",
          "`II@CFspqJp",
          "`II@CF[@hM@prB`",
          "`H@[T[|B`XN@PdM@p|@bHrBcDk@",
  		"RG@DXMj}F@",
  		"QM`BN`[L~b@",
  		"RG@DTMj}D@",
  		"QMHAIXFt~j@",
  		"QMDARVA}L@",
  		"RFPDXMj}D@",
  		"sKP@Di\\YZ@[TQXqaa`@",
  		"eF`BHD" /* "RG@DXMH" */ };
  
  	private static StereoMolecule[]  sKeyFragment;
  	private SSSearcher			mSSSearcher;
  	private StereoMolecule		mMolecule,mFragment;
  	private int[]				mMoleculeIndexInt,mFragmentIndexInt;
  	private long[]				mMoleculeIndexLong,mFragmentIndexLong;
  	private byte[]				mMoleculeIDCode,mFragmentIDCode;
  	private AbstractDescriptorHandlerLongFP<StereoMolecule> mDescriptorHandler;
  
  	public static int getNoOfKeys() {
          return cKeyIDCode.length;
  	   	}
  
  	public SSSearcherWithIndex(AbstractDescriptorHandlerLongFP<StereoMolecule> dh) {
  		mSSSearcher = new SSSearcher();
  		mDescriptorHandler = dh;
  		init();
  		}
  
  
  	public SSSearcherWithIndex() {
  		mSSSearcher = new SSSearcher();
  		init();
  		}
  
  
  	public SSSearcherWithIndex(int matchMode) {
  		mSSSearcher = new SSSearcher(matchMode);
  		init();
  		}
  
  
  	public static StereoMolecule getKeyFragment(int no) {
  		return sKeyFragment[no];
  		}
  
  
  	@Deprecated // Use long version of this method
  	public void setFragment(StereoMolecule fragment, int[] index) {
  		mFragmentIDCode = null;
  		mFragmentIndexLong = null;
  		mFragment = fragment;
  		if (index == null)
  			mFragmentIndexInt = createIndex(fragment);
  		else
  			mFragmentIndexInt = index;
  		}
  
  
  	@Deprecated // Use long version of this method
  	public void setFragment(String idcode, int[] index) {
  	    setFragment(idcode.getBytes(StandardCharsets.UTF_8), index);
  		}
  
  
  	@Deprecated // Use long version of this method
  	public void setFragment(byte[] idcode, int[] index) {
  		mFragmentIDCode = idcode;
  		mFragmentIndexLong = null;
  		if (index == null) {
  			mFragment = (new IDCodeParser(false)).getCompactMolecule(idcode);
  			mFragmentIndexInt = createIndex(mFragment);
  			}
  		else {
  			mFragment = null;
  			mFragmentIndexInt = index;
  			}
  		}
  
  
  	@Deprecated // Use long version of this method
  	public void setMolecule(StereoMolecule molecule, int[] index) {
  		mMoleculeIDCode = null;
  		mMoleculeIndexLong = null;
  		mMolecule = molecule;
  		if (index == null)
  			mMoleculeIndexInt = createIndex(molecule);
  		else
  			mMoleculeIndexInt = index;
  		}
  
  
  	@Deprecated // Use long version of this method
  	public void setMolecule(String idcode, int[] index) {
  	    setMolecule(idcode.getBytes(StandardCharsets.UTF_8), index);
  		}
  
  
  	@Deprecated // Use long version of this method
  	public void setMolecule(byte[] idcode, int[] index) {
  		mMoleculeIDCode = idcode;
  		mMoleculeIndexLong = null;
  		if (index == null) {
  			mMolecule = (new IDCodeParser(false)).getCompactMolecule(idcode);
  			mMoleculeIndexInt = createIndex(mMolecule);
  			}
  		else {
  			mMolecule = null;
  			mMoleculeIndexInt = index;
  			}
  		}
  
  
  	public void setFragment(StereoMolecule fragment, long[] index) {
  		mFragmentIDCode = null;
  		mFragmentIndexInt = null;
  		mFragment = fragment;
  		if (index == null)
  			mFragmentIndexLong = createLongIndex(fragment);
  		else
  			mFragmentIndexLong = index;
  		}
  
  
  	public void setFragment(String idcode, long[] index) {
  		setFragment(idcode.getBytes(StandardCharsets.UTF_8), index);
  	}
  
  
  	public void setFragment(byte[] idcode, long[] index) {
  		mFragmentIDCode = idcode;
  		mFragmentIndexInt = null;
  		if (index == null) {
  			mFragment = (new IDCodeParser(false)).getCompactMolecule(idcode);
  			mFragmentIndexLong = createLongIndex(mFragment);
  			}
  		else {
  			mFragment = null;
  			mFragmentIndexLong = index;
  			}
  		}
  
  
  	public void setMolecule(StereoMolecule molecule, long[] index) {
  		mMoleculeIDCode = null;
  		mMoleculeIndexInt = null;
  		mMolecule = molecule;
  		if (index == null)
  			mMoleculeIndexLong = createLongIndex(molecule);
  		else
  			mMoleculeIndexLong = index;
  	}
  
  
  	public void setMolecule(String idcode, long[] index) {
  		setMolecule(idcode.getBytes(StandardCharsets.UTF_8), index);
  	}
  
  
  	public void setMolecule(byte[] idcode, long[] index) {
  		mMoleculeIDCode = idcode;
  		mMoleculeIndexInt = null;
  		if (index == null) {
  			mMolecule = (new IDCodeParser(false)).getCompactMolecule(idcode);
  			mMoleculeIndexLong = createLongIndex(mMolecule);
  		}
  		else {
  			mMolecule = null;
  			mMoleculeIndexLong = index;
  		}
  	}
  
  
  	public int getFirstHittingLongIndexBlockNo() {
  		if (mMoleculeIndexLong != null) {
  			for (int i=0; i<mMoleculeIndexLong.length; i++)
  				if ((mFragmentIndexLong[i] & ~mMoleculeIndexLong[i]) != 0)
  					return i;
  			}
  		else if (mMoleculeIndexInt != null) {
  			for (int i=0; i<mMoleculeIndexInt.length; i++)
  				if ((mFragmentIndexInt[i] & ~mMoleculeIndexInt[i]) != 0)
  					return i;
  			}
  		return -1;
  		}
  
  
  	/**
  	 * Returns the most recently defined molecule. If the molecule was
  	 * passed as idcode and was not created yet, then a molecule is
  	 * constructed from the idcode and returned.
  	 * @return current Molecule (null, if setMolecule() has never been called)
  	 */
  	public StereoMolecule getMolecule() {
          if (mMolecule == null && mMoleculeIDCode != null)
              mMolecule = (new IDCodeParser(false)).getCompactMolecule(mMoleculeIDCode);
  
          return mMolecule;
  	    }
  
  
  	/**
  	 * @return whether the fragment fingerprint bits are all present in the molecule bits
  	 */
  	public boolean isFragmentIndexInMoleculeIndex() {
  		if (mMoleculeIndexLong != null) {
  			for (int i=0; i<mMoleculeIndexLong.length; i++)
  				if ((mFragmentIndexLong[i] & ~mMoleculeIndexLong[i]) != 0)
  					return false;
  			}
  		else if (mMoleculeIndexInt != null) {
  			for (int i=0; i<mMoleculeIndexInt.length; i++)
  				if ((mFragmentIndexInt[i] & ~mMoleculeIndexInt[i]) != 0)
  					return false;
  			}
  		else {
  			return false;
  			}
  
  		return true;
  		}
  
  
  	public boolean isFragmentInMoleculeWithoutIndex() {
  		if (mMolecule == null)
  			mMolecule = (new IDCodeParser(false)).getCompactMolecule(mMoleculeIDCode);
  		if (mFragment == null)
  			mFragment = (new IDCodeParser(false)).getCompactMolecule(mFragmentIDCode);
  
  		mSSSearcher.setMolecule(mMolecule);
  		mSSSearcher.setFragment(mFragment);
  		return mSSSearcher.isFragmentInMolecule();
  		}
  
  
  	public int findFragmentInMoleculeWithoutIndex(int countMode) {
  		if (mMolecule == null)
  			mMolecule = (new IDCodeParser(false)).getCompactMolecule(mMoleculeIDCode);
  		if (mFragment == null)
  			mFragment = (new IDCodeParser(false)).getCompactMolecule(mFragmentIDCode);
  
  		mSSSearcher.setMolecule(mMolecule);
  		mSSSearcher.setFragment(mFragment);
  		return mSSSearcher.findFragmentInMolecule(countMode, SSSearcher.cDefaultMatchMode);
  		}
  
  
  	public boolean isFragmentInMolecule() {
  		if (mMoleculeIndexLong != null) {
  			for (int i=0; i<mMoleculeIndexLong.length; i++)
  				if ((mFragmentIndexLong[i] & ~mMoleculeIndexLong[i]) != 0)
  					return false;
  			}
  		else if (mMoleculeIndexInt != null) {
  			for (int i=0; i<mMoleculeIndexInt.length; i++)
  				if ((mFragmentIndexInt[i] & ~mMoleculeIndexInt[i]) != 0)
  					return false;
  			}
  		else {
  			return false;
  			}
  
  		return isFragmentInMoleculeWithoutIndex();
  		}
  
  
  	public int findFragmentInMolecule() {
  		if (mMoleculeIndexLong != null) {
  			for (int i=0; i<mMoleculeIndexLong.length; i++)
  				if ((mFragmentIndexLong[i] & ~mMoleculeIndexLong[i]) != 0)
  					return 0;
  			}
  		else if (mMoleculeIndexInt != null) {
  			for (int i=0; i<mMoleculeIndexInt.length; i++)
  				if ((mFragmentIndexInt[i] & ~mMoleculeIndexInt[i]) != 0)
  					return 0;
  			}
  		else {
  			return 0;
  			}
  
  		if (mMolecule == null)
  			mMolecule = (new IDCodeParser(false)).getCompactMolecule(mMoleculeIDCode);
  		if (mFragment == null)
  			mFragment = (new IDCodeParser(false)).getCompactMolecule(mFragmentIDCode);
  
  		mSSSearcher.setMolecule(mMolecule);
  		mSSSearcher.setFragment(mFragment);
  		return mSSSearcher.findFragmentInMolecule();
  		}
  
  
  	public int findFragmentInMolecule(int countMode, int matchMode) {
  	    return findFragmentInMolecule(countMode, matchMode, null);
  	    }
  
  	public int findFragmentInMolecule(int countMode, int matchMode, final boolean[] atomExcluded) {
  		if (mMoleculeIndexLong != null) {
  			for (int i=0; i<mMoleculeIndexLong.length; i++)
  				if ((mFragmentIndexLong[i] & ~mMoleculeIndexLong[i]) != 0)
  					return 0;
  			}
  		else if (mMoleculeIndexInt != null) {
  			for (int i=0; i<mMoleculeIndexInt.length; i++)
  				if ((mFragmentIndexInt[i] & ~mMoleculeIndexInt[i]) != 0)
  					return 0;
  			}
  		else {
  			return 0;
  			}
  
  		if (mMolecule == null)
  			mMolecule = (new IDCodeParser(false)).getCompactMolecule(mMoleculeIDCode);
  		if (mFragment == null)
  			mFragment = (new IDCodeParser(false)).getCompactMolecule(mFragmentIDCode);
  
  		mSSSearcher.setMolecule(mMolecule);
  		mSSSearcher.setFragment(mFragment);
  		return mSSSearcher.findFragmentInMolecule(countMode, matchMode, atomExcluded);
  		}
  
  
  	/**
  	 * @return the SSSearcher employed for the graph matching by this index accelerated SS-searcher
  	 */
  	public SSSearcher getGraphMatcher() {
  		return mSSSearcher;
  		}
  
  	@Deprecated // switch to long version; this version does not support the AllFragmentIndex
  	public int[] createIndex(StereoMolecule mol) {
  		if (mol == null)
  			return null;
  
  		int[] index = new int[(cKeyIDCode.length+31)/32];
  		mol = removeExcludeGroups(mol);
  		mSSSearcher.setMolecule(mol);
  		for (int i=0; i<cKeyIDCode.length; i++) {
  			mSSSearcher.setFragment(sKeyFragment[i]);
  			if (mSSSearcher.isFragmentInMolecule(SSSearcher.cIndexMatchMode))
  				index[i/32] |= (1 << (31-i%32));
  			}
  
  		return index;
  		}
  
  
  	public long[] createLongIndex(StereoMolecule mol) {
  		if (mol == null)
  			return null;
  
  		if (mDescriptorHandler != null)
  			return mDescriptorHandler.createDescriptor(mol);
  
  		long[] index = new long[(cKeyIDCode.length+63)/64];
  		mol = removeExcludeGroups(mol);
  		mSSSearcher.setMolecule(mol);
  		for (int i=0; i<cKeyIDCode.length; i++) {
  			mSSSearcher.setFragment(sKeyFragment[i]);
  			if (mSSSearcher.isFragmentInMolecule(SSSearcher.cIndexMatchMode))
  				index[i/64] |= (1L << (63-i%64));
  			}
  
  		return index;
  		}
  
  
  	private StereoMolecule removeExcludeGroups(StereoMolecule mol) {
  		if (mol.isFragment()) {
  			for (int atom=0; atom<mol.getAllAtoms(); atom++) {
  				if (mol.isExcludeGroupAtom(atom)) {
  					mol = new StereoMolecule(mol);
  					for (int i=atom; i<mol.getAllAtoms(); i++)
  						if (mol.isExcludeGroupAtom(i))
  							mol.markAtomForDeletion(i);
  					mol.deleteMarkedAtomsAndBonds();
  					}
  				}
  			}
  		return mol;
  		}
  
  
  	public static float getSimilarityTanimoto(final int[] index1, final int[] index2) {
          int sharedKeys = 0;
          int allKeys = 0;
          for (int i=0; i<index1.length; i++) {
              sharedKeys += Integer.bitCount(index1[i] & index2[i]);
              allKeys += Integer.bitCount(index1[i] | index2[i]);
          	}
          return (float)sharedKeys/(float)allKeys;
          }
  
  
  	public static float getSimilarityTanimoto(final long[] index1, final long[] index2) {
  		int sharedKeys = 0;
  		int allKeys = 0;
  		for (int i=0; i<index1.length; i++) {
  			sharedKeys += Long.bitCount(index1[i] & index2[i]);
  			allKeys += Long.bitCount(index1[i] | index2[i]);
  			}
  		return (float)sharedKeys/(float)allKeys;
  		}
  
  
      public static float getSimilarityAngleCosine(int[] index1, int[] index2) {
          int sharedKeys = 0;
          int index1Keys = 0;
          int index2Keys = 0;
          for (int i=0; i<index1.length; i++) {
              sharedKeys += Integer.bitCount(index1[i] & index2[i]);
              index1Keys += Integer.bitCount(index1[i]);
              index2Keys += Integer.bitCount(index2[i]);
          	}
          return (float)sharedKeys/(float)Math.sqrt(index1Keys*index2Keys);
      	}
  
  
  	public static int[] getIndexFromHexString(String hex) {
  		if (hex.length() == 0 || (hex.length() & 7) != 0)
  			return null;
  
  		int[] index = new int[hex.length()/8];
  		for (int i=0; i<hex.length(); i++) {
  			int j = i/8;
  			int code = hex.charAt(i) - '0';
  			if (code > 16)
  				code -= 7;
  			index[j] <<= 4;
  			index[j] += code;
  			}
  
  		return index;
  		}
  
  
  	public static int[] getIndexFromHexString(byte[] bytes) {
  		if (bytes==null || bytes.length==0 || (bytes.length & 7) != 0)
  			return null;
  
  		int[] index = new int[bytes.length/8];
  		for (int i=0; i<bytes.length; i++) {
  			int j = i/8;
  			int code = (int)bytes[i] - '0';
  			if (code > 16)
  				code -= 7;
  			index[j] <<= 4;
  			index[j] += code;
  			}
  
  		return index;
  		}
  
  
  	public static long[] getLongIndexFromHexString(String hex) {
  		if (hex.length() == 0 || (hex.length() & 15) != 0)
  			return null;
  
  		long[] index = new long[hex.length()/16];
  		for (int i=0; i<hex.length(); i++) {
  			int j = i/16;
  			long code = hex.charAt(i) - '0';
  			if (code > 16)
  				code -= 7;
  			index[j] <<= 4;
  			index[j] += code;
  			}
  
  		return index;
  		}
  
  
  	public static long[] getLongIndexFromHexString(byte[] bytes) {
  		if (bytes==null || bytes.length==0 || (bytes.length & 15) != 0)
  			return null;
  
  		long[] index = new long[bytes.length/16];
  		for (int i=0; i<bytes.length; i++) {
  			int j = i/16;
  			long code = (long)bytes[i] - '0';
  			if (code > 16)
  				code -= 7;
  			index[j] <<= 4;
  			index[j] += code;
  			}
  
  		return index;
  		}
  
  
  	public static String getHexStringFromIndex(int[] index) {
  	    if (index == null)
  	        return null;
  
  	    byte[] bytes = new byte[index.length*8];
  		for (int i=0; i<index.length; i++) {
  			int value = index[i];
  			for (int j=7; j>=0; j--) {
  				int code = value & 15;
  				if (code > 9)
  					code += 7;
  				bytes[i*8+j] = (byte)('0'+code);
  				value >>= 4;
  				}
  			}
  
  		return new String(bytes, StandardCharsets.UTF_8);
  		}
  
  	
  /*  use Integer.bitCount() instead, which is supposed to use the POPCNT processor command on x86
  
      public static int bitCount(int x) {
          int temp;
  
          temp = 0x55555555;
          x = (x & temp) + (x >>> 1 & temp);
          temp = 0x33333333;
          x = (x & temp) + (x >>> 2 & temp);
          temp = 0x07070707;
          x = (x & temp) + (x >>> 4 & temp);
          temp = 0x000F000F;
          x = (x & temp) + (x >>> 8 & temp);
  
          return (x & 0x1F) + (x >>> 16);
      	}   */
  
  
  	private void init() {
  		synchronized(SSSearcherWithIndex.class) {
  		    if (sKeyFragment == null) {
  	    		IDCodeParser theParser = new IDCodeParser(false);
  	    		sKeyFragment = new StereoMolecule[cKeyIDCode.length];
  	    		for (int i=0; i<cKeyIDCode.length; i++) {
  	    			sKeyFragment[i] = theParser.getCompactMolecule(cKeyIDCode[i]);
  	    			sKeyFragment[i].ensureHelperArrays(Molecule.cHelperNeighbours);
  	    			}
  	    		}
  			}
  	    }
  
  	public void stop() {
  		mSSSearcher.stop();
  		}
  	}
  