/*
  Copyright (C) 2009-2018  Brazil
  Copyright (C) 2018-2024  Sutou Kouhei <kou@clear-code.com>

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

#pragma once

#include "grn.h"

#include "grn_ctx.h"
#include "grn_options.h"
#include "grn_rset.h"
#include "grn_store.h"

#include <groonga/command.h>
#include <groonga/token_filter.h>
#include <groonga/scorer.h>

#include <float.h>

#ifdef __cplusplus
extern "C" {
#endif

#define GRN_DB_DELIMITER            '.'
#define GRN_DB_PSEUDO_COLUMN_PREFIX '_'

#define GRN_N_RESERVED_TYPES        256

/* #define GRN_REFERENCE_COUNT_DEBUG */
#ifdef GRN_REFERENCE_COUNT_DEBUG
#  define grn_log_reference_count(...) printf(__VA_ARGS__)
#else
#  define grn_log_reference_count(...)
#endif

#define GRN_TABLE_GROUPED (0x01 << 0)
#define GRN_TABLE_IS_GROUPED(table)                                            \
  ((table)->header.impl_flags & GRN_TABLE_GROUPED)
#define GRN_TABLE_GROUPED_ON(table)                                            \
  ((table)->header.impl_flags |= GRN_TABLE_GROUPED)
#define GRN_TABLE_IS_MULTI_KEYS_GROUPED(table)                                 \
  (GRN_TABLE_IS_GROUPED(table) && table->header.domain == GRN_ID_NIL)

#define DB_OBJ(obj) ((grn_db_obj *)obj)

#define GRN_DEFINE_NAME_CUSTOM(obj, prefix)                                    \
  const char *prefix;                                                          \
  char prefix##_buffer[GRN_TABLE_MAX_KEY_SIZE];                                \
  int prefix##_size;                                                           \
  do {                                                                         \
    if (!obj) {                                                                \
      prefix = "(NULL)";                                                       \
      prefix##_size = (int)strlen(prefix);                                     \
    } else if (DB_OBJ(obj)->id == GRN_ID_NIL) {                                \
      prefix = "(temporary)";                                                  \
      prefix##_size = (int)strlen(prefix);                                     \
    } else {                                                                   \
      prefix##_size = grn_obj_name(ctx,                                        \
                                   (grn_obj *)obj,                             \
                                   prefix##_buffer,                            \
                                   GRN_TABLE_MAX_KEY_SIZE);                    \
      prefix = prefix##_buffer;                                                \
      if (prefix##_size == 0) {                                                \
        prefix = "(anonymous)";                                                \
        prefix##_size = (int)strlen(prefix);                                   \
      } else if (prefix##_size < GRN_TABLE_MAX_KEY_SIZE) {                     \
        prefix##_buffer[prefix##_size] = '\0';                                 \
      }                                                                        \
    }                                                                          \
  } while (false)

#define GRN_DEFINE_NAME(obj) GRN_DEFINE_NAME_CUSTOM(obj, name)

typedef struct _grn_db grn_db;
typedef struct _grn_proc grn_proc;

typedef struct {
  uint32_t count;
  grn_obj ids;
} grn_deferred_unref;

struct _grn_db {
  grn_db_obj obj;
  grn_obj *keys;
  grn_ja *specs;
  grn_hash *config;
  grn_tiny_array values;
  grn_critical_section lock;
  grn_cache *cache;
  grn_options *options;
  bool is_closing;
  grn_array *deferred_unrefs;
  bool is_deferred_unrefing;
};

#define GRN_SERIALIZED_SPEC_INDEX_SPEC          0
#define GRN_SERIALIZED_SPEC_INDEX_PATH          1
#define GRN_SERIALIZED_SPEC_INDEX_SOURCE        2
#define GRN_SERIALIZED_SPEC_INDEX_HOOK          3
#define GRN_SERIALIZED_SPEC_INDEX_TOKEN_FILTERS 4
#define GRN_SERIALIZED_SPEC_INDEX_NORMALIZERS   5
#define GRN_SERIALIZED_SPEC_INDEX_EXPR          4
#define GRN_SERIALIZED_SPEC_INDEX_JA_GENERATOR  4
#define GRN_SERIALIZED_SPEC_INDEX_RA_GENERATOR  4

typedef struct {
  grn_obj_header header;
  grn_id range;
} grn_obj_spec;

bool
grn_obj_spec_unpack(grn_ctx *ctx,
                    grn_id id,
                    void *encoded_spec,
                    uint32_t encoded_spec_size,
                    grn_obj_spec **spec,
                    grn_obj *decoded_spec,
                    const char *error_message_tag);

void
grn_obj_spec_get_path(grn_ctx *ctx,
                      grn_obj_spec *spec,
                      grn_id id,
                      char *buffer,
                      grn_db *db,
                      grn_obj *decoded_spec);

#define GRN_DB_EACH_SPEC_BEGIN(ctx, cursor, id, spec)                          \
  do {                                                                         \
    grn_obj *db = grn_ctx_db((ctx));                                           \
    grn_db *db_raw = (grn_db *)db;                                             \
    grn_obj decoded_spec;                                                      \
    grn_io_win iw;                                                             \
    bool iw_need_unref = false;                                                \
    GRN_OBJ_INIT(&decoded_spec, GRN_VECTOR, 0, GRN_DB_TEXT);                   \
    GRN_TABLE_EACH_BEGIN((ctx), db, cursor, id)                                \
    {                                                                          \
      void *encoded_spec;                                                      \
      uint32_t encoded_spec_size;                                              \
      bool success;                                                            \
      grn_obj_spec *spec;                                                      \
                                                                               \
      if (iw_need_unref) {                                                     \
        grn_ja_unref(ctx, &iw);                                                \
        iw_need_unref = false;                                                 \
      }                                                                        \
      encoded_spec =                                                           \
        grn_ja_ref((ctx), db_raw->specs, id, &iw, &encoded_spec_size);         \
      if (!encoded_spec) {                                                     \
        continue;                                                              \
      }                                                                        \
      iw_need_unref = true;                                                    \
                                                                               \
      GRN_BULK_REWIND(&decoded_spec);                                          \
      success = grn_obj_spec_unpack(ctx,                                       \
                                    id,                                        \
                                    encoded_spec,                              \
                                    encoded_spec_size,                         \
                                    &spec,                                     \
                                    &decoded_spec,                             \
                                    __FUNCTION__);                             \
      if (!success) {                                                          \
        continue;                                                              \
      }

#define GRN_DB_EACH_SPEC_END(ctx, cursor)                                      \
  }                                                                            \
  GRN_TABLE_EACH_END(ctx, cursor);                                             \
  if (iw_need_unref) {                                                         \
    grn_ja_unref(ctx, &iw);                                                    \
  }                                                                            \
  GRN_OBJ_FIN((ctx), &decoded_spec);                                           \
  }                                                                            \
  while (false)

void
grn_db_init_from_env(void);

void
grn_db_wal_recover(grn_ctx *ctx, grn_db *db);

GRN_API grn_rc
grn_db_close(grn_ctx *ctx, grn_obj *db);

grn_obj *
grn_db_keys(grn_obj *s);

void
grn_db_generate_pathname(grn_ctx *ctx, grn_obj *db, grn_id id, char *buffer);
grn_rc
grn_db_clear_dirty(grn_ctx *ctx, grn_obj *db);

grn_rc
grn_db_set_option_values(grn_ctx *ctx,
                         grn_obj *db,
                         grn_id id,
                         const char *name,
                         int name_length,
                         grn_obj *values);
grn_option_revision
grn_db_get_option_values(grn_ctx *ctx,
                         grn_obj *db,
                         grn_id id,
                         const char *name,
                         int name_length,
                         grn_option_revision revision,
                         grn_obj *values);
grn_rc
grn_db_clear_option_values(grn_ctx *ctx, grn_obj *db, grn_id id);
grn_rc
grn_obj_refer_auto_release(grn_ctx *ctx, grn_obj *obj, uint32_t count);
grn_rc
grn_obj_refer_recursive_auto_release(grn_ctx *ctx,
                                     grn_obj *obj,
                                     uint32_t count);
grn_rc
grn_obj_refer_recursive_dependent_auto_release(grn_ctx *ctx,
                                               grn_obj *obj,
                                               uint32_t count);
grn_rc
grn_db_add_deferred_unref(grn_ctx *ctx,
                          grn_obj *db,
                          grn_deferred_unref *deferred_unref);
grn_rc
grn_db_remove_deferred_unref(grn_ctx *ctx, grn_obj *db, grn_id id);
grn_rc
grn_db_command_processed(grn_ctx *ctx, grn_obj *db);

typedef struct {
  grn_obj *table;
  grn_id id;
  const void *key;
  uint32_t key_size;
  grn_table_delete_optarg *optarg;
  grn_obj columns;
  grn_obj index_columns;
} grn_table_delete_data;

grn_rc
grn_table_delete_data_init(grn_ctx *ctx,
                           grn_table_delete_data *data,
                           grn_obj *table);
grn_rc
grn_table_delete_data_fin(grn_ctx *ctx, grn_table_delete_data *data);

grn_rc
grn_table_delete_by_id_without_lock(grn_ctx *ctx, grn_table_delete_data *data);

grn_id
grn_table_get_v(
  grn_ctx *ctx, grn_obj *table, const void *key, int key_size, void **value);
grn_id
grn_table_get_by_key(grn_ctx *ctx, grn_obj *table, grn_obj *key);
grn_id
grn_table_add_v(grn_ctx *ctx,
                grn_obj *table,
                const void *key,
                int key_size,
                void **value,
                int *added);
typedef struct {
  bool added;
  bool ignore_empty_normalized_key;
  bool ignored;
} grn_table_add_options;
grn_id
grn_table_add_by_key(grn_ctx *ctx,
                     grn_obj *table,
                     grn_obj *key,
                     grn_table_add_options *options);
GRN_API grn_rc
grn_table_get_info(grn_ctx *ctx,
                   grn_obj *table,
                   grn_table_flags *flags,
                   grn_encoding *encoding,
                   grn_obj **tokenizer,
                   grn_obj **normalizer,
                   grn_obj **token_filters);
const char *
_grn_table_key(grn_ctx *ctx, grn_obj *table, grn_id id, uint32_t *key_size);

grn_rc
grn_table_search(grn_ctx *ctx,
                 grn_obj *table,
                 const void *key,
                 uint32_t key_size,
                 grn_operator mode,
                 grn_obj *res,
                 grn_operator op);

grn_rc
grn_table_fuzzy_search(grn_ctx *ctx,
                       grn_obj *table,
                       const void *key,
                       uint32_t key_size,
                       grn_fuzzy_search_optarg *args,
                       grn_obj *res,
                       grn_operator op);

grn_id
grn_table_next(grn_ctx *ctx, grn_obj *table, grn_id id);

int
grn_table_get_key2(grn_ctx *ctx, grn_obj *table, grn_id id, grn_obj *bulk);

grn_table_cursor *
grn_table_cursor_open_by_id(
  grn_ctx *ctx, grn_obj *table, grn_id min, grn_id max, int flags);

static inline void
grn_table_add_subrec(grn_ctx *ctx,
                     grn_obj *table,
                     grn_rset_recinfo *ri,
                     double score,
                     grn_rset_posinfo *pi,
                     int dir)
{
  if (DB_OBJ(table)->header.flags & GRN_OBJ_WITH_SUBREC) {
    ri->score += score;
    ri->n_subrecs += 1;
    /* This is a duplicated check but it reduces the number of
     * function calls. It improves performance when many records are
     * matched. */
    uint32_t limit = DB_OBJ(table)->max_n_subrecs;
    if (limit > 0) {
      grn_rset_add_subrec(ctx, ri, table, score, pi, dir);
    }
  }
}

grn_obj *
grn_obj_graft(grn_ctx *ctx, grn_obj *obj);

grn_rc
grn_column_name_(grn_ctx *ctx, grn_obj *obj, grn_obj *buf);

typedef enum {
  PROC_INIT = 0,
  PROC_NEXT,
  PROC_FIN
} grn_proc_phase;

struct _grn_type {
  grn_db_obj obj;
};

#define GRN_TYPE_SIZE(type) ((type)->range)

#define GRN_TABLE_SORT_GEO  (0x02 << 0)

#define GRN_OBJ_TMP_OBJECT  0x80000000
#define GRN_OBJ_TMP_COLUMN  0x40000000

#define GRN_DB_OBJP(obj)                                                       \
  (obj && ((GRN_SNIP == ((grn_db_obj *)obj)->header.type) ||                   \
           ((GRN_CURSOR_TABLE_HASH_KEY <= ((grn_db_obj *)obj)->header.type) && \
            (((grn_db_obj *)obj)->header.type <= GRN_COLUMN_INDEX))))

#define GRN_OBJ_TABLEP(obj)                                                    \
  (obj && (GRN_TABLE_HASH_KEY <= ((grn_db_obj *)obj)->header.type) &&          \
   (((grn_db_obj *)obj)->header.type <= GRN_DB))

#define GRN_OBJ_INDEX_COLUMNP(obj)                                             \
  (obj && DB_OBJ(obj)->header.type == GRN_COLUMN_INDEX)

#define GRN_OBJ_VECTOR_COLUMNP(obj)                                            \
  (obj && DB_OBJ(obj)->header.type == GRN_COLUMN_VAR_SIZE &&                   \
   (DB_OBJ(obj)->header.flags & GRN_OBJ_COLUMN_TYPE_MASK) ==                   \
     GRN_OBJ_COLUMN_VECTOR)

#define GRN_OBJ_WEIGHT_VECTOR_COLUMNP(obj)                                     \
  (GRN_OBJ_VECTOR_COLUMNP(obj) &&                                              \
   (DB_OBJ(obj)->header.flags & GRN_OBJ_WITH_WEIGHT))

struct _grn_hook {
  grn_hook *next;
  grn_proc *proc;
  uint32_t data_size;
};

typedef struct _grn_proc_ctx grn_proc_ctx;

struct _grn_proc_ctx {
  grn_user_data user_data;
  grn_proc *proc;
  grn_obj *caller;
  //  grn_obj *obj;
  grn_hook *hooks;
  grn_hook *currh;
  grn_proc_phase phase;
  unsigned short nargs;
  unsigned short offset;
  grn_user_data data[16];
};

static inline void
grn_proc_ctx_init(grn_proc_ctx *pctx,
                  grn_hook *hooks,
                  unsigned short n_args,
                  unsigned short offset)
{
  memset(pctx, 0, sizeof(*pctx));
  pctx->proc = hooks->proc;
  pctx->hooks = hooks;
  pctx->currh = hooks;
  pctx->phase = PROC_INIT;
  pctx->nargs = n_args;
  pctx->offset = offset;
}

struct _grn_proc {
  grn_db_obj obj;
  grn_obj name_buf;
  grn_expr_var *vars;
  uint32_t nvars;
  /* -- compatible with grn_expr -- */
  grn_proc_type type;
  grn_proc_func *funcs[3];

  union {
    struct {
      grn_selector_func *selector;
      grn_operator selector_op;
      bool is_stable;
      grn_applier_func *applier;
    } function;
    struct {
      grn_command_run_func *run;
    } command;
    struct {
      grn_tokenizer_init_func *init;
      grn_tokenizer_next_func *next;
      grn_tokenizer_fin_func *fin;
    } tokenizer;
    struct {
      grn_token_filter_init_func *init;
      grn_token_filter_init_query_func *init_query;
      grn_token_filter_filter_func *filter;
      grn_token_filter_fin_func *fin;
    } token_filter;
    struct {
      grn_scorer_score_func *score;
    } scorer;
    grn_window_function_func *window_function;
    struct {
      grn_aggregator_init_func *init;
      grn_aggregator_update_func *update;
      grn_aggregator_fin_func *fin;
    } aggregator;
  } callbacks;

  void *user_data;

  grn_id module;
  //  uint32_t nargs;
  //  uint32_t nresults;
  //  grn_obj results[16];
};

#define GRN_PROC_GET_VARS() (grn_proc_get_vars(ctx, user_data))
#define GRN_PROC_GET_VAR(name)                                                 \
  (grn_proc_get_var(ctx, user_data, name, strlen(name)))
#define GRN_PROC_GET_VAR_BY_OFFSET(offset)                                     \
  (grn_proc_get_var_by_offset(ctx, user_data, offset))
#define GRN_PROC_GET_OR_ADD_VAR(name)                                          \
  (grn_proc_get_or_add_var(ctx, user_data, name, strlen(name)))
#define GRN_PROC_ALLOC(domain, flags)                                          \
  (grn_proc_alloc(ctx, user_data, domain, flags))

grn_obj *
grn_proc_get_vars(grn_ctx *ctx, grn_user_data *user_data);

grn_obj *
grn_proc_get_var(grn_ctx *ctx,
                 grn_user_data *user_data,
                 const char *name,
                 unsigned int name_size);

GRN_API grn_obj *
grn_proc_get_var_by_offset(grn_ctx *ctx,
                           grn_user_data *user_data,
                           unsigned int offset);
GRN_API grn_obj *
grn_proc_get_or_add_var(grn_ctx *ctx,
                        grn_user_data *user_data,
                        const char *name,
                        unsigned int name_size);

GRN_API grn_obj *
grn_proc_alloc(grn_ctx *ctx,
               grn_user_data *user_data,
               grn_id domain,
               unsigned char flags);

GRN_API grn_rc
grn_proc_call(grn_ctx *ctx, grn_obj *proc, int nargs, grn_obj *caller);

grn_obj *
grn_expr_get_or_add_var(grn_ctx *ctx,
                        grn_obj *expr,
                        const char *name,
                        unsigned int name_size);

typedef struct _grn_accessor grn_accessor;

struct _grn_accessor {
  grn_obj_header header;
  grn_id range;
  /* -- compatible with grn_db_obj -- */
  uint8_t action;
  int offset;
  grn_obj *obj;
  grn_accessor *next;
  uint32_t reference_count;
};

typedef enum {
  GRN_ACCESSOR_VOID = 0,
  GRN_ACCESSOR_GET_ID,
  GRN_ACCESSOR_GET_KEY,
  GRN_ACCESSOR_GET_VALUE,
  GRN_ACCESSOR_GET_SCORE,
  GRN_ACCESSOR_GET_NSUBRECS,
  GRN_ACCESSOR_GET_MAX,
  GRN_ACCESSOR_GET_MIN,
  GRN_ACCESSOR_GET_SUM,
  GRN_ACCESSOR_GET_AVG,
  GRN_ACCESSOR_GET_MEAN,
  GRN_ACCESSOR_GET_COLUMN_VALUE,
  GRN_ACCESSOR_GET_DB_OBJ,
  GRN_ACCESSOR_LOOKUP,
  GRN_ACCESSOR_FUNCALL
} grn_accessor_action;

GRN_API const char *
grn_obj_get_value_(grn_ctx *ctx, grn_obj *obj, grn_id id, uint32_t *size);
void
grn_obj_get_range_info(grn_ctx *ctx,
                       grn_obj *obj,
                       grn_id *range_id,
                       grn_obj_flags *range_flags);

grn_rc
grn_db_init_builtin_types(grn_ctx *ctx);

/* flag value used for grn_obj.header.flags */

#define GRN_OBJ_CUSTOM_NAME (0x01 << 12) /* db_obj which has custom name */

#define GRN_OBJ_RESOLVE(ctx, obj)                                              \
  (((obj)->header.type != GRN_PTR) ? (obj)                                     \
   : GRN_PTR_VALUE(obj)            ? GRN_PTR_VALUE(obj)                        \
                                   : grn_ctx_at((ctx), (obj)->header.domain))

/* expr */

typedef struct _grn_expr grn_expr;

/**
 * grn_table_open:
 * @name: The table name to be opened. `NULL` means anonymous table.
 * @path: The path of the table to be opened.
 *
 * Opens an existing table. The table is associated with @name in DB
 * that is used by @ctx. grn_ctx_get() is better rather than this
 * function when you want to open a permanent named table that is
 * registered in DB.
 **/
GRN_API grn_obj *
grn_table_open(grn_ctx *ctx,
               const char *name,
               unsigned int name_size,
               const char *path);

/**
 * grn_column_open:
 * @table: The table for the opened column.
 * @name: The column name to be opened.
 * @path: The path of the column to be opened.
 * @type: The type of the column value.
 *
 * Opens an existing permanent column. The column is associated with
 * @name in @table. grn_ctx_get() is better rather than this function
 * when you want to open a column of an permanent table in DB.
 **/
grn_obj *
grn_column_open(grn_ctx *ctx,
                grn_obj *table,
                const char *name,
                unsigned int name_size,
                const char *path,
                grn_obj *type);

/**
 * grn_obj_path_rename:
 * @old_path: The current file path.
 * @new_path: The new file path.
 *
 * It renames object's path that is stored in @old_path to @new_path.
 **/
grn_rc
grn_obj_path_rename(grn_ctx *ctx, const char *old_path, const char *new_path);

grn_rc
grn_db_check_name(grn_ctx *ctx, const char *name, unsigned int name_size);
#define GRN_DB_CHECK_NAME_ERR(error_context, name, name_size)                  \
  ERR(GRN_INVALID_ARGUMENT,                                                    \
      "%s name can't start with '%c' and contains only 0-9, A-Z, a-z, #, @, "  \
      "- or _: <%.*s>",                                                        \
      error_context,                                                           \
      GRN_DB_PSEUDO_COLUMN_PREFIX,                                             \
      name_size,                                                               \
      name)

#define GRN_DB_P(s)            ((s) && ((grn_db *)s)->obj.header.type == GRN_DB)
#define GRN_DB_PERSISTENT_P(s) (((grn_db *)s)->specs)

#define GRN_OBJ_GET_VALUE_IMD  (0xffffffffU)

grn_rc
grn_db_obj_init(grn_ctx *ctx, grn_obj *db, grn_id id, grn_db_obj *obj);

#define GRN_ACCESSORP(obj)                                                     \
  ((obj) && (((grn_obj *)(obj))->header.type == GRN_ACCESSOR))

uint32_t
grn_obj_reference_count(grn_ctx *ctx, grn_obj *obj);

grn_id
grn_obj_register(grn_ctx *ctx,
                 grn_obj *db,
                 const char *name,
                 unsigned int name_size);
int
grn_obj_is_persistent(grn_ctx *ctx, grn_obj *obj);
void
grn_obj_spec_save(grn_ctx *ctx, grn_db_obj *obj);

grn_rc
grn_obj_reinit_for(grn_ctx *ctx, grn_obj *obj, grn_obj *domain_obj);

void
grn_obj_ensure_bulk(grn_ctx *ctx, grn_obj *obj);
void
grn_obj_ensure_vector(grn_ctx *ctx, grn_obj *obj);

void
grn_expr_pack(grn_ctx *ctx, grn_obj *buf, grn_obj *expr);
GRN_API grn_rc
grn_expr_inspect(grn_ctx *ctx, grn_obj *buf, grn_obj *expr);
grn_hash *
grn_expr_get_vars(grn_ctx *ctx, grn_obj *expr, unsigned int *nvars);
grn_obj *
grn_expr_open(grn_ctx *ctx,
              grn_obj_spec *spec,
              const uint8_t *p,
              const uint8_t *pe);

GRN_API grn_rc
grn_table_group_with_range_gap(grn_ctx *ctx,
                               grn_obj *table,
                               grn_table_sort_key *group_key,
                               grn_obj *result_set,
                               uint32_t range_gap);

grn_obj *
grn_table_column(grn_ctx *ctx,
                 grn_obj *table,
                 const char *name,
                 ssize_t name_size);
grn_rc
grn_table_parse_load_columns(grn_ctx *ctx,
                             grn_obj *table,
                             const char *input,
                             size_t input_size,
                             grn_obj *columns);

typedef struct {
  grn_id target;
  unsigned int section;
} grn_obj_default_set_value_hook_data;

grn_obj *
grn_obj_default_set_value_hook(grn_ctx *ctx,
                               int nargs,
                               grn_obj **args,
                               grn_user_data *user_data);

grn_rc
grn_column_get_all_token_columns(grn_ctx *ctx,
                                 grn_obj *obj,
                                 grn_obj *token_columns);
grn_rc
grn_column_get_all_hooked_columns(grn_ctx *ctx,
                                  grn_obj *obj,
                                  grn_obj *hooked_columns);

grn_rc
grn_pvector_fin(grn_ctx *ctx, grn_obj *obj);

typedef struct grn_id_map {
  grn_hash *hash;
  grn_critical_section critical_section;
} grn_id_map;

static inline grn_id_map *
grn_id_map_open(grn_ctx *ctx)
{
  grn_id_map *id_map = (grn_id_map *)GRN_MALLOC(sizeof(grn_id_map));
  if (!id_map) {
    return NULL;
  }
  id_map->hash = grn_hash_create(ctx,
                                 NULL,
                                 sizeof(grn_id),
                                 sizeof(grn_id),
                                 GRN_OBJ_TABLE_HASH_KEY | GRN_HASH_TINY);
  CRITICAL_SECTION_INIT(id_map->critical_section);
  return id_map;
}

static inline grn_rc
grn_id_map_close(grn_ctx *ctx, grn_id_map *id_map)
{
  if (!id_map) {
    return GRN_SUCCESS;
  }
  grn_rc rc = grn_hash_close(ctx, id_map->hash);
  CRITICAL_SECTION_FIN(id_map->critical_section);
  GRN_FREE(id_map);
  return rc;
}

static inline void
grn_id_map_add(grn_ctx *ctx, grn_id_map *id_map, grn_id old_id, grn_id new_id)
{
  if (!id_map) {
    return;
  }
  CRITICAL_SECTION_ENTER(id_map->critical_section);
  void *value;
  grn_hash_add(ctx, id_map->hash, &old_id, sizeof(grn_id), &value, NULL);
  *(grn_id *)value = new_id;
  CRITICAL_SECTION_LEAVE(id_map->critical_section);
}

static inline grn_id
grn_id_map_resolve(grn_ctx *ctx, grn_id_map *id_map, grn_id id)
{
  if (id == GRN_ID_NIL) {
    return id;
  }
  if (!id_map) {
    return id;
  }
  grn_id resolved_id = id;
  CRITICAL_SECTION_ENTER(id_map->critical_section);
  void *value;
  if (grn_hash_get(ctx, id_map->hash, &id, sizeof(grn_id), &value) !=
      GRN_ID_NIL) {
    resolved_id = *(grn_id *)value;
  }
  CRITICAL_SECTION_LEAVE(id_map->critical_section);
  return resolved_id;
}

#define GRN_DB_WAL_DISABLE_WAL_BEGIN(ctx)                                      \
  do {                                                                         \
    grn_wal_role wal_role_current = grn_ctx_get_wal_role(ctx);                 \
    grn_ctx_set_wal_role(ctx, GRN_WAL_ROLE_NONE);

#define GRN_DB_WAL_DISABLE_WAL_END(ctx)                                        \
  grn_ctx_set_wal_role(ctx, wal_role_current);                                 \
  }                                                                            \
  while (false)

static inline grn_obj *
grn_table_create_similar_id_map(grn_ctx *ctx,
                                const char *name,
                                uint32_t name_size,
                                const char *path,
                                grn_obj *base_table,
                                grn_id_map *id_map)
{
  const char *tag = "[table][create][similar]";
  if (!grn_obj_is_table(ctx, base_table)) {
    grn_obj inspected;
    GRN_TEXT_INIT(&inspected, 0);
    grn_inspect_limited(ctx, &inspected, base_table);
    ERR(GRN_INVALID_ARGUMENT,
        "%s must be table: %.*s",
        tag,
        (int)GRN_TEXT_LEN(&inspected),
        GRN_TEXT_VALUE(&inspected));
    GRN_OBJ_FIN(ctx, &inspected);
    return NULL;
  }

  grn_table_flags flags = 0;
  grn_id domain = GRN_ID_NIL;
  grn_id range = GRN_ID_NIL;
  grn_obj *key_type = NULL;
  grn_obj *value_type = NULL;

  if (grn_obj_is_persistent(ctx, base_table)) {
    flags |= GRN_OBJ_PERSISTENT;
  }

  switch (base_table->header.type) {
  case GRN_TABLE_HASH_KEY:
    flags |= GRN_OBJ_TABLE_HASH_KEY;
    if (((grn_hash *)base_table)->header.common->flags & GRN_OBJ_KEY_LARGE) {
      flags |= GRN_OBJ_KEY_LARGE;
    }
    domain = base_table->header.domain;
    range = DB_OBJ(base_table)->range;
    break;
  case GRN_TABLE_PAT_KEY:
    flags |= GRN_OBJ_TABLE_PAT_KEY;
    if (base_table->header.flags & GRN_OBJ_KEY_WITH_SIS) {
      flags |= GRN_OBJ_KEY_WITH_SIS;
    }
    domain = base_table->header.domain;
    range = DB_OBJ(base_table)->range;
    break;
  case GRN_TABLE_DAT_KEY:
    flags |= GRN_OBJ_TABLE_DAT_KEY;
    domain = base_table->header.domain;
    break;
  case GRN_TABLE_NO_KEY:
    flags |= GRN_OBJ_TABLE_NO_KEY;
    range = DB_OBJ(base_table)->range;
    break;
  default:
    break;
  }
  if (domain != GRN_ID_NIL) {
    domain = grn_id_map_resolve(ctx, id_map, domain);
    key_type = grn_ctx_at(ctx, domain);
  }
  if (range != GRN_ID_NIL) {
    range = grn_id_map_resolve(ctx, id_map, range);
    value_type = grn_ctx_at(ctx, range);
  }
  grn_obj *table =
    grn_table_create(ctx, name, name_size, path, flags, key_type, value_type);
  GRN_DB_WAL_DISABLE_WAL_BEGIN(ctx)
  {
    if (key_type) {
      grn_obj_unref(ctx, key_type);
    }
    if (value_type) {
      grn_obj_unref(ctx, value_type);
    }
  }
  GRN_DB_WAL_DISABLE_WAL_END(ctx);
  if (!table) {
    return NULL;
  }

  grn_obj buffer;
  GRN_TEXT_INIT(&buffer, 0);
  if (grn_obj_is_table_with_key(ctx, base_table)) {
    GRN_BULK_REWIND(&buffer);
    grn_table_get_default_tokenizer_string(ctx, base_table, &buffer);
    if (GRN_TEXT_LEN(&buffer) > 0) {
      grn_obj_set_info(ctx, table, GRN_INFO_DEFAULT_TOKENIZER, &buffer);
      if (ctx->rc != GRN_SUCCESS) {
        goto exit;
      }
    }

    GRN_BULK_REWIND(&buffer);
    grn_table_get_normalizers_string(ctx, base_table, &buffer);
    if (GRN_TEXT_LEN(&buffer) > 0) {
      grn_obj_set_info(ctx, table, GRN_INFO_NORMALIZERS, &buffer);
      if (ctx->rc != GRN_SUCCESS) {
        goto exit;
      }
    }

    GRN_BULK_REWIND(&buffer);
    grn_table_get_token_filters_string(ctx, base_table, &buffer);
    if (GRN_TEXT_LEN(&buffer) > 0) {
      grn_obj_set_info(ctx, table, GRN_INFO_TOKEN_FILTERS, &buffer);
      if (ctx->rc != GRN_SUCCESS) {
        goto exit;
      }
    }
  }

exit:
  GRN_OBJ_FIN(ctx, &buffer);

  if (ctx->rc != GRN_SUCCESS) {
    grn_obj_remove(ctx, table);
    return NULL;
  }

  return table;
}

static inline grn_obj *
grn_column_create_similar_id_map(grn_ctx *ctx,
                                 grn_obj *table,
                                 const char *name,
                                 uint32_t name_size,
                                 const char *path,
                                 grn_obj *base_column,
                                 grn_id_map *id_map)
{
  const char *tag = "[column][create][similar]";

  if (!grn_obj_is_table(ctx, table)) {
    grn_obj inspected;
    GRN_TEXT_INIT(&inspected, 0);
    grn_inspect_limited(ctx, &inspected, table);
    ERR(GRN_INVALID_ARGUMENT,
        "%s must be table: %.*s",
        tag,
        (int)GRN_TEXT_LEN(&inspected),
        GRN_TEXT_VALUE(&inspected));
    GRN_OBJ_FIN(ctx, &inspected);
    return NULL;
  }
  if (!name || name_size == 0) {
    ERR(GRN_INVALID_ARGUMENT, "%s name is missing", tag);
    return NULL;
  }
  if (!grn_obj_is_column(ctx, base_column)) {
    grn_obj inspected;
    GRN_TEXT_INIT(&inspected, 0);
    grn_inspect_limited(ctx, &inspected, base_column);
    ERR(GRN_INVALID_ARGUMENT,
        "%s must be column: %.*s",
        tag,
        (int)GRN_TEXT_LEN(&inspected),
        GRN_TEXT_VALUE(&inspected));
    GRN_OBJ_FIN(ctx, &inspected);
    return NULL;
  }

  grn_column_flags flags = 0;
  if (grn_obj_is_persistent(ctx, table)) {
    flags |= GRN_OBJ_PERSISTENT;
  }
  grn_id type_id = DB_OBJ(base_column)->range;
  type_id = grn_id_map_resolve(ctx, id_map, type_id);
  grn_obj *type = grn_ctx_at(ctx, type_id);
  flags |= grn_column_get_flags(ctx, base_column);
  grn_obj *column =
    grn_column_create(ctx, table, name, name_size, path, flags, type);
  if (!column) {
    return NULL;
  }

  switch (column->header.type) {
  case GRN_COLUMN_VAR_SIZE:
    {
      grn_obj base_source_ids;
      GRN_RECORD_INIT(&base_source_ids, GRN_OBJ_VECTOR, GRN_ID_NIL);
      grn_obj_get_info(ctx, base_column, GRN_INFO_SOURCE, &base_source_ids);
      size_t n = GRN_RECORD_VECTOR_SIZE(&base_source_ids);
      if (n > 0) {
        grn_obj source_ids;
        GRN_RECORD_INIT(&source_ids, GRN_OBJ_VECTOR, GRN_ID_NIL);
        size_t i;
        for (i = 0; i < n; i++) {
          grn_id base_source_column_id =
            GRN_RECORD_VALUE_AT(&base_source_ids, i);
          grn_obj *base_source_column = grn_ctx_at(ctx, base_source_column_id);
          if (!base_source_column) {
            ERR(GRN_INVALID_ARGUMENT,
                "%s base column's source doesn't exist: %u",
                tag,
                base_source_column_id);
            break;
          }
          char source_column_name[GRN_TABLE_MAX_KEY_SIZE];
          int source_column_name_length =
            grn_column_name(ctx,
                            base_source_column,
                            source_column_name,
                            sizeof(source_column_name));
          GRN_DB_WAL_DISABLE_WAL_BEGIN(ctx)
          {
            grn_obj_unref(ctx, base_source_column);
          }
          GRN_DB_WAL_DISABLE_WAL_END(ctx);
          grn_obj *source_column = grn_table_column(ctx,
                                                    table,
                                                    source_column_name,
                                                    source_column_name_length);
          if (!source_column) {
            ERR(GRN_INVALID_ARGUMENT,
                "%s source column doesn't exist: <%.*s.%.*s>",
                tag,
                (int)name_size,
                name,
                source_column_name_length,
                source_column_name);
            break;
          }
          GRN_RECORD_PUT(ctx, &source_ids, DB_OBJ(source_column)->id);
          GRN_DB_WAL_DISABLE_WAL_BEGIN(ctx)
          {
            grn_obj_unref(ctx, source_column);
          }
          GRN_DB_WAL_DISABLE_WAL_END(ctx);
        }
        if (ctx->rc == GRN_SUCCESS) {
          grn_obj_set_info(ctx, column, GRN_INFO_SOURCE, &source_ids);
        }
        GRN_OBJ_FIN(ctx, &source_ids);
      }
      GRN_OBJ_FIN(ctx, &base_source_ids);
      if (ctx->rc != GRN_SUCCESS) {
        grn_rc original_rc = ctx->rc;
        ctx->rc = GRN_SUCCESS;
        grn_obj_remove(ctx, column);
        ctx->rc = original_rc;
        return NULL;
      }
    }
    break;
  case GRN_COLUMN_INDEX:
    {
      grn_obj source_ids;
      GRN_RECORD_INIT(&source_ids, GRN_OBJ_VECTOR, GRN_ID_NIL);
      grn_obj_get_info(ctx, base_column, GRN_INFO_SOURCE, &source_ids);
      size_t n_source_ids = GRN_RECORD_VECTOR_SIZE(&source_ids);
      if (n_source_ids > 0) {
        grn_obj resolved_source_ids;
        GRN_RECORD_INIT(&resolved_source_ids, GRN_OBJ_VECTOR, GRN_ID_NIL);
        size_t i;
        for (i = 0; i < n_source_ids; i++) {
          grn_id source_id = GRN_RECORD_VALUE_AT(&source_ids, i);
          grn_id resolved_source_id =
            grn_id_map_resolve(ctx, id_map, source_id);
          GRN_RECORD_PUT(ctx, &resolved_source_ids, resolved_source_id);
        }
        grn_obj_set_info(ctx, column, GRN_INFO_SOURCE, &resolved_source_ids);
        GRN_OBJ_FIN(ctx, &resolved_source_ids);
      }
      GRN_OBJ_FIN(ctx, &source_ids);
      if (ctx->rc != GRN_SUCCESS) {
        grn_rc original_rc = ctx->rc;
        ctx->rc = GRN_SUCCESS;
        grn_obj_remove(ctx, column);
        ctx->rc = original_rc;
        return NULL;
      }
    }
    break;
  default:
    break;
  }

  return column;
}

bool
grn_ctx_is_removing(grn_ctx *ctx, grn_id id);

#ifdef __cplusplus
}
#endif
