/**
 * Copyright (c) 2021 OceanBase
 * OceanBase Database Proxy(ODP) 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.
 */

#include <cmath>
#include "common/ob_obj_cast.h"
#include "share/part/ob_part_desc_hash.h"
#include "lib/charset/ob_mysql_global.h"

namespace oceanbase
{
namespace common
{
ObPartDescHash::ObPartDescHash() : is_oracle_mode_(false)
                                 , part_num_(0)
                                 , part_space_(0)
                                 , part_array_(NULL)
{
}

/*
  GET PARTITION ID

  If input is not single int value, get all partition ids; otherwise, get the particular one.
 */
int ObPartDescHash::get_part(ObNewRange &range,
                             ObIAllocator &allocator,
                             ObIArray<int64_t> &part_ids)
{
  int ret = OB_SUCCESS;
  if (1 != range.get_start_key().get_obj_cnt()) { // single value
    ret = OB_INVALID_ARGUMENT;
    COMMON_LOG(DEBUG, "hash part should be single key",
                      "obj_cnt", range.get_start_key().get_obj_cnt(), K(ret));
  } else {
    int64_t part_idx = -1;
    ObObj &src_obj = const_cast<ObObj &>(range.get_start_key().get_obj_ptr()[0]);
    if (is_oracle_mode_) {
      ret = calc_value_for_oracle(src_obj, allocator, part_idx);
    } else {
      ret = calc_value_for_mysql(src_obj, allocator, part_idx);
    }

    if (OB_SUCC(ret)) {
      int64_t part_id = -1;
      if (OB_FAIL(get_part_hash_idx(part_idx, part_id))) {
        COMMON_LOG(WARN, "fail to get part hash id", K(ret));
      } else if (OB_FAIL(part_ids.push_back(part_id))) {
        COMMON_LOG(WARN, "fail to push part_id", K(ret));
      }
    }
  }

  return ret;
}

bool ObPartDescHash::is_oracle_supported_type(const ObObjType type)
{
  bool supported = false;
  switch (type) {
    case ObIntType:
    case ObFloatType:
    case ObDoubleType:
    case ObNumberType:
    case ObDateTimeType:
    case ObCharType:
    case ObVarcharType:
        /*
    case ObTimestampTZType:
    case ObTimestampLTZType:
    case ObTimestampNanoType:
    */
    case ObRawType:
        /*
    case ObIntervalYMType:
    case ObIntervalDSType:
    */
    case ObNumberFloatType:
    case ObNCharType:
    case ObNVarchar2Type:
    {
      supported = true;
      break;
    }
    default: {
      supported = false;
    }
  }
  return supported;
}

uint64_t ObPartDescHash::calc_hash_value_with_seed(const ObObj &obj, int64_t seed)
{
  uint64 hval = 0;
  ObObjType type = obj.get_type();

  // set thread context before call hash func
  lib::set_oracle_mode(true);

  if (ObCharType == type || ObNCharType == type) {
    ObObj obj_trimmed;
    int32_t val_len = obj.get_val_len();
    const char* obj_str = obj.get_string_ptr();
    while (val_len > 1) {
      if (OB_PADDING_CHAR == *(obj_str + val_len - 1)) {
        --val_len;
      } else {
        break;
      }
    }
    obj_trimmed.set_collation_type(obj.get_collation_type());
    obj_trimmed.set_string(type, obj.get_string_ptr(), val_len);
    if (share::schema::PARTITION_FUNC_TYPE_HASH_V2 == part_func_type_) {
      hval = obj_trimmed.hash_murmur(seed);
    } else {
      hval = obj_trimmed.hash(seed);
    }
  } else {
    if (share::schema::PARTITION_FUNC_TYPE_HASH_V2 == part_func_type_) {
      hval = obj.hash_murmur(seed);
    } else {
      hval = obj.hash(seed);
    }
  }

  // clear context
  lib::set_oracle_mode(false);

  return hval;
}

int ObPartDescHash::calc_value_for_oracle(ObObj &src_obj, ObIAllocator &allocator, int64_t &part_idx)
{
  int ret = OB_SUCCESS;

  uint64_t hash_val = 0;

  src_obj.set_collation_type(cs_type_);
  ObCastCtx cast_ctx(&allocator, NULL, CM_NULL_ON_WARN, cs_type_);
  // use src_obj as buf_obj
  if (OB_FAIL(ObObjCasterV2::to_type(obj_type_, cast_ctx, src_obj, src_obj))) {
    COMMON_LOG(WARN, "failed to cast obj", K(src_obj), K(obj_type_), K(cs_type_), K(ret));
  } else {
    //1. calc hash value
    const ObObjType type = src_obj.get_type();
    if (ObNullType == type) {
      //do nothing, hash_code not changed
    } else if (!is_oracle_supported_type(type)) {
      ret = OB_INVALID_ARGUMENT;
      COMMON_LOG(WARN, "type is wrong", K(ret), K(src_obj), K(type));
    } else {
      hash_val = calc_hash_value_with_seed(src_obj, hash_val);
    }
  }

  // calc logic part
  if (OB_SUCC(ret)) {
    int64_t N = 0;
    int64_t powN = 0;
    const static int64_t max_part_num_log2 = 64;

    int64_t result_num = static_cast<int64_t>(hash_val);
    result_num = result_num < 0 ? -result_num : result_num;

    N = static_cast<int64_t>(std::log(part_num_) / std::log(2));
    if (N >= max_part_num_log2) {
      ret = OB_ERR_UNEXPECTED;
      COMMON_LOG(WARN, "result is too big", K(N), K(part_num_), K(result_num));
    } else {
      powN = (1ULL << N);
      part_idx = result_num % powN;
      if (part_idx + powN < part_num_ && (result_num & powN) == powN) {
        part_idx += powN;
      }
    }
    COMMON_LOG(DEBUG, "get hash part idx for oracle mode", K(ret), K(result_num), K(part_num_), K(N), K(powN), K(part_idx));
  }

  return ret;
}

int ObPartDescHash::calc_value_for_mysql(ObObj &src_obj, ObIAllocator &allocator, int64_t &part_idx)
{
  int ret = OB_SUCCESS;

  ObCastCtx cast_ctx(&allocator, NULL, CM_NULL_ON_WARN, CS_TYPE_INVALID);
  ObObjType target_type = ObIntType;
  if (OB_FAIL(ObObjCasterV2::to_type(target_type, cast_ctx, src_obj, src_obj))) {
    COMMON_LOG(INFO, "failed to cast to target type", K(target_type), K(src_obj), K(ret));
  } else {
    int64_t val = 0;
    if (OB_FAIL(src_obj.get_int(val))) {
      COMMON_LOG(WARN, "fail to get int", K(src_obj), K(ret));
    } else {
      part_idx = val % part_num_;
      COMMON_LOG(DEBUG, "get hash part idx for mysql mode", K(ret), K(val), K(part_num_), K(part_idx));
    }
  }

  return ret;
}

int ObPartDescHash::get_part_hash_idx(const int64_t part_idx, int64_t &part_id)
{
  int ret =OB_SUCCESS;

  part_id = part_idx;
  if (share::schema::PARTITION_LEVEL_ONE == part_level_) {
    if (part_idx >= 0 && NULL != part_array_) {
      part_id = part_array_[part_idx];
    }
  }
  if (OB_SUCC(ret)) {
    part_id = part_space_ << OB_PART_IDS_BITNUM | part_id;
    COMMON_LOG(DEBUG, "get hash part id", K(ret), K(part_num_), K(part_id));
  }
  return ret;
}
} // end of common
} // end of oceanbase
