/**
 * Copyright (c) 2021 OceanBase
 * OceanBase CE is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#define USING_LOG_PREFIX SQL_ENG
#include "sql/engine/expr/ob_expr_arg_case.h"
#include "sql/engine/expr/ob_expr_operator.h"
#include "sql/engine/expr/ob_expr_equal.h"
#include "sql/engine/expr/ob_expr_result_type_util.h"
#include "sql/session/ob_sql_session_info.h"

namespace oceanbase {
using namespace common;
namespace sql {

ObExprArgCase::ObExprArgCase(ObIAllocator& alloc)
    : ObExprOperator(alloc, T_OP_ARG_CASE, N_ARG_CASE, MORE_THAN_ONE, NOT_ROW_DIMENSION), need_cast_(true)
{
  disable_operand_auto_cast();
}

ObExprArgCase::~ObExprArgCase()
{}

int ObExprArgCase::deserialize(const char* buf, const int64_t data_len, int64_t& pos)
{
  int ret = OB_SUCCESS;
  need_cast_ = true;
  ret = ObExprOperator::deserialize(buf, data_len, pos);
  if (OB_SUCC(ret)) {
    if (OB_UNLIKELY(input_types_.count() < 2 || input_types_.count() % 2 != 0)) {
      ret = OB_ERR_UNEXPECTED;
      LOG_WARN("unexpected error. invalid input type count", K(input_types_.count()));
    } else {
      int64_t loop = input_types_.count() / 2;
      bool all_are_numeric = ob_is_accurate_numeric_type(input_types_.at(0).get_calc_type());
      bool all_same_type = true;
      ObObjMeta case_meta = input_types_.at(0).get_calc_meta();
      for (int64_t i = 1; OB_SUCC(ret) && i < loop; ++i) {
        ObObjType type = input_types_.at(i).get_calc_type();
        ObObjMeta param_meta = input_types_.at(i).get_calc_meta();
        if (all_same_type && !ObSQLUtils::is_same_type_for_compare(case_meta, param_meta)) {
          all_same_type = false;
        }
        if (all_are_numeric && (!ob_is_accurate_numeric_type(type))) {
          all_are_numeric = false;
        }
      }  // end for
      if (OB_SUCC(ret)) {
        need_cast_ = !(all_same_type || all_are_numeric);
      }
    }
  }
  return ret;
}

int ObExprArgCase::assign(const ObExprOperator& other)
{
  int ret = OB_SUCCESS;
  const ObExprArgCase* tmp_other = dynamic_cast<const ObExprArgCase*>(&other);
  if (OB_UNLIKELY(NULL == tmp_other)) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("invalid argument. wrong type for other", K(ret), K(other));
  } else if (OB_LIKELY(this != tmp_other)) {
    if (OB_FAIL(ObExprOperator::assign(other))) {
      LOG_WARN("copy in Base class ObExprOperator failed", K(ret));
    } else {
      this->need_cast_ = tmp_other->need_cast_;
    }
  }
  return ret;
}

int ObExprArgCase::calc_result_typeN(
    ObExprResType& type, ObExprResType* types_stack, int64_t param_num, ObExprTypeCtx& type_ctx) const
{
  // mysql> drop table t1; create table t1(a date, b int);
  // mysql> insert into t1 values (20160622, 20160622);
  // mysql> select a, b, case '160622' when b then 'equal to b' when a then 'equal to a' end from t1;
  // +------------+----------+---------------------------------------------------------------------+
  // | a          | b        | case '160622' when b then 'equal to b' when a then 'equal to a' end |
  // +------------+----------+---------------------------------------------------------------------+
  // | 2016-06-22 | 20160622 | equal to a                                                          |
  // +------------+----------+---------------------------------------------------------------------+
  // cmp type of '160622' VS b(int 20160622) is NOT date, not equal.
  // cmp type of '160622' VS a(date 2016-06-22) is date (or maybe datetime), equal.
  // so we need a cmp type array, not a single cmp type.

  // case c1
  //  when 10 then expr1
  //  when 11 then expr2
  //  else expr3
  // param_count = 3, param[i] is the type of expri
  // if [else expr3] not provided, param[2] is NULL and param_count is 3
  int ret = OB_SUCCESS;
  if (OB_ISNULL(types_stack)) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("null types", K(ret));
  } else if (OB_UNLIKELY(param_num < 3 || param_num % 2 != 0)) {  // an implicit 'else expr' element is added by caller
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("param_num is not correct", K(param_num));
  } else {  // param_num >= 4 and param_num is even

    /*  take "case c1 when x1 then y1 when x2 then y2 else y3" as an example
        After studying the behavior of mysql,
        we use c1, x1, x2 to deduce calc collation which will be used in comparison
        we use y1, y2, y3 to deduce the result type and collation
    */
    int64_t cond_type_count = param_num / 2;
    int64_t val_type_count = param_num / 2;
    ObExprResType tmp_res_type;
    const ObLengthSemantics default_length_semantics =
        (OB_NOT_NULL(type_ctx.get_session()) ? type_ctx.get_session()->get_actual_nls_length_semantics() : LS_BYTE);
    if (OB_FAIL(aggregate_result_type_for_case(tmp_res_type,
            types_stack,
            cond_type_count,
            type_ctx.get_coll_type(),
            lib::is_oracle_mode(),
            default_length_semantics,
            type_ctx.get_session(),
            FALSE))) {
      LOG_WARN("failed to get result type for cmp", K(ret));
    } else if (OB_FAIL(aggregate_result_type_for_case(type,
                   types_stack + cond_type_count,
                   val_type_count,
                   type_ctx.get_coll_type(),
                   lib::is_oracle_mode(),
                   default_length_semantics,
                   type_ctx.get_session()))) {
      LOG_WARN("failed to get result type", K(ret));
    } else {
      // cmp type will be computed dynamically
      type.set_calc_collation_type(tmp_res_type.get_collation_type());
      type.set_calc_collation_level(tmp_res_type.get_collation_level());
      ObExprOperator::calc_result_flagN(type, types_stack + cond_type_count, val_type_count);
      for (int64_t i = 1; OB_SUCC(ret) && i < cond_type_count; ++i) {
        ObObjType calc_type;
        if (ob_is_enumset_tc(types_stack[i].get_type())) {
          if (OB_FAIL(ObExprResultTypeUtil::get_relational_cmp_type(
                  calc_type, types_stack[0].get_type(), types_stack[i].get_type()))) {
            LOG_WARN("failed to get_cmp_type", K(types_stack[0]), K(types_stack[i]), K(ret));
          } else {
            types_stack[i].set_calc_type(calc_type);
          }
        }
      }
    }
  }
  return ret;
}

int ObExprArgCase::calc_resultN(ObObj& result, const ObObj* objs_stack, int64_t param_num, ObExprCtx& expr_ctx) const
{
  int ret = OB_SUCCESS;
  EXPR_DEFINE_CAST_CTX(expr_ctx, CM_NONE);
  ObCompareCtx cmp_ctx(
      ObMaxType, result_type_.get_calc_collation_type(), false, expr_ctx.tz_offset_, default_null_pos());
  if (OB_UNLIKELY(need_cast_)) {
    ret = calc_with_cast(result, objs_stack, param_num, cmp_ctx, cast_ctx, result_type_, ObExprArgCase::get_cmp_type);
  } else {
    ret = calc_without_cast(result, objs_stack, param_num, cast_ctx, cmp_ctx, result_type_);
  }
  return ret;
}

int ObExprArgCase::calc_with_cast(ObObj& result, const ObObj* objs_stack, int64_t param_num, ObCompareCtx& cmp_ctx,
    ObCastCtx& cast_ctx, const ObExprResType& res_type, const ob_get_cmp_type_func get_cmp_type_func)
{
  int ret = OB_SUCCESS;
  bool match_when = false;
  ObObj tmp_result;
  ObObj equal_result;
  if (OB_ISNULL(objs_stack) || OB_ISNULL(get_cmp_type_func)) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("stack or get_cmp_type_func is null", K(objs_stack), K(get_cmp_type_func));
  } else if (OB_UNLIKELY(param_num < 2)) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("invalid param_num", K(param_num));
  } else {
    // case c1
    //  when 10 then expr1
    //  when 11 then expr2
    //  else expr3
    // param_count = 2+2N
    //
    // case c1 when 10 then expr1
    //         when 11 then expr2
    // param_count = 1+2N

    const bool has_else = (param_num % 2 == 0);
    int64_t loop = (has_else) ? param_num - 2 : param_num;
    for (int64_t i = 1; OB_SUCC(ret) && i < loop; i += 2) {
      if (OB_FAIL(get_cmp_type_func(
              cmp_ctx.cmp_type_, objs_stack[0].get_type(), objs_stack[i].get_type(), res_type.get_calc_type()))) {
        LOG_WARN("Get cmp type failed", K(ret));
      } else {
        cast_ctx.dest_collation_ = cmp_ctx.cmp_cs_type_;
        ret = ObExprEqual::calc_cast(equal_result, objs_stack[0], objs_stack[i], cmp_ctx, cast_ctx);
        if (OB_SUCC(ret)) {
          if (ObNullType == equal_result.get_type()) {
          } else if (OB_UNLIKELY(ObInt32Type != equal_result.get_type())) {  // ObExprEqual::calc returns int32
            ret = OB_ERR_UNEXPECTED;
            LOG_WARN("unexpected calc result type. must be tiny", K(equal_result));
          } else if (equal_result.is_true()) {
            match_when = true;
            tmp_result = objs_stack[i + 1];
            break;
          }
        }
      }
    }
    if (OB_SUCC(ret)) {
      if (!match_when) {
        if (param_num % 2 == 0) {
          LOG_DEBUG("match else wrong", K(param_num));
          tmp_result = objs_stack[param_num - 1];  // match else (default value)
        } else {
          tmp_result.set_null();
        }
      }
      cast_ctx.dest_collation_ = res_type.get_collation_type();
      ret = ObObjCaster::to_type(res_type.get_type(), res_type.get_collation_type(), cast_ctx, tmp_result, result);
    }
  }
  return ret;
}

int ObExprArgCase::calc_without_cast(ObObj& result, const ObObj* objs_stack, int64_t param_num, ObCastCtx& cast_ctx,
    ObCompareCtx& cmp_ctx, const ObExprResType& res_type)
{
  int ret = OB_SUCCESS;
  bool match_when = false;
  ObObj tmp_result;
  ObObj equal_result;
  int64_t i = 0;
  if (OB_ISNULL(objs_stack)) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("stack is null", K(objs_stack), K(ret));
  } else if (OB_UNLIKELY(param_num < 2)) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("invalid param_num", K(param_num));
  } else {
    // case c1
    //  when 10 then expr1
    //  when 11 then expr2
    //  else expr3
    // param_count = 2+2N
    //
    // case c1 when 10 then expr1
    //         when 11 then expr2
    // param_count = 1+2N

    const bool has_else = (param_num % 2 == 0);
    int64_t loop = (has_else) ? param_num - 2 : param_num;
    bool need_cast = false;
    for (i = 1; OB_SUCC(ret) && i < loop; i += 2) {
      need_cast = true;
      if (OB_FAIL(ObExprEqual::calc_without_cast(equal_result, objs_stack[0], objs_stack[i], cmp_ctx, need_cast))) {
        LOG_WARN("calc failed", K(objs_stack[0]), K(objs_stack[i]), K(ret));
      } else if (need_cast) {
        ret = OB_ERR_UNEXPECTED;
        LOG_WARN("unexpected error. cast should not be necessary", K(objs_stack[0]), K(objs_stack[i]), K(ret));
      } else {
        if (ObNullType == equal_result.get_type()) {
        } else if (OB_UNLIKELY(ObInt32Type != equal_result.get_type())) {  // ObExprEqual::calc returns int32 result
          ret = OB_ERR_UNEXPECTED;
          LOG_WARN("unexpected calc result type. must be tiny", K(equal_result));
        } else if (equal_result.is_true()) {
          match_when = true;
          tmp_result = objs_stack[i + 1];
          break;
        }
      }
    }  // end for
    if (OB_SUCC(ret)) {
      if (!match_when) {
        if (param_num % 2 == 0) {
          LOG_DEBUG("match else wrong", K(param_num));
          tmp_result = objs_stack[param_num - 1];  // match else (default value)
        } else {
          tmp_result.set_null();
        }
      }
      cast_ctx.dest_collation_ = res_type.get_collation_type();
      ret = ObObjCaster::to_type(res_type.get_type(), res_type.get_collation_type(), cast_ctx, tmp_result, result);
    }  // end ob_succ(ret)
  }
  return ret;
}

int ObExprArgCase::cg_expr(ObExprCGCtx&, const ObRawExpr&, ObExpr&) const
{
  int ret = OB_ERR_UNEXPECTED;
  LOG_WARN("this expr should be rewrote in new engine", K(ret));
  return ret;
}

}  // namespace sql
}  // namespace oceanbase
