#include "bingo_pg_fix_pre.h"

extern "C" {
#include "postgres.h"
#include "fmgr.h"
#include "utils/relcache.h"
#include "storage/lock.h"
#include "access/heapam.h"
#include "storage/bufmgr.h"
#if PG_VERSION_NUM / 100 >= 903
#include "lib/stringinfo.h"
#include "access/xlog_internal.h"
#endif
}


#include "bingo_pg_fix_post.h"

#include "bingo_postgres.h"

#include "base_cpp/scanner.h"
#include "bingo_core_c.h"

#include "bingo_pg_common.h"
#include "pg_bingo_context.h"
#include "bingo_pg_config.h"
#include "bingo_pg_text.h"
#include "bingo_pg_buffer.h"
#include "bingo_pg_config.h"
#include "bingo_pg_cursor.h"
#include "bingo_pg_index.h"

extern "C"  {
BINGO_FUNCTION_EXPORT(bingo_markpos);

BINGO_FUNCTION_EXPORT(bingo_restrpos);

BINGO_FUNCTION_EXPORT(getindexstructurescount);

BINGO_FUNCTION_EXPORT(_get_structures_count);

BINGO_FUNCTION_EXPORT(_get_block_count);

BINGO_FUNCTION_EXPORT(_precache_database);

BINGO_FUNCTION_EXPORT(getversion);

BINGO_FUNCTION_EXPORT(filetotext);

BINGO_FUNCTION_EXPORT(filetoblob);

BINGO_FUNCTION_EXPORT(getname);

BINGO_FUNCTION_EXPORT(exportsdf);

BINGO_FUNCTION_EXPORT(exportrdf);

BINGO_FUNCTION_EXPORT(_reset_profiling_info);

BINGO_FUNCTION_EXPORT(_get_profiling_info);

BINGO_FUNCTION_EXPORT(_print_profiling_info);
}



using namespace indigo;

Datum getindexstructurescount(PG_FUNCTION_ARGS){
   Oid relOid = PG_GETARG_OID(0);

   int result = 0;
   Relation rel;

   rel = relation_open(relOid, AccessShareLock);
   PG_BINGO_BEGIN
   {
      BingoPgBuffer meta_buffer;
      meta_buffer.readBuffer(rel, BINGO_METAPAGE, BINGO_PG_READ);
      BingoMetaPage meta_page = BingoPageGetMeta(BufferGetPage(meta_buffer.getBuffer()));
      result = meta_page->n_molecules;
   }
   PG_BINGO_END

   relation_close(rel, AccessShareLock);

   PG_RETURN_INT32(result);
}



Datum _get_structures_count(PG_FUNCTION_ARGS){
   Oid relOid = PG_GETARG_OID(0);

   int result = 0;
   Relation rel;

   rel = relation_open(relOid, AccessShareLock);
   PG_BINGO_BEGIN
   {
      BingoPgBuffer meta_buffer;
      meta_buffer.readBuffer(rel, BINGO_METAPAGE, BINGO_PG_READ);
      BingoMetaPage meta_page = BingoPageGetMeta(BufferGetPage(meta_buffer.getBuffer()));
      result = meta_page->n_molecules;
   }
   PG_BINGO_END

   relation_close(rel, AccessShareLock);

   PG_RETURN_INT32(result);
}

Datum _get_block_count(PG_FUNCTION_ARGS){
   Oid relOid = PG_GETARG_OID(0);

   int result = 0;
   Relation rel;

   rel = relation_open(relOid, AccessShareLock);
   PG_BINGO_BEGIN
   {
      BingoPgBuffer meta_buffer;
      meta_buffer.readBuffer(rel, BINGO_METAPAGE, BINGO_PG_READ);
      BingoMetaPage meta_page = BingoPageGetMeta(BufferGetPage(meta_buffer.getBuffer()));
      result = meta_page->n_sections;
   }
   PG_BINGO_END

   relation_close(rel, AccessShareLock);

   PG_RETURN_INT32(result);
}

class CacheParams {
public:
   enum {
      SIZE_IN_BYTES,
      SIZE_IN_KB,
      SIZE_IN_MB,
      SIZE_IN_GB
   };
   
   int size_in;

   
   CacheParams(){
      size_in = SIZE_IN_MB;
   }
   void getSizeIn(Array<char>& out) {
      switch(size_in) {
      case SIZE_IN_BYTES:
         out.readString("B", true);
         break;
      case SIZE_IN_KB:
         out.readString("KB", true);
         break;
      case SIZE_IN_MB:
         out.readString("MB", true);
         break;
      case SIZE_IN_GB:
         out.readString("GB", true);
         break;
      default:
         break;
      }
   }
   qword getSize(qword size_b) {
      qword res = 0;
      switch(size_in) {
      case SIZE_IN_BYTES:
         res = size_b;
         break;
      case SIZE_IN_KB:
         res = size_b>>10;
         break;
      case SIZE_IN_MB:
         res = size_b>>20;
         break;
      case SIZE_IN_GB:
         res = size_b>>30;
         break;
      default:
         break;
      }
      if(res == 0 && size_b > 0)
         res = 1;
      return res;
   }
   void parseParameters(const char* params_str) {
   BufferScanner scanner(params_str);

   QS_DEF(Array<char>, buf_word);

   scanner.skipSpace();

   while (!scanner.isEOF()) {
      scanner.readWord(buf_word, 0);
      scanner.skipSpace();

      if(strcasecmp(buf_word.ptr(), "B")==0) {
         size_in = CacheParams::SIZE_IN_BYTES;
      } else if(strcasecmp(buf_word.ptr(), "KB")==0) {
         size_in = CacheParams::SIZE_IN_KB;
      } else if(strcasecmp(buf_word.ptr(), "MB")==0) {
         size_in = CacheParams::SIZE_IN_MB;
      } else if(strcasecmp(buf_word.ptr(), "GB")==0) {
         size_in = CacheParams::SIZE_IN_GB;
      } else {
         throw BingoPgError("unknown parameter: %s", buf_word.ptr());
      }

      if (scanner.isEOF())
         break;
      scanner.skipSpace();
   }
}
   ~CacheParams(){}
private:
   CacheParams(const CacheParams&);
};

Datum _precache_database(PG_FUNCTION_ARGS){
   Oid relOid = PG_GETARG_OID(0);
   Datum parameters_datum = PG_GETARG_DATUM(1);

   void* res = 0;
   Relation rel;
   /*
   *
   */

   rel = relation_open(relOid, AccessShareLock);
   PG_BINGO_BEGIN
   {
      BingoPgText parameters_text(parameters_datum);
      /*
      * Parse parameters
      */
      CacheParams params;
      params.parseParameters(parameters_text.getString());

      Array<char> tmp_buffer;
      Array<int> tmp_buffer2;
      Array<char> result_buf;
      ArrayOutput result(result_buf);
      ItemPointerData item_buf;

      int buf_size = 0;
      int processed_num = 0;

      qword total_cache_size = 0;
      qword total_index_size = 0;

      qword index_metapages_size = 0;
      qword block_metapages_size = 0;

      qword cmf_real_size = 0;
      qword cmf_cache_size = 0;
      qword sim_real_size = 0;
      qword sim_cache_size = 0;
      qword xyz_real_size = 0;
      qword xyz_cache_size = 0;
      qword fp_cache_size = 0;

      result.printfCR("{");
      
      BingoPgIndex bingo_index(rel);
      int section_idx = bingo_index.readBegin();
      int section_num = bingo_index.readEnd();

      result.printfCR("structures_number : %d,", bingo_index.getStructuresNumber());
      result.printfCR("blocks_number : %d,", bingo_index.getSectionNumber());
      
      params.getSizeIn(tmp_buffer);
      result.printfCR("size_in : '%s',", tmp_buffer.ptr());

      /*
      * Calc dictionary buf size
      */
      buf_size = bingo_index.getDictCount() * BingoPgBufferCacheBin::BUFFER_SIZE;
      result.printfCR("dict_cache_size : %d,", params.getSize(buf_size));
      total_cache_size += buf_size;
      
      total_index_size += BINGO_DICTIONARY_BLOCKS_NUM * BingoPgBufferCacheBin::BUFFER_SIZE;
      /*
       * Read config and offset blocks as meta
       */
      BingoPgConfig bingo_config;
      bingo_index.readConfigParameters(bingo_config);
      index_metapages_size += BINGO_METABLOCKS_NUM * BingoPgBufferCacheBin::BUFFER_SIZE;

      total_cache_size += index_metapages_size;
      total_index_size += index_metapages_size;

      buf_size = section_num / BINGO_SECTION_OFFSET_PER_BLOCK + 1;

      index_metapages_size += buf_size * BingoPgBufferCacheBin::BUFFER_SIZE;
      total_cache_size += buf_size * BingoPgBufferCacheBin::BUFFER_SIZE;
      
      total_index_size += BINGO_SECTION_OFFSET_BLOCKS_NUM * BingoPgBufferCacheBin::BUFFER_SIZE;

      BingoPgExternalBitset bitset_buf;
      int str_num = 0;

      for (; section_idx < bingo_index.readEnd(); section_idx = bingo_index.readNext(section_idx)) {

         str_num = bingo_index.getSectionStructuresNumber(section_idx);
         /*
          * Exist structures as section metapages
          */
         bingo_index.getSectionBitset(section_idx, bitset_buf);
         block_metapages_size += 2* BingoPgBufferCacheBin::BUFFER_SIZE;
        
         cmf_real_size = 0;
         for(int str_idx = 0; str_idx < str_num; ++str_idx) {
            /*
            * Cmf real size
            */
            bingo_index.readCmfItem(section_idx, str_idx, tmp_buffer);
            cmf_real_size += tmp_buffer.sizeInBytes();

            /*
            * Mapping buffers
            */
            bingo_index.readTidItem(section_idx, str_idx, &item_buf);
            
         }
         /*
         * Sim buffers
         */
         //bingo_index.getSectionBitsCount(section_idx, tmp_buffer2);
         //sim_real_size += tmp_buffer2.sizeInBytes();

         /*
         * Cmf buffer size
         */
         cmf_cache_size += ((cmf_real_size / BingoPgBufferCacheBin::MAX_SIZE) + 1) * BingoPgBufferCacheBin::BUFFER_SIZE;

         /*
         * Block mapping as metapages size
         */
         buf_size = str_num / BINGO_MOLS_PER_MAPBLOCK + 1;
         block_metapages_size += buf_size * BingoPgBufferCacheBin::BUFFER_SIZE;

         /*
         * Sim buffers
         */
         buf_size = str_num / BingoPgSection::SECTION_BITS_PER_BLOCK + 1;
         sim_cache_size += buf_size * BingoPgBufferCacheBin::BUFFER_SIZE;
         
         int fp_size = bingo_index.getSectionInfo(section_idx).n_blocks_for_fp;

         bitset_buf.clear();
         for(int fp_idx = 0; fp_idx < fp_size; ++fp_idx) {
            bingo_index.andWithBitset(section_idx, fp_idx, bitset_buf);
         }
         fp_cache_size += (fp_size * BingoPgBufferCacheBin::BUFFER_SIZE);
         
         /*
          * Total section size
          */
         
         buf_size = bingo_index.getSectionInfo(section_idx).section_size;
         total_index_size += buf_size * BingoPgBufferCacheBin::BUFFER_SIZE;

         elog(NOTICE, "%d blocks processed", section_idx + 1);
      }
      total_cache_size += sim_cache_size;
      total_cache_size += fp_cache_size;
      total_cache_size += block_metapages_size;
      total_cache_size += cmf_cache_size;
      
      result.printfCR("index_metapages_size : %d,", params.getSize(index_metapages_size));
      result.printfCR("block_metapages_size : %d,", params.getSize(block_metapages_size));
      result.printfCR("cmf_cache_size : %d,", params.getSize(cmf_cache_size));
      result.printfCR("sim_cache_size : %d,", params.getSize(sim_cache_size));
      result.printfCR("fp_cache_size : %d,", params.getSize(fp_cache_size));
      result.printfCR("total_cache_size : %d", params.getSize(total_cache_size));
      result.printfCR("total_index_size : %d", params.getSize(total_index_size));
      result.printf("}");
      result_buf.push(0);
      elog(NOTICE, "%s", result_buf.ptr());
      BingoPgText res_text;
      res_text.initFromString(result_buf.ptr());
      res = res_text.release();
   }
   PG_BINGO_END

   relation_close(rel, AccessShareLock);

   if(res == 0)
      PG_RETURN_NULL();
   
   PG_RETURN_TEXT_P(res);
}





//Datum bingo_test(PG_FUNCTION_ARGS) {
//   elog(NOTICE, "start test function 3");
//   PG_RETURN_VOID();
//}
//
//Datum bingo_test_tid(PG_FUNCTION_ARGS) {
//   elog(NOTICE, "start test function tid");
//
//   ItemPointer pp = (ItemPointer) palloc0(sizeof(ItemPointerData));
//
//   ItemPointerSet(pp, 1, 2);
//
//   PG_RETURN_POINTER(pp);
//}
//
//static Oid getFunc(const char* name, Array<Oid>& types) {
//   Array<char> fname;
//   fname.readString(name, true);
//   Value* func_name = makeString(fname.ptr());
//
//   List* func_list = list_make1(func_name);
//   Oid func_oid = LookupFuncName(func_list, types.size(), types.ptr(), false);
//
//   if(func_oid == InvalidOid)
//      elog(ERROR, "can not find the function %s", name);
//
//   list_free(func_list);
//   return func_oid;
//}

//Datum bingo_test_select(PG_FUNCTION_ARGS) {
//   elog(INFO, "start test select");
//
//   Array<Oid> func_type;
//   func_type.push(TEXTOID);
//
//   Oid func_begin_oid = getFunc("bingo_test_cur_begin", func_type);
//
//   FmgrInfo f_begin_info;
//   fmgr_info(func_begin_oid, &f_begin_info);
//
//   func_type.clear();
//   func_type.push(REFCURSOROID);
//
//   Oid func_next_oid = getFunc("bingo_test_cur_next", func_type);
//
//   FmgrInfo f_next_info;
//   fmgr_info(func_next_oid, &f_next_info);
//
//   elog(INFO, "func = %d", func_begin_oid);
//
//   BingoPgText test_select;
//   test_select.initFromString("btest_shadow");
//
//   Datum cursor_ref = FunctionCall1(&f_begin_info, PointerGetDatum(test_select.ptr()));
//
//   BingoPgText res_text(cursor_ref);
//   elog(INFO, "res text = %s", res_text.getString());
//
//
//   Datum record;
//   ItemPointer tup;
//   for (int i = 0; i < 5; ++i) {
//      record = FunctionCall1(&f_next_info, cursor_ref);
//      if(record == 0) {
//         elog(INFO, "Rec is null");
//         continue;
//      }
//      tup = (ItemPointer) DatumGetPointer(record);
//      elog(INFO, "block = %d off = %d", ItemPointerGetBlockNumber(tup), ItemPointerGetOffsetNumber(tup));
//   }
//
//   PG_RETURN_VOID();
//}


/*
 * Save current scan position
 */
Datum
bingo_markpos(PG_FUNCTION_ARGS) {
   elog(ERROR, "bingo does not support mark/restore");
   PG_RETURN_VOID();
}

/*
 * Restore scan to last saved position
 */
Datum
bingo_restrpos(PG_FUNCTION_ARGS) {
   elog(ERROR, "bingo does not support mark/restore");
   PG_RETURN_VOID();
}

void
bingo_redo(XLogRecPtr lsn, XLogRecord *record) {
   elog(PANIC, "bingo_redo: unimplemented");
}

void
bingo_desc(StringInfo buf, uint8 xl_info, char *rec) {
}

Datum getversion(PG_FUNCTION_ARGS) {
   BingoPgText result_text;
   result_text.initFromString(bingoGetVersion());

   PG_RETURN_TEXT_P(result_text.release());
}

Datum filetotext(PG_FUNCTION_ARGS) {
   Datum file_name_datum = PG_GETARG_DATUM(0);

   void* result = 0;
   PG_BINGO_BEGIN
   {
      BingoPgText fname_text(file_name_datum);
      BingoPgText result_text;

      QS_DEF(Array<char>, buffer);
      buffer.clear();
      /*
       * Read file
       */
      FileScanner f_scanner(fname_text.getString());
      f_scanner.readAll(buffer);

      result_text.initFromArray(buffer);

      result = result_text.release();
   }
   PG_BINGO_END

   if(result == 0)
      PG_RETURN_NULL();
   
   PG_RETURN_TEXT_P(result);
}

Datum filetoblob(PG_FUNCTION_ARGS) {
   Datum file_name_datum = PG_GETARG_DATUM(0);
   void* result = 0;
   PG_BINGO_BEGIN
   {
      BingoPgText fname_text(file_name_datum);
      BingoPgText result_text;
      QS_DEF(Array<char>, buffer);
      buffer.clear();
      /*
       * Read file
       */
      FileScanner f_scanner(fname_text.getString());
      f_scanner.readAll(buffer);

      result_text.initFromArray(buffer);
      
      result = result_text.release();

   }
   PG_BINGO_END

   if(result == 0)
      PG_RETURN_NULL();
   
   PG_RETURN_BYTEA_P(result);
}

Datum getname(PG_FUNCTION_ARGS) {
   Datum target_datum = PG_GETARG_DATUM(0);
   void* result = 0;
   PG_BINGO_BEGIN
   {
      BingoPgCommon::BingoSessionHandler bingo_handler(fcinfo->flinfo->fn_oid);
      bingo_handler.setFunctionName("getname");

      BingoPgText mol_text(target_datum);
      int buf_size;
      const char* target_buf = mol_text.getText(buf_size);

      const char* bingo_result = bingoGetNameCore(target_buf, buf_size);
      if(bingo_result == 0) {
         CORE_HANDLE_WARNING(0, 1, "bingo.getname", bingoGetError());
         PG_RETURN_NULL();
      }
      
      BingoPgText result_text;
      result_text.initFromString(bingo_result);
      result = result_text.release();
   }
   PG_BINGO_END

   if (result == 0)
      PG_RETURN_NULL();

   PG_RETURN_TEXT_P(result);
}

static void _parseQueryFieldList(const char* fields_str, RedBlackStringMap<int, false >& field_list) {
   BufferScanner scanner(fields_str);

   QS_DEF(Array<char>, buf_word);

   scanner.skipSpace();
   int column_idx = field_list.size();

   while (!scanner.isEOF()) {
      scanner.readWord(buf_word, " ,");
      scanner.skipSpace();

      if (field_list.find(buf_word.ptr()))
         throw BingoPgError("parseQueryFieldList(): key %s is already presented in the query list", buf_word.ptr());

      ++column_idx;
      field_list.insert(buf_word.ptr(), column_idx);

      if (scanner.isEOF())
         break;

      if (scanner.readChar() != ',')
         throw BingoPgError("parseQueryFieldList(): comma expected");

      scanner.skipSpace();
   }
}

static void checkExportNull(Datum text_datum, const char* message, BingoPgText& text) {
   if(text_datum == 0)
      throw BingoPgError("can not export structures: %s is empty", message);
   text.init(text_datum);
   int text_size = 0;
   text.getText(text_size);
   if(text_size == 0)
      throw BingoPgError("can not export structures: %s is empty", message);
}
static void checkExportEmpty(Datum text_datum, BingoPgText& text) {
   if(text_datum == 0)
      text.initFromString("");
   else
      text.init(text_datum);
}

static int _initializeColumnQuery(Datum table_datum, Datum column_datum, Datum other_column_datum, Array<char>& query_str, RedBlackStringMap<int, false >& field_list) {
   BingoPgText tablename_text;
   BingoPgText column_text;
   BingoPgText other_column_text;

   checkExportNull(table_datum, "table name", tablename_text);
   checkExportNull(column_datum, "column name", column_text);
   checkExportEmpty(other_column_datum, other_column_text);

   field_list.clear();
   
   field_list.insert(column_text.getString(), 0);
   int data_key = field_list.begin();

   ArrayOutput query_out(query_str);
   query_out.printf("SELECT %s", column_text.getString());
   
   if(other_column_datum != 0 ) {
      const char* columns_list = other_column_text.getString();
      if(strcmp(columns_list, "") !=0) {
         _parseQueryFieldList(columns_list, field_list);
         query_out.printf(", %s", columns_list);
      }
   }

   query_out.printf(" FROM %s", tablename_text.getString());
   query_out.writeChar(0);
   
   return data_key;
}

//class BingoExportSdfHandler : public BingoPgCommon::BingoSessionHandler {
//public:
//   BingoExportSdfHandler(unsigned int func_id, const char* fname):BingoSessionHandler(func_id, true) {
//      setFunctionName("exportSDF");
//      bingoSDFExportOpen(fname);
////      SPI_connect();
//   }
//   virtual ~BingoImportSdfHandler() {
////      SPI_finish();
//      bingoSDFExportClose();
//   }
//
//private:
//   BingoExportSdfHandler(const BingoExportSdfHandler&); //no implicit copy
//};

Datum exportsdf(PG_FUNCTION_ARGS) {
   Datum table_datum = PG_GETARG_DATUM(0);
   Datum column_datum = PG_GETARG_DATUM(1);
   Datum other_columns_datum = PG_GETARG_DATUM(2);
   Datum file_name_datum = PG_GETARG_DATUM(3);

   PG_BINGO_BEGIN
   {
      QS_DEF(Array<char>, query_str);
      RedBlackStringMap<int, false > field_list;

      BingoPgText fname_text;
      checkExportNull(file_name_datum, "file name", fname_text);
      FileOutput file_output(fname_text.getString());

      int data_key = _initializeColumnQuery(table_datum, column_datum, other_columns_datum, query_str, field_list);

      BingoPgCursor table_cursor(query_str.ptr());
      BingoPgText buf_text;

      while (table_cursor.next()) {
         table_cursor.getText(1, buf_text);
         file_output.writeStringCR(buf_text.getString());
         
         for (int k = field_list.begin(); k != field_list.end(); k = field_list.next(k)) {
            if(data_key == k)
               continue;
            
            int col_idx = field_list.value(k);
            const char* col_name = field_list.key(k);
            table_cursor.getText(col_idx, buf_text);
            file_output.printf(">  <%s>\n", col_name);
            file_output.printf("%s\n\n", buf_text.getString());
         }
         file_output.printf("\n$$$$\n");
      }
   }
   PG_BINGO_END

   PG_RETURN_VOID();
}

Datum exportrdf(PG_FUNCTION_ARGS) {
   Datum table_datum = PG_GETARG_DATUM(0);
   Datum column_datum = PG_GETARG_DATUM(1);
   Datum other_columns_datum = PG_GETARG_DATUM(2);
   Datum file_name_datum = PG_GETARG_DATUM(3);

   PG_BINGO_BEGIN
   {
      QS_DEF(Array<char>, query_str);
      RedBlackStringMap<int, false > field_list;

      BingoPgText fname_text;
      checkExportNull(file_name_datum, "file name", fname_text);
      FileOutput file_output(fname_text.getString());

      int data_key = _initializeColumnQuery(table_datum, column_datum, other_columns_datum, query_str, field_list);

      BingoPgCursor table_cursor(query_str.ptr());
      BingoPgText buf_text;

      file_output.printf("$RDFILE 1\n");

      int str_idx = 0;
      while (table_cursor.next()) {
         ++str_idx;
         table_cursor.getText(1, buf_text);
         file_output.printf("$MFMT $MIREG %d\n", str_idx);
         file_output.writeStringCR(buf_text.getString());

         for (int k = field_list.begin(); k != field_list.end(); k = field_list.next(k)) {
            if(data_key == k)
               continue;

            int col_idx = field_list.value(k);
            const char* col_name = field_list.key(k);
            table_cursor.getText(col_idx, buf_text);
            file_output.printf("$DTYPE %s\n", col_name);
            file_output.printf("$DATUM %s\n", buf_text.getString());
         }
      }
   }
   PG_BINGO_END

   PG_RETURN_VOID();
}

Datum _reset_profiling_info(PG_FUNCTION_ARGS) {
   bingoProfilingReset(true);
   PG_RETURN_VOID();
}

Datum _get_profiling_info(PG_FUNCTION_ARGS) {
   char* result = 0;
   PG_BINGO_BEGIN
   {
      const char* bingo_result = bingoProfilingGetStatistics(true);
      result = BingoPgCommon::releaseString(bingo_result);
   }
   PG_BINGO_END

   if (result == 0)
      PG_RETURN_NULL();

   PG_RETURN_CSTRING(result);
}

Datum _print_profiling_info(PG_FUNCTION_ARGS) {
   PG_BINGO_BEGIN
   {
      const char* bingo_result = bingoProfilingGetStatistics(true);
      elog(NOTICE, "\n%s", bingo_result);
   }
   PG_BINGO_END
   PG_RETURN_VOID();
}