extern "C" {
#include "postgres.h"
#include "storage/itemptr.h"
#include "storage/block.h"
}
#ifdef qsort
#undef qsort
#endif

#include "bingo_pg_section.h"
#include "bingo_pg_index.h"
#include "bingo_pg_search_engine.h"
#include "bingo_pg_common.h"
#include "base_cpp/tlscont.h"
#include "base_cpp/profiling.h"


using namespace indigo;

IMPL_ERROR(BingoPgSection, "bingo postgres section");

BingoPgSection::BingoPgSection(BingoPgIndex& bingo_idx, int idx_strategy, int offset):
_index(bingo_idx.getIndexPtr()),
_offset(offset),
_idxStrategy(idx_strategy){

   /*
    * Clear section metainfo
    */
   clear();

   /*
    * Prepare section strategy
    */
   bool write = (_idxStrategy == BingoPgIndex::BUILDING_STRATEGY);
   if(write) {
      /*
       * If write then create new buffers
       */
      _sectionInfoBuffer.writeNewBuffer(_index, offset);
      _sectionInfoBuffer.changeAccess(BINGO_PG_NOLOCK);
      _sectionInfo.n_blocks_for_map = bingo_idx.getMapSize();
      _sectionInfo.n_blocks_for_fp = bingo_idx.getFpSize();
      /*
       * Initialize existing structures fingerprint
       */
      _existStructures.reset(new BingoPgBufferCacheFp(offset + 1, _index, true));

      /*
       * Initialize bits number buffers
       */
      for (int idx = 0; idx < SECTION_BITSNUMBER_PAGES; ++idx) {
         BingoPgBuffer& bits_buffer = _bitsCountBuffers.push();
         bits_buffer.writeNewBuffer(_index, offset + idx + SECTION_META_PAGES);
         bits_buffer.formEmptyIndexTuple(SECTION_BITS_PER_BLOCK * sizeof(unsigned short));
         bits_buffer.changeAccess(BINGO_PG_NOLOCK);
      }

   } else {
      /*
       * Read section meta info
       */
      _sectionInfoBuffer.readBuffer(_index, _offset, BINGO_PG_READ);
      int data_len;
      BingoSectionInfoData* data = (BingoSectionInfoData*)_sectionInfoBuffer.getIndexData(data_len);
      _sectionInfo = *data;
      _sectionInfoBuffer.changeAccess(BINGO_PG_NOLOCK);
      
      _existStructures.reset(new BingoPgBufferCacheFp(offset + 1, _index, false));
   }
   int map_count = _sectionInfo.n_blocks_for_map;
   int fp_count = _sectionInfo.n_blocks_for_fp;
   int bin_count = _sectionInfo.n_blocks_for_bin;
   /*
    * Prepare cache arrays
    */
   _buffersMap.expand(map_count);
   _buffersFp.expand(fp_count);
   _buffersBin.expand(bin_count);
   /*
    * Prepare offset arrays
    */
   _offsetMap.expand(map_count);
   _offsetFp.expand(fp_count);
   _offsetBin.expand(bin_count);
   /*
    * Prepare for reading or writing all the data buffers
    */
   int block_offset = offset + SECTION_META_PAGES + SECTION_BITSNUMBER_PAGES;
   for (int i = 0; i < map_count; ++i) {
      _offsetMap[i] = block_offset;
      ++block_offset;
   }
   for (int i = 0; i < fp_count; ++i) {
      _offsetFp[i] = block_offset;
      ++block_offset;
   }
   for (int i = 0; i < bin_count; ++i) {
      _offsetBin[i] = block_offset;
      ++block_offset;
   }
   if (_idxStrategy != BingoPgIndex::READING_STRATEGY) {
      for (int i = 0; i < map_count; ++i) {
         getMapBufferCache(i);
      }
      for (int i = 0; i < fp_count; ++i) {
         getFpBufferCache(i);
      }
      for (int i = 0; i < bin_count; ++i) {
         getBinBufferCache(i);
      }
   }

}

BingoPgSection::~BingoPgSection() {
   /*
    * Write meta info
    */
   _sectionInfo.n_blocks_for_bin = _buffersBin.size();
   _sectionInfo.section_size = getPagesCount();
   if(_idxStrategy == BingoPgIndex::BUILDING_STRATEGY) {
      _sectionInfoBuffer.changeAccess(BINGO_PG_WRITE);
      _sectionInfoBuffer.formIndexTuple(&_sectionInfo, sizeof(_sectionInfo));
      _sectionInfoBuffer.changeAccess(BINGO_PG_NOLOCK);
   } else if(_idxStrategy == BingoPgIndex::UPDATING_STRATEGY){
      _sectionInfoBuffer.changeAccess(BINGO_PG_WRITE);
      int data_len;
      BingoSectionInfoData* data = (BingoSectionInfoData*)_sectionInfoBuffer.getIndexData(data_len);
      *data = _sectionInfo;
      _sectionInfoBuffer.changeAccess(BINGO_PG_NOLOCK);
   }
}

void BingoPgSection::clear() {
   _sectionInfo.n_blocks_for_bin = 0;
   _sectionInfo.n_blocks_for_fp = 0;
   _sectionInfo.n_blocks_for_map = 0;
   _sectionInfo.n_structures = 0;
   _sectionInfo.section_size = 0;
   _sectionInfo.last_cmf = -1;
   _sectionInfo.last_xyz = -1;
   _sectionInfo.has_removed = 0;
   _sectionInfoBuffer.clear();
   _existStructures.reset(0);
   _buffersMap.clear();
   _buffersFp.clear();
   _buffersBin.clear();
   _offsetBin.clear();
   _offsetFp.clear();
   _offsetMap.clear();
}

bool BingoPgSection::isExtended() {
   return _sectionInfo.n_structures < BINGO_MOLS_PER_SECTION;
}

void BingoPgSection::addStructure(BingoPgFpData& item_data) {
   int current_str = _sectionInfo.n_structures;
   elog(DEBUG1, "bingo: section: insert a structure %d", current_str);
   /*
    * Set fp bits
    */
   for (int idx = item_data.bitBegin(); idx != item_data.bitEnd(); idx = item_data.bitNext(idx)) {
      int bit_idx = item_data.getBit(idx);
      BingoPgBufferCacheFp& buffer_fp = getFpBufferCache(bit_idx);
      buffer_fp.setBit(current_str, true);
   }

   int map_buf_idx = current_str / BINGO_MOLS_PER_MAPBLOCK;
   int map_idx = current_str % BINGO_MOLS_PER_MAPBLOCK;
   /*
    * Set tid map
    */
   elog(DEBUG1, "bingo: section: set tid map: map buffer idx = %d offset = %d", map_buf_idx, map_idx);
   BingoPgBufferCacheMap& buffer_map = getMapBufferCache(map_buf_idx);
   buffer_map.setTidItem(map_idx, item_data.getTidItem());

   /*
    * Prepare and set cmf map
    */
   _setCmfData(item_data.getCmfBuf(), map_buf_idx, map_idx);
   /*
    * Prepare and set xyz map
    */
   _setXyzData(item_data.getXyzBuf(), map_buf_idx, map_idx);

   /*
    * Set bits number
    */
    _setBitsCountData(item_data.getBitsCount());
   /*
    * Set structure index
    */
   item_data.setStructureIdx(_sectionInfo.n_structures);

   /*
    * Set structure bit
    */
   _existStructures->setBit(current_str, true);

   /*
    * Increment structures number
    */
   ++_sectionInfo.n_structures;

}

int BingoPgSection::getPagesCount() const {
   return _buffersMap.size() + _buffersFp.size() + _buffersBin.size() + SECTION_META_PAGES + SECTION_BITSNUMBER_PAGES;
}

void BingoPgSection::getSectionStructures(BingoPgExternalBitset& section_bitset) {
   _existStructures->getCopy(section_bitset);
}

void BingoPgSection::removeStructure(int mol_idx) {
   _existStructures->setBit(mol_idx, false);
   _sectionInfo.has_removed = 1;
}

bool BingoPgSection::isStructureRemoved(int mol_idx) {
   if(_sectionInfo.has_removed == 0)
      return false;
   return (!_existStructures->getBit(mol_idx));
}

BingoPgBufferCacheFp& BingoPgSection::getFpBufferCache(int fp_idx) {
   BingoPgBufferCacheFp* elem = _buffersFp.at(fp_idx);
   if(elem == 0) {
      bool write = (_idxStrategy == BingoPgIndex::BUILDING_STRATEGY);
      int block_offset = _offsetFp[fp_idx];
      elem = new BingoPgBufferCacheFp(block_offset, _index, write);
      _buffersFp.set(fp_idx, elem);
   }
   return *elem;
}

BingoPgBufferCacheMap& BingoPgSection::getMapBufferCache(int map_idx) {
   BingoPgBufferCacheMap* elem = _buffersMap.at(map_idx);
   if(elem == 0) {
      bool write = (_idxStrategy == BingoPgIndex::BUILDING_STRATEGY);
      int block_offset = _offsetMap[map_idx];
      elem = new BingoPgBufferCacheMap(block_offset, _index, write);
      _buffersMap.set(map_idx, elem);
   }
   return *elem;
}

BingoPgBufferCacheBin& BingoPgSection::getBinBufferCache(int bin_idx) {
   return *_getBufferBin(bin_idx);
}


void BingoPgSection::readSectionBitsCount(indigo::Array<int>& bits_number) {
   bits_number.resize(_sectionInfo.n_structures);
   bits_number.zerofill();

   if(_bitsCountBuffers.size() == 0)
      _bitsCountBuffers.resize(SECTION_BITSNUMBER_PAGES);
   
   int data_len, str_idx;
   unsigned short* buffer_data;
   for (int buf_idx = 0; buf_idx < SECTION_BITSNUMBER_PAGES; ++buf_idx) {
      if(buf_idx * SECTION_BITS_PER_BLOCK >= _sectionInfo.n_structures)
         break;

      BingoPgBuffer& bits_buffer = _bitsCountBuffers[buf_idx];
      bits_buffer.readBuffer(_index, _offset + buf_idx + SECTION_META_PAGES, BINGO_PG_READ);
      buffer_data = (unsigned short*) bits_buffer.getIndexData(data_len);
      for (int page_str_idx = 0; page_str_idx < SECTION_BITS_PER_BLOCK; ++page_str_idx) {
         str_idx = buf_idx * SECTION_BITS_PER_BLOCK + page_str_idx;
         if (str_idx >= _sectionInfo.n_structures)
            break;
         bits_number[str_idx] = buffer_data[page_str_idx];
      }
      bits_buffer.changeAccess(BINGO_PG_NOLOCK);
   }

   
}

void BingoPgSection::_setCmfData(indigo::Array<char>& cmf_buf, int map_buf_idx, int map_idx) {
   /*
    * Set binary info
    */
   ItemPointerData cmf_item;
   _setBinData(cmf_buf, _sectionInfo.last_cmf, cmf_item);
   /*
    * Set mappings
    */
   BingoPgBufferCacheMap& buffer_map = getMapBufferCache(map_buf_idx);
   buffer_map.setCmfItem(map_idx, cmf_item);

   elog(DEBUG1, "bingo: section: set cmf map: buffer = %d, offset = %d", ItemPointerGetBlockNumber(&cmf_item), ItemPointerGetOffsetNumber(&cmf_item));

}

void BingoPgSection::_setXyzData(indigo::Array<char>& xyz_buf, int map_buf_idx, int map_idx) {
   /*
    * Set binary info
    */
   ItemPointerData xyz_item;
   _setBinData(xyz_buf, _sectionInfo.last_xyz, xyz_item);
   /*
    * Set mappings
    */
   BingoPgBufferCacheMap& buffer_map = getMapBufferCache(map_buf_idx);
   buffer_map.setXyzItem(map_idx, xyz_item);

   elog(DEBUG1, "bingo: section: set xyz map: buffer = %d, offset = %d", ItemPointerGetBlockNumber(&xyz_item), ItemPointerGetOffsetNumber(&xyz_item));
}

void BingoPgSection::_setBinData(indigo::Array<char>& buf, int& last_buf, ItemPointerData& item_data) {
   if(buf.size() == 0) {
      BINGO_PG_TRY {
         ItemPointerSet(&item_data, InvalidBlockNumber, 0);
      } BINGO_PG_HANDLE(throw Error("internal error: can not set block data: %s", message));
      return;
   }
   /*
    * Dynamic binary buffers
    */

   if(last_buf == -1) {
      int block_off = _offset + getPagesCount();
      _buffersBin.add(new BingoPgBufferCacheBin(block_off, _index, true));
      last_buf = _buffersBin.size() - 1;
      _offsetBin.push(block_off);
   }
   
   BingoPgBufferCacheBin* cache_bin = _getBufferBin(last_buf);
   /*
    * If not enough space for inserting a new structure - then create and new buffer
    */
   if(!cache_bin->isEnoughSpace(buf.sizeInBytes())) {
      int block_off = _offset + getPagesCount();
      _buffersBin.add(new BingoPgBufferCacheBin(block_off, _index, true));
      last_buf = _buffersBin.size() - 1;
      _offsetBin.push(block_off);
   }
   cache_bin = _getBufferBin(last_buf);

   /*
    * Get cmf offset for storing cmf mapping
    */
   unsigned short bin_offset = cache_bin->addBin(buf);

   /*
    * Set mappings
    */
   BINGO_PG_TRY {
      ItemPointerSet(&item_data, last_buf, bin_offset);
   } BINGO_PG_HANDLE(throw Error("internal error: can not set block data: %s", message));
}

void BingoPgSection::_setBitsCountData(unsigned short bits_count) {
   
   if(_bitsCountBuffers.size() == 0)
      _bitsCountBuffers.resize(SECTION_BITSNUMBER_PAGES);
   
   int data_len;
   int buf_idx = _sectionInfo.n_structures / SECTION_BITS_PER_BLOCK;
   int page_str_idx = _sectionInfo.n_structures % SECTION_BITS_PER_BLOCK;

   BingoPgBuffer& bits_buffer = _bitsCountBuffers[buf_idx];
   bits_buffer.readBuffer(_index, _offset + buf_idx + SECTION_META_PAGES, BINGO_PG_WRITE);
   unsigned short* buffer_data = (unsigned short*) bits_buffer.getIndexData(data_len);
   buffer_data[page_str_idx] = bits_count;
   bits_buffer.changeAccess(BINGO_PG_NOLOCK);
}



BingoPgBufferCacheBin* BingoPgSection::_getBufferBin(int idx) {
   BingoPgBufferCacheBin* elem = _buffersBin.at(idx);
   if(elem == 0) {
      bool write = (_idxStrategy == BingoPgIndex::BUILDING_STRATEGY);
      int block_offset = _offsetBin[idx];
      elem = new BingoPgBufferCacheBin(block_offset, _index, write);
      _buffersBin.set(idx, elem);
   }
   return elem;
}
