/*
  Copyright (C) 2018  Brazil
  Copyright (C) 2019-2025  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
*/

#include "grn.h"
#include "grn_accessor.h"
#include "grn_db.h"
#include "grn_ii.h"
#include "grn_report.h"
#include "grn_posting.h"

#include <stdio.h>

grn_accessor *
grn_accessor_new(grn_ctx *ctx)
{
  grn_accessor *res = GRN_CALLOC(sizeof(grn_accessor));
  if (!res) {
    return NULL;
  }

  res->header.type = GRN_ACCESSOR;
  res->header.impl_flags = GRN_OBJ_ALLOCATED;
  res->header.flags = 0;
  res->header.domain = GRN_ID_NIL;
  res->range = GRN_ID_NIL;
  res->action = GRN_ACCESSOR_VOID;
  res->offset = 0;
  res->obj = NULL;
  res->next = NULL;
  res->reference_count = 1;
  grn_log_reference_count("%p: accessor: %p: %u\n",
                          ctx,
                          res,
                          res->reference_count);
  return res;
}

grn_obj *
grn_accessor_copy(grn_ctx *ctx, grn_obj *accessor)
{
  grn_accessor *new_accessor = grn_accessor_new(ctx);
  grn_accessor *a = (grn_accessor *)accessor;
  grn_accessor *new_a = new_accessor;
  for (; a; a = a->next) {
    new_a->action = a->action;
    new_a->obj = a->obj;
    if (new_a->obj) {
      grn_obj_refer(ctx, new_a->obj);
    }
    if (a->next) {
      new_a->next = grn_accessor_new(ctx);
      new_a = new_a->next;
    }
  }
  return (grn_obj *)new_accessor;
}

grn_accessor_action
grn_accessor_get_action(grn_ctx *ctx, grn_obj *accessor)
{
  grn_accessor *a = (grn_accessor *)accessor;
  return a->action;
}

grn_obj *
grn_accessor_get_obj(grn_ctx *ctx, grn_obj *accessor)
{
  grn_accessor *a = (grn_accessor *)accessor;
  return a->obj;
}

grn_obj *
grn_accessor_get_next(grn_ctx *ctx, grn_obj *accessor)
{
  grn_accessor *a = (grn_accessor *)accessor;
  return (grn_obj *)(a->next);
}

static grn_rc
grn_accessor_resolve_one_index_column(grn_ctx *ctx,
                                      grn_accessor *accessor,
                                      grn_obj *current_res,
                                      grn_obj **next_res)
{
  grn_rc rc = GRN_SUCCESS;
  grn_obj *column = NULL;
  grn_id next_res_domain_id = GRN_ID_NIL;

  {
    grn_obj *index;
    grn_obj source_ids;
    size_t i, n_ids;

    index = accessor->obj;
    next_res_domain_id = index->header.domain;

    GRN_RECORD_INIT(&source_ids, GRN_OBJ_VECTOR, GRN_DB_VOID);
    grn_obj_get_info(ctx, index, GRN_INFO_SOURCE, &source_ids);
    n_ids = GRN_RECORD_VECTOR_SIZE(&source_ids);
    for (i = 0; i < n_ids; i++) {
      grn_id source_id;
      grn_obj *source;

      source_id = GRN_RECORD_VALUE_AT(&source_ids, i);
      source = grn_ctx_at(ctx, source_id);
      if (DB_OBJ(source)->range == next_res_domain_id) {
        column = source;
        break;
      }
      grn_obj_unlink(ctx, source);
    }

    if (!column) {
      return GRN_INVALID_ARGUMENT;
    }
  }

  {
    grn_rc rc;
    grn_obj *next_res_domain = grn_ctx_at(ctx, next_res_domain_id);
    *next_res = grn_table_create(ctx,
                                 NULL,
                                 0,
                                 NULL,
                                 GRN_OBJ_TABLE_HASH_KEY | GRN_OBJ_WITH_SUBREC,
                                 next_res_domain,
                                 NULL);
    rc = ctx->rc;
    grn_obj_unlink(ctx, next_res_domain);
    if (!*next_res) {
      return rc;
    }
  }

  {
    grn_obj_flags column_value_flags = 0;
    grn_obj column_value;
    grn_posting_internal add_posting = {0};

    if (column->header.type == GRN_COLUMN_VAR_SIZE) {
      column_value_flags |= GRN_OBJ_VECTOR;
    }
    GRN_VALUE_FIX_SIZE_INIT(&column_value,
                            column_value_flags,
                            next_res_domain_id);

    add_posting.sid = 0;
    add_posting.pos = 0;
    add_posting.weight_float = 0;

    GRN_HASH_EACH_BEGIN(ctx, (grn_hash *)current_res, cursor, id)
    {
      void *key;
      void *value;
      grn_hash_cursor_get_key_value(ctx, cursor, &key, NULL, &value);

      grn_id *tid = key;
      grn_rset_recinfo *recinfo = value;

      add_posting.weight_float = (float)(recinfo->score);

      GRN_BULK_REWIND(&column_value);
      grn_obj_get_value(ctx, column, *tid, &column_value);

      size_t n_elements = GRN_BULK_VSIZE(&column_value) / sizeof(grn_id);
      for (size_t i = 0; i < n_elements; i++) {
        add_posting.rid = GRN_RECORD_VALUE_AT(&column_value, i);
        rc = grn_ii_posting_add_float(ctx,
                                      (grn_posting *)(&add_posting),
                                      (grn_hash *)*next_res,
                                      GRN_OP_OR);
        if (rc != GRN_SUCCESS) {
          break;
        }
      }
      if (rc != GRN_SUCCESS) {
        break;
      }
    }
    GRN_HASH_EACH_END(ctx, cursor);

    GRN_OBJ_FIN(ctx, &column_value);
  }

  if (rc != GRN_SUCCESS) {
    grn_obj_unlink(ctx, *next_res);
  }

  return rc;
}

static grn_rc
grn_accessor_resolve_one_table(grn_ctx *ctx,
                               grn_accessor *accessor,
                               grn_obj *current_res,
                               grn_obj **next_res)
{
  grn_rc rc = GRN_SUCCESS;
  grn_obj *table;

  table = accessor->obj;
  *next_res = grn_table_create(ctx,
                               NULL,
                               0,
                               NULL,
                               GRN_OBJ_TABLE_HASH_KEY | GRN_OBJ_WITH_SUBREC,
                               table,
                               NULL);
  if (!*next_res) {
    return ctx->rc;
  }

  grn_report_table(ctx, "[accessor][resolve]", "", table);

  {
    grn_posting_internal posting = {0};

    GRN_HASH_EACH_BEGIN(ctx, (grn_hash *)current_res, cursor, id)
    {
      void *key;
      void *value;
      grn_id *record_id;
      grn_rset_recinfo *recinfo;
      grn_id next_record_id;

      grn_hash_cursor_get_key_value(ctx, cursor, &key, NULL, &value);
      record_id = key;
      recinfo = value;
      next_record_id = grn_table_get(ctx, table, record_id, sizeof(grn_id));
      if (next_record_id == GRN_ID_NIL) {
        continue;
      }

      posting.rid = next_record_id;
      posting.weight_float = (float)(recinfo->score);
      rc = grn_ii_posting_add_float(ctx,
                                    (grn_posting *)(&posting),
                                    (grn_hash *)*next_res,
                                    GRN_OP_OR);
      if (rc != GRN_SUCCESS) {
        break;
      }
    }
    GRN_HASH_EACH_END(ctx, cursor);
  }

  if (rc != GRN_SUCCESS) {
    grn_obj_unlink(ctx, *next_res);
  }

  return rc;
}

static grn_rc
grn_accessor_resolve_one_data_column_sequential(grn_ctx *ctx,
                                                grn_accessor *accessor,
                                                grn_obj *current_res,
                                                grn_obj **next_res)
{
  grn_obj *column = accessor->obj;
  grn_report_column(ctx, "[accessor][resolve][data-column]", "", column);

  grn_id next_res_domain_id = column->header.domain;
  grn_obj *next_res_domain = grn_ctx_at(ctx, next_res_domain_id);
  *next_res = grn_table_create(ctx,
                               NULL,
                               0,
                               NULL,
                               GRN_OBJ_TABLE_HASH_KEY | GRN_OBJ_WITH_SUBREC,
                               next_res_domain,
                               NULL);
  if (!*next_res) {
    return ctx->rc;
  }

  {
    grn_obj value;
    GRN_VOID_INIT(&value);
    GRN_TABLE_EACH_BEGIN(ctx, next_res_domain, cursor, id)
    {
      GRN_BULK_REWIND(&value);
      grn_obj_get_value(ctx, column, id, &value);
      bool found = false;
      switch (value.header.type) {
      case GRN_BULK:
        found =
          (grn_table_get(ctx,
                         current_res,
                         GRN_BULK_HEAD(&value),
                         (unsigned int)GRN_BULK_VSIZE(&value)) != GRN_ID_NIL);

        break;
      case GRN_UVECTOR:
        {
          size_t i, n_elements;
          n_elements = GRN_BULK_VSIZE(&value) / sizeof(grn_id);
          for (i = 0; i < n_elements; i++) {
            grn_id id = GRN_RECORD_VALUE_AT(&value, i);
            found = (grn_table_get(ctx, current_res, &id, sizeof(grn_id)) !=
                     GRN_ID_NIL);
            if (found) {
              break;
            }
          }
        }
        break;
      default:
        break;
      }
      if (found) {
        grn_posting_internal posting = {0};
        posting.rid = id;
        grn_ii_posting_add_float(ctx,
                                 (grn_posting *)(&posting),
                                 (grn_hash *)*next_res,
                                 GRN_OP_OR);
      }
    }
    GRN_TABLE_EACH_END(ctx, cursor);
    GRN_OBJ_FIN(ctx, &value);
  }

  return ctx->rc;
}

static grn_rc
grn_accessor_resolve_one_data_column_index(grn_ctx *ctx,
                                           grn_index_datum *index_datum,
                                           grn_obj *current_res,
                                           grn_obj **next_res)
{
  if (index_datum->index->header.domain != current_res->header.domain) {
    char index_name[GRN_TABLE_MAX_KEY_SIZE];
    int index_name_size;
    grn_obj *expected;
    char expected_name[GRN_TABLE_MAX_KEY_SIZE];
    int expected_name_size;

    index_name_size =
      grn_obj_name(ctx, index_datum->index, index_name, GRN_TABLE_MAX_KEY_SIZE);
    expected = grn_ctx_at(ctx, current_res->header.domain);
    expected_name_size =
      grn_obj_name(ctx, expected, expected_name, GRN_TABLE_MAX_KEY_SIZE);
    ERR(GRN_INVALID_ARGUMENT,
        "[accessor][resolve][data-column] lexicon mismatch index: "
        "<%.*s> "
        "expected:<%.*s>",
        index_name_size,
        index_name,
        expected_name_size,
        expected_name);
    return ctx->rc;
  }

  grn_report_index(ctx,
                   "[accessor][resolve][data-column]",
                   "",
                   index_datum->index);
  {
    grn_rc rc;
    grn_id next_res_domain_id = DB_OBJ(index_datum->index)->range;
    grn_obj *next_res_domain = grn_ctx_at(ctx, next_res_domain_id);
    *next_res = grn_table_create(ctx,
                                 NULL,
                                 0,
                                 NULL,
                                 GRN_OBJ_TABLE_HASH_KEY | GRN_OBJ_WITH_SUBREC,
                                 next_res_domain,
                                 NULL);
    rc = ctx->rc;
    grn_obj_unlink(ctx, next_res_domain);
    if (!*next_res) {
      return rc;
    }
  }

  {
    grn_rc rc = GRN_SUCCESS;
    grn_obj *index_column = index_datum->index;
    grn_obj *index_cursor =
      grn_index_cursor_open(ctx, NULL, index_column, GRN_ID_NIL, GRN_ID_MAX, 0);
    if (!index_cursor) {
      grn_obj_unlink(ctx, *next_res);
      return ctx->rc;
    }
    if (index_datum->section > 0) {
      rc = grn_index_cursor_set_section_id(ctx,
                                           index_cursor,
                                           index_datum->section);
    }
    if (rc != GRN_SUCCESS) {
      grn_obj_close(ctx, index_cursor);
      grn_obj_unlink(ctx, *next_res);
      return ctx->rc;
    }

    GRN_HASH_EACH_BEGIN(ctx, (grn_hash *)current_res, cursor, id)
    {
      void *key;
      void *value;
      grn_hash_cursor_get_key_value(ctx, cursor, &key, NULL, &value);

      grn_id *term_id = key;
      grn_rset_recinfo *recinfo = value;

      rc = grn_index_cursor_set_term_id(ctx, index_cursor, *term_id);
      if (rc == GRN_SUCCESS) {
        rc = grn_result_set_add_index_cursor(ctx,
                                             (grn_hash *)(*next_res),
                                             index_cursor,
                                             recinfo->score,
                                             1,
                                             GRN_OP_OR);
      }

      if (rc != GRN_SUCCESS) {
        break;
      }
    }
    GRN_HASH_EACH_END(ctx, cursor);
    grn_obj_close(ctx, index_cursor);

    if (rc != GRN_SUCCESS) {
      grn_obj_unlink(ctx, *next_res);
    }

    return rc;
  }
}

static grn_rc
grn_accessor_resolve_one_data_column(grn_ctx *ctx,
                                     grn_accessor *accessor,
                                     grn_obj *current_res,
                                     grn_obj **next_res)
{
  grn_rc rc = GRN_SUCCESS;
  grn_index_datum index_datum;
  unsigned int n_index_data;

  n_index_data =
    grn_column_get_all_index_data(ctx, accessor->obj, &index_datum, 1);
  if (n_index_data == 0) {
    rc = grn_accessor_resolve_one_data_column_sequential(ctx,
                                                         accessor,
                                                         current_res,
                                                         next_res);
  } else {
    rc = grn_accessor_resolve_one_data_column_index(ctx,
                                                    &index_datum,
                                                    current_res,
                                                    next_res);
    grn_obj_unref(ctx, index_datum.index);
  }

  return rc;
}

grn_rc
grn_accessor_resolve(grn_ctx *ctx,
                     grn_obj *accessor,
                     int depth,
                     grn_obj *base_res,
                     grn_obj *res,
                     grn_operator op)
{
  grn_rc rc = GRN_SUCCESS;
  grn_accessor *a;
  grn_obj accessor_stack;
  int i, n_accessors;
  grn_obj *current_res = base_res;

  GRN_PTR_INIT(&accessor_stack, GRN_OBJ_VECTOR, GRN_ID_NIL);
  n_accessors = 0;
  for (a = (grn_accessor *)accessor; a; a = a->next) {
    if (depth == n_accessors) {
      break;
    }
    GRN_PTR_PUT(ctx, &accessor_stack, a);
    n_accessors++;
  }

  for (i = n_accessors; i > 0; i--) {
    grn_obj *next_res = NULL;

    a = (grn_accessor *)GRN_PTR_VALUE_AT(&accessor_stack, i - 1);
    if (a->obj->header.type == GRN_COLUMN_INDEX) {
      rc =
        grn_accessor_resolve_one_index_column(ctx, a, current_res, &next_res);
    } else if (grn_obj_is_table(ctx, a->obj)) {
      rc = grn_accessor_resolve_one_table(ctx, a, current_res, &next_res);
    } else {
      rc = grn_accessor_resolve_one_data_column(ctx, a, current_res, &next_res);
    }

    if (current_res != base_res) {
      grn_obj_unlink(ctx, current_res);
    }

    if (rc != GRN_SUCCESS) {
      break;
    }

    current_res = next_res;
  }

  if (rc == GRN_SUCCESS && current_res != base_res) {
    GRN_HASH_EACH_BEGIN(ctx, (grn_hash *)current_res, cursor, id)
    {
      void *key;
      void *value;
      grn_id *record_id;
      grn_rset_recinfo *recinfo;
      grn_posting_internal posting = {0};

      grn_hash_cursor_get_key_value(ctx, cursor, &key, NULL, &value);
      record_id = key;
      recinfo = value;
      posting.rid = *record_id;
      posting.sid = 1;
      posting.pos = 0;
      posting.weight_float = (float)(recinfo->score);
      grn_ii_posting_add_float(ctx,
                               (grn_posting *)(&posting),
                               (grn_hash *)res,
                               op);
      if (ctx->rc != GRN_SUCCESS) {
        break;
      }
    }
    GRN_HASH_EACH_END(ctx, cursor);
    grn_obj_unlink(ctx, current_res);
    grn_ii_resolve_sel_and(ctx, (grn_hash *)res, op);
  } else {
    if (rc == GRN_SUCCESS) {
      rc = GRN_INVALID_ARGUMENT;
    }
  }

  GRN_OBJ_FIN(ctx, &accessor_stack);

  rc = grn_ctx_rc_propagate(ctx, rc);
  return rc;
}

grn_id
grn_accessor_resolve_id(grn_ctx *ctx, grn_obj *accessor, grn_id id)
{
  GRN_API_ENTER;

  grn_accessor *a;
  for (a = (grn_accessor *)accessor; a->next; a = a->next) {
    switch (a->action) {
    case GRN_ACCESSOR_GET_KEY:
      {
        grn_id next_id;
        int key_size =
          grn_table_get_key(ctx, a->obj, id, &next_id, sizeof(grn_id));
        if (key_size != sizeof(grn_id)) {
          GRN_API_RETURN(id);
        }
        id = next_id;
      }
      break;
    case GRN_ACCESSOR_GET_VALUE:
      {
        grn_obj value;
        GRN_VOID_INIT(&value);
        grn_obj_get_value(ctx, a->obj, id, &value);
        if (GRN_BULK_VSIZE(&value) < sizeof(grn_id)) {
          GRN_OBJ_FIN(ctx, &value);
          GRN_API_RETURN(id);
        }
        id = GRN_RECORD_VALUE(&value);
        GRN_OBJ_FIN(ctx, &value);
      }
      break;
    default:
      GRN_API_RETURN(id);
    }
  }

  GRN_API_RETURN(id);
}

grn_rc
grn_accessor_execute(grn_ctx *ctx,
                     grn_obj *accessor,
                     grn_accessor_execute_func execute,
                     void *execute_data,
                     grn_operator execute_op,
                     grn_obj *res,
                     grn_operator logical_op)
{
  GRN_API_ENTER;

  if (!grn_obj_is_accessor(ctx, accessor)) {
    grn_obj inspected;
    GRN_TEXT_INIT(&inspected, 0);
    grn_inspect(ctx, &inspected, accessor);
    ERR(GRN_INVALID_ARGUMENT,
        "[accessor][execute] must be accessor: %.*s",
        (int)GRN_TEXT_LEN(&inspected),
        GRN_TEXT_VALUE(&inspected));
    GRN_OBJ_FIN(ctx, &inspected);
    GRN_API_RETURN(ctx->rc);
  }

  int depth = 0;
  grn_accessor *last_a = NULL;
  grn_accessor *second_to_last_a = NULL;
  {
    grn_accessor *a;
    for (last_a = a = (grn_accessor *)accessor; a->next; last_a = a = a->next) {
      depth++;
      second_to_last_a = a;
    }
  }

  grn_index_datum index_data;
  unsigned int n_index_datum;
  grn_obj *index;
  grn_error_cancel(ctx);
  n_index_datum =
    grn_column_find_index_data(ctx, last_a->obj, execute_op, &index_data, 1);
  if (n_index_datum == 0) {
    if (ctx->rc != GRN_SUCCESS) {
      GRN_API_RETURN(ctx->rc);
    }
    index = (grn_obj *)last_a;
  } else {
    index = index_data.index;
  }

  grn_rc rc;
  if (depth == 0) {
    rc = execute(ctx, index, execute_op, res, logical_op, execute_data);
  } else {
    grn_obj *base_table = NULL;
    bool base_table_need_unref = false;
    if (grn_obj_is_table(ctx, last_a->obj)) {
      base_table = last_a->obj;
    } else {
      base_table_need_unref = true;
      base_table = grn_ctx_at(ctx, last_a->obj->header.domain);
    }

    grn_obj *base_res =
      grn_table_create(ctx,
                       NULL,
                       0,
                       NULL,
                       GRN_OBJ_TABLE_HASH_KEY | GRN_OBJ_WITH_SUBREC,
                       base_table,
                       NULL);
    if (base_res) {
      grn_operator base_logical_op = GRN_OP_OR;
      if (execute_op != GRN_OP_NOT_EQUAL && second_to_last_a &&
          grn_obj_is_result_set(ctx, second_to_last_a->obj) &&
          second_to_last_a->obj->header.domain == base_res->header.domain) {
        GRN_LOG(ctx,
                GRN_REPORT_INDEX_LOG_LEVEL,
                "[accessor][execute][initial] <%u>",
                grn_table_size(ctx, second_to_last_a->obj));
        GRN_TABLE_EACH_BEGIN(ctx, second_to_last_a->obj, cursor, id)
        {
          void *key;
          int key_size = grn_table_cursor_get_key(ctx, cursor, &key);
          grn_hash_add(ctx,
                       (grn_hash *)base_res,
                       key,
                       (unsigned int)key_size,
                       NULL,
                       NULL);
        }
        GRN_TABLE_EACH_END(ctx, cursor);
        base_logical_op = GRN_OP_AND;
      }
      int original_flags = ctx->flags;
      ctx->flags &= ~GRN_CTX_TEMPORARY_DISABLE_II_RESOLVE_SEL_AND;
      rc = execute(ctx,
                   index,
                   execute_op,
                   base_res,
                   base_logical_op,
                   execute_data);
      ctx->flags = original_flags;
      if (rc == GRN_SUCCESS) {
        rc =
          grn_accessor_resolve(ctx, accessor, depth, base_res, res, logical_op);
      }
      grn_obj_close(ctx, base_res);
    } else {
      rc = ctx->rc;
      if (rc == GRN_SUCCESS) {
        rc = GRN_UNKNOWN_ERROR;
      }
      char errbuf[GRN_CTX_MSGSIZE];
      grn_strcpy(errbuf, GRN_CTX_MSGSIZE, ctx->errbuf);
      ERR(rc,
          "[accessor][execute] failed to create internal result set: %s",
          errbuf);
    }
    if (base_table_need_unref) {
      grn_obj_unref(ctx, base_table);
    }
  }

  if (n_index_datum > 0) {
    grn_obj_unref(ctx, index_data.index);
  }

  rc = grn_ctx_rc_propagate(ctx, rc);
  GRN_API_RETURN(rc);
}

typedef struct {
  grn_obj *accessor;
  grn_obj *query;
  grn_search_optarg *optarg;
  grn_obj *index;
  bool index_need_unref;
  grn_operator operator_;
  grn_obj query_casted;
  const char *query_raw;
  uint32_t query_raw_len;
  uint32_t n_base_records;
  uint32_t n_target_records;
  uint32_t depth;
} grn_accessor_estimate_size_for_query_data;

static void
grn_accessor_estimate_size_for_query_data_fin(
  grn_ctx *ctx, grn_accessor_estimate_size_for_query_data *data)
{
  if (data->index_need_unref) {
    grn_obj_unref(ctx, data->index);
  }
  GRN_OBJ_FIN(ctx, &(data->query_casted));
}

static void
grn_accessor_estimate_size_for_query_cast_failed(
  grn_ctx *ctx, grn_accessor_estimate_size_for_query_data *data)
{
  grn_obj detail;
  GRN_TEXT_INIT(&detail, 0);
  GRN_TEXT_PUTS(ctx, &detail, "<");
  grn_inspect(ctx, &detail, data->query);
  GRN_TEXT_PUTS(ctx, &detail, "> -> ");
  grn_obj *domain = grn_ctx_at(ctx, data->query_casted.header.domain);
  grn_inspect(ctx, &detail, domain);
  grn_obj_unlink(ctx, domain);
  GRN_TEXT_PUTS(ctx, &detail, ": ");
  grn_inspect(ctx, &detail, data->accessor);
  ERR(GRN_INVALID_ARGUMENT,
      "[accessor][estimate-size][query][cast] "
      "failed: %.*s",
      (int)GRN_TEXT_LEN(&detail),
      GRN_TEXT_VALUE(&detail));
  GRN_OBJ_FIN(ctx, &detail);
}

static bool
grn_accessor_estimate_size_for_query_prepare(
  grn_ctx *ctx, grn_accessor_estimate_size_for_query_data *data)
{
  grn_accessor *a = (grn_accessor *)(data->accessor);
  if (grn_obj_is_table(ctx, a->obj)) {
    data->n_target_records = grn_table_size(ctx, a->obj);
  } else {
    grn_obj *target_table = grn_ctx_at(ctx, a->obj->header.domain);
    data->n_target_records = grn_table_size(ctx, target_table);
    grn_obj_unlink(ctx, target_table);
  }

  if (data->n_target_records == 0) {
    return false;
  }

  for (; a->next; a = a->next) {
    data->depth++;
  }

  if (grn_obj_is_table(ctx, a->obj)) {
    data->n_base_records = grn_table_size(ctx, a->obj);
  } else {
    grn_obj *base_table = grn_ctx_at(ctx, a->obj->header.domain);
    data->n_base_records = grn_table_size(ctx, base_table);
    grn_obj_unlink(ctx, base_table);
  }

  if (data->optarg) {
    if (data->optarg->mode == GRN_OP_EXACT) {
      data->operator_ = GRN_OP_MATCH;
    } else {
      data->operator_ = data->optarg->mode;
    }
  }
  grn_index_datum index_data;
  unsigned int n_index_datum;
  n_index_datum =
    grn_column_find_index_data(ctx, a->obj, data->operator_, &index_data, 1);
  if (n_index_datum == 0) {
    if (!grn_obj_is_table(ctx, a->obj)) {
      return false;
    }
    data->index = a->obj;
  } else {
    data->index = index_data.index;
    data->index_need_unref = true;
  }

  grn_obj *lexicon;
  bool lexicon_need_unref = false;
  if (grn_obj_is_table(ctx, data->index)) {
    lexicon = data->index;
  } else {
    lexicon = grn_ctx_at(ctx, data->index->header.domain);
    lexicon_need_unref = true;
  }

  if (data->query->header.domain == lexicon->header.domain) {
    data->query_raw = GRN_BULK_HEAD(data->query);
    data->query_raw_len = (uint32_t)(GRN_BULK_VSIZE(data->query));
  } else {
    grn_obj_reinit_for(ctx, &(data->query_casted), lexicon);
    grn_rc rc = grn_obj_cast(ctx, data->query, &(data->query_casted), false);
    if (rc == GRN_SUCCESS) {
      data->query_raw = GRN_BULK_HEAD(&(data->query_casted));
      data->query_raw_len = (uint32_t)(GRN_BULK_VSIZE(&(data->query_casted)));
    } else {
      grn_accessor_estimate_size_for_query_cast_failed(ctx, data);
    }
  }

  if (lexicon_need_unref) {
    grn_obj_unref(ctx, lexicon);
  }

  return ctx->rc == GRN_SUCCESS;
}

uint32_t
grn_accessor_estimate_size_for_query(grn_ctx *ctx,
                                     grn_obj *accessor,
                                     grn_obj *query,
                                     grn_search_optarg *optarg)
{
  GRN_API_ENTER;

  if (!grn_obj_is_accessor(ctx, accessor)) {
    grn_obj inspected;
    GRN_TEXT_INIT(&inspected, 0);
    grn_inspect(ctx, &inspected, accessor);
    ERR(GRN_INVALID_ARGUMENT,
        "[accessor][estimate-size][query] must be accessor: %.*s",
        (int)GRN_TEXT_LEN(&inspected),
        GRN_TEXT_VALUE(&inspected));
    GRN_OBJ_FIN(ctx, &inspected);
    GRN_API_RETURN(0);
  }

  if (!query) {
    GRN_API_RETURN(0);
  }

  grn_accessor_estimate_size_for_query_data data;
  data.accessor = accessor;
  data.query = query;
  data.optarg = optarg;
  data.index = NULL;
  data.index_need_unref = false;
  data.operator_ = GRN_OP_MATCH;
  GRN_VOID_INIT(&(data.query_casted));
  data.query_raw = NULL;
  data.query_raw_len = 0;
  data.n_target_records = 0;
  data.depth = 0;

  if (!grn_accessor_estimate_size_for_query_prepare(ctx, &data)) {
    grn_accessor_estimate_size_for_query_data_fin(ctx, &data);
    GRN_API_RETURN(0);
  }

  uint32_t estimated_size = 0;
  if (data.n_base_records == 0) {
    estimated_size = 0;
  } else {
    uint32_t base_estimated_size;
    if (grn_obj_is_table(ctx, data.index)) {
      if (data.operator_ == GRN_OP_EQUAL) {
        if (grn_table_get(ctx,
                          data.index,
                          data.query_raw,
                          data.query_raw_len)) {
          base_estimated_size = 1;
        } else {
          base_estimated_size = 0;
        }
      } else {
        /* TODO */
        base_estimated_size = data.n_base_records;
      }
    } else {
      base_estimated_size =
        grn_ii_estimate_size_for_query(ctx,
                                       (grn_ii *)(data.index),
                                       data.query_raw,
                                       data.query_raw_len,
                                       optarg);
    }
    if (base_estimated_size >= data.n_base_records) {
      estimated_size = data.n_target_records;
    } else {
      estimated_size =
        (uint32_t)(data.n_target_records *
                   (base_estimated_size / (double)(data.n_base_records)));
    }
  }

  grn_accessor_estimate_size_for_query_data_fin(ctx, &data);

  GRN_API_RETURN(estimated_size);
}

typedef enum {
  GRN_ACCESSOR_TO_STRING_USE_CASE_NAME,
  GRN_ACCESSOR_TO_STRING_USE_CASE_SCRIPT_SYNTAX,
} GRN_ACCESSOR_TO_STRING_USE_CASE;

static grn_rc
grn_accessor_to_string(grn_ctx *ctx,
                       grn_obj *accessor,
                       grn_obj *buffer,
                       GRN_ACCESSOR_TO_STRING_USE_CASE use_case,
                       const char *tag)
{
  grn_accessor *accessor_;
  GRN_API_ENTER;

  bool need_context = true;
  bool need_preceding_keys = true;
  if (use_case == GRN_ACCESSOR_TO_STRING_USE_CASE_SCRIPT_SYNTAX) {
    need_context = false;
    need_preceding_keys = false;
  }

  if (!grn_obj_is_accessor(ctx, accessor)) {
    grn_obj inspected;
    GRN_TEXT_INIT(&inspected, 0);
    grn_inspect(ctx, &inspected, accessor);
    ERR(GRN_INVALID_ARGUMENT,
        "%s must be accessor: %.*s",
        tag,
        (int)GRN_TEXT_LEN(&inspected),
        GRN_TEXT_VALUE(&inspected));
    GRN_OBJ_FIN(ctx, &inspected);
    GRN_API_RETURN(ctx->rc);
  }

  uint32_t n_components = 0;
  for (accessor_ = (grn_accessor *)accessor; accessor_;
       accessor_ = accessor_->next) {
    bool need_continue = false;
    bool show_obj_name = false;
    bool show_obj_domain_name = false;

    if (n_components > 0) {
      GRN_TEXT_PUTS(ctx, buffer, ".");
    }
    switch (accessor_->action) {
    case GRN_ACCESSOR_GET_ID:
      GRN_TEXT_PUT(ctx, buffer, GRN_COLUMN_NAME_ID, GRN_COLUMN_NAME_ID_LEN);
      show_obj_name = true;
      break;
    case GRN_ACCESSOR_GET_KEY:
      if (!need_preceding_keys && accessor_->next) {
        need_continue = true;
      } else {
        GRN_TEXT_PUT(ctx, buffer, GRN_COLUMN_NAME_KEY, GRN_COLUMN_NAME_KEY_LEN);
        show_obj_name = true;
      }
      break;
    case GRN_ACCESSOR_GET_VALUE:
      GRN_TEXT_PUT(ctx,
                   buffer,
                   GRN_COLUMN_NAME_VALUE,
                   GRN_COLUMN_NAME_VALUE_LEN);
      show_obj_name = true;
      break;
    case GRN_ACCESSOR_GET_SCORE:
      GRN_TEXT_PUT(ctx,
                   buffer,
                   GRN_COLUMN_NAME_SCORE,
                   GRN_COLUMN_NAME_SCORE_LEN);
      break;
    case GRN_ACCESSOR_GET_NSUBRECS:
      GRN_TEXT_PUT(ctx,
                   buffer,
                   GRN_COLUMN_NAME_NSUBRECS,
                   GRN_COLUMN_NAME_NSUBRECS_LEN);
      break;
    case GRN_ACCESSOR_GET_MAX:
      GRN_TEXT_PUT(ctx, buffer, GRN_COLUMN_NAME_MAX, GRN_COLUMN_NAME_MAX_LEN);
      break;
    case GRN_ACCESSOR_GET_MIN:
      GRN_TEXT_PUT(ctx, buffer, GRN_COLUMN_NAME_MIN, GRN_COLUMN_NAME_MIN_LEN);
      break;
    case GRN_ACCESSOR_GET_SUM:
      GRN_TEXT_PUT(ctx, buffer, GRN_COLUMN_NAME_SUM, GRN_COLUMN_NAME_SUM_LEN);
      break;
    case GRN_ACCESSOR_GET_AVG:
      GRN_TEXT_PUT(ctx, buffer, GRN_COLUMN_NAME_AVG, GRN_COLUMN_NAME_AVG_LEN);
      break;
    case GRN_ACCESSOR_GET_MEAN:
      GRN_TEXT_PUT(ctx, buffer, GRN_COLUMN_NAME_MEAN, GRN_COLUMN_NAME_MEAN_LEN);
      break;
    case GRN_ACCESSOR_GET_COLUMN_VALUE:
      grn_column_name_(ctx, accessor_->obj, buffer);
      show_obj_domain_name = true;
      break;
    case GRN_ACCESSOR_GET_DB_OBJ:
      grn_text_printf(ctx, buffer, "(_db)");
      break;
    case GRN_ACCESSOR_LOOKUP:
      grn_text_printf(ctx, buffer, "(_lookup)");
      break;
    case GRN_ACCESSOR_FUNCALL:
      grn_text_printf(ctx, buffer, "(_funcall)");
      break;
    default:
      grn_text_printf(ctx, buffer, "(unknown:%u)", accessor_->action);
      break;
    }

    if (need_continue) {
      continue;
    }

    if (need_context && (show_obj_name || show_obj_domain_name)) {
      grn_obj *target = accessor_->obj;
      char target_name[GRN_TABLE_MAX_KEY_SIZE];
      int target_name_size;

      if (show_obj_domain_name) {
        target = grn_ctx_at(ctx, target->header.domain);
      }

      target_name_size =
        grn_obj_name(ctx, target, target_name, GRN_TABLE_MAX_KEY_SIZE);
      GRN_TEXT_PUTS(ctx, buffer, "(");
      if (target_name_size == 0) {
        GRN_TEXT_PUTS(ctx, buffer, "anonymous");
      } else {
        GRN_TEXT_PUT(ctx, buffer, target_name, target_name_size);
      }
      GRN_TEXT_PUTS(ctx, buffer, ")");
      if (show_obj_domain_name && target) {
        grn_obj_unref(ctx, target);
      }
    }
    n_components++;
  }

  GRN_API_RETURN(GRN_SUCCESS);
}

grn_rc
grn_accessor_name(grn_ctx *ctx, grn_obj *accessor, grn_obj *name)
{
  GRN_API_ENTER;
  grn_rc rc = grn_accessor_to_string(ctx,
                                     accessor,
                                     name,
                                     GRN_ACCESSOR_TO_STRING_USE_CASE_NAME,
                                     "[accessor][name]");
  GRN_API_RETURN(rc);
}

grn_rc
grn_accessor_to_script_syntax(grn_ctx *ctx, grn_obj *accessor, grn_obj *buffer)
{
  GRN_API_ENTER;
  grn_rc rc =
    grn_accessor_to_string(ctx,
                           accessor,
                           buffer,
                           GRN_ACCESSOR_TO_STRING_USE_CASE_SCRIPT_SYNTAX,
                           "[accessor][to-script-syntax]");
  GRN_API_RETURN(rc);
}
