/* Copyright (C) 2025 MariaDB Corporation

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; version 2 of
   the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
   MA 02110-1301, USA. */

#include "ha_mcs_execplan_helpers.h"
#include "constantcolumn.h"

namespace cal_impl_if
{
bool nonConstFunc(Item_func* ifp)
{
  if (strcasecmp(ifp->func_name(), "rand") == 0 || strcasecmp(ifp->func_name(), "sysdate") == 0 ||
      strcasecmp(ifp->func_name(), "idblocalpm") == 0)
    return true;

  for (uint32_t i = 0; i < ifp->argument_count(); i++)
  {
    if (ifp->arguments()[i]->type() == Item::FUNC_ITEM && nonConstFunc(((Item_func*)ifp->arguments()[i])))
      return true;
    if (ifp->arguments()[i]->type() == Item::CACHE_ITEM)
      return true;
  }

  return false;
}

/*
  Create a ConstantColumn according to cmp_type().
  But do not set the time zone yet.

  Handles NULL and NOT NULL values.

  Uses a simplified logic regarding to data types:
    always extracts the value through val_str().

  Should probably be joined with the previous function, to have
  a single function which can at the same time:
  a. handle both NULL and NOT NULL values
  b. extract values using a native val_xxx() method,
     to avoid possible negative effects mentioned in the comments
     to newConstantColumnNotNullUsingValNativeNoTz().
*/
execplan::ConstantColumn* newConstantColumnMaybeNullFromValStrNoTz(const Item* item,
                                                                          const ValStrStdString& valStr,
                                                                          gp_walk_info& gwi)
{
  if (valStr.isNull())
    return new execplan::ConstantColumnNull();

  switch (item->result_type())
  {
    case STRING_RESULT: return new execplan::ConstantColumnString(valStr);
    case DECIMAL_RESULT: return buildDecimalColumn(item, valStr, gwi);
    case TIME_RESULT:
    case INT_RESULT:
    case REAL_RESULT:
    case ROW_RESULT: return new execplan::ConstantColumnNum(colType_MysqlToIDB(item), valStr);
  }
  return nullptr;
}

// Create a ConstantColumn from a previously evaluated val_str() result,
// Supports both NULL and NOT NULL values.
// Sets the time zone according to gwi.
execplan::ConstantColumn* buildConstantColumnMaybeNullFromValStr(const Item* item, const ValStrStdString& valStr,
                                                              gp_walk_info& gwi)
{
  execplan::ConstantColumn* rc = newConstantColumnMaybeNullFromValStrNoTz(item, valStr, gwi);
  if (rc)
    rc->timeZone(gwi.timeZone);
  return rc;
}

// Create a ConstantColumn by calling val_str().
// Supports both NULL and NOT NULL values.
// Sets the time zone according to gwi.

execplan::ConstantColumn* buildConstantColumnMaybeNullUsingValStr(Item* item, gp_walk_info& gwi)
{
  return buildConstantColumnMaybeNullFromValStr(item, ValStrStdString(item), gwi);
}

// Create a ConstantColumn for a NOT NULL expression.
// Sets the time zone according to gwi.
execplan::ConstantColumn* buildConstantColumnNotNullUsingValNative(Item* item, gp_walk_info& gwi)
{
  execplan::ConstantColumn* rc = newConstantColumnNotNullUsingValNativeNoTz(item, gwi);
  if (rc)
    rc->timeZone(gwi.timeZone);
  return rc;
}

/*
  Create a ConstantColumn according to cmp_type().
  But do not set the time zone yet.

  Handles NOT NULL values.

  Three ways of value extraction are used depending on the data type:
  1. Using a native val_xxx().
  2. Using val_str() with further convertion to the native representation.
  3. Using both val_str() and a native val_xxx().

  We should eventually get rid of N2 and N3 and use N1 for all data types:
  - N2 contains a redundant code for str->native conversion.
    It should be replaced to an existing code (a Type_handler method call?).
  - N3 performs double evalation of the value, which may cause
    various negative effects (double side effects or double warnings).
*/
execplan::ConstantColumn* newConstantColumnNotNullUsingValNativeNoTz(Item* item, gp_walk_info& gwi)
{
  DBUG_ASSERT(item->const_item());

  switch (item->cmp_type())
  {
    case INT_RESULT:
    {
      if (item->unsigned_flag)
        return new execplan::ConstantColumnUInt((uint64_t)item->val_uint(), (int8_t)item->decimal_scale(),
                                      (uint8_t)item->decimal_precision());
      ValStrStdString str(item);
      DBUG_ASSERT(!str.isNull());
      return new execplan::ConstantColumnSInt(colType_MysqlToIDB(item), str, (int64_t)item->val_int());
    }
    case STRING_RESULT:
    {
      // Special handling for 0xHHHH literals
      if (item->type_handler() == &type_handler_hex_hybrid)
        return new execplan::ConstantColumn((int64_t)item->val_int(), execplan::ConstantColumn::NUM);
      ValStrStdString str(item);
      DBUG_ASSERT(!str.isNull());
      return new execplan::ConstantColumnString(str);
    }
    case REAL_RESULT:
    {
      ValStrStdString str(item);
      DBUG_ASSERT(!str.isNull());
      return new execplan::ConstantColumnReal(colType_MysqlToIDB(item), str, item->val_real());
    }
    case DECIMAL_RESULT:
    {
      ValStrStdString str(item);
      DBUG_ASSERT(!str.isNull());
      return buildDecimalColumn(item, str, gwi);
    }
    case TIME_RESULT:
    {
      ValStrStdString str(item);
      DBUG_ASSERT(!str.isNull());
      return new execplan::ConstantColumnTemporal(colType_MysqlToIDB(item), str);
    }
    default:
    {
      gwi.fatalParseError = true;
      gwi.parseErrorText = "Unknown item type";
      break;
    }
  }

  return nullptr;
}

bool isSupportedAggregateWithOneConstArg(const Item_sum* item, Item** orig_args)
{
  if (item->argument_count() != 1 || !orig_args[0]->const_item())
    return false;
  switch (orig_args[0]->cmp_type())
  {
    case INT_RESULT:
    case STRING_RESULT:
    case REAL_RESULT:
    case DECIMAL_RESULT: return true;
    default: break;
  }
  return false;
}

}  // namespace cal_impl_if