/**
 * 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_md5.h"
#include <openssl/md5.h>
#include "share/object/ob_obj_cast.h"
//#include "sql/engine/expr/ob_expr_promotion_util.h"
#include "sql/session/ob_sql_session_info.h"

namespace oceanbase {
using namespace common;
namespace sql {

ObExprMd5::ObExprMd5(ObIAllocator& alloc) : ObStringExprOperator(alloc, T_FUN_SYS_MD5, N_MD5, 1)
{}

ObExprMd5::~ObExprMd5()
{}

inline int ObExprMd5::calc_result_type1(ObExprResType& type, ObExprResType& str, ObExprTypeCtx& type_ctx) const
{
  int ret = OB_SUCCESS;
  static const int64_t MD5_RES_BIT_LENGTH = 32;
  type.set_varchar();
  type.set_length(MD5_RES_BIT_LENGTH);
  type.set_collation_type(get_default_collation_type(type.get_type(), *type_ctx.get_session()));
  type.set_collation_level(CS_LEVEL_COERCIBLE);

  str.set_calc_type(ObVarcharType);

  return ret;
}

int ObExprMd5::calc_result1(ObObj& result, const ObObj& str_obj, 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));
  } else if (OB_UNLIKELY(str_obj.is_null())) {
    result.set_null();
  } else {
    TYPE_CHECK(str_obj, ObVarcharType);
    ObString raw_str = str_obj.get_string();
    ret = calc_md5(result, raw_str, expr_ctx.calc_buf_, result_type_.get_collation_type());
    if (OB_LIKELY(OB_SUCCESS == ret && !result.is_null())) {
      result.set_collation(result_type_);
    }
  }
  return ret;
}

int ObExprMd5::calc_md5(
    ObObj& result, const ObString& str, common::ObIAllocator* allocator, ObCollationType col_type) const
{
  int ret = OB_SUCCESS;
  UNUSED(col_type);
  char* md5_sum_buf = NULL;
  char* md5_str_buf = NULL;
  ObString::obstr_size_t md5_sum_len = MD5_LENGTH;
  // convert md5 sum to hexadecimal string, we need double bytes.
  // an extra byte for '\0' at the end of md5 str.
  ObString::obstr_size_t md5_str_len = MD5_LENGTH * 2 + 1;
  if (OB_ISNULL(allocator)) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("allocator null pointer", K(allocator));
  } else if (OB_ISNULL(md5_sum_buf = static_cast<char*>(allocator->alloc(md5_sum_len))) ||
             OB_ISNULL(md5_str_buf = static_cast<char*>(allocator->alloc(md5_str_len)))) {
    result.set_null();
    ret = OB_ALLOCATE_MEMORY_FAILED;
    LOG_ERROR("alloc memory failed", K(ret), K(md5_sum_buf), K(md5_str_buf), K(md5_sum_len), K(md5_str_len));
  } else {
    unsigned char* res = MD5(
        reinterpret_cast<const unsigned char*>(str.ptr()), str.length(), reinterpret_cast<unsigned char*>(md5_sum_buf));
    if (OB_ISNULL(res)) {
      // MD5() in openssl always return an pointer not NULL, so we need not check return value.
      // see:
      // http://www.openssl.org/docs/crypto/md5.html#DESCRIPTION
      // http://www.openssl.org/docs/crypto/md5.html#RETURN_VALUES
      // Even so, we HAVE TO check it here. You know it.
      ret = OB_ERR_UNEXPECTED;
      LOG_WARN("md5 res null pointer", K(ret), K(res));
    } else if (OB_FAIL(to_hex_cstr(md5_sum_buf, md5_sum_len, md5_str_buf, md5_str_len))) {
      result.set_null();
      LOG_WARN("to hex cstr error", K(ret));
    } else {
      size_t tmp_len = ObCharset::casedn(CS_TYPE_UTF8MB4_BIN, md5_str_buf, md5_str_len, md5_str_buf, md5_str_len);
      ObString::obstr_size_t len = static_cast<ObString::obstr_size_t>(tmp_len);
      ObString md5_str(len, len - 1 /* do not contain \0 in the result */, md5_str_buf);
      result.set_varbinary(md5_str);
    }
  }
  return ret;
}

int ObExprMd5::cg_expr(ObExprCGCtx& op_cg_ctx, const ObRawExpr& raw_expr, ObExpr& rt_expr) const
{
  UNUSED(op_cg_ctx);
  UNUSED(raw_expr);
  int ret = OB_SUCCESS;
  if (rt_expr.arg_cnt_ != 1) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("md5 expr should have one param", K(ret), K(rt_expr.arg_cnt_));
  } else if (OB_ISNULL(rt_expr.args_) || OB_ISNULL(rt_expr.args_[0])) {
    ret = OB_ERR_UNEXPECTED;
    LOG_WARN("children of md5 expr is null", K(ret), K(rt_expr.args_));
  } else {
    CK(ObVarcharType == rt_expr.args_[0]->datum_meta_.type_);
    rt_expr.eval_func_ = ObExprMd5::calc_md5;
  }
  return ret;
}

int ObExprMd5::calc_md5(const ObExpr& expr, ObEvalCtx& ctx, ObDatum& expr_datum)
{
  int ret = OB_SUCCESS;
  ObDatum* param_datum = NULL;
  if (OB_FAIL(expr.args_[0]->eval(ctx, param_datum))) {
    LOG_WARN("eval param value failed");
  } else if (OB_UNLIKELY(param_datum->is_null())) {
    expr_datum.set_null();
  } else {
    ObString raw_str = param_datum->get_string();
    ObString::obstr_size_t md5_raw_res_len = MD5_LENGTH;
    // convert md5 sum to hexadecimal string, we need double bytes.
    // an extra byte for '\0' at the end of md5 str.
    ObString::obstr_size_t md5_hex_res_len = MD5_LENGTH * 2 + 1;
    char* md5_raw_res_buf = static_cast<char*>(ctx.get_reset_tmp_alloc().alloc(md5_raw_res_len));
    char* md5_hex_res_buf = expr.get_str_res_mem(ctx, md5_hex_res_len);
    if (OB_ISNULL(md5_raw_res_buf) || OB_ISNULL(md5_hex_res_buf)) {
      expr_datum.set_null();
      ret = OB_ALLOCATE_MEMORY_FAILED;
      LOG_ERROR("alloc memory failed",
          K(ret),
          K(md5_raw_res_buf),
          K(md5_hex_res_buf),
          K(md5_raw_res_len),
          K(md5_hex_res_len));
    } else {
      unsigned char* res = MD5(reinterpret_cast<const unsigned char*>(raw_str.ptr()),
          raw_str.length(),
          reinterpret_cast<unsigned char*>(md5_raw_res_buf));
      if (OB_ISNULL(res)) {
        // MD5() in openssl always return an pointer not NULL, so we need not check return value.
        // Even so, we HAVE TO check it here. You know it.
        ret = OB_ERR_UNEXPECTED;
        LOG_WARN("md5 res null pointer", K(ret), K(res));
      } else if (OB_FAIL(to_hex_cstr(md5_raw_res_buf, md5_raw_res_len, md5_hex_res_buf, md5_hex_res_len))) {
        expr_datum.set_null();
        LOG_WARN("to hex cstr error", K(ret));
      } else {
        size_t tmp_len =
            ObCharset::casedn(CS_TYPE_UTF8MB4_BIN, md5_hex_res_buf, md5_hex_res_len, md5_hex_res_buf, md5_hex_res_len);
        ObString::obstr_size_t len = static_cast<ObString::obstr_size_t>(tmp_len);
        ObString md5_str(len, len - 1 /* do not contain \0 in the result */, md5_hex_res_buf);
        expr_datum.set_string(md5_str);
      }
    }
  }
  return ret;
}

}  // namespace sql
}  // namespace oceanbase
