/****************************************************************************
 * Copyright (C) 2009-2013 GGA Software Services LLC
 * 
 * This file is part of Indigo toolkit.
 * 
 * This file may be distributed and/or modified under the terms of the
 * GNU General Public License version 3 as published by the Free Software
 * Foundation and appearing in the file LICENSE.GPL included in the
 * packaging of this file.
 * 
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 ***************************************************************************/

#include "oracle/ora_wrap.h"

#include "base_c/bitarray.h"
#include "oracle/bingo_fingerprints.h"
#include "base_cpp/output.h"
#include "oracle/ora_wrap.h"
#include "core/bingo_context.h"
#include "base_cpp/scanner.h"
#include "base_c/nano.h"
#include "bingo_storage.h"
#include "base_cpp/profiling.h"

IMPL_ERROR(BingoFingerprints, "screening");

CP_DEF(BingoFingerprints);

BingoFingerprints::BingoFingerprints (int context_id) :
CP_INIT, TL_CP_GET(_table_name)
{
   ArrayOutput output(_table_name);

   output.printf("FINGERPRINTS_%d", context_id);
   output.writeChar(0);

   _part_adding = -1;
   _total_count_cached = -1;
}

void BingoFingerprints::init (BingoContext &context, int fp_bytes, int fp_priority_bytes_min, int fp_priority_bytes_max)
{
   _fp_bytes = fp_bytes;
   if (fp_priority_bytes_min != -1)
   {
      _fp_priority_bytes_min = fp_priority_bytes_min;
      _fp_priority_bytes_max = fp_priority_bytes_max;
   }
   else
   {
      _fp_priority_bytes_min = 0;
      _fp_priority_bytes_max = fp_bytes;
   }

   _chunk_qwords = context.fp_chunk_qwords;
}

void BingoFingerprints::create (OracleEnv &env)
{
   const char *tn = _table_name.ptr();
   const char *securefile = "";

   if (env.serverMajorVersion() >= 11)
      securefile = "securefile";

   OracleStatement::executeSingle(env,
      "CREATE TABLE %s(part number, used number, counters BLOB, mapping BLOB, "
              "bit_starts BLOB, bit_ends BLOB, bits BLOB) PCTFREE 0 NOLOGGING "
      "lob(bits) store as %s (DISABLE STORAGE IN ROW CACHE READS NOLOGGING PCTVERSION 0) "
      "lob(counters) store as %s (DISABLE STORAGE IN ROW CACHE READS NOLOGGING PCTVERSION 0) "
      "lob(bit_starts) store as %s (DISABLE STORAGE IN ROW CACHE READS NOLOGGING PCTVERSION 0) "
      "lob(bit_ends) store as %s (DISABLE STORAGE IN ROW CACHE READS NOLOGGING PCTVERSION 0) "
      "lob(mapping) store as %s (DISABLE STORAGE IN ROW CACHE READS NOLOGGING PCTVERSION 0) ", tn,
           securefile, securefile, securefile, securefile, securefile);
   OracleStatement::executeSingle(env, "CREATE INDEX %s_part ON %s(part)", tn, tn);
}

void BingoFingerprints::drop (OracleEnv &env)
{
   OracleStatement::executeSingle(env, "BEGIN DropTable('%s'); END;", _table_name.ptr());
}

void BingoFingerprints::truncate (OracleEnv &env)
{
   OracleStatement::executeSingle(env, "TRUNCATE TABLE %s", _table_name.ptr());
}

void BingoFingerprints::addFingerprint (OracleEnv &env, const byte *fp)
{
   if (_pending_block.used == _chunk_qwords * 64)
      flush(env);

   byte *ptr = (byte *)_pending_bits.ptr();

   for (int i = 0; i < _fp_bytes * 8; i++)
   {
      if (bitGetBit(fp, i))
      {
         bitSetBit(ptr, _pending_block.used, 1);
         if (_pending_block.counters[i] < 65535)
            _pending_block.counters[i]++;
         _pending_block.bit_ends[i] = _pending_block.used;
      }
      ptr += 8 * _chunk_qwords;
   }
   _pending_block.mapping.push(_pending_block.mapping.size());
   _pending_block.used++;
}

void BingoFingerprints::flush (OracleEnv &env)
{
   if (_pending_block.used < 1)
      return;

   env.dbgPrintfTS("flushing %d items\n", _pending_block.used);

   // Transpose bits
   env.dbgPrintf("  optimizing pending block...");
   _optimizePendingBlock();
   env.dbgPrintf("ok\n");

   if (!_flush_Update(env, true))
      _flush_Insert(env);

   _initBlock(_pending_block, true);
   _part_adding++;

   _all_blocks.clear();
}

void BingoFingerprints::_initBlock (Block &block, bool is_pending_block)
{
   block.used = 0;

   if (is_pending_block)
   {
      _pending_bits.clear_resize(_fp_bytes * _chunk_qwords * 8);
      _pending_bits.zerofill();
   }

   block.bit_starts.clear_resize(_fp_bytes * 8);
   block.bit_starts.zerofill();
   block.bit_ends.clear_resize(_fp_bytes * 8);
   block.bit_ends.zerofill();
   block.counters.clear_resize(_fp_bytes * 8);
   block.counters.zerofill();
   block.mapping.clear();
}

bool BingoFingerprints::_flush_Update (OracleEnv &env, bool update_counter)
{
   OracleStatement statement(env);
   OracleLOB 
      lob_counters(env),
      lob_bits(env),
      lob_mapping(env),
      lob_bit_starts(env),
      lob_bit_ends(env);

   statement.append("SELECT counters, mapping, bit_starts, bit_ends, bits from %s WHERE part = :part FOR UPDATE",
                    _table_name.ptr());
   statement.prepare();
   statement.bindIntByName(":part", &_part_adding);
   statement.defineBlobByPos(1, lob_counters);
   statement.defineBlobByPos(2, lob_mapping);
   statement.defineBlobByPos(3, lob_bit_starts);
   statement.defineBlobByPos(4, lob_bit_ends);
   statement.defineBlobByPos(5, lob_bits);

   if (!statement.executeAllowNoData())
      return false;

   lob_bits.write(0, (char *)_pending_bits.ptr(), _pending_bits.size() * 8);

   Block &block = _pending_block;
   lob_counters.write(0, (char *)block.counters.ptr(), block.counters.sizeInBytes());
   lob_mapping.write(0, (char *)block.mapping.ptr(), block.mapping.sizeInBytes());
   lob_bit_starts.write(0, (char *)block.bit_starts.ptr(), block.bit_starts.sizeInBytes());
   lob_bit_ends.write(0, (char *)block.bit_ends.ptr(), block.bit_ends.sizeInBytes());

   if (update_counter)
   {
      OracleStatement statement1(env);
      statement1.append("UPDATE %s SET used = :used WHERE part = :part", _table_name.ptr());
      statement1.prepare();
      statement1.bindIntByName(":used", &_pending_block.used);
      statement1.bindIntByName(":part", &_part_adding);
      statement1.execute();
   }
   return true;
}

void BingoFingerprints::_flush_Insert (OracleEnv &env)
{
   OracleStatement statement(env);
   statement.append("INSERT INTO %s VALUES(:part, :used, "
      "empty_blob(), empty_blob(), empty_blob(), empty_blob(), empty_blob())", _table_name.ptr());

   statement.prepare();
   statement.bindIntByName(":part", &_part_adding);
   statement.bindIntByName(":used", &_pending_block.used);
   statement.execute();

   _flush_Update(env, false);
}

void BingoFingerprints::_flush_Insert_OLD (OracleEnv &env)
{
   OracleStatement statement(env);
   OracleLOB
      lob_counters(env),
      lob_bits(env),
      lob_mapping(env),
      lob_bit_starts(env),
      lob_bit_ends(env);

   lob_counters.createTemporaryBLOB();
   lob_bits.createTemporaryBLOB();
   lob_mapping.createTemporaryBLOB();
   lob_bit_starts.createTemporaryBLOB();
   lob_bit_ends.createTemporaryBLOB();

   Block &block = _pending_block;
   lob_bits.write(0, (char *)_pending_bits.ptr(), _pending_bits.size() * 8);
   lob_counters.write(0, (char *)block.counters.ptr(), block.counters.sizeInBytes());
   lob_mapping.write(0, (char *)block.mapping.ptr(), block.mapping.sizeInBytes());
   lob_bit_starts.write(0, (char *)block.bit_starts.ptr(), block.bit_starts.sizeInBytes());
   lob_bit_ends.write(0, (char *)block.bit_ends.ptr(), block.bit_ends.sizeInBytes());
   
   statement.append("INSERT INTO %s VALUES(:part, :used, :counters, "
                    ":mapping, :bit_starts, :bit_ends, :bits)", _table_name.ptr());

   statement.prepare();
   statement.bindIntByName(":used", &_pending_block.used);
   statement.bindIntByName(":part", &_part_adding);
   statement.bindBlobByName(":counters", lob_counters);
   statement.bindBlobByName(":mapping", lob_mapping);
   statement.bindBlobByName(":bit_starts", lob_bit_starts);
   statement.bindBlobByName(":bit_ends", lob_bit_ends);
   statement.bindBlobByName(":bits", lob_bits);
   statement.execute();
}

void BingoFingerprints::screenInit (const byte *fp, Screening &screening)
{
   screening.query_ones.clear();
   screening.passed.clear();

   for (int i = 0; i < _fp_bytes * 8; i++)
      if (bitGetBit(fp, i))
         screening.query_ones.push(i);

   screening.part = 0;
   screening.block = 0;
   screening.items_read = 0;
   screening.items_passed = 0;
}

bool BingoFingerprints::ableToScreen (Screening &screening)
{
   return screening.query_ones.size() > 0;
}

bool BingoFingerprints::screenPart_Init (OracleEnv &env, Screening &screening)
{
   screening.passed.clear();

   if (screening.query_ones.size() < 1)
      throw Error("no screening bits");

   int used_qwords;

   if (screening.part != _part_adding)
   {
      if (screening.part >= _all_blocks.size())
         return false;

      screening.block = &_all_blocks[screening.part];

      screening.statement.create(env);
      screening.bits_lob.create(env);
      screening.statement->append("SELECT bits FROM %s WHERE part = :part", _table_name.ptr());
      screening.statement->prepare();
	  screening.statement->bindIntByName(":part", &screening.part);
      screening.statement->defineBlobByPos(1, screening.bits_lob.ref());
      screening.statement->execute();
   }
   else
      screening.block = &_pending_block;

   used_qwords = (screening.block->used + 63) / 64;
   screening.items_read += screening.block->used;

   if (used_qwords == 0)
      return false;

   screening.data = this;
   screening.query_ones.qsort(_cmp_counters, &screening);

   screening.fp_final.clear_resize(used_qwords);
   screening.fp_final.zerofill();
   screening.query_bit_idx = 0;

   return true;
}

bool BingoFingerprints::screenPart_Next (OracleEnv &env, Screening &screening)
{
   QS_DEF(Array<qword>, fp_inter); // i.e. intermediate
   int used_bytes = (screening.block->used + 7) / 8;


   if (screening.query_bit_idx >= screening.query_ones.size())
      return false;

   int used_qwords = screening.fp_final.size();

   fp_inter.clear_resize(used_qwords);
   screening.passed_pre.clear();

   int query_bit = screening.query_ones[screening.query_bit_idx];

   if (screening.query_bit_idx == 0)
   {
      profTimerStart(tread, "fingerprints.read");

      word bit_start_read = screening.block->bit_starts[query_bit];
      word bit_end_read = screening.block->bit_ends[query_bit];

      if (bit_start_read > bit_end_read)
      {
         // This bit wasn't used in fingerprints in current block
         screening.query_bit_idx++;
         screening.fp_final.zerofill();
         screening.start_offset = -1;
         screening.end_offset = -1;
         return true;
      }

      screening.start_offset = bit_start_read / 8;
      screening.end_offset = bit_end_read / 8;

      int read_bytes_count = screening.end_offset - screening.start_offset + 1;

      if (screening.part != _part_adding)
         screening.bits_lob->read(query_bit * _chunk_qwords * 8 + screening.start_offset,
            (char *)screening.fp_final.ptr() + screening.start_offset, read_bytes_count);
      else
         memcpy(screening.fp_final.ptr(), _pending_bits.ptr() + query_bit * _chunk_qwords, used_qwords * 8);

      profIncCounter("fingerprints.read_nbytes", read_bytes_count);
      profIncCounter("fingerprints.read_nbytes_old", used_qwords * 8);
   }
   else
   {
      profTimerStart(tread, "fingerprints.read");

      if (screening.start_offset < 0)
      {
         // The first bit was not used in fingerprints in current block
         screening.query_bit_idx++;
         return true;
      }

      // reduce range to reduce number of reading bytes
      const char *bits_final = (const char *)screening.fp_final.ptr();
      
      while (bits_final[screening.start_offset] == 0)
      {
         screening.start_offset++;
         if (screening.start_offset >= used_qwords * 8)
            break;
      }
      while (screening.end_offset >= 0 && bits_final[screening.end_offset] == 0)
      {
         screening.end_offset--;
      }

      int read_bytes_count = screening.end_offset - screening.start_offset + 1;

      if (read_bytes_count <= 0)
      {
         screening.fp_final.zerofill();
         screening.query_bit_idx++;
         return true;
      }
      else
      {
         if (screening.part != _part_adding)
            screening.bits_lob->read(query_bit * _chunk_qwords * 8 +
                                    screening.start_offset, (char *)fp_inter.ptr() + screening.start_offset, read_bytes_count);
         else
            memcpy(fp_inter.ptr(), _pending_bits.ptr() + query_bit * _chunk_qwords, used_qwords * 8);

         profTimerStop(tread);
         profIncCounter("fingerprints.read_nbytes", read_bytes_count);
         profIncCounter("fingerprints.read_nbytes_old", used_qwords * 8);

         bitAnd((byte*)screening.fp_final.ptr(), (byte*)fp_inter.ptr(), used_bytes);
      }
   }

   for (int i = 0; i < screening.block->used; i++)
      if (bitGetBit(screening.fp_final.ptr(), i))
         screening.passed_pre.push(i);
   
   screening.query_bit_idx++;

   return true;

}

void BingoFingerprints::screenPart_End (OracleEnv &env, Screening &screening)
{
   int i;

   screening.bits_lob.free();
   screening.statement.free();

   profTimerStart(tsort, "fingerprints.end");
   for (i = 0; i < screening.passed_pre.size(); i++)
      screening.passed_pre[i] = getStorageIndex(screening, screening.passed_pre[i]);

   screening.passed_pre.qsort(_cmp_int, 0);

   for (i = 0; i < screening.passed_pre.size(); i++)
      screening.passed.add(screening.passed_pre[i]);

   screening.part++;
}

int BingoFingerprints::_cmp_int (int a, int b, void *context)
{
   return a - b;
}

int BingoFingerprints::_cmp_counters (int a, int b, void *context)
{
   const Screening &screening = *(const Screening *)context;
   const BingoFingerprints &self = *(const BingoFingerprints *)screening.data;

   if (screening.part == self._part_adding)
      return self._pending_block.counters[a] - self._pending_block.counters[b];

   const Block &block = self._all_blocks[screening.part];
   return block.counters[a] - block.counters[b];
}

int BingoFingerprints::getStorageIndex (Screening &screening, int local_idx)
{
   return screening.block->mapping[local_idx] + screening.part * (_chunk_qwords * 64);
}

int BingoFingerprints::getStorageIndex_NoMap (Screening &screening, int local_idx)
{
   return local_idx + screening.part * (_chunk_qwords * 64);
}


bool BingoFingerprints::countOnes_Init (OracleEnv &env, Screening &screening)
{
   if (screening.query_ones.size() < 1)
      throw Error("no screening bits");

   if (screening.part != _part_adding)
   {
      if (screening.part >= _all_blocks.size())
         return false;

      screening.statement.create(env);
      screening.bits_lob.create(env);
      screening.statement->append("SELECT bits FROM %s WHERE part = :part",
                        _table_name.ptr());
      screening.statement->prepare();
      screening.statement->bindIntByName(":part", &screening.part);
      screening.statement->defineBlobByPos(1, screening.bits_lob.ref());
      screening.statement->execute();

      screening.block = &_all_blocks[screening.part];
   }
   else
      screening.block = &_pending_block;

   screening.data = this;
   screening.query_ones.qsort(_cmp_counters, &screening);
   
   screening.one_counters.clear_resize(screening.block->used);
   screening.one_counters.zerofill();

   screening.query_bit_idx = 0;

   
   return true;
}

bool BingoFingerprints::countOnes_Next (OracleEnv &env, Screening &screening)
{
   int used_qwords = (screening.block->used + 63) / 64;
   int used_bytes = (screening.block->used + 7) / 8;

   QS_DEF(Array<qword>, fp);

   fp.clear_resize(used_qwords);

   if (screening.query_bit_idx >= screening.query_ones.size())
      return false;

   if (screening.part != _part_adding)
      screening.bits_lob->read(screening.query_ones[screening.query_bit_idx] * _chunk_qwords * 8,
                     (char *)fp.ptr(), used_qwords * 8);
   else
      memcpy(fp.ptr(), _pending_bits.ptr() + screening.query_ones[screening.query_bit_idx] *
                                             _chunk_qwords, used_qwords * 8);

   for (int j = 0; j < 8 * used_bytes; j++)
   {
      if (bitGetBit(fp.ptr(), j))
         screening.one_counters[screening.block->mapping[j]]++;
   }
   screening.query_bit_idx++;
   return true;
}

void BingoFingerprints::countOnes_End (OracleEnv &env, Screening &screening)
{
   screening.bits_lob.free();
   screening.statement.free();
   screening.part++;
}



void BingoFingerprints::validateForUpdate (OracleEnv &env)
{
   OracleStatement statement(env);
   OracleLOB 
      counters_lob(env), mapping_lob(env), 
      bit_starts_lob(env), bit_ends_lob(env),
      bits_lob(env);
   int used, part = -1;

   statement.append("SELECT used, counters, mapping, bit_starts, bit_ends, part, bits FROM %s ORDER BY part DESC",
      _table_name.ptr());

   statement.prepare();
   statement.defineIntByPos(1, &used);
   statement.defineBlobByPos(2, counters_lob);
   statement.defineBlobByPos(3, mapping_lob);
   statement.defineBlobByPos(4, bit_starts_lob);
   statement.defineBlobByPos(5, bit_ends_lob);
   statement.defineIntByPos(6, &part);
   statement.defineBlobByPos(7, bits_lob);

   bool ex = statement.executeAllowNoData();

   if (ex && used < _chunk_qwords * 64)
   {
      if (_part_adding != part)
      {
         _part_adding = part;

         _initBlock(_pending_block, true);
         Block &block = _pending_block;

         block.used = used;

         counters_lob.read(0, (char *)block.counters.ptr(), block.counters.sizeInBytes());
         bit_starts_lob.read(0, (char *)block.bit_starts.ptr(), block.bit_starts.sizeInBytes());
         bit_ends_lob.read(0, (char *)block.bit_ends.ptr(), block.bit_ends.sizeInBytes());

         block.mapping.clear_resize(used);
         mapping_lob.read(0, (char *)block.mapping.ptr(), block.mapping.sizeInBytes());

         _pending_bits_2.clear_resize(_pending_bits.size());
         _pending_bits_2.zerofill();

         bits_lob.read(0, (char *)_pending_bits_2.ptr(), _pending_bits.size() * 8);

         int i;

         for (i = 0; i < _fp_bytes * 8; i++)
         {
            int offset = i * _chunk_qwords * 8;
            const byte *fp_row_src = (byte *)_pending_bits_2.ptr() + offset;
            byte *fp_row_dest = (byte *)_pending_bits.ptr() + offset;

            for (int j = 0; j < _pending_block.used; j++)
            {
               int mapped_bit = _pending_block.mapping[j];
               if (bitGetBit(fp_row_src, j))
                  bitSetBit(fp_row_dest, mapped_bit, 1);
            }
         }

         for (i = 0; i < _pending_block.mapping.size(); i++)
            _pending_block.mapping[i] = i;

      }
   }
   else
   {
      if (ex)
      {
         if (_part_adding == part + 1) // are we already inserting into pending block?
            ;
         else
            _pending_block.used = 0;
      }
      if (_pending_block.used == 0)
      {
         _part_adding = part + 1; // will start from 0 if there are no rows in the table
         _initBlock(_pending_block, true);
      }
   }
}

void BingoFingerprints::validate (OracleEnv &env)
{
   env.dbgPrintf("validating screening data... ");

   if (_all_blocks.size() > 0)
   {
      // TODO: reload data if table was updated
      env.dbgPrintf("already loaded\n");
      return;
   }

   profTimerStart(tread, "fingerprints.validate");

   OracleStatement statement(env);
   OracleLOB 
      counters_lob(env), mapping_lob(env), 
      bit_starts_lob(env), bit_ends_lob(env);
   int used;

   statement.append("SELECT used, counters, mapping, bit_starts, bit_ends FROM %s ORDER BY part", 
      _table_name.ptr());
   statement.prepare();
   statement.defineIntByPos(1, &used);
   statement.defineBlobByPos(2, counters_lob);
   statement.defineBlobByPos(3, mapping_lob);
   statement.defineBlobByPos(4, bit_starts_lob);
   statement.defineBlobByPos(5, bit_ends_lob);

   if (!statement.executeAllowNoData())
   {
      env.dbgPrintf("no data?\n");
      return;
   }

   do
   {
      Block &block = _all_blocks.push();

      _initBlock(block, false);
      block.used = used;

      counters_lob.read(0, (char *)block.counters.ptr(), block.counters.sizeInBytes());
      bit_starts_lob.read(0, (char *)block.bit_starts.ptr(), block.bit_starts.sizeInBytes());
      bit_ends_lob.read(0, (char *)block.bit_ends.ptr(), block.bit_ends.sizeInBytes());

      block.mapping.clear_resize(used);
      mapping_lob.read(0, (char *)block.mapping.ptr(), block.mapping.sizeInBytes());
   } while (statement.fetch());

   env.dbgPrintf("\n");
}

void BingoFingerprints::analyze (OracleEnv &env)
{
   env.dbgPrintf("analyzing fingerprints table\n");
   OracleStatement::executeSingle(env, "ANALYZE TABLE %s ESTIMATE STATISTICS", _table_name.ptr());
}

int BingoFingerprints::countOracleBlocks (OracleEnv &env)
{
   int res;

   if (_chunk_qwords < 1)
      return 0;

   if (!OracleStatement::executeSingleInt(res, env, "select sum(length(bits)) / %d from %s",
           _chunk_qwords * 8, _table_name.ptr()))
      return 0;

   return res;
}

float BingoFingerprints::queryOnesRatio (Screening &screening)
{
   if (_fp_bytes == 0)
      throw Error("_fp_bytes = 0 -> division by zero");

   return (float)screening.query_ones.size() / (_fp_bytes * 8);
}

int BingoFingerprints::getTotalCount (OracleEnv &env)
{
   if (_total_count_cached >= 0)
      return _total_count_cached;

   _total_count_cached = 0;

   if (_all_blocks.size() > 0)
   {
      for (int i = 0; i < _all_blocks.size(); i++)
         _total_count_cached += _all_blocks[i].used;
   }
   else
   {
      OracleStatement::executeSingleInt(_total_count_cached, env,
              "SELECT sum(used) FROM %s", _table_name.ptr());
   }

   return _total_count_cached;
}

int BingoFingerprints::_cmp_optimize_counters (int a, int b, void *context)
{
   const BingoFingerprints &self = *(const BingoFingerprints *)context;

   bool a_priority = a >= self._fp_priority_bytes_min * 8 && a < self._fp_priority_bytes_max * 8;
   bool b_priority = b >= self._fp_priority_bytes_min * 8 && b < self._fp_priority_bytes_max * 8;
   if (a_priority && !b_priority)
      return -1;
   if (!a_priority && b_priority)
      return 1;

   return self._pending_block.counters[a] - self._pending_block.counters[b];
}

void BingoFingerprints::_optimizePendingBlock ()
{
   profTimerStart(tall, "fingerprints.optimize");
   // Find better ordering for molecules to reduce 
   // number of bytes to read during screening
   
   // Find bit order for better screening
   QS_DEF(Array<int>, counters_order);
   counters_order.clear_resize(_fp_bytes * 8);
   for (int i = 0; i < _fp_bytes * 8; i++)
      counters_order[i] = i;

   counters_order.qsort(_cmp_optimize_counters, this);

   // Find better mapping 
   QS_DEF(Array<word>, post_mapping);
   post_mapping.clear_resize(_pending_block.used);
   for (int i = 0; i < _pending_block.used; i++)
      post_mapping[i] = i;

   int last_zero_start = 0;
   for (int i = 0; i < _fp_bytes * 8; i++)
   {
      int bit = counters_order[i];
      int offset = bit * _chunk_qwords * 8;
      const byte *fp_row = (byte *)_pending_bits.ptr() + offset;

      // Split molecules by specified bit
      int low_start = last_zero_start;
      int high_start = _pending_block.used - 1;
      while (true)
      {
         int low = low_start;
         while (low < _pending_block.used && bitGetBit(fp_row, post_mapping[low]) != 0)
            low++;

         int high = high_start;
         while (high > low && bitGetBit(fp_row, post_mapping[high]) != 1)
            high--;

         if (high <= low)
         {
            last_zero_start = low;
            break;
         }

         // swap 
         int tmp = post_mapping[low];
         post_mapping[low] = post_mapping[high];
         post_mapping[high] = tmp;

         low_start = low + 1;
         high_start = high - 1;
      }
   }
   // Apply post_mapping to the existing mapping
   for (int i = 0; i < _pending_block.used; i++)
      _pending_block.mapping[i] = post_mapping[_pending_block.mapping[i]];

   // Find bit start and end positions
   _pending_block.bit_starts.fffill();
   _pending_block.bit_ends.zerofill();
   for (int i = 0; i < _fp_bytes * 8; i++)
   {
      int offset = i * _chunk_qwords * 8;
      const byte *fp_row = (byte *)_pending_bits.ptr() + offset;

      for (int j = 0; j < _pending_block.used; j++)
      {
         int mapped_bit = post_mapping[j];
         if (bitGetBit(fp_row, mapped_bit))
         {
            word &start_index = _pending_block.bit_starts[i];
            if (start_index > j)
               start_index = j;

            word &end_index = _pending_block.bit_ends[i];
            if (end_index < j)
               end_index = j;
         }
      }
   }

   // Reorder bits according to the found mapping
   _pending_bits_2.clear_resize(_pending_bits.size());
   _pending_bits_2.zerofill();

   for (int i = 0; i < _fp_bytes * 8; i++)
   {
      int offset = i * _chunk_qwords * 8;
      const byte *fp_row_src = (byte *)_pending_bits.ptr() + offset;
      byte *fp_row_dest = (byte *)_pending_bits_2.ptr() + offset;

      for (int j = 0; j < _pending_block.used; j++)
      {
         //int mapped_bit = _pending_block.mapping[j];
         int mapped_bit = post_mapping[j];
         if (bitGetBit(fp_row_src, mapped_bit))
            bitSetBit(fp_row_dest, j, 1);
      }
   }
   _pending_bits.swap(_pending_bits_2);
}

//
// BingoFingerprints::Screening
//

CP_DEF(BingoFingerprints::Screening);

BingoFingerprints::Screening::Screening () :
CP_INIT,
TL_CP_GET(query_ones),
TL_CP_GET(passed),
TL_CP_GET(one_counters),
TL_CP_GET(passed_pre),
TL_CP_GET(fp_final)
{
}
