extern "C" {
#include "postgres.h"
#include "fmgr.h"
#include "access/genam.h"
#include "access/relscan.h"
#include "utils/typcache.h"
}

#ifdef qsort
#undef qsort
#endif
#ifdef printf
#undef printf
#endif

#include "ringo_pg_search_engine.h"

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

#include "bingo_pg_text.h"
#include "bingo_pg_common.h"
#include "bingo_pg_config.h"
#include "bingo_pg_index.h"


using namespace indigo;

IMPL_ERROR(RingoPgSearchEngine, "reaction search engine");

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

RingoPgSearchEngine::~RingoPgSearchEngine() {
   bingoIndexEnd();
}

bool RingoPgSearchEngine::matchTarget(int section_idx, int structure_idx) {
   bool result = false;
   int bingo_res;
   QS_DEF(Array<char>, react_buf);
   react_buf.clear();

   _bufferIndexPtr->readCmfItem(section_idx, structure_idx, react_buf);
   bingo_res = ringoMatchTargetBinary(react_buf.ptr(), react_buf.sizeInBytes());
   CORE_HANDLE_ERROR_TID(bingo_res, -1,  "reaction search engine: error while matching target", section_idx, structure_idx,bingoGetError());
   CORE_RETURN_WARNING_TID(bingo_res, 0, "reaction search engine: error while matching target", section_idx, structure_idx, bingoGetWarning());

   result =  (bingo_res == 1);
   
   return result;
}

void RingoPgSearchEngine::prepareQuerySearch(BingoPgIndex& bingo_idx, PG_OBJECT scan_desc_ptr) {

   IndexScanDesc scan_desc = (IndexScanDesc) scan_desc_ptr;

   if(scan_desc->numberOfKeys >= 1 && scan_desc->numberOfKeys <= 2) {
      _searchType = scan_desc->keyData[0].sk_strategy;
   } else {
      throw Error("unsupported number of scan keys %d", scan_desc->numberOfKeys);
   }
   _queryFpData.reset(new RingoPgFpData());

   _setBingoContext();
   BingoPgSearchEngine::prepareQuerySearch(bingo_idx, scan_desc);

   switch(_searchType) {
      case BingoPgCommon::REACT_SUB:
         _prepareSubSearch(scan_desc);
         break;
      case BingoPgCommon::REACT_EXACT:
         _prepareExactSearch(scan_desc);
         break;
      case BingoPgCommon::REACT_SMARTS:
         _prepareSmartsSearch(scan_desc);
         break;
      default:
         throw Error("unsupported search type");
   }

}

bool RingoPgSearchEngine::searchNext(PG_OBJECT result_ptr) {

   bool result = false;
   _setBingoContext();
   
   if (_searchType == BingoPgCommon::REACT_EXACT) {
      result = _searchNextCursor(result_ptr);
   } else if(_searchType == BingoPgCommon::REACT_SUB || _searchType == BingoPgCommon::REACT_SMARTS) {
      result = _searchNextSub(result_ptr);
   }

   return result;
}

void RingoPgSearchEngine::_errorHandler(const char* message, void*) {
   throw Error("Error while searching a reaction: %s", message);
}

void RingoPgSearchEngine::_prepareExactQueryStrings(indigo::Array<char>& what_clause_str, indigo::Array<char>& from_clause_str, indigo::Array<char>& where_clause_str) {
   ArrayOutput what_clause(what_clause_str);
   ArrayOutput from_clause(from_clause_str);
   ArrayOutput where_clause(where_clause_str);

   what_clause.printf("b_id");
   from_clause.printf("%s", _shadowRelName.ptr());

   dword ex_hash;
   int bingo_res = ringoGetHash(0, &ex_hash);
   CORE_HANDLE_ERROR(bingo_res, 1, "reaction search engine: error while getting hash", bingoGetError());

   where_clause.printf("ex_hash=%d", ex_hash);

   what_clause_str.push(0);
   from_clause_str.push(0);
   where_clause_str.push(0);

}

void RingoPgSearchEngine::_prepareSubSearch(PG_OBJECT scan_desc_ptr) {
   IndexScanDesc scan_desc = (IndexScanDesc) scan_desc_ptr;
   QS_DEF(Array<char>, search_type);
   int bingo_res;
   Array<char> search_query;
   Array<char> search_options;
   BingoPgFpData& data = _queryFpData.ref();

   BingoPgCommon::getSearchTypeString(_searchType, search_type, false);

   _getScanQueries(scan_desc->keyData[0].sk_argument, search_query, search_options);

   /*
    * Get block parameters and split search options
    */
   _getBlockParameters(search_options);

   /*
    * Set up matching parameters
    */
   bingo_res = ringoSetupMatch(search_type.ptr(), search_query.ptr(), search_options.ptr());
   CORE_HANDLE_ERROR(bingo_res, 1, "reaction search engine: can not set rsub search context", bingoGetError());

   const char* fingerprint_buf;
   int fp_len;

   bingo_res = ringoGetQueryFingerprint(&fingerprint_buf, &fp_len);
   CORE_HANDLE_ERROR(bingo_res, 1, "reaction search engine: can not get query fingerprint", bingoGetError());

   int size_bits = fp_len * 8;
   data.setFingerPrints(fingerprint_buf, size_bits);

}

void RingoPgSearchEngine::_prepareExactSearch(PG_OBJECT scan_desc_ptr) {
   IndexScanDesc scan_desc = (IndexScanDesc) scan_desc_ptr;
   QS_DEF(Array<char>, what_clause);
   QS_DEF(Array<char>, from_clause);
   QS_DEF(Array<char>, where_clause);
   QS_DEF(Array<char>, search_type);
   Array<char> search_query;
   Array<char> search_options;
   int bingo_res;

   BingoPgCommon::getSearchTypeString(_searchType, search_type, false);

   _getScanQueries(scan_desc->keyData[0].sk_argument, search_query, search_options);

   /*
    * Set up matching parameters
    */
   bingo_res = ringoSetupMatch(search_type.ptr(), search_query.ptr(), search_options.ptr());
   CORE_HANDLE_ERROR(bingo_res, 1, "reaction search engine: can not set rexact search context", bingoGetError());

   _prepareExactQueryStrings(what_clause, from_clause, where_clause);

   _searchCursor.reset(new BingoPgCursor("SELECT %s FROM %s WHERE %s",what_clause.ptr(), from_clause.ptr(), where_clause.ptr()));
}

void RingoPgSearchEngine::_prepareSmartsSearch(PG_OBJECT scan_desc_ptr) {
   _prepareSubSearch(scan_desc_ptr);
}

void RingoPgSearchEngine::_getScanQueries(uintptr_t arg_datum, indigo::Array<char>& str1_out, indigo::Array<char>& str2_out) {
   /*
    * Get query info
    */
   BINGO_PG_TRY
   {
      HeapTupleHeader query_data = DatumGetHeapTupleHeader(arg_datum);
      Oid tupType = HeapTupleHeaderGetTypeId(query_data);
      int32 tupTypmod = HeapTupleHeaderGetTypMod(query_data);
      TupleDesc tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
      int ncolumns = tupdesc->natts;

      if (ncolumns != 2)
         throw Error("internal error: expecting two columns in query but was %d", ncolumns);

      HeapTupleData tuple;
      /*
       * Build a temporary HeapTuple control structure
       */
      tuple.t_len = HeapTupleHeaderGetDatumLength(query_data);
      ItemPointerSetInvalid(&(tuple.t_self));
      tuple.t_tableOid = InvalidOid;
      tuple.t_data = query_data;

      Datum *values = (Datum *) palloc(ncolumns * sizeof (Datum));
      bool *nulls = (bool *) palloc(ncolumns * sizeof (bool));

      /*
       *  Break down the tuple into fields
       */
      heap_deform_tuple(&tuple, tupdesc, values, nulls);

      /*
       * Query tuple consist of query and options
       */
      BingoPgText str1, str2;
      str1.init(values[0]);
      str2.init(values[1]);

      str1_out.readString(str1.getString(), true);
      str2_out.readString(str2.getString(), true);

      pfree(values);
      pfree(nulls);
      ReleaseTupleDesc(tupdesc);
   }
   BINGO_PG_HANDLE(throw Error("internal error: can not get scan query: %s", message));
}

