extern "C" {
#include "postgres.h"
#include "fmgr.h"
#include "storage/bufmgr.h"
#include "access/itup.h"
}
#ifdef qsort
#undef qsort
#endif

#ifdef printf
#undef printf
#endif

#include "bingo_pg_search_engine.h"

#include "base_c/bitarray.h"
#include "base_cpp/tlscont.h"
#include "base_cpp/array.h"
#include "base_cpp/profiling.h"

#include "bingo_core_c.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 BingoPgFpData::setTidItem(PG_OBJECT item_ptr) {

   ItemPointerData& item_p = *(ItemPointer) item_ptr;
   _mapData.tid_map = (ItemPointerData&)item_p;
}

void BingoPgFpData::setFingerPrints(const char* fp_buf, int size_bits) {
   _fingerprintBits.clear();

   for (int bit_idx = 0; bit_idx < size_bits; ++bit_idx) {
      if (bitGetBit(fp_buf, bit_idx)) {
         _fingerprintBits.push(bit_idx);
      }
   }
}

void BingoPgFpData::setCmf(const char* cmf_buf, int cmf_len) {
   _cmfBuf.copy(cmf_buf, cmf_len);
}

void BingoPgFpData::setXyz(const char* xyz_buf, int xyz_len) {
   _xyzBuf.copy(xyz_buf, xyz_len);
}

BingoPgSearchEngine::BingoPgSearchEngine():
_fetchFound(false),
_currentSection(-1),
_currentIdx(-1),
_blockBegin(0),
_blockEnd(0),
_bufferIndexPtr(0),
_sectionBitset(BINGO_MOLS_PER_SECTION){
   _bingoSession = bingoAllocateSessionID();
}

BingoPgSearchEngine::~BingoPgSearchEngine(){
   bingoReleaseSessionID(_bingoSession);
}

void BingoPgSearchEngine::setItemPointer(PG_OBJECT result_ptr) {
   _bufferIndexPtr->readTidItem(_currentSection, _currentIdx, result_ptr);
}

void BingoPgSearchEngine::loadDictionary(BingoPgIndex& bingo_index) {
   QS_DEF(Array<char>, dict);
   bingo_index.readDictionary(dict);
   bingoSetConfigBin("cmf_dict", dict.ptr(), dict.sizeInBytes());
}

//const char* BingoPgSearchEngine::getDictionary(int& size) {
//   _setBingoContext();
//
//   const char* dict_buf;
//
//   bingoGetConfigBin("cmf-dict", &dict_buf, &size);
//
//   return dict_buf;
//}

bool BingoPgSearchEngine::matchTarget(ItemPointerData& item_data) {
   return matchTarget(ItemPointerGetBlockNumber(&item_data), ItemPointerGetOffsetNumber(&item_data));
}

void BingoPgSearchEngine::prepareQuerySearch(BingoPgIndex& bingo_idx, PG_OBJECT) {
   _bufferIndexPtr = &bingo_idx;
   _currentSection = -1;
   _currentIdx = -1;
   _fetchFound = false;
   _blockBegin=0;
   _blockEnd=bingo_idx.getSectionNumber();
}

bool BingoPgSearchEngine::_searchNextCursor(PG_OBJECT result_ptr) {
   profTimerStart(t0, "bingo_pg.search_cursor");
   ItemPointerData cmf_item;
   /*
    * Iterate through the cursor
    */
   while (_searchCursor->next()) {
      _searchCursor->getId(1, cmf_item);

      /*
       * If structure is removed from index then seek to the next
       */
      if(_bufferIndexPtr->isStructureRemoved(cmf_item))
         continue;
      /*
       * If structure is not match then seek to the next
       */
      if (!matchTarget(cmf_item))
         continue;
      /*
       * Return tid map
       */
      _bufferIndexPtr->readTidItem(cmf_item, result_ptr);
      return true;
   }

   _searchCursor.free();
   return false;
}

bool BingoPgSearchEngine::_searchNextSub(PG_OBJECT result_ptr) {
   
   profTimerStart(t0, "bingo_pg.search_sub");
   BingoPgFpData& query_data = _queryFpData.ref();
   BingoPgIndex& bingo_index = *_bufferIndexPtr;
   /*
    * If there are matches found on the previous steps
    */
   if(_fetchFound) {
       if(_fetchForNext()) {
          setItemPointer(result_ptr);
          return true;
       } else {
          _fetchFound = false;
          ++_currentSection;
       }
   }
   profTimerStart(t1, "bingo_pg.search_fp");
   
   if(_currentSection < 0)
      _currentSection = _blockBegin;
   /*
    * Iterate through the sections bingo_index.readEnd()
    */
   for (; _currentSection < _blockEnd; ++_currentSection) {
      /*
       * Get section existing structures
       */
      bingo_index.getSectionBitset(_currentSection, _sectionBitset);
      _currentIdx = -1;
      /*
       * If there is no fingerprints then check every molecule
       */
      if (query_data.bitEnd() != 0) {
         /*
          * Iterate through the query bits
          */
         for (int fp_idx = query_data.bitBegin(); fp_idx != query_data.bitEnd() && _sectionBitset.hasBits(); fp_idx = query_data.bitNext(fp_idx)) {
            int fp_block = query_data.getBit(fp_idx);
            /*
             * Get fingerprint buffer in the current section
             */
            bingo_index.andWithBitset(_currentSection, fp_block, _sectionBitset);
         }
      }
      /*
       * If bitset is not null then matches are found
       */
      if (_sectionBitset.hasBits()) {
         /*
          * Set first match as an answer
          */
         if(_fetchForNext()) {
            profTimerStop(t1);
            setItemPointer(result_ptr);
            /*
             * Set fetch found to return on the next steps
             */
            _fetchFound = true;
            return true;
         }

      }
   }

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

using namespace indigo;


void BingoPgSearchEngine::_setBingoContext() {
   bingoSetSessionID(_bingoSession);
   bingoSetContext(0);
}

bool BingoPgSearchEngine::_fetchForNext() {
   /*
    * Seek for next target matched by fp engine
    */
   if(_currentIdx == -1)
      _currentIdx = _sectionBitset.begin();
   else
      _currentIdx = _sectionBitset.next(_currentIdx);

   for (; _currentIdx != _sectionBitset.end(); _currentIdx = _sectionBitset.next(_currentIdx)) {
      /*
       * Match the next target
       */
      if(matchTarget(_currentSection, _currentIdx))
         return true;
   }

   return false;
}

void BingoPgSearchEngine::_getBlockParameters(Array<char>& params) {
   QS_DEF(Array<char>, block_params);
   QS_DEF(Array<char>, tmp);
   block_params.clear();

   for(int i = 0; i < params.size(); ++i) {
      if(params[i] == ';') {
         block_params.copy(params.ptr(), i);
         block_params.push(0);
         tmp.copy(params);
         params.clear();
         for(int j = i+1; j < params.size(); ++j) {
            params.push(tmp[j]);
         }
         params.push(0);
      }
   }
   bool verify_split = false;

   if(block_params.size() == 0) {
      block_params.copy(params);
      verify_split = true;
   }

   BufferScanner scanner(block_params);
   QS_DEF(Array<char>, word);

   int block_id=0,block_count=0;

   while(!scanner.isEOF()) {
      scanner.skipSpace();
      scanner.readWord(word, 0);

      if (strcasecmp(word.ptr(), "B_ID") == 0) {
         scanner.skipSpace();
         block_id = scanner.readInt();
         if(block_id < 1)
            throw BingoPgError("B_ID should be a positive value: %d", block_id);
      } else if(strcasecmp(word.ptr(), "B_COUNT") == 0) {
         scanner.skipSpace();
         block_count = scanner.readInt();
         if(block_count < 1)
            throw BingoPgError("B_COUNT should be a positive value: %d", block_count);
      } else if(strcasecmp(word.ptr(), "") == 0) {
         break;
      } else {
         if(verify_split) 
            return;
         else
            throw BingoPgError("unknown block type: %s", word.ptr());
      }
   }

   if(verify_split) {
      params.clear();
      params.push(0);
   }
   /*
    * Return if block id was not specified
    */
   if(block_id==0) 
      return;

   int max_blocks = _bufferIndexPtr->getSectionNumber();

   if(block_count > 0) {
      if(block_count > max_blocks)
         throw BingoPgError("B_COUNT %d can not be greater then maximum block count %d", block_count, max_blocks);
      if(block_id>block_count)
         throw BingoPgError("B_ID %d can not be greater then B_COUNT %d", block_id, block_count);

      double b = block_id-1;
      b =  (double)(b / block_count) * max_blocks;
      double e = block_id;
      e = (double)(e / block_count) * max_blocks;
      _blockBegin = (int)b;
      _blockEnd = (int)e;

   } else {
      if(block_id>max_blocks)
         throw BingoPgError("B_ID %d can not be greater then maximum block count %d", block_id, max_blocks);

      _blockBegin = block_id - 1;
      _blockEnd = block_id;
   }



}