/****************************************************************************
 * Copyright (C) 2009-2015 EPAM Systems
 * 
 * This file is part of Indigo toolkit.
 * 
 * This file may be distributed and/or modified under the terms of the
 * GNU General Public License version 3 as published by the Free Software
 * Foundation and appearing in the file LICENSE.GPL included in the
 * packaging of this file.
 * 
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 ***************************************************************************/

#include "bingo_core_c_internal.h"

#include "base_cpp/profiling.h"
#include "gzip/gzip_scanner.h"

using namespace indigo::bingo_core;

BingoCore::BingoCore ()
{
   bingo_context = 0;
   mango_context = 0;
   ringo_context = 0;
   reset();
}

void BingoCore::reset ()
{
   if (bingo_context != 0)
   {
      int id = bingo_context->id;
      bingo_context->reset();
   }

   mango_search_type = _UNDEF;
   mango_search_type_non = false;
   bingo_context = 0;
   mango_context = 0;
   ringo_context = 0;
   error_handler = 0;
   error_handler_context = 0;
   skip_calculate_fp = false;
   smiles_scanner = 0;

   // Clear warning and error message
   warning.clear();
   warning.push(0);
   error.clear();
   error.push(0);
}

TL_DECL(BingoCore, self);

BingoCore& BingoCore::getInstance ()
{
   TL_GET(BingoCore, self);
   return self;
}

CEXPORT const char * bingoGetVersion ()
{
   return BINGO_VERSION;  
}                    

CEXPORT const char * bingoGetError ()
{
   BINGO_BEGIN
   {
      return self.error.ptr();
   }
   BINGO_END("", "")
}

CEXPORT const char * bingoGetWarning ()
{
   BINGO_BEGIN
   {
      return self.warning.ptr();
   }
   BINGO_END("", "")
}

CEXPORT qword bingoAllocateSessionID ()
{
   qword id = TL_ALLOC_SESSION_ID();

   TL_GET_BY_ID(BingoCore, self, id);
   self.reset();

   return id;
}

CEXPORT void bingoReleaseSessionID (qword session_id)
{
   return TL_RELEASE_SESSION_ID(session_id);
}

CEXPORT void bingoSetSessionID (qword session_id)
{
   TL_SET_SESSION_ID(session_id);
}

CEXPORT qword bingoGetSessionID ()
{
   return TL_GET_SESSION_ID();
}

CEXPORT void bingoSetErrorHandler (BINGO_ERROR_HANDLER handler, void *context)
{
   BingoCore &self = BingoCore::getInstance();
   self.error_handler = handler;
   self.error_handler_context = context;
}

CEXPORT int bingoSetContext (int id)
{
   BINGO_BEGIN
   {
      self.bingo_context = BingoContext::get(id);
      self.mango_context = MangoContext::get(id);
      self.ringo_context = RingoContext::get(id);
   }
   BINGO_END(1, 0);
}

CEXPORT int bingoSetConfigInt (const char *name, int value)
{
   BINGO_BEGIN
   {
      if (self.bingo_context == 0)
         throw BingoError("context not set");
      if (strcasecmp(name, "treat-x-as-pseudoatom") == 0 || strcasecmp(name, "treat_x_as_pseudoatom") == 0)
         self.bingo_context->treat_x_as_pseudoatom = (value != 0);
      else if (strcasecmp(name, "ignore-closing-bond-direction-mismatch") == 0 || strcasecmp(name, "ignore_closing_bond_direction_mismatch") == 0)
         self.bingo_context->ignore_closing_bond_direction_mismatch = (value != 0);
      else if (strcasecmp(name, "nthreads") == 0)
         self.bingo_context->nthreads = value;
      else if (strcasecmp(name, "timeout") == 0)
         self.bingo_context->timeout = value;
      else if (strcasecmp(name, "ignore-cistrans-errors") == 0 || strcasecmp(name, "ignore_cistrans_errors") == 0)
         self.bingo_context->ignore_cistrans_errors = (value != 0);
      else if (strcasecmp(name, "ignore-stereocenter-errors") == 0 || strcasecmp(name, "ignore_stereocenter_errors") == 0)
         self.bingo_context->ignore_stereocenter_errors = (value != 0);
      else if (strcasecmp(name, "stereochemistry-bidirectional-mode") == 0 || strcasecmp(name, "stereochemistry_bidirectional_mode") == 0)
         self.bingo_context->stereochemistry_bidirectional_mode = (value != 0);
      else if (strcasecmp(name, "stereochemistry-detect-haworth-projection") == 0 || strcasecmp(name, "stereochemistry_detect_haworth_projection") == 0)
         self.bingo_context->stereochemistry_detect_haworth_projection = (value != 0);
      else if (strcasecmp(name, "allow-non-unique-dearomatization") == 0 || strcasecmp(name, "allow_non_unique_dearomatization") == 0)
         self.bingo_context->allow_non_unique_dearomatization = (value != 0);
      else if (strcasecmp(name, "zero-unknown-aromatic-hydrogens") == 0 || strcasecmp(name, "zero_unknown_aromatic_hydrogens") == 0)
         self.bingo_context->zero_unknown_aromatic_hydrogens = (value != 0);
      else if (strcasecmp(name, "reject-invalid-structures") == 0 || strcasecmp(name, "reject_invalid_structures") == 0)
         self.bingo_context->reject_invalid_structures = (value != 0);
      else
      {
         bool set = true;
         if (strcasecmp(name, "FP_ORD_SIZE") == 0)
            self.bingo_context->fp_parameters.ord_qwords = value;
         else if (strcasecmp(name, "FP_ANY_SIZE") == 0)
            self.bingo_context->fp_parameters.any_qwords = value;
         else if (strcasecmp(name, "FP_TAU_SIZE") == 0)
            self.bingo_context->fp_parameters.tau_qwords = value;
         else if (strcasecmp(name, "FP_SIM_SIZE") == 0)
            self.bingo_context->fp_parameters.sim_qwords = value;
         else if (strcasecmp(name, "SUB_SCREENING_MAX_BITS") == 0)
            self.sub_screening_max_bits = value;
         else if (strcasecmp(name, "SIM_SCREENING_PASS_MARK") == 0)
            self.sim_screening_pass_mark = value;
         else
            set = false;

         if (set)
         {
            self.bingo_context->fp_parameters.ext = true;
            self.bingo_context->fp_parameters_ready = true;
         }

         if (!set)
            throw BingoError("Unknown parameter name: '%s'", name);

      }
   }
   BINGO_END(1, 0);
}

CEXPORT int bingoGetConfigInt (const char *name, int *value)
{
   BINGO_BEGIN
   {
      if (self.bingo_context == 0)
         throw BingoError("context not set");

      if (strcasecmp(name, "treat-x-as-pseudoatom") == 0 || strcasecmp(name, "treat_x_as_pseudoatom") == 0)
         *value = (int)self.bingo_context->treat_x_as_pseudoatom;
      else if (strcasecmp(name, "ignore-closing-bond-direction-mismatch") == 0 || strcasecmp(name, "ignore_closing_bond_direction_mismatch") == 0)
         *value = (int) self.bingo_context->ignore_closing_bond_direction_mismatch;
      else if (strcasecmp(name, "fp-size-bytes") == 0 || strcasecmp(name, "fp_size_bytes") == 0)
         *value = self.bingo_context->fp_parameters.fingerprintSize();
      else if (strcasecmp(name, "reaction-fp-size-bytes") == 0 || strcasecmp(name, "reaction_fp_size_bytes") == 0)
         *value = self.bingo_context->fp_parameters.fingerprintSizeExtOrd() * 2;
      else if (strcasecmp(name, "SUB_SCREENING_MAX_BITS") == 0)
         *value = self.sub_screening_max_bits;
      else if (strcasecmp(name, "SIM_SCREENING_PASS_MARK") == 0)
         *value = self.sim_screening_pass_mark;
      else if (strcasecmp(name, "nthreads") == 0)
         *value = self.bingo_context->nthreads;
      else if (strcasecmp(name, "timeout") == 0)
         *value = self.bingo_context->timeout;
      else if (strcasecmp(name, "ignore-cistrans-errors") == 0 || strcasecmp(name, "ignore_cistrans_errors") == 0)
         *value = (int)self.bingo_context->ignore_cistrans_errors;
      else if (strcasecmp(name, "ignore-stereocenter-errors") == 0 || strcasecmp(name, "ignore_stereocenter_errors") == 0)
         *value = (int)self.bingo_context->ignore_stereocenter_errors;
      else if (strcasecmp(name, "stereochemistry-bidirectional-mode") == 0 || strcasecmp(name, "stereochemistry_bidirectional_mode") == 0)
         *value = (int) self.bingo_context->stereochemistry_bidirectional_mode;
      else if (strcasecmp(name, "stereochemistry-detect-haworth-projection") == 0 || strcasecmp(name, "stereochemistry_detect_haworth_projection") == 0)
         *value = (int) self.bingo_context->stereochemistry_detect_haworth_projection;
      else if (strcasecmp(name, "allow-non-unique-dearomatization") == 0 || strcasecmp(name, "allow_non_unique_dearomatization") == 0)
         *value = (int)self.bingo_context->allow_non_unique_dearomatization;
      else if (strcasecmp(name, "zero-unknown-aromatic-hydrogens") == 0 || strcasecmp(name, "zero_unknown_aromatic_hydrogens") == 0)
         *value = (int)self.bingo_context->zero_unknown_aromatic_hydrogens;
      else if (strcasecmp(name, "reject-invalid-structures") == 0 || strcasecmp(name, "reject_invalid_structures") == 0)
         *value = (int) self.bingo_context->reject_invalid_structures;
      else
         throw BingoError("unknown parameter name: %s", name);
   }
   BINGO_END(1, 0);
}

CEXPORT int bingoGetConfigBin (const char *name, const char **value, int *len)
{
   BINGO_BEGIN
   {
      if (self.bingo_context == 0)
         throw BingoError("context not set");

      if (strcasecmp(name, "cmf-dict") == 0 || strcasecmp(name, "cmf_dict") == 0)
      {
         ArrayOutput output(self.buffer);
         self.bingo_context->cmf_dict.save(output);
         *value = self.buffer.ptr();
         *len = self.buffer.size();
      }            
      else
         throw BingoError("unknown parameter name: %s", name);
   }
   BINGO_END(1, 0);
}

CEXPORT int bingoSetConfigBin (const char *name, const char *value, int len)
{
   BINGO_BEGIN
   {
      if (self.bingo_context == 0)
         throw BingoError("context not set");

      if (strcasecmp(name, "cmf-dict") == 0 || strcasecmp(name, "cmf_dict") == 0)
      {
         BufferScanner scanner(value, len);
         self.bingo_context->cmf_dict.load(scanner);
      }
      else
         throw BingoError("unknown parameter name: %s", name);
   }
   BINGO_END(1, 0);
}

CEXPORT int bingoClearTautomerRules ()
{
   BINGO_BEGIN
   {
      if (self.bingo_context == 0)
         throw BingoError("context not set");

      self.bingo_context->tautomer_rules.clear();
      self.bingo_context->tautomer_rules_ready = false;
   }
   BINGO_END(1, 0);
}

CEXPORT int bingoAddTautomerRule (int n, const char *beg, const char *end)
{
   BINGO_BEGIN
   {
      if (self.bingo_context == 0)
         throw BingoError("context not set");

      if (n < 1 || n >= 32)
         throw BingoError("tautomer rule index %d is out of range", n);

      AutoPtr<TautomerRule> rule(new TautomerRule());

      bingoGetTauCondition(beg, rule->aromaticity1, rule->list1);
      bingoGetTauCondition(end, rule->aromaticity2, rule->list2);

      self.bingo_context->tautomer_rules.expand(n);
      self.bingo_context->tautomer_rules.reset(n - 1);
      self.bingo_context->tautomer_rules.set(n - 1, rule.release());
   }
   BINGO_END(1, 0);
}

CEXPORT int bingoTautomerRulesReady (int n, const char *beg, const char *end)
{
   BINGO_BEGIN
   {
      if (self.bingo_context == 0)
         throw BingoError("context not set");

      self.bingo_context->tautomer_rules_ready = true;
   }
   BINGO_END(1, 0);
}

CEXPORT int bingoImportParseFieldList(const char *fields_str) {
   BINGO_BEGIN
   {
      QS_DEF(Array<char>, prop);
      QS_DEF(Array<char>, column);
      BufferScanner scanner(fields_str);

      self.import_properties.free();
      self.import_columns.free();
      self.import_properties.create();
      self.import_columns.create();
      
      scanner.skipSpace();

      while (!scanner.isEOF()) {
         scanner.readWord(prop, " ,");
         scanner.skipSpace();
         scanner.readWord(column, " ,");
         scanner.skipSpace();

         self.import_properties.ref().add(prop.ptr());
         self.import_columns.ref().add(column.ptr());

         if (scanner.isEOF())
            break;

         if (scanner.readChar() != ',')
            throw BingoError("importParseFieldList(): comma expected");
         scanner.skipSpace();
      }
      return self.import_properties.ref().size();
   }
   BINGO_END(0, -1);

}
CEXPORT const char* bingoImportGetColumnName(int idx) {
   BINGO_BEGIN
   {
      if (self.import_columns.get() == 0)
         throw BingoError("bingo import list has not been parsed yet");
      return self.import_columns.ref().at(idx);
   }
   BINGO_END("", "")
}
CEXPORT const char* bingoImportGetPropertyName(int idx) {
   BINGO_BEGIN
   {
      if (self.import_properties.get() == 0)
         throw BingoError("bingo import list has not been parsed yet");
      return self.import_properties.ref().at(idx);
   }
   BINGO_END("", "")
}
/*
 * Get value by parsed field list
 */
CEXPORT const char * bingoImportGetPropertyValue (int idx) {
   BINGO_BEGIN
   {
      if (self.import_properties.get() == 0)
         throw BingoError("bingo import list has not been parsed yet");
      const char* property_name = self.import_properties.ref().at(idx);
      if(self.sdf_loader.get()) {
         return self.sdf_loader->properties.at(property_name);
      } else if(self.rdf_loader.get()) {
         return self.rdf_loader->properties.at(property_name);
      } else {
         throw BingoError("bingo import has not been initialized yet");
      }
   }
   BINGO_END("", 0)
}



CEXPORT int bingoSDFImportOpen (const char *file_name)
{
   BINGO_BEGIN
   {
      bingoSDFImportClose();
      self.file_scanner.create(file_name);
      self.sdf_loader.create(self.file_scanner.ref());
      return 1;
   }
   BINGO_END(-1, -1)
}

CEXPORT int bingoSDFImportClose ()
{
   BINGO_BEGIN
   {
      self.sdf_loader.free();
      self.file_scanner.free();
   }
   BINGO_END(0, -1);
}

CEXPORT int bingoSDFImportEOF ()
{
   BINGO_BEGIN
   {
      return self.sdf_loader->isEOF() ? 1 : 0;
   }
   BINGO_END(0, -1)
}
CEXPORT const char * bingoSDFImportGetNext ()
{
   BINGO_BEGIN
   {
      profTimerStart(t, "sdf_loader.readNext");
      self.sdf_loader->readNext();
      self.sdf_loader->data.push(0);
      return self.sdf_loader->data.ptr();
   }
   BINGO_END("", 0)
}

CEXPORT const char * bingoSDFImportGetProperty (const char *param_name)
{
   BINGO_BEGIN
   {
      return self.sdf_loader->properties.at(param_name);
   }
   BINGO_END("", 0)
}

CEXPORT int bingoRDFImportOpen (const char *file_name)
{
   BINGO_BEGIN
   {
      bingoRDFImportClose();
      self.file_scanner.create(file_name);
      self.rdf_loader.create(self.file_scanner.ref());
      return 1;
   }
   BINGO_END(-1, -1)
}

CEXPORT int bingoRDFImportClose ()
{
   BINGO_BEGIN
   {
      self.rdf_loader.free();
      self.file_scanner.free();
   }
   BINGO_END(0, -1);
}

CEXPORT int bingoRDFImportEOF ()
{
   BINGO_BEGIN
   {
      return self.rdf_loader->isEOF() ? 1 : 0;
   }
   BINGO_END(0, -1)
}
CEXPORT const char * bingoRDFImportGetNext ()
{
   BINGO_BEGIN
   {
      self.rdf_loader->readNext();
      self.rdf_loader->data.push(0);
      return self.rdf_loader->data.ptr();
   }
   BINGO_END("", 0)
}

CEXPORT const char * bingoRDFImportGetProperty (const char *param_name)
{
   BINGO_BEGIN
   {
      return self.rdf_loader->properties.at(param_name);
   }
   BINGO_END("", 0)
}


CEXPORT void bingoProfilingReset (byte reset_whole_session)
{
   ProfilingSystem::getInstance().reset(reset_whole_session != 0);
}

CEXPORT const char* bingoProfilingGetStatistics (bool for_session)
{
   BINGO_BEGIN
   {
      ArrayOutput output(self.buffer);
      profGetStatistics(output, for_session);
      output.writeByte(0);
      return self.buffer.ptr();
   }
   BINGO_END("<unknown>", "<unknown>")
}

CEXPORT float bingoProfilingGetTime (const char *counter_name, byte for_session)
{
   BINGO_BEGIN
   {
      return ProfilingSystem::getInstance().getLabelExecTime(counter_name, 
         for_session != 0);
   }
   BINGO_END(-1, -1)
}

CEXPORT qword bingoProfilingGetValue (const char *counter_name, byte for_session)
{
   BINGO_BEGIN
   {
      return ProfilingSystem::getInstance().getLabelValue(counter_name, 
         for_session != 0);
   }
   BINGO_END(-1, -1)
}

CEXPORT qword bingoProfilingGetCount (const char *counter_name, byte for_session)
{
   BINGO_BEGIN
   {
      return ProfilingSystem::getInstance().getLabelCallCount(counter_name, 
         for_session != 0);
   }
   BINGO_END(-1, -1)
}

#include <exception>

CEXPORT int bingoCheckMemoryAllocate (int size)
{
   BINGO_BEGIN
   {
      try {
         self.test_ptr = 0;
         self.test_ptr = (byte*)malloc(size);

         if (self.test_ptr == 0)
         {
            self.error.readString("self.test_ptr == 0", true); 
            return -1;
         }
         for (int i = 0; i < size; i++)
            self.test_ptr[i] = i;

         return 1;
      }
      catch (std::exception &ex)
      {
         self.error.readString(ex.what(), true); 
         return -1;
      }
   }
   BINGO_END(-1, -1)
}

CEXPORT int bingoCheckMemoryFree ()
{
   BINGO_BEGIN
   {
      if (self.test_ptr != 0)
      {
         free(self.test_ptr);
      }

      self.test_ptr = 0;
      return 1;
   }
   BINGO_END(-1, -1)
}

CEXPORT qword bingoProfNanoClock ()
{
   return nanoClock();
}

CEXPORT void bingoProfIncTimer (const char *name, qword dt)
{
   ProfilingSystem &inst = ProfilingSystem::getInstance();
   int name_index = inst.getNameIndex(name);
   inst.addTimer(name_index, dt);
}

CEXPORT void bingoProfIncCounter (const char *name, int dv)
{
   ProfilingSystem &inst = ProfilingSystem::getInstance();
   int name_index = inst.getNameIndex(name);
   inst.addCounter(name_index, dv);
}

CEXPORT const char * bingoGetNameCore (const char *target_buf, int target_buf_len)
{
   BINGO_BEGIN
   {
      QS_DEF(Array<char>, source);
      QS_DEF(Array<char>, name);

      BufferScanner scanner(target_buf, target_buf_len);
      bingoGetName(scanner, self.buffer);
      self.buffer.push(0);
      return self.buffer.ptr();
   }
   BINGO_END(0, 0)
}

CEXPORT int bingoIndexMarkTermintate ()
{
   BINGO_BEGIN
   {
      if (self.parallel_indexing_dispatcher.get())
         self.parallel_indexing_dispatcher->markToTerminate();
      return 1;
   }
   BINGO_END(-2, -2)
}

static void _bingoIndexEnd (BingoCore &self)
{
   if (self.parallel_indexing_dispatcher.get())
   {
      self.parallel_indexing_dispatcher->terminate();
      self.parallel_indexing_dispatcher.reset(0);
   }

   if (self.single_mango_index.get())
      self.single_mango_index.free();
   if (self.single_ringo_index.get())
      self.single_ringo_index.free();

   self.mango_index = 0;
   self.ringo_index = 0;
   self.index_record_data_id = -1;
   self.index_record_data.free();
}

CEXPORT int bingoIndexEnd ()
{
   BINGO_BEGIN
   {
      _bingoIndexEnd(self);
      return 1;
   }
   BINGO_END(-2, -2)
}

CEXPORT int bingoIndexBegin ()
{
   BINGO_BEGIN
   {
      if (!self.bingo_context->fp_parameters_ready)
         throw BingoError("fingerprint parameters not set");

      _bingoIndexEnd(self);

      self.index_record_data.create();
      return 1;
   }
   BINGO_END(-2, -2)
}

CEXPORT int bingoIndexSetSkipFP (bool skip)
{
   BINGO_BEGIN
   {
      self.skip_calculate_fp = skip;
      return 1;
   }
   BINGO_END(-2, -2)
}

CEXPORT int bingoSMILESImportOpen (const char *file_name)
{
   BINGO_BEGIN
   {
      self.file_scanner.free();
      self.file_scanner.create(file_name);

      // detect if input is gzipped
      byte magic[2];
      int pos = self.file_scanner->tell();
      self.file_scanner->readCharsFix(2, (char *)magic);
      self.file_scanner->seek(pos, SEEK_SET);
      if (magic[0] == 0x1f && magic[1] == 0x8b)
      {
         self.gz_scanner.reset(new GZipScanner(self.file_scanner.ref()));
         self.smiles_scanner = self.gz_scanner.get();
      }
      else
         self.smiles_scanner = self.file_scanner.get();
      return 1;
   }
   BINGO_END(-2, -2)
}

CEXPORT int bingoSMILESImportClose ()
{
   BINGO_BEGIN
   {
      self.gz_scanner.reset(0);
      self.file_scanner.free();
      self.smiles_scanner = 0;
   }
   BINGO_END(0, -1)
}

CEXPORT int bingoSMILESImportEOF ()
{
   BINGO_BEGIN
   {
      if (self.smiles_scanner == 0)
         throw BingoError("SMILES import wasn't initialized");
      return self.smiles_scanner->isEOF() ? 1 : 0;
   }
   BINGO_END(-2, -2)
}

CEXPORT const char * bingoSMILESImportGetNext ()
{
   BINGO_BEGIN
   {
      if (self.smiles_scanner == 0)
         throw BingoError("SMILES import wasn't initialized");
      // TODO: Name should be also extracted here...
      self.smiles_scanner->readLine(self.buffer, true);
      return self.buffer.ptr();
   }
   BINGO_END("", 0)
}

CEXPORT const char * bingoSMILESImportGetId ()
{
   BINGO_BEGIN
   {
      if (self.smiles_scanner == 0)
         throw BingoError("SMILES import wasn't initialized");
      /*
       * Extract id name by skipping | symbols
       */
      BufferScanner strscan(self.buffer.ptr());

      strscan.skipSpace();
      while (!strscan.isEOF() && !isspace(strscan.readChar()));
      strscan.skipSpace();
      if (strscan.lookNext() == '|')
      {
         strscan.readChar();
         while (!strscan.isEOF() && strscan.readChar() != '|');
         strscan.skipSpace();
      }

      if (strscan.isEOF())
         return 0;
      else
         return (const char*)strscan.curptr();
   }
   BINGO_END("", 0)
}
