#define PREFER_MY_CONFIG_H
#include <my_config.h>
// #include <cmath>
#include <iostream>
#include <sstream>

#include "idb_mysql.h"

#include "errorids.h"
#include "idberrorinfo.h"
using namespace logging;

#include "pseudocolumn.h"
#include "functioncolumn.h"
#include "constantcolumn.h"
using namespace execplan;

#include "functor.h"
#include "functor_str.h"

#include "ha_mcs.h"
#include "ha_mcs_impl_if.h"
#include "ha_mcs_sysvars.h"
using namespace cal_impl_if;
using namespace std;

namespace
{
/*******************************************************************************
 * Pseudo column connector interface
 *
 * idbdbroot
 * idbpm
 * idbextentrelativerid
 * idbsegmentdir
 * idbsegment
 * idbpartition
 * idbextentmin
 * idbextentmax
 * idbextentid
 * idbblockid
 *
 * All the pseudo column functions are only executed in InfiniDB.
 */

void bailout(char* error, const string& funcName)
{
  string errMsg = IDBErrorInfo::instance()->errorMsg(ERR_PSEUDOCOL_IDB_ONLY, funcName);
  current_thd->get_stmt_da()->set_overwrite_status(true);
  current_thd->raise_error_printf(ER_INTERNAL_ERROR, errMsg.c_str());
  *error = 1;
}

int64_t idblocalpm()
{
  if (get_fe_conn_info_ptr() == NULL)
  {
    set_fe_conn_info_ptr((void*)new cal_connection_info());
    thd_set_ha_data(current_thd, mcs_hton, get_fe_conn_info_ptr());
  }

  cal_connection_info* ci = reinterpret_cast<cal_connection_info*>(get_fe_conn_info_ptr());

  if (ci->localPm == -1)
  {
    string module = ClientRotator::getModule();

    if (module.size() >= 3 && (module[0] == 'p' || module[0] == 'P'))
      ci->localPm = atol(module.c_str() + 2);
    else
      ci->localPm = 0;
  }

  return ci->localPm;
}

extern "C"
{
  /**
   * IDBDBROOT
   */
  my_bool idbdbroot_init(UDF_INIT* /*initid*/, UDF_ARGS* args, char* message)
  {
    if (args->arg_count != 1)
    {
      strcpy(message, "idbdbroot() requires one argument");
      return 1;
    }

    return 0;
  }

  void idbdbroot_deinit(UDF_INIT* /*initid*/)
  {
  }

  long long idbdbroot(UDF_INIT* /*initid*/, UDF_ARGS* /*args*/, char* /*is_null*/, char* error)
  {
    bailout(error, "idbdbroot");
    return 0;
  }

  /**
   * IDBPM
   */

  my_bool idbpm_init(UDF_INIT* /*initid*/, UDF_ARGS* args, char* message)
  {
    if (args->arg_count != 1)
    {
      strcpy(message, "idbpm() requires one argument");
      return 1;
    }

    return 0;
  }

  void idbpm_deinit(UDF_INIT* /*initid*/)
  {
  }

  long long idbpm(UDF_INIT* /*initid*/, UDF_ARGS* /*args*/, char* /*is_null*/, char* error)
  {
    bailout(error, "idbpm");
    return 0;
  }

  /**
   * IDBEXTENTRELATIVERID
   */

  my_bool idbextentrelativerid_init(UDF_INIT* /*initid*/, UDF_ARGS* args, char* message)
  {
    if (args->arg_count != 1)
    {
      strcpy(message, "idbextentrelativerid() requires one argument");
      return 1;
    }

    return 0;
  }

  void idbextentrelativerid_deinit(UDF_INIT* /*initid*/)
  {
  }

  long long idbextentrelativerid(UDF_INIT* /*initid*/, UDF_ARGS* /*args*/, char* /*is_null*/, char* error)
  {
    bailout(error, "idbextentrelativerid");
    return 0;
  }

  /**
   * IDBBLOCKID
   */

  my_bool idbblockid_init(UDF_INIT* /*initid*/, UDF_ARGS* args, char* message)
  {
    if (args->arg_count != 1)
    {
      strcpy(message, "idbblockid() requires one argument");
      return 1;
    }

    return 0;
  }

  void idbblockid_deinit(UDF_INIT* /*initid*/)
  {
  }

  long long idbblockid(UDF_INIT* /*initid*/, UDF_ARGS* /*args*/, char* /*is_null*/, char* error)
  {
    bailout(error, "idbblockid");
    return 0;
  }

  /**
   * IDBEXTENTID
   */

  my_bool idbextentid_init(UDF_INIT* /*initid*/, UDF_ARGS* args, char* message)
  {
    if (args->arg_count != 1)
    {
      strcpy(message, "idbextentid() requires one argument");
      return 1;
    }

    return 0;
  }

  void idbextentid_deinit(UDF_INIT* /*initid*/)
  {
  }

  long long idbextentid(UDF_INIT* /*initid*/, UDF_ARGS* /*args*/, char* /*is_null*/, char* error)
  {
    bailout(error, "idbextentid");
    return 0;
  }

  /**
   * IDBSEGMENT
   */

  my_bool idbsegment_init(UDF_INIT* /*initid*/, UDF_ARGS* args, char* message)
  {
    if (args->arg_count != 1)
    {
      strcpy(message, "idbsegment() requires one argument");
      return 1;
    }

    return 0;
  }

  void idbsegment_deinit(UDF_INIT* /*initid*/)
  {
  }

  long long idbsegment(UDF_INIT* /*initid*/, UDF_ARGS* /*args*/, char* /*is_null*/, char* error)
  {
    bailout(error, "idbsegment");
    return 0;
  }

  /**
   * IDBSEGMENTDIR
   */

  my_bool idbsegmentdir_init(UDF_INIT* /*initid*/, UDF_ARGS* args, char* message)
  {
    if (args->arg_count != 1)
    {
      strcpy(message, "idbsegmentdir() requires one argument");
      return 1;
    }

    return 0;
  }

  void idbsegmentdir_deinit(UDF_INIT* /*initid*/)
  {
  }

  long long idbsegmentdir(UDF_INIT* /*initid*/, UDF_ARGS* /*args*/, char* /*is_null*/, char* error)
  {
    bailout(error, "idbsegmentdir");
    return 0;
  }

  /**
   * IDBPARTITION
   */

  my_bool idbpartition_init(UDF_INIT* /*initid*/, UDF_ARGS* args, char* message)
  {
    if (args->arg_count != 1)
    {
      strcpy(message, "idbpartition() requires one argument");
      return 1;
    }

    return 0;
  }

  void idbpartition_deinit(UDF_INIT* /*initid*/)
  {
  }

  const char* idbpartition(UDF_INIT* /*initid*/, UDF_ARGS* /*args*/, char* result, unsigned long* /*length*/,
                           char* /*is_null*/, char* error)
  {
    bailout(error, "idbpartition");
    return result;
  }

  /**
   * IDBEXTENTMIN
   */

  my_bool idbextentmin_init(UDF_INIT* initid, UDF_ARGS* args, char* message)
  {
    if (args->arg_count != 1)
    {
      strcpy(message, "idbpm() requires one argument");
      return 1;
    }

    initid->maybe_null = 1;
    return 0;
  }

  void idbextentmin_deinit(UDF_INIT* /*initid*/)
  {
  }

  const char* idbextentmin(UDF_INIT* /*initid*/, UDF_ARGS* /*args*/, char* result, unsigned long* /*length*/,
                           char* /*is_null*/, char* error)
  {
    bailout(error, "idbextentmin");
    return result;
  }

  /**
   * IDBEXTENTMAX
   */

  my_bool idbextentmax_init(UDF_INIT* initid, UDF_ARGS* args, char* message)
  {
    if (args->arg_count != 1)
    {
      strcpy(message, "idbextentmax() requires one argument");
      return 1;
    }

    initid->maybe_null = 1;
    return 0;
  }

  void idbextentmax_deinit(UDF_INIT* /*initid*/)
  {
  }

  const char* idbextentmax(UDF_INIT* /*initid*/, UDF_ARGS* /*args*/, char* result, unsigned long* /*length*/,
                           char* /*is_null*/, char* error)
  {
    bailout(error, "idbextentmax");
    return result;
  }

  /**
   * IDBLOCALPM
   */

  my_bool idblocalpm_init(UDF_INIT* initid, UDF_ARGS* args, char* message)
  {
    if (args->arg_count != 0)
    {
      strcpy(message, "idblocalpm() should take no argument");
      return 1;
    }

    initid->maybe_null = 1;
    return 0;
  }

  void idblocalpm_deinit(UDF_INIT* /*initid*/)
  {
  }

  long long idblocalpm(UDF_INIT* /*initid*/, UDF_ARGS* /*args*/, char* is_null, char* /*error*/)
  {
    longlong localpm = idblocalpm();

    if (localpm == 0)
      *is_null = 1;

    return localpm;
  }
}

}  // namespace

namespace cal_impl_if
{
ReturnedColumn* nullOnError(gp_walk_info& gwi, string& funcName)
{
  gwi.fatalParseError = true;
  gwi.parseErrorText =
      logging::IDBErrorInfo::instance()->errorMsg(logging::ERR_PSEUDOCOL_WRONG_ARG, funcName);
  return NULL;
}

uint32_t isPseudoColumn(string funcName)
{
  return execplan::PseudoColumn::pseudoNameToType(funcName);
}

execplan::ReturnedColumn* buildPseudoColumn(Item* item, gp_walk_info& gwi, bool& /*nonSupport*/,
                                            uint32_t pseudoType)
{
  if (get_fe_conn_info_ptr() == NULL)
  {
    set_fe_conn_info_ptr((void*)new cal_connection_info());
    thd_set_ha_data(current_thd, mcs_hton, get_fe_conn_info_ptr());
  }

  cal_connection_info* ci = reinterpret_cast<cal_connection_info*>(get_fe_conn_info_ptr());

  Item_func* ifp = (Item_func*)item;

  // idblocalpm is replaced by constant
  if (pseudoType == PSEUDO_LOCALPM)
  {
    int64_t localPm = idblocalpm();
    ConstantColumn* cc;

    if (localPm)
      cc = new ConstantColumn(localPm);
    else
      cc = new ConstantColumn("", ConstantColumn::NULLDATA);
    cc->timeZone(gwi.timeZone);

    cc->alias(ifp->full_name() ? ifp->full_name() : "");
    return cc;
  }

  // convert udf item to pseudocolumn item.
  // adjust result type
  // put arg col to column map
  string funcName = ifp->func_name();

  if (ifp->argument_count() != 1 || !(ifp->arguments()) || !(ifp->arguments()[0]) ||
      ifp->arguments()[0]->type() != Item::FIELD_ITEM)
    return nullOnError(gwi, funcName);

  Item_field* field = (Item_field*)(ifp->arguments()[0]);

  // @todo rule out derive table
  if (!field->field || !field->db_name.str || strlen(field->db_name.str) == 0)
    return nullOnError(gwi, funcName);

  SimpleColumn* sc = buildSimpleColumn(field, gwi);

  if (!sc)
    return nullOnError(gwi, funcName);

  if ((pseudoType == PSEUDO_EXTENTMIN || pseudoType == PSEUDO_EXTENTMAX) &&
      (sc->colType().colDataType == CalpontSystemCatalog::VARBINARY ||
       (sc->colType().colDataType == CalpontSystemCatalog::TEXT) ||
       (sc->colType().colDataType == CalpontSystemCatalog::VARCHAR && sc->colType().colWidth > 7) ||
       (sc->colType().colDataType == CalpontSystemCatalog::CHAR && sc->colType().colWidth > 8)))
    return nullOnError(gwi, funcName);

  // put arg col to column map
  if (gwi.clauseType == SELECT || gwi.clauseType == GROUP_BY)  // select clause
  {
    SRCP srcp(sc);
    gwi.columnMap.insert(CalpontSelectExecutionPlan::ColumnMap::value_type(sc->columnName(), srcp));
    gwi.tableMap[make_aliastable(sc->schemaName(), sc->tableName(), sc->tableAlias(), sc->isColumnStore())] =
        make_pair(1, field->cached_table);
  }
  else if (!gwi.rcWorkStack.empty())
  {
    delete gwi.rcWorkStack.top();
    gwi.rcWorkStack.pop();
  }

  if (pseudoType == PSEUDO_PARTITION)
  {
    // parms: psueducolumn dbroot, segmentdir, segment
    SPTP sptp;
    FunctionColumn* fc = new FunctionColumn(funcName);
    funcexp::FunctionParm parms;
    PseudoColumn* dbroot = new PseudoColumn(*sc, PSEUDO_DBROOT);
    sptp.reset(new ParseTree(dbroot));
    parms.push_back(sptp);
    PseudoColumn* pp = new PseudoColumn(*sc, PSEUDO_SEGMENTDIR);
    sptp.reset(new ParseTree(pp));
    parms.push_back(sptp);
    PseudoColumn* seg = new PseudoColumn(*sc, PSEUDO_SEGMENT);
    sptp.reset(new ParseTree(seg));
    parms.push_back(sptp);
    fc->functionParms(parms);
    fc->expressionId(ci->expressionId++);
    fc->timeZone(gwi.timeZone);

    // string result type
    CalpontSystemCatalog::ColType ct;
    ct.colDataType = CalpontSystemCatalog::VARCHAR;
    ct.colWidth = 256;
    fc->resultType(ct);

    // operation type integer
    funcexp::Func_idbpartition* idbpartition = new funcexp::Func_idbpartition();
    fc->operationType(idbpartition->operationType(parms, fc->resultType()));
    fc->alias(ifp->full_name() ? ifp->full_name() : "");
    delete idbpartition;
    return fc;
  }

  PseudoColumn* pc = new PseudoColumn(*sc, pseudoType);

  // @bug5892. set alias for derived table column matching.
  pc->alias(ifp->name.length ? ifp->name.str : "");
  return pc;
}

}  // namespace cal_impl_if
