/**
 * 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.
 */

#ifndef OCEANBASE_ENGINE_OB_SERIALIZABLE_FUNCTION_H_
#define OCEANBASE_ENGINE_OB_SERIALIZABLE_FUNCTION_H_

#include "lib/utility/serialization.h"
#include "lib/hash_func/murmur_hash.h"

namespace oceanbase {
namespace sql {

struct ObSerializeFuncTag {};
typedef void (*serializable_function)(ObSerializeFuncTag&);

// serialize help macro, can be used in OB_SERIALIZE_MEMBER like this:
// OB_SERIALIZE_MEMBER(Foo, SER_FUNC(func_));
#define SER_FUNC(f) *(oceanbase::sql::serializable_function*)(&f)

// Serialize function array (SFA) id define, append only (before OB_SFA_MAX)
// can not delete or reorder.
#define SER_FUNC_ARRAY_ID_ENUM                                                                              \
  OB_SFA_MIN, OB_SFA_ALL_MISC, OB_SFA_DATUM_NULLSAFE_CMP, OB_SFA_DATUM_NULLSAFE_STR_CMP, OB_SFA_EXPR_BASIC, \
      OB_SFA_EXPR_STR_BASIC, OB_SFA_RELATION_EXPR_EVAL, OB_SFA_RELATION_EXPR_EVAL_STR, OB_SFA_DATUM_CMP,    \
      OB_SFA_DATUM_CMP_STR, OB_SFA_DATUM_CAST_ORACLE_IMPLICIT, OB_SFA_DATUM_CAST_ORACLE_EXPLICIT,           \
      OB_SFA_DATUM_CAST_MYSQL_IMPLICIT, OB_SFA_DATUM_CAST_MYSQL_ENUMSET_IMPLICIT, OB_SFA_SQL_EXPR_EVAL,     \
      OB_SFA_SQL_EXPR_ABS_EVAL, OB_SFA_SQL_EXPR_NEG_EVAL, OB_SFA_MAX

enum ObSerFuncArrayID { SER_FUNC_ARRAY_ID_ENUM };

// add unused ObSerFuncArrayID here
#define UNUSED_SER_FUNC_ARRAY_ID_ENUM OB_SFA_MIN, OB_SFA_MAX

class ObFuncSerialization {
public:
  // called before worker threads started.
  static void init()
  {
    get_hash_table();
  }

  // used in REG_SER_FUNC_ARRAY macro, can not used directly
  static bool reg_func_array(const ObSerFuncArrayID id, void** array, const int64_t size);

  // get serialize index by function pointer
  // return zero if fun is NULL
  // return non zero fun is serializable
  // return OB_INVALID_INDEX if function is not serializable
  static uint64_t get_serialize_index(void* func);

  // get function by serialize index
  // return NULL if %idx out of bound.
  OB_INLINE static void* get_serialize_func(const uint64_t idx);

  //
  // Convert N x N two dimension array to single dimension array which index is stable
  // while N extending. e.g:
  //
  //  00 01 02
  //  10 11 12   ==>  00 01 10 11 02 20 12 21 22
  //  20 21 22
  //
  //
  // Usage:
  //
  // fuc_array[N][N][2] can convert to ser_func_array[N * N][2] with:
  // convert_NxN_function_array(ser_func_array, func_array, N, 2, 0, 2).
  //
  // func_array[N][N][2] extend to func_array[N][N][3], the serialize function array should
  // split into to array:
  //   ser_func_array0[N * N][2] with: convert_NxN_array(ser_func_array0, func_array, N, 3, 0, 2).
  //   ser_func_array1[N * N][2] with: convert_NxN_array(ser_func_array0, func_array, N, 3, 2, 1).
  //
  //
  static bool convert_NxN_array(void** dst, void** src, const int64_t n, const int64_t row_size = 1,
      const int64_t copy_row_idx = 0, const int64_t copy_row_cnt = 1);

  struct FuncIdx {
    void* func_;
    uint64_t idx_;
  };

  struct FuncIdxTable {
    FuncIdx* buckets_;
    uint64_t bucket_size_;
    uint64_t bucket_size_mask_;
  };

private:
  const static int64_t ARRAY_IDX_SHIFT_BIT = 32;

  struct FuncArray {
    void** funcs_;
    int64_t size_;
  };

  static uint64_t get_array_idx(const uint64_t idx)
  {
    return idx >> ARRAY_IDX_SHIFT_BIT;
  }
  static uint64_t get_func_idx(const uint64_t idx)
  {
    return idx & ((1ULL << ARRAY_IDX_SHIFT_BIT) - 1);
  }
  static uint64_t make_combine_idx(const uint64_t array_idx, const uint64_t func_idx)
  {
    return (array_idx << ARRAY_IDX_SHIFT_BIT) | (((1ULL << ARRAY_IDX_SHIFT_BIT) - 1) & func_idx);
  }

  static inline uint64_t hash(const void* func)
  {
    return common::murmurhash(&func, sizeof(func), 0);
  }

  static const FuncIdxTable& get_hash_table()
  {
    static FuncIdxTable* g_table = NULL;
    if (OB_UNLIKELY(NULL == g_table)) {
      g_table = &create_hash_table();
      check_hash_table_valid();
    }
    return *g_table;
  }

  static void check_hash_table_valid();

  // create hash table never fail, return a default empty hash table if OOM.
  static FuncIdxTable& create_hash_table();

  static FuncArray g_all_func_arrays[OB_SFA_MAX];
};

inline uint64_t ObFuncSerialization::get_serialize_index(void* func)
{
  uint64_t idx = 0;
  if (OB_LIKELY(0 != func)) {
    const FuncIdxTable& ht = get_hash_table();
    const uint64_t hash_val = hash(func);
    for (uint64_t i = 0; i < ht.bucket_size_; i++) {
      const FuncIdx& fi = ht.buckets_[(hash_val + i) & ht.bucket_size_mask_];
      if (fi.func_ == func) {
        idx = fi.idx_;
        break;
      } else if (NULL == fi.func_) {
        idx = common::OB_INVALID_INDEX;
        break;
      }
    }
  }
  return idx;
}

OB_INLINE void* ObFuncSerialization::get_serialize_func(const uint64_t idx)
{
  void* func = NULL;
  if (OB_LIKELY(idx > 0)) {
    const uint64_t array_idx = get_array_idx(idx);
    const uint64_t func_idx = get_func_idx(idx);
    if (OB_UNLIKELY(array_idx >= OB_SFA_MAX) || OB_UNLIKELY(func_idx >= g_all_func_arrays[array_idx].size_)) {
      int ret = common::OB_ERR_UNEXPECTED;
      SQL_ENG_LOG(WARN, "function not found", K(ret), K(idx), K(array_idx), K(func_idx));
    } else {
      func = g_all_func_arrays[array_idx].funcs_[func_idx];
    }
  }
  return func;
}

#define REG_SER_FUNC_ARRAY(id, array, size)                    \
  static_assert(id >= 0 && id < OB_SFA_MAX, "too big id" #id); \
  bool g_reg_ser_func_##id = ObFuncSerialization::reg_func_array(id, reinterpret_cast<void**>(array), size);

}  // end namespace sql

namespace common {
namespace serialization {

inline int64_t encoded_length(sql::serializable_function)
{
  return sizeof(uint64_t);
}

inline int encode(char* buf, const int64_t buf_len, int64_t& pos, sql::serializable_function func)
{
  int ret = OB_SUCCESS;
  const uint64_t idx = sql::ObFuncSerialization::get_serialize_index(reinterpret_cast<void*>(func));
  if (OB_UNLIKELY(OB_INVALID_INDEX == idx)) {
    ret = OB_INVALID_INDEX;
    SQL_LOG(WARN, "function not serializable", K(ret), KP(func));
  } else {
    ret = encode_i64(buf, buf_len, pos, idx);
  }
  return ret;
}

inline int decode(const char* buf, const int64_t data_len, int64_t& pos, sql::serializable_function& func)
{
  int ret = OB_SUCCESS;
  uint64_t idx = 0;
  ret = decode_i64(buf, data_len, pos, reinterpret_cast<int64_t*>(&idx));
  if (OB_SUCC(ret)) {
    if (OB_UNLIKELY(0 == idx)) {
      func = NULL;
    } else {
      func = reinterpret_cast<sql::serializable_function>(sql::ObFuncSerialization::get_serialize_func(idx));
      if (NULL == func) {
        ret = OB_ERR_UNEXPECTED;
        SQL_LOG(WARN, "function not found by idx", K(ret), K(idx));
      }
    }
  }
  return ret;
}

}  // end namespace serialization
}  // end namespace common
}  // end namespace oceanbase

#endif  // OCEANBASE_ENGINE_OB_SERIALIZABLE_FUNCTION_H_
