#include "bingo_pg_fix_pre.h"

extern "C" {
#include "postgres.h"
#include "fmgr.h"
#include "access/genam.h"
#include "access/relscan.h"
#include "utils/typcache.h"
}

#include "bingo_pg_fix_post.h"

#include "mango_pg_search_engine.h"

#include "bingo_core_c.h"
#include "base_c/bitarray.h"
#include "base_cpp/tlscont.h"
#include "base_cpp/scanner.h"
#include "base_cpp/output.h"
#include "base_cpp/tlscont.h"
#include "base_cpp/profiling.h"

#include "bingo_pg_text.h"
#include "bingo_pg_common.h"
#include "bingo_pg_config.h"
#include "bingo_pg_index.h"


using namespace indigo;

void MangoPgFpData::insertHash(dword hash, int c_cnt) {
   int *comp_count = _hashes.at2(hash);
   if(comp_count) {
      (*comp_count) += c_cnt;
   } else {
      _hashes.insert(hash, c_cnt);
   }
}

void MangoPgFpData::setGrossStr(const char* gross_str, const char* counter_str) {
   _gross.readString("'", true);
   _gross.appendString(gross_str, true);
   _gross.appendString("'", true);
   _gross.appendString(counter_str, true);
}

IMPL_ERROR(MangoPgSearchEngine, "molecule search engine");

MangoPgSearchEngine::MangoPgSearchEngine(BingoPgConfig& bingo_config, const char* rel_name):
BingoPgSearchEngine(),
_searchType(-1) {
   _setBingoContext();
   /*
    * Set up bingo configuration
    */
   bingo_config.setUpBingoConfiguration();
   bingoTautomerRulesReady(0,0,0);
   bingoIndexBegin();

   _relName.readString(rel_name, true);
   _shadowRelName.readString(rel_name, true);
   _shadowRelName.appendString("_shadow", true);
   _shadowHashRelName.readString(rel_name, true);
   _shadowHashRelName.appendString("_shadow_hash", true);
}

MangoPgSearchEngine::~MangoPgSearchEngine() {
   _setBingoContext();
   bingoIndexEnd();
}

bool MangoPgSearchEngine::matchTarget(int section_idx, int structure_idx) {
   
   profTimerStart(t0, "mango_pg.match_target");
   
   if(_searchType == BingoPgCommon::MOL_SIM || _searchType == BingoPgCommon::MOL_MASS) {
      return true;
   }
   
   bool result = false;
   int bingo_res;
   QS_DEF(Array<char>, mol_buf);
   QS_DEF(Array<char>, xyz_buf);
   mol_buf.clear();
   xyz_buf.clear();


   if(_searchType == BingoPgCommon::MOL_SUB || _searchType == BingoPgCommon::MOL_EXACT || _searchType == BingoPgCommon::MOL_SMARTS) {
      _bufferIndexPtr->readCmfItem(section_idx, structure_idx, mol_buf);
      bingo_res = mangoNeedCoords();
      CORE_HANDLE_ERROR(bingo_res, 0, "molecule search engine: error while getting coordinates flag", bingoGetError());
      
      if(bingo_res > 0) {
         _bufferIndexPtr->readXyzItem(section_idx, structure_idx, xyz_buf);
      }

//      CORE_HANDLE_WARNING_TID(0, 1, "matching binary target", section_idx, structure_idx, " ");
      bingo_res = mangoMatchTargetBinary(mol_buf.ptr(), mol_buf.sizeInBytes(), xyz_buf.ptr(), xyz_buf.sizeInBytes());
      CORE_HANDLE_ERROR_TID(bingo_res, -1, "molecule search engine: error while matching binary target", section_idx, structure_idx, bingoGetError());
      CORE_RETURN_WARNING_TID(bingo_res, 0, "molecule search engine: error while matching binary target", section_idx, structure_idx, bingoGetWarning());

      result = (bingo_res > 0);
   } else if(_searchType == BingoPgCommon::MOL_GROSS) {
      BingoPgText gross_text;
      _searchCursor->getText(2, gross_text);

      int gross_len;
      gross_text.getText(gross_len);
      
      bingo_res = mangoMatchTarget(gross_text.getString(), gross_len);
      CORE_HANDLE_ERROR(bingo_res, -1, "molecule search engine: error while matching gross target", bingoGetError());
      CORE_RETURN_WARNING(bingo_res, 0, "molecule search engine: error while matching gross target", bingoGetWarning());
      
      result = (bingo_res > 0);
   } else {
      throw Error("internal error: undefined search type");
   }
   return result;
}

void MangoPgSearchEngine::prepareQuerySearch(BingoPgIndex& bingo_idx, PG_OBJECT scan_desc_ptr) {

   profTimerStart(t0, "mango_pg.prepare_query_search");

   IndexScanDesc scan_desc = (IndexScanDesc) scan_desc_ptr;

   if(scan_desc->numberOfKeys >= 1 && scan_desc->numberOfKeys <= 2) {
      _searchType = scan_desc->keyData[0].sk_strategy;
   } else {
      throw Error("unsupported number of scan keys %d", scan_desc->numberOfKeys);
   }
   /*
    * Set pseudo types
    */
   if(_searchType == BingoPgCommon::MOL_MASS_GREAT || _searchType == BingoPgCommon::MOL_MASS_LESS)
      _searchType = BingoPgCommon::MOL_MASS;
   
   _queryFpData.reset(new MangoPgFpData());

   _setBingoContext();
   
   BingoPgSearchEngine::prepareQuerySearch(bingo_idx, scan_desc);

   switch(_searchType) {
      case BingoPgCommon::MOL_SUB:
         _prepareSubSearch(scan_desc);
         break;
      case BingoPgCommon::MOL_EXACT:
         _prepareExactSearch(scan_desc);
         break;
      case BingoPgCommon::MOL_GROSS:
         _prepareGrossSearch(scan_desc);
         break;
      case BingoPgCommon::MOL_SMARTS:
         _prepareSmartsSearch(scan_desc);
         break;
      case BingoPgCommon::MOL_MASS:
         _prepareMassSearch(scan_desc);
         break;
      case BingoPgCommon::MOL_SIM:
         _prepareSimSearch(scan_desc);
         break;
      default:
         throw Error("unsupported search type %d", _searchType);
         break;
   }


}

bool MangoPgSearchEngine::searchNext(PG_OBJECT result_ptr) {

   bool result = false;
   _setBingoContext();
   if (_searchType == BingoPgCommon::MOL_EXACT || _searchType == BingoPgCommon::MOL_GROSS || _searchType == BingoPgCommon::MOL_MASS) {
      result = _searchNextCursor(result_ptr);
   } else if(_searchType == BingoPgCommon::MOL_SUB || _searchType == BingoPgCommon::MOL_SMARTS) {
      result = _searchNextSub(result_ptr);
   } else if(_searchType == BingoPgCommon::MOL_SIM) {
      result = _searchNextSim(result_ptr);
   }

   return result;
}

void MangoPgSearchEngine::_errorHandler(const char* message, void*) {
   throw Error("Error while searching a molecule: %s", message);
}

void MangoPgSearchEngine::_prepareExactQueryStrings(indigo::Array<char>& what_clause_str, indigo::Array<char>& from_clause_str, indigo::Array<char>& where_clause_str) {
   int hash_elements_count, count, bingo_res;
   dword hash;

   ArrayOutput what_clause(what_clause_str);
   ArrayOutput from_clause(from_clause_str);
   ArrayOutput where_clause(where_clause_str);

   what_clause.printf("sh.b_id");

   bingo_res = mangoGetHash(false, -1, &hash_elements_count, &hash);
   CORE_HANDLE_ERROR(bingo_res, 1, "molecule search engine: error while getting hash", bingoGetError());

   if(hash_elements_count > MAX_HASH_ELEMENTS)
      hash_elements_count = MAX_HASH_ELEMENTS;

   from_clause.printf("%s sh", _shadowRelName.ptr());

   for (int i = 0; i < hash_elements_count; i++)
      from_clause.printf(", %s t%d", _shadowHashRelName.ptr(), i);

   /*
    * Create complex WHERE clause
    */

   bool where_was_added = false;
   if (hash_elements_count > 0) {
      where_was_added = true;
      /*
       * molecule ids must be same
       */
      where_clause.printf("sh.b_id = t0.b_id AND ");
      for (int i = 1; i < hash_elements_count; i++)
         where_clause.printf("t%d.b_id = t%d.b_id AND ", i - 1, i);
      /*
       * query components must match target components
       */
      for (int i = 0; i < hash_elements_count; i++) {
         bingo_res = mangoGetHash(false, i, &count, &hash);
         CORE_HANDLE_ERROR(bingo_res, 1, "molecule search engine: error while getting hash", bingoGetError());

         where_clause.printf("t%d.ex_hash = %d AND ", i, hash);
      }

      /*
       * components count mast must target components count
       */
      Array<char> rel;
      bingo_res = mangoExactNeedComponentMatching();
      CORE_HANDLE_ERROR(bingo_res, 0, "molecule search engine: error while getting need matching", bingoGetError());

      if (bingo_res > 0)
         rel.readString(">=", true);
      else
         rel.readString("=", true);

      for (int i = 0; i < hash_elements_count; i++) {
         if (i != 0)
            where_clause.printf("AND ");
         bingo_res = mangoGetHash(false, i, &count, &hash);
         CORE_HANDLE_ERROR(bingo_res, 1, "molecule search engine: error while getting hash", bingoGetError());

         where_clause.printf("t%d.f_count %s %d ", i, rel.ptr(), count);
      }
   }
   bingo_res = mangoExactNeedComponentMatching();
   CORE_HANDLE_ERROR(bingo_res, 0, "molecule search engine: error while getting need matching", bingoGetError());
   if (bingo_res == 0) {
      if (where_was_added)
         where_clause.printf("AND ");

      /*
       * There must be no other components in target
       */
      int query_fragments_count = 0;
      for (int i = 0; i < hash_elements_count; i++) {
         bingo_res = mangoGetHash(false, i, &count, &hash);
         CORE_HANDLE_ERROR(bingo_res, 1, "molecule search engine: error while getting hash", bingoGetError());
         query_fragments_count += count;
      }
      where_clause.printf("sh.fragments = %d", query_fragments_count);
   }
   what_clause_str.push(0);
   from_clause_str.push(0);
   where_clause_str.push(0);

}

void MangoPgSearchEngine::_prepareExactTauStrings(indigo::Array<char>& what_clause_str, indigo::Array<char>& from_clause_str, indigo::Array<char>& where_clause_str) {
   ArrayOutput what_clause(what_clause_str);
   ArrayOutput from_clause(from_clause_str);
   ArrayOutput where_clause(where_clause_str);

   what_clause.printf("b_id");
   from_clause.printf("%s", _shadowRelName.ptr());

   const char* query_gross = mangoTauGetQueryGross();
   if(query_gross == 0)
      CORE_HANDLE_ERROR(0, 1, "molecule search engine: error while constructing gross string", bingoGetError());
   
   where_clause.printf("gross='%s' OR gross LIKE '%s H%%'", query_gross, query_gross);

   what_clause_str.push(0);
   from_clause_str.push(0);
   where_clause_str.push(0);

}

void MangoPgSearchEngine::_prepareSubSearch(PG_OBJECT scan_desc_ptr) {
   IndexScanDesc scan_desc = (IndexScanDesc) scan_desc_ptr;
   Array<char> search_type;
   Array<char> search_query;
   Array<char> search_options;
   
   if(scan_desc->numberOfKeys != 1) {
      throw BingoPgError("molecule search engine: unsupported condition number '%d': "
              "if you want to search several queries please use 'matchSub' or 'matchSmarts' functions for a secondary condition",  
              scan_desc->numberOfKeys);
   }
   
   int bingo_res;
   BingoPgFpData& data = _queryFpData.ref();

   BingoPgCommon::getSearchTypeString(_searchType, search_type, true);

   _getScanQueries(scan_desc->keyData[0].sk_argument, search_query, search_options);
   /*
    * Get block parameters and split search options
    */
   _getBlockParameters(search_options);

   /*
    * Set up matching parameters
    */
   bingo_res = mangoSetupMatch(search_type.ptr(), search_query.ptr(), search_options.ptr());
   CORE_HANDLE_ERROR(bingo_res, 1, "molecule search engine: can not set sub search context", bingoGetError());

   const char* fingerprint_buf;
   int fp_len;

   bingo_res = mangoGetQueryFingerprint(&fingerprint_buf, &fp_len);
   CORE_HANDLE_ERROR(bingo_res, 1, "molecule search engine: can not get sub query fingerprint", bingoGetError());


   int size_bits = fp_len * 8;
   data.setFingerPrints(fingerprint_buf, size_bits);

}

void MangoPgSearchEngine::_prepareExactSearch(PG_OBJECT scan_desc_ptr) {
   IndexScanDesc scan_desc = (IndexScanDesc) scan_desc_ptr;
   QS_DEF(Array<char>, what_clause);
   QS_DEF(Array<char>, from_clause);
   QS_DEF(Array<char>, where_clause);
   QS_DEF(Array<char>, search_type);
   Array<char> search_query;
   Array<char> search_options;
   int bingo_res;
   
   if(scan_desc->numberOfKeys != 1) {
      throw BingoPgError("molecule search engine: unsupported condition number '%d': "
              "if you want to search several queries please use 'matchExact'  for a secondary condition",  
              scan_desc->numberOfKeys);
   }

   profTimerStart(t0, "mango_pg.prepare_exact_search");

   BingoPgCommon::getSearchTypeString(_searchType, search_type, true);
   _getScanQueries(scan_desc->keyData[0].sk_argument, search_query, search_options);

   /*
    * Set up matching parameters
    */
//   elog(WARNING, "processing query: %s", search_query.ptr());
   bingo_res = mangoSetupMatch(search_type.ptr(), search_query.ptr(), search_options.ptr());
   CORE_HANDLE_ERROR(bingo_res, 1, "molecule search engine: can not set exact search context", bingoGetError());

   if (strcasestr(search_options.ptr(), "TAU") != 0) {
      _prepareExactTauStrings(what_clause, from_clause, where_clause);
   } else {
      _prepareExactQueryStrings(what_clause, from_clause, where_clause);
   }
   _searchCursor.free();
   profTimerStart(t4, "mango_pg.exact_search_cursor");
   _searchCursor.reset(new BingoPgCursor("SELECT %s FROM %s WHERE %s",what_clause.ptr(), from_clause.ptr(), where_clause.ptr()));
   profTimerStop(t4);
//   if(nanoHowManySeconds(profTimerGetTime(t4) )> 1)
//      elog(WARNING, "select %s from %s where %s", what_clause.ptr(), from_clause.ptr(), where_clause.ptr());
}

void MangoPgSearchEngine::_prepareGrossSearch(PG_OBJECT scan_desc_ptr) {
   IndexScanDesc scan_desc = (IndexScanDesc) scan_desc_ptr;

   QS_DEF(Array<char>, gross_query);
   QS_DEF(Array<char>, search_type);
   Array<char> search_sigh;
   Array<char> search_mol;
   int bingo_res;
   
   if(scan_desc->numberOfKeys != 1) {
      throw BingoPgError("molecule search engine: unsupported condition number '%d': "
              "if you want to search several queries please use 'matchGross'  for a secondary condition",  
              scan_desc->numberOfKeys);
   }

   BingoPgCommon::getSearchTypeString(_searchType, search_type, true);

   _getScanQueries(scan_desc->keyData[0].sk_argument, search_sigh, search_mol);

   gross_query.readString(search_sigh.ptr(), true);
   gross_query.appendString(" ", true);
   gross_query.appendString(search_mol.ptr(), true);

   /*
    * Set up matching parameters
    */
   bingo_res = mangoSetupMatch(search_type.ptr(), gross_query.ptr(), 0);
   CORE_HANDLE_ERROR(bingo_res, 1, "molecule search engine: can not set gross search context", bingoGetError());

   const char* gross_conditions = mangoGrossGetConditions();
   if(gross_conditions == 0)
      CORE_HANDLE_ERROR(0, 1, "molecule search engine: can not get gross conditions", bingoGetError());

   _searchCursor.free();
   _searchCursor.reset(new BingoPgCursor("SELECT b_id, gross FROM %s WHERE %s", _shadowRelName.ptr(), gross_conditions));
}

void MangoPgSearchEngine::_prepareSmartsSearch(PG_OBJECT scan_desc_ptr) {
   _prepareSubSearch(scan_desc_ptr);
}

void MangoPgSearchEngine::_prepareMassSearch(PG_OBJECT scan_desc_ptr) {
   IndexScanDesc scan_desc = (IndexScanDesc) scan_desc_ptr;
   bool min_mass_flag = false;
   bool max_mass_flag = false;
   float min_mass = 0;
   float max_mass = FLT_MAX;
   QS_DEF(Array<char>, where_clause_str);
   ArrayOutput where_clause(where_clause_str);

   for (int arg_idx = 0; arg_idx < scan_desc->numberOfKeys; ++arg_idx) {
      ScanKeyData& key_data = scan_desc->keyData[arg_idx];
      if(key_data.sk_strategy == BingoPgCommon::MOL_MASS_LESS) {
         char* mass_string = DatumGetCString(key_data.sk_argument);
         BufferScanner mass_scanner(mass_string);
         max_mass = mass_scanner.readFloat();
         max_mass_flag = true;
      } else if(key_data.sk_strategy == BingoPgCommon::MOL_MASS_GREAT) {
         char* mass_string = DatumGetCString(key_data.sk_argument);
         BufferScanner mass_scanner(mass_string);
         min_mass = mass_scanner.readFloat();
         min_mass_flag = true;
      } else {
         throw Error("unsupported search mass and other types");
      }
   }

   if(min_mass_flag)
      where_clause.printf("mass > %f", min_mass);
   if(max_mass_flag)
      where_clause.printf("AND mass < %f", max_mass);
   where_clause.writeChar(0);

   _searchCursor.free();
   _searchCursor.reset(new BingoPgCursor("SELECT b_id FROM %s WHERE %s",_shadowRelName.ptr(), where_clause_str.ptr()));

}

void MangoPgSearchEngine::_prepareSimSearch(PG_OBJECT scan_desc_ptr) {
   IndexScanDesc scan_desc = (IndexScanDesc) scan_desc_ptr;
   QS_DEF(Array<char>, search_type);
   Array<char> search_query;
   Array<char> search_options;
   int bingo_res;
   float min_bound = 0, max_bound = 1;
   BingoPgFpData& data = _queryFpData.ref();

   BingoPgCommon::getSearchTypeString(_searchType, search_type, true);
   
   if(scan_desc->numberOfKeys != 1) {
      throw BingoPgError("molecule search engine: unsupported condition number '%d': "
              "if you want to search several queries please use 'matchSim'  for a secondary condition",  
              scan_desc->numberOfKeys);
   }

   _getScanQueries(scan_desc->keyData[0].sk_argument, min_bound, max_bound, search_query, search_options);
   /*
    * Get block parameters and split search options
    */
   _getBlockParameters(search_options);
   /*
    * Set up matching parameters
    */
   bingo_res = mangoSetupMatch(search_type.ptr(), search_query.ptr(), search_options.ptr());
   CORE_HANDLE_ERROR(bingo_res, 1, "molecule search engine: can not set sim search context", bingoGetError());

   if(min_bound > max_bound)
      throw Error("min bound %f can not be greater then max bound %f", min_bound, max_bound);
   
   bingo_res = mangoSimilaritySetMinMaxBounds(min_bound, max_bound);
   CORE_HANDLE_ERROR(bingo_res, 1, "molecule search engine: can not get similarity min max bounds", bingoGetError());

   const char* fingerprint_buf;
   int fp_len;

   bingo_res = mangoGetQueryFingerprint(&fingerprint_buf, &fp_len);
   CORE_HANDLE_ERROR(bingo_res, 1, "molecule search engine: can not get query fingerprint", bingoGetError());

   int size_bits = fp_len * 8;
   data.setFingerPrints(fingerprint_buf, size_bits);

}

void MangoPgSearchEngine::_getScanQueries(uintptr_t arg_datum, Array<char>& str1_out, Array<char>& str2_out) {
   /*
    * Get query info
    */
   BINGO_PG_TRY
   {
      HeapTupleHeader query_data = DatumGetHeapTupleHeader(arg_datum);
      Oid tupType = HeapTupleHeaderGetTypeId(query_data);
      int32 tupTypmod = HeapTupleHeaderGetTypMod(query_data);
      TupleDesc tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
      int ncolumns = tupdesc->natts;

      if (ncolumns != 2)
         elog(ERROR, "internal error: expecting two columns in query but was %d", ncolumns);

      HeapTupleData tuple;
      /*
       * Build a temporary HeapTuple control structure
       */
      tuple.t_len = HeapTupleHeaderGetDatumLength(query_data);
      ItemPointerSetInvalid(&(tuple.t_self));
      tuple.t_tableOid = InvalidOid;
      tuple.t_data = query_data;

      Datum *values = (Datum *) palloc(ncolumns * sizeof (Datum));
      bool *nulls = (bool *) palloc(ncolumns * sizeof (bool));

      /*
       *  Break down the tuple into fields
       */
      heap_deform_tuple(&tuple, tupdesc, values, nulls);

      /*
       * Query tuple consist of query and options
       */
      BingoPgText str1, str2;
      
      str1.init(values[0]);
      str2.init(values[1]);

      str1_out.readString(str1.getString(), true);
      str2_out.readString(str2.getString(), true);

      pfree(values);
      pfree(nulls);
      ReleaseTupleDesc(tupdesc);
   }
   BINGO_PG_HANDLE(throw Error("internal error: can not get scan query: %s", message));
}

void MangoPgSearchEngine::_getScanQueries(uintptr_t  arg_datum, float& min_bound, float& max_bound, indigo::Array<char>& str1_out, indigo::Array<char>& str2_out) {
   /*
    * Get query info
    */
   BINGO_PG_TRY
   {
      HeapTupleHeader query_data = DatumGetHeapTupleHeader(arg_datum);
      Oid tupType = HeapTupleHeaderGetTypeId(query_data);
      int32 tupTypmod = HeapTupleHeaderGetTypMod(query_data);
      TupleDesc tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
      int ncolumns = tupdesc->natts;

      if (ncolumns != 4)
         elog(ERROR, "internal error: expecting four columns in query but was %d", ncolumns);

      HeapTupleData tuple;
      /*
       * Build a temporary HeapTuple control structure
       */
      tuple.t_len = HeapTupleHeaderGetDatumLength(query_data);
      ItemPointerSetInvalid(&(tuple.t_self));
      tuple.t_tableOid = InvalidOid;
      tuple.t_data = query_data;

      Datum *values = (Datum *) palloc(ncolumns * sizeof (Datum));
      bool *nulls = (bool *) palloc(ncolumns * sizeof (bool));

      /*
       *  Break down the tuple into fields
       */
      heap_deform_tuple(&tuple, tupdesc, values, nulls);

      /*
       * Query tuple consist of query and options
       */
      min_bound = DatumGetFloat4(values[0]);
      max_bound = DatumGetFloat4(values[1]);
      BingoPgText str1, str2;
      str1.init(values[2]);
      str2.init(values[3]);

      str1_out.readString(str1.getString(), true);
      str2_out.readString(str2.getString(), true);

      pfree(values);
      pfree(nulls);
      ReleaseTupleDesc(tupdesc);
   }
   BINGO_PG_HANDLE(throw Error("internal error: can not get scan query: %s", message));
}

bool MangoPgSearchEngine::_searchNextSim(PG_OBJECT result_ptr) {

   profTimerStart(t0, "mango_pg.search_sim");
   /*
    * If there are mathces found on the previous steps
    */
   if(_fetchFound) {
       if(_fetchForNext()) {
          setItemPointer(result_ptr);
          return true;
       } else {
          _fetchFound = false;
          ++_currentSection;
       }
   }
   
   BingoPgFpData& query_data = _queryFpData.ref();
   BingoPgIndex& bingo_index = *_bufferIndexPtr;
   QS_DEF(Array<int>, bits_count);
   QS_DEF(Array<int>, common_ones);
   BingoPgExternalBitset screening_bitset(BINGO_MOLS_PER_SECTION);

   int* min_bounds, * max_bounds, bingo_res;
   /*
    * Read first section
    */
   if(_currentSection < 0)
      _currentSection = _blockBegin;
   /*
    * Iterate through the sections
    */
   for (; _currentSection < _blockEnd; ++_currentSection) {
      _currentIdx = -1;
      /*
       * Get section existing structures
       */
      bingo_index.getSectionBitset(_currentSection, _sectionBitset);
      int possible_str_count = _sectionBitset.bitsNumber();
      /*
       * If there is no bits then screen whole the structures
       */
      if (query_data.bitEnd() != 0 && possible_str_count > 0) {
         /*
          * Read structures bits count
          */
//         profTimerStart(t5, "mango_pg.get_section_bits");
         bingo_index.getSectionBitsCount(_currentSection, bits_count);
//         profTimerStop(t5);
         /*
          * Prepare min max bounds
          */
//         profTimerStart(t3, "mango_pg.get_min_max");
         bingo_res =  mangoSimilarityGetBitMinMaxBoundsArray(bits_count.size(), bits_count.ptr(), &min_bounds, &max_bounds);
//         profTimerStop(t3);
         
         CORE_HANDLE_ERROR(bingo_res, 1, "molecule search engine: error while getting similarity bounds array", bingoGetError());

         /*
          * Prepare common bits array
          */
         common_ones.resize(bits_count.size());
         common_ones.zerofill();
         /*
          * Iterate through the query bits
          */
         int iteration_idx = 0;
         int fp_count = query_data.bitEnd();
         for (int fp_idx = query_data.bitBegin(); fp_idx != query_data.bitEnd() && possible_str_count > 0; fp_idx = query_data.bitNext(fp_idx)) {
            int fp_block = query_data.getBit(fp_idx);
            /*
             * Copy passed structures on each iteration step
             */
//            profTimerStart(t7, "mango_pg.copy");
            screening_bitset.copy(_sectionBitset);
//            profTimerStop(t7);

            /*
             * Get commons in fingerprint buffer
             */
            
//            profTimerStart(t2, "mango_pg.andwith");
            bingo_index.andWithBitset(_currentSection, fp_block, screening_bitset);
//            profTimerStop(t2);

//            profTimerStart(t4, "mango_pg.new_common_ones");
            int screen_idx = screening_bitset.begin();
            for (; screen_idx != screening_bitset.end() && possible_str_count > 0; screen_idx = screening_bitset.next(screen_idx)) {
               /*
                * Calculate new common ones
                */
               int& one_counter = common_ones[screen_idx];
               ++one_counter;
               /*
                * If common ones is out of bounds then it is not passed the screening
                */
               if ((one_counter > max_bounds[screen_idx]) || ((one_counter + fp_count - iteration_idx) < min_bounds[screen_idx])) {
                  _sectionBitset.set(screen_idx, false);
                  --possible_str_count;
               }
            }
//            profTimerStop(t4);
            ++iteration_idx;
         }

         /*
          * Screen the last time for all the possible structures
          */
//         profTimerStart(t6, "mango_pg.lst_screen");
         int screen_idx = _sectionBitset.begin();
         for (; screen_idx != _sectionBitset.end() && possible_str_count > 0; screen_idx = _sectionBitset.next(screen_idx)) {
            int& one_counter = common_ones[screen_idx];
            if ((one_counter > max_bounds[screen_idx]) || (one_counter < min_bounds[screen_idx])) {
               /*
                * Not passed screening
                */
               _sectionBitset.set(screen_idx, false);
               --possible_str_count;
            }
         }
//         profTimerStop(t6);

      }
      /*
       * Return false on empty fingerprint
       */
      if(query_data.bitEnd() == 0)
         return false;

      /*
       * If bitset is not null then matches are found
       */
      if (_sectionBitset.hasBits()) {
         /*
          * Set first match as an answer
          */
         if(_fetchForNext()) {
            profTimerStop(t0);
            setItemPointer(result_ptr);
            /*
             * Set fetch found to return on the next steps
             */
            _fetchFound = true;
            return true;
         }

      }
   }

   /*
    * No matches or section ends
    */
   return false;
}