/**
 * 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
#define OB_MAX_WEIGHT  OB_MAX_VARCHAR_LENGTH

#include <string.h>
#include "sql/parser/ob_item_type.h"
#include "sql/engine/expr/ob_expr_weight_string.h"
#include "sql/engine/expr/ob_expr_operator.h"
#include "share/object/ob_obj_cast.h"
#include "lib/oblog/ob_log.h"
#include "sql/engine/expr/ob_datum_cast.h"
#include "ob_expr_util.h"

using namespace oceanbase::common;

namespace oceanbase
{
namespace sql
{
ObExprWeightString::ObExprWeightString(ObIAllocator &alloc)
    : ObStringExprOperator(alloc, T_FUN_SYS_WEIGHT_STRING, N_WEIGHT_STRING, MORE_THAN_ZERO)
{
}
ObExprWeightString::~ObExprWeightString()
{
}
int ObExprWeightString::calc_result_typeN(ObExprResType &type,
                                          ObExprResType *types_stack,
                                          int64_t param_num,
                                          ObExprTypeCtx &type_ctx) const
{
  int ret = OB_SUCCESS;
  UNUSED(param_num);
  CK (OB_NOT_NULL(type_ctx.get_session()));
  if (NOT_ROW_DIMENSION != row_dimension_ || ObMaxType == types_stack[0].get_type()) {
    ret = OB_ERR_INVALID_TYPE_FOR_OP;
  } else {
    if (types_stack[0].get_type() > ObUNumberType ) {
      // 输入不是数字类型时
      type_ctx.set_cast_mode(type_ctx.get_cast_mode() | CM_NULL_ON_WARN);
      types_stack[0].set_calc_type(ObVarcharType);
    }
    int max_length = OB_MAX_VARBINARY_LENGTH; // The maximum length of the result of WEIGHT_STRING()
    int result_length = types_stack[1].get_param().get_int();
    int nweight = types_stack[2].get_param().get_int();
    bool as_binary = types_stack[4].get_param().get_int();
    ObCollationLevel coll_level = CS_LEVEL_INVALID;
    if (as_binary) {
      coll_level = CS_LEVEL_IMPLICIT;
    } else {
      coll_level = types_stack[0].get_collation_level();
    }
    ObCollationType collation_type = types_stack[0].get_collation_type();
    const ObCharsetInfo *cs = ObCharset::get_charset(collation_type); 
    if (types_stack[0].get_type() == ObDateTimeType ||
        types_stack[0].get_type() == ObTimestampType || 
        types_stack[0].get_type() == ObDateType ||
        types_stack[0].get_type() == ObTimeType ) {
      // 日期、时间等类型，max_lenght是输入的类型的长度
      max_length = types_stack[0].get_length();
    } else if (result_length > 0) {
      max_length = result_length;
    } else if (as_binary) {
      // as_binary的情况下，以nweight作为输出结果的max_length
      max_length = nweight; 
    } else {
      // 输入为 char的情况下，使用cs->mbmaxlen计算max_length
      max_length = cs->mbmaxlen * max(nweight, types_stack[0].get_length()*cs->mbmaxlen);
    }
    ObObj aaa = types_stack[0].get_param();
    // 推导结果
    type.set_varchar();
    type.set_collation_type(CS_TYPE_BINARY);
    type.set_collation_level(coll_level);
    type.set_length(max_length);
  }
  return ret;
}

int ObExprWeightString::calc_resultN(common::ObObj &result , const common::ObObj *objs_array,
                                      int64_t param_num,common::ObExprCtx &expr_ctx) const
{
  int ret = OB_SUCCESS;
  if (OB_ISNULL(expr_ctx.calc_buf_)) {
    ret = OB_NOT_INIT;
    LOG_WARN("varchar buffer not init", K(ret));
  }
  if (param_num == 5) {
    if (OB_UNLIKELY(objs_array[0].is_null()) ||
        OB_UNLIKELY(objs_array[1].is_null()) ||
        OB_UNLIKELY(objs_array[2].is_null()) ||
        OB_UNLIKELY(objs_array[3].is_null()) ||
        OB_UNLIKELY(objs_array[4].is_null()) ) {
      result.set_null();
    } else {
      const ObString str =  objs_array[0].get_string();
      int result_length = objs_array[1].get_int();
      int nweights = objs_array[2].get_int();
      int flags = objs_array[3].get_int();
      bool as_binary = objs_array[4].get_int();
      ObCollationType collation_type = CS_TYPE_INVALID;
      if (as_binary) {
        collation_type  = CS_TYPE_BINARY;
      } else {
        collation_type = objs_array[0].get_collation_type();
      }
      const ObCharsetInfo *cs = ObCharset::get_charset(collation_type);
      flags = ob_strxfrm_flag_normalize(flags, cs->levels_for_order);
      // calc the length of result
      size_t frm_length = 0; 
      size_t tmp_length = 0;
      if (result_length > 0) {
        tmp_length = result_length;
      } else {
        tmp_length = cs->coll->strnxfrmlen(cs, cs->mbmaxlen*max(str.length() , nweights));
      }
      int is_valid_unicode_tmp = 1;
      char *out_buf;
      if (OB_ISNULL(out_buf = static_cast<char*>(expr_ctx.calc_buf_->alloc(tmp_length)))) {
        result.set_null();
        ret = OB_ALLOCATE_MEMORY_FAILED;
        LOG_ERROR("alloc memory failed",K(ret), K(out_buf), K(tmp_length));
      } else {
        frm_length = cs->coll->strnxfrm(cs,
                                      reinterpret_cast<uchar *>(out_buf), 
                                      tmp_length, 
                                      nweights ? nweights: tmp_length,
                                      reinterpret_cast<const uchar *>(str.ptr()), 
                                      str.length(),
                                      flags, 
                                      &is_valid_unicode_tmp);
        result.set_varchar(out_buf,frm_length);
      }
    } 
  } else {
    ret = OB_INVALID_ARGUMENT_NUM;
    result.set_null();
    LOG_ERROR("invalid argument num",K(ret), K(param_num), K(objs_array));
  }
  return ret;
}

int ObExprWeightString::eval_weight_string(const ObExpr &expr, ObEvalCtx &ctx, ObDatum &res_datum)
{
  int ret = OB_SUCCESS;
  ObDatum *arg = NULL;
  ObDatum *result_length_arg = NULL;
  ObDatum *nweights_arg = NULL;
  ObDatum *flags_arg = NULL;
  ObDatum *as_binary_arg = NULL;
  if (OB_FAIL(expr.args_[0]->eval(ctx, arg)) ||
      OB_FAIL(expr.args_[1]->eval(ctx, result_length_arg)) ||
      OB_FAIL(expr.args_[2]->eval(ctx, nweights_arg)) ||
      OB_FAIL(expr.args_[3]->eval(ctx, flags_arg)) ||
      OB_FAIL(expr.args_[4]->eval(ctx, as_binary_arg))) {
    LOG_WARN("eval arg failed", K(ret), 
                                KP(arg),
                                KP(result_length_arg),
                                KP(nweights_arg),
                                KP(flags_arg),
                                KP(as_binary_arg));
  } else if (arg->is_null() ||
             arg->get_string() == NULL ||
             expr.args_[0]->datum_meta_.type_ <= ObUNumberType ) {
    // The input string is NULL or numeric
    res_datum.set_null();
  } else {
    const ObString str = arg->get_string();
    int result_length = result_length_arg->get_int();
    int nweights = nweights_arg->get_int();
    int flags = flags_arg->get_int();
    bool as_binary = as_binary_arg->get_int();

    // Get the character set and collation information of the input string
    ObCollationType collation_type = CS_TYPE_INVALID;
    if (as_binary) {
      collation_type  = CS_TYPE_BINARY;
    } else {
      collation_type = expr.args_[0]->datum_meta_.cs_type_;
    }
    const ObCharsetInfo *cs = ObCharset::get_charset(collation_type); 
    flags = ob_strxfrm_flag_normalize(flags, cs->levels_for_order);
    // calc the length of result
    size_t frm_length = 0; 
    size_t tmp_length = 0;
    if (result_length > 0) {
      tmp_length = result_length;
    } else {
      tmp_length = cs->coll->strnxfrmlen(cs, cs->mbmaxlen*max(str.length() , nweights));
    }
    int is_valid_unicode_tmp = 1;
    char *out_buf = expr.get_str_res_mem(ctx, tmp_length);
    if (OB_ISNULL(out_buf)) {
      ret = OB_ALLOCATE_MEMORY_FAILED;
    } else {
      frm_length = cs->coll->strnxfrm(cs,
                                    reinterpret_cast<uchar *>(out_buf), 
                                    tmp_length, 
                                    nweights ? nweights: tmp_length,
                                    reinterpret_cast<const uchar *>(str.ptr()), 
                                    str.length(),
                                    flags, 
                                    &is_valid_unicode_tmp);
      res_datum.set_string(out_buf,frm_length);
    }
  }
  return ret;
}

int ObExprWeightString::cg_expr(ObExprCGCtx &op_cg_ctx, const ObRawExpr &raw_expr,
    ObExpr &rt_expr) const
{
  UNUSED(op_cg_ctx);
  UNUSED(raw_expr);
  rt_expr.eval_func_ = ObExprWeightString::eval_weight_string;
  return OB_SUCCESS;
}

uint64_t ObExprWeightString::ob_strxfrm_flag_normalize(uint64_t flags, uint64_t maximum)
{
   /* If levels are omitted, then 1-maximum is assumed*/
  if (!(flags & OB_STRXFRM_LEVEL_ALL)) {
    static uint64_t def_level_flags[]= {0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F };
    uint64_t flag_pad= flags & (OB_STRXFRM_PAD_WITH_SPACE | OB_STRXFRM_PAD_TO_MAXLEN);
    flags= def_level_flags[maximum] | flag_pad;
  }
  else {
    uint64_t i = 0;
    uint64_t flag_lev= flags & OB_STRXFRM_LEVEL_ALL;
    uint64_t flag_dsc= (flags >> OB_STRXFRM_DESC_SHIFT) & OB_STRXFRM_LEVEL_ALL;
    uint64_t flag_rev= (flags >> OB_STRXFRM_REVERSE_SHIFT) & OB_STRXFRM_LEVEL_ALL;
    uint64_t flag_pad= flags & (OB_STRXFRM_PAD_WITH_SPACE | OB_STRXFRM_PAD_TO_MAXLEN);
    /*
      If any level number is greater than the maximum,
      it is treated as the maximum.
    */
    maximum--;
    flags= 0;
    for (i= 0; i < OB_STRXFRM_NLEVELS; i++)
    {
      uint64_t src_bit= 1 << i;
      if (flag_lev & src_bit) {
        uint64_t dst_bit= 1 << std::min(i, maximum);
        flags|= dst_bit;
        flags|= (flag_dsc & dst_bit) << OB_STRXFRM_DESC_SHIFT;
        flags|= (flag_rev & dst_bit) << OB_STRXFRM_REVERSE_SHIFT;
      }
    }
    flags|= flag_pad;
  }
  return flags;
}


}
}
