/****************************************************************************
 * 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 <oci.h>

#include "oracle/bingo_oracle.h"
#include "oracle/ora_wrap.h"
#include "oracle/ora_logger.h"
#include "core/ringo_matchers.h"
#include "oracle/ringo_fetch_context.h"
#include "oracle/bingo_oracle_context.h"
#include "oracle/ringo_oracle.h"

static void _ringoIndexStart (OracleEnv &env, RingoFetchContext &context,
                              const char *oper, const Array<char> &query_buf,
                              OCINumber *p_strt, OCINumber *p_stop, const char *params)
{
   RingoShadowFetch &shadow_fetch = context.shadow_fetch.ref();
   RingoFastIndex   &fast_index   = context.fast_index.ref();

   if (strcasecmp(oper, "RSUB") == 0)
   {
      if (!context.substructure.parse(params))
         throw BingoError("can not parse parameters: %s", params);
      
      context.substructure.loadQuery(query_buf);

      int right = bingoGetExactRightPart(env, p_strt, p_stop, 64);

      if (right == 1)
      {
         fast_index.prepareSubstructure(env);
         context.fetch_engine = &fast_index;
      }
      else // right == 0
      {
         shadow_fetch.prepareNonSubstructure(env);
         context.fetch_engine = &shadow_fetch;
      }
   }
   else if (strcasecmp(oper, "RSMARTS") == 0)
   {
      context.substructure.loadSMARTS(query_buf);

      int right = bingoGetExactRightPart(env, p_strt, p_stop, 64);

      if (right == 1)
      {
         fast_index.prepareSubstructure(env);
         context.fetch_engine = &fast_index;
      }
      else // right == 0
      {
         shadow_fetch.prepareNonSubstructure(env);
         context.fetch_engine = &shadow_fetch;
      }
   }
   else if (strcasecmp(oper, "REXACT") == 0)
   {
      context.exact.setParameters(params);
      context.exact.loadQuery(query_buf);

      int right = bingoGetExactRightPart(env, p_strt, p_stop, 64);

      shadow_fetch.prepareExact(env, right);
      context.fetch_engine = &shadow_fetch;
   }
   else
      throw BingoError("unknown operator: %s", oper);
}

ORAEXT int oraRingoIndexStart (OCIExtProcContext *ctx,
               int context_id,
               const char    *oper,      short oper_ind,
               OCILobLocator *query_loc, short query_ind,
               OCINumber     *p_strt,    short strt_ind,
               OCINumber     *p_stop,    short stop_ind,
               const char    *params,    short params_ind)
{
   ORABLOCK_BEGIN
   {
      OracleEnv env(ctx, logger);

      env.dbgPrintf("IndexStart... ");

      if (oper_ind != OCI_IND_NOTNULL)
         throw BingoError("null operator given");
      if (query_ind != OCI_IND_NOTNULL)
         throw BingoError("null query given");
      if (params_ind != OCI_IND_NOTNULL)
         params = 0;
      if (strt_ind != OCI_IND_NOTNULL)
         p_strt = 0;
      if (stop_ind != OCI_IND_NOTNULL)
         p_stop = 0;

      QS_DEF(Array<char>, query_buf);
      OracleLOB query_lob(env, query_loc);

      query_lob.readAll(query_buf, false);

      QS_DEF(Array<char>, query_id);

      bingoBuildQueryID(env, oper, query_buf, p_strt, p_stop, 0, params, query_id);

      RingoOracleContext &context = RingoOracleContext::get(env, context_id, false);
      RingoFetchContext *fetch_context = RingoFetchContext::findFresh(context_id, query_id);

      if (fetch_context == 0)
      {
         fetch_context = &RingoFetchContext::create(context, query_id);
         _ringoIndexStart(env, *fetch_context, oper, query_buf, p_strt, p_stop, params);
         env.dbgPrintf("created fetcher #%d\n", fetch_context->id);
      }
      else
      {
         fetch_context->fresh = false;
         env.dbgPrintf("found fresh fetcher #%d\n", fetch_context->id);
      }
      return fetch_context->id;
   }
   ORABLOCK_END

   return -1;
}

ORAEXT int oraRingoIndexFetch (OCIExtProcContext *ctx, int fetch_id,
                               int maxrows,
                               OCIArray **array, short array_indicator)
{
   ORABLOCK_BEGIN
   {
      OracleEnv env(ctx, logger);

      RingoFetchContext &context = RingoFetchContext::get(fetch_id);

      if (context.fetch_engine == 0)
         throw BingoError("fetch_engine = 0 in oraRingoIndexFetch()");

      BingoFetchEngine &fetch_engine = *context.fetch_engine;

      ORA_TRY_FETCH_BEGIN
      {
         if (maxrows > 100)
            maxrows = 100;

         // can have fetched rowid-s from the selectivity computation phase
         maxrows -= bingoPopRowidsToArray(env, fetch_engine.matched, maxrows, *array);

         if (maxrows > 0 && !fetch_engine.end())
         {
            env.dbgPrintf("[fetcher #%d] ", context.id);

            fetch_engine.fetch(env, maxrows);
            maxrows -= bingoPopRowidsToArray(env, fetch_engine.matched, maxrows, *array);
         }

         return fetch_engine.end() ? 0 : 1;
      }
      ORA_TRY_FETCH_END
   }
   ORABLOCK_END

   return 0;
}

ORAEXT void oraRingoIndexClose (OCIExtProcContext *ctx, int fetch_id)
{
   ORABLOCK_BEGIN
   {
      OracleEnv env(ctx, logger);

      env.dbgPrintfTS("closing fetch #%d\n", fetch_id);

      RingoFetchContext::remove(fetch_id);
   }
   ORABLOCK_END
}

ORAEXT OCINumber * oraRingoIndexSelectivity (OCIExtProcContext *ctx, int context_id,
               const char    *oper,      short oper_ind,
               OCILobLocator *query_loc, short query_ind,
               OCINumber     *p_strt,    short strt_ind,
               OCINumber     *p_stop,    short stop_ind,
               const char    *params,    short params_ind,
                                         short *return_ind)
{
   *return_ind = OCI_IND_NULL;

   ORABLOCK_BEGIN
   {
      OracleEnv env(ctx, logger);

      env.dbgPrintf("IndexSelectivity... ");

      if (oper_ind != OCI_IND_NOTNULL)
         throw BingoError("null operator given");
      if (query_ind != OCI_IND_NOTNULL)
         throw BingoError("null query given");
      if (params_ind != OCI_IND_NOTNULL)
         params = 0;
      if (strt_ind != OCI_IND_NOTNULL)
         p_strt = 0;
      if (stop_ind != OCI_IND_NOTNULL)
         p_stop = 0;

      RingoOracleContext &context = RingoOracleContext::get(env, context_id, false);

      QS_DEF(Array<char>, query_id);
      QS_DEF(Array<char>, query_buf);
      OracleLOB query_lob(env, query_loc);

      query_lob.readAll(query_buf, false);

      bingoBuildQueryID(env, oper, query_buf, p_strt, p_stop, 0, params, query_id);

      RingoFetchContext * fetch_context = RingoFetchContext::findFresh(context_id, query_id);

      if (fetch_context == 0)
      {
         fetch_context = &RingoFetchContext::create(context, query_id);
         env.dbgPrintf("created fresh fetcher #%d\n", fetch_context->id);
      }
      else
         env.dbgPrintf("found fresh fetcher #%d\n", fetch_context->id);

      _ringoIndexStart(env, *fetch_context, oper, query_buf, p_strt, p_stop, params);

      env.dbgPrintf("[fetcher #%d] ", fetch_context->id);
      fetch_context->fetch_engine->fetch(env, 100);
      fetch_context->fresh = true;

      float res = fetch_context->fetch_engine->calcSelectivity(env, context.fingerprints.getTotalCount(env));

      env.dbgPrintfTS("calculated selectivity = %.2f\n", res);

      OCINumber *result = OracleExtproc::createDouble(env, (double)res);

      *return_ind = OCI_IND_NOTNULL;
      return result;
   }
   ORABLOCK_END
   
   return 0;
}

ORAEXT void oraRingoIndexCost (OCIExtProcContext *ctx, int context_id,
               OCINumber     *p_sel,     short sel_ind,
               const char    *oper,      short oper_ind,
               OCILobLocator *query_loc, short query_ind,
               OCINumber     *p_strt,    short strt_ind,
               OCINumber     *p_stop,    short stop_ind,
               const char    *params,    short params_ind,
               int *iocost, int *cpucost)
{
   ORABLOCK_BEGIN
   {
      OracleEnv env(ctx, logger);

      env.dbgPrintfTS("IndexCost... ");

      if (sel_ind != OCI_IND_NOTNULL)
         throw BingoError("null selectivity given");

      if (oper_ind != OCI_IND_NOTNULL)
         throw BingoError("null operator given");
      if (query_ind != OCI_IND_NOTNULL)
         throw BingoError("null query given");
      if (params_ind != OCI_IND_NOTNULL)
         params = 0;
      if (strt_ind != OCI_IND_NOTNULL)
         p_strt = 0;
      if (stop_ind != OCI_IND_NOTNULL)
         p_stop = 0;


      float sel = OracleUtil::numberToFloat(env, p_sel);

      QS_DEF(Array<char>, query_buf);
      OracleLOB query_lob(env, query_loc);

      query_lob.readAll(query_buf, false);

      QS_DEF(Array<char>, query_id);

      bingoBuildQueryID(env, oper, query_buf, p_strt, p_stop, 0, params, query_id);

      RingoOracleContext &context = RingoOracleContext::get(env, context_id, false);
      RingoFetchContext *fetch_context = RingoFetchContext::findFresh(context_id, query_id);

      if (fetch_context == 0)
      {
         fetch_context = &RingoFetchContext::create(context, query_id);
         _ringoIndexStart(env, *fetch_context, oper, query_buf, p_strt, p_stop, params);
         fetch_context->fresh = true;
         env.dbgPrintf("created fresh fetcher #%d\n", fetch_context->id);
      }
      else
         env.dbgPrintf("found fresh fetcher #%d\n", fetch_context->id);

      *iocost = fetch_context->fetch_engine->getIOCost(env, sel);
      *cpucost = (int)(context.fingerprints.getTotalCount(env)  * sel);

      env.dbgPrintfTS("calculated iocost = %d, cpucost = %d\n", *iocost, *cpucost);
   }
   ORABLOCK_END
}


ORAEXT void oraRingoCollectStatistics (OCIExtProcContext *ctx, int context_id)
{
   ORABLOCK_BEGIN
   {
      OracleEnv env(ctx, logger);

      RingoOracleContext & context = RingoOracleContext::get(env, context_id, false);

      context.shadow_table.analyze(env);
   }
   ORABLOCK_END
}
