extern "C" {
#include "postgres.h"
#include "fmgr.h"
}
#ifdef qsort
#undef qsort
#endif

#include "mango_pg_build_engine.h"

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

#include "mango_pg_search_engine.h"
#include "bingo_pg_text.h"
#include "bingo_pg_common.h"
#include "bingo_pg_config.h"
#include "bingo_pg_index.h"
#include <float.h>


using namespace indigo;

MangoPgBuildEngine::MangoPgBuildEngine(BingoPgConfig& bingo_config, const char* rel_name):
BingoPgBuildEngine(),
_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);

   elog(DEBUG1, "bingo: mango build: start building '%s'", _relName.ptr());
}

MangoPgBuildEngine::~MangoPgBuildEngine() {
   elog(DEBUG1, "bingo: mango build: finish building '%s'", _relName.ptr());
   _setBingoContext();
   bingoIndexEnd();
}

bool MangoPgBuildEngine::processStructure(StructCache& struct_cache) {

   _setBingoContext();
   int bingo_res;

   BingoPgText& struct_text = struct_cache.text.ref();
   ItemPointer item_ptr = &struct_cache.ptr;
   int block_number = ItemPointerGetBlockNumber(item_ptr);
   int offset_number = ItemPointerGetOffsetNumber(item_ptr);

   int struct_size;
   const char* struct_ptr = struct_text.getText(struct_size);
   /*
    * Set target data
    */
   bingoSetIndexRecordData(0, struct_ptr, struct_size);
   /*
    * Process target
    */
   bingo_res = mangoIndexProcessSingleRecord();

   CORE_HANDLE_WARNING_TID(bingo_res, 1, "molecule build engine: error while processing record", block_number, offset_number, bingoGetWarning());
   if(bingo_res < 1)
      return false;

   AutoPtr<MangoPgFpData> fp_data(new MangoPgFpData());
   if(_readPreparedInfo(0, fp_data.ref(), getFpSize())) {
      struct_cache.data.reset(fp_data.release());
      struct_cache.data->setTidItem(item_ptr);
   } else {
      elog(WARNING, "molecule build engine: internal error while processing record with ctid='(%d,%d)'::tid: see at the previous warning", block_number, offset_number);
      return false;
   }
   return true;
}

void MangoPgBuildEngine::processStructures(ObjArray<StructCache>& struct_caches) {
   _setBingoContext();
   int bingo_res;

   _currentCache = 0;
   _structCaches = &struct_caches;
   _fpSize = getFpSize();
   
   /*
    * Process target
    */
   bingo_res = bingoIndexProcess(false, _getNextRecordCb, _processResultCb, _processErrorCb, this);
   CORE_HANDLE_ERROR(bingo_res, 0, "molecule build engine: error while processing records", bingoGetError());
   _setBingoContext();
}

void MangoPgBuildEngine::insertShadowInfo(BingoPgFpData& item_data) {
   MangoPgFpData& data = (MangoPgFpData&)item_data;

   const char* shadow_rel_name = _shadowRelName.ptr();
   const char* shadow_hash_name = _shadowHashRelName.ptr();
   ItemPointerData* tid_ptr = &data.getTidItem();

   BingoPgCommon::executeQuery("INSERT INTO %s(b_id,tid_map,mass,fragments,gross,cnt_C,cnt_N,cnt_O,cnt_P,cnt_S,cnt_H) VALUES ("
   "'(%d, %d)'::tid, '(%d, %d)'::tid, %f, %d, %s)",
           shadow_rel_name,
           data.getSectionIdx(), data.getStructureIdx(),
           ItemPointerGetBlockNumber(tid_ptr), ItemPointerGetOffsetNumber(tid_ptr),
           data.getMass(),
           data.getFragmentsCount(),
           data.getGrossStr());

   const RedBlackMap<dword, int>& hashes = data.getHashes();
   for (int h_idx = hashes.begin(); h_idx != hashes.end(); h_idx = hashes.next(h_idx)) {
      BingoPgCommon::executeQuery("INSERT INTO %s(b_id, ex_hash, f_count) VALUES ('(%d, %d)'::tid, %d, %d)",
              shadow_hash_name,
              data.getSectionIdx(), data.getStructureIdx(),
              hashes.key(h_idx), hashes.value(h_idx));
   }


}

int MangoPgBuildEngine::getFpSize() {
   int result;
   _setBingoContext();

   bingoGetConfigInt("fp-size-bytes", &result);

   return result * 8;
}


void MangoPgBuildEngine::prepareShadowInfo(const char* schema_name, const char* index_schema) {
   /*
    * Create auxialiry tables
    */
   const char* rel_name = _relName.ptr();
   const char* shadow_rel_name = _shadowRelName.ptr();
   const char* shadow_hash_name = _shadowHashRelName.ptr();

   /*
    * Drop table if exists (in case of truncate index)
    */
   if(BingoPgCommon::tableExists(index_schema, shadow_rel_name)) {
      BingoPgCommon::dropDependency(schema_name, index_schema, shadow_rel_name);
      BingoPgCommon::executeQuery("DROP TABLE %s.%s", index_schema, shadow_rel_name);
   }

   BingoPgCommon::executeQuery("CREATE TABLE %s.%s ("
   "b_id tid,"
   "tid_map tid,"
   "mass real,"
   "fragments integer,"
   "gross text,"
   "cnt_C integer,"
   "cnt_N integer,"
   "cnt_O integer,"
   "cnt_P integer,"
   "cnt_S integer,"
   "cnt_H integer,"
   "xyz bytea)", index_schema, shadow_rel_name);

   if(BingoPgCommon::tableExists(index_schema, shadow_hash_name)) {
      BingoPgCommon::dropDependency(schema_name, index_schema, shadow_hash_name);
      BingoPgCommon::executeQuery("DROP TABLE %s.%s", index_schema, shadow_hash_name);
   }
   BingoPgCommon::executeQuery("CREATE TABLE %s.%s (b_id tid, ex_hash integer, f_count integer)", index_schema, shadow_hash_name);
   /*
    * Create dependency for new tables
    */
   BingoPgCommon::createDependency(schema_name, index_schema, shadow_rel_name, rel_name);
   BingoPgCommon::createDependency(schema_name, index_schema, shadow_hash_name, rel_name);
}

void MangoPgBuildEngine::finishShadowProcessing() {
   /*
    * Create shadow indexes
    */
   const char* shadow_rel_name = _shadowRelName.ptr();
   const char* shadow_hash_rel_name = _shadowHashRelName.ptr();

   BingoPgCommon::executeQuery("CREATE INDEX %s_mass_idx ON %s using hash(mass)", shadow_rel_name,  shadow_rel_name);
   BingoPgCommon::executeQuery("CREATE INDEX %s_cmf_idx ON %s(b_id)", shadow_rel_name,  shadow_rel_name);
   BingoPgCommon::executeQuery("CREATE INDEX %s_hash_idx ON %s using hash(ex_hash)", shadow_hash_rel_name,  shadow_hash_rel_name);

}

void MangoPgBuildEngine::_processResultCb (void *context) {
   MangoPgBuildEngine* engine = (MangoPgBuildEngine*)context;
   ObjArray<StructCache>& struct_caches = *(engine->_structCaches);
   int cache_idx = -1;
   AutoPtr<MangoPgFpData> fp_data(new MangoPgFpData());
   /*
    * Prepare info
    */
   if(_readPreparedInfo(&cache_idx, fp_data.ref(), engine->_fpSize)) {
      StructCache& struct_cache = struct_caches[cache_idx];
      struct_cache.data.reset(fp_data.release());
      struct_cache.data->setTidItem(&struct_cache.ptr);
   } else {
      if(cache_idx != -1) {
         ItemPointer item_ptr = &(struct_caches[cache_idx].ptr);
         int block_number = ItemPointerGetBlockNumber(item_ptr);
         int offset_number = ItemPointerGetOffsetNumber(item_ptr);
         elog(WARNING, "molecule build engine: internal error while processing record with ctid='(%d,%d)'::tid: see at the previous warning", block_number, offset_number);
      } else {
         elog(WARNING, "molecule build engine: internal error while processing record: see at the previous warning");
      }
   }

}

bool MangoPgBuildEngine::_readPreparedInfo(int* id, MangoPgFpData& data, int fp_size) {
   int bingo_res;
   const char* cmf_buf;
   int cmf_len;
   const char*xyz_buf;
   int xyz_len;
   const char*gross_str;
   const char*counter_elements_str;
   const char*fp_buf;
   int fp_len;
   const char *fp_sim_str;
   float mass;
   int sim_fp_bits_count;
   /*
    * Get prepared data
    */
   bingo_res = mangoIndexReadPreparedMolecule(id, &cmf_buf, &cmf_len, &xyz_buf, &xyz_len,
                 &gross_str, &counter_elements_str, &fp_buf, &fp_len,
                 &fp_sim_str, &mass, &sim_fp_bits_count);

   CORE_HANDLE_WARNING(bingo_res, 1, "molecule build engine: error while prepare record", bingoGetError());
   if(bingo_res < 1)
      return false;

   /*
    * Set gross formula
    */
   data.setGrossStr(gross_str, counter_elements_str);

   /*
    * Set hash information
    */
   dword ex_hash;
   int ex_hash_count;
   bingo_res = mangoGetHash(1, -1, &ex_hash_count, &ex_hash);

   CORE_HANDLE_WARNING(bingo_res, 1, "molecule build engine: error while calculating hash for a record", bingoGetError());
   if(bingo_res < 1)
      return false;

   int target_fragments = 0;
   for (int comp_idx = 0; comp_idx < ex_hash_count; ++comp_idx) {
      int comp_count;
      bingo_res = mangoGetHash(1, comp_idx, &comp_count, &ex_hash);

      CORE_HANDLE_WARNING(bingo_res, 1, "molecule build engine: error while calculating hash for a record", bingoGetError());
      if(bingo_res < 1)
         return false;
      data.insertHash(ex_hash, comp_count);
      target_fragments += comp_count;
   }
   data.setFragmentsCount(target_fragments);

   /*
    * Set common info
    */
   data.setCmf(cmf_buf, cmf_len);
   data.setXyz(xyz_buf, xyz_len);
   data.setFingerPrints(fp_buf, fp_size);
   data.setMass(mass);
   data.setBitsCount(sim_fp_bits_count);
   return true;
}



