/*
  Copyright (C) 2018  Brazil
  Copyright (C) 2020-2023  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_ctx.h"
#include "grn_float.h"
#include "grn_io.h"
#include "grn_vector.h"

#include <groonga/bulk.hpp>

#include <cstring>

namespace grn {
  namespace {
    template <typename NUMERIC, typename FLOAT>
    NUMERIC
    cast_value(const char *raw_value)
    {
      return static_cast<NUMERIC>(*reinterpret_cast<const FLOAT *>(raw_value));
    }

    template <>
    bool
    cast_value<bool, float>(const char *raw_value)
    {
      float value = *reinterpret_cast<const float *>(raw_value);
      return grn_float32_is_zero(value);
    }

    template <>
    bool
    cast_value<bool, double>(const char *raw_value)
    {
      double value = *reinterpret_cast<const double *>(raw_value);
      return grn_float_is_zero(value);
    }
  } // namespace

  template <typename NUMERIC>
  NUMERIC
  vector_get_element(grn_ctx *ctx,
                     grn_obj *vector,
                     uint32_t offset,
                     NUMERIC default_value)
  {
    const char *raw_value = NULL;
    grn_id domain;
    uint32_t length;
    NUMERIC value = default_value;

    GRN_API_ENTER;

    length =
      grn_vector_get_element(ctx, vector, offset, &raw_value, NULL, &domain);
    if (length > 0) {
      switch (domain) {
      case GRN_DB_BOOL:
        value = *reinterpret_cast<const bool *>(raw_value);
        break;
      case GRN_DB_INT8:
        value =
          static_cast<NUMERIC>(*reinterpret_cast<const int8_t *>(raw_value));
        break;
      case GRN_DB_UINT8:
        value =
          static_cast<NUMERIC>(*reinterpret_cast<const uint8_t *>(raw_value));
        break;
      case GRN_DB_INT16:
        value =
          static_cast<NUMERIC>(*reinterpret_cast<const int16_t *>(raw_value));
        break;
      case GRN_DB_UINT16:
        value =
          static_cast<NUMERIC>(*reinterpret_cast<const uint16_t *>(raw_value));
        break;
      case GRN_DB_INT32:
        value =
          static_cast<NUMERIC>(*reinterpret_cast<const int32_t *>(raw_value));
        break;
      case GRN_DB_UINT32:
        value =
          static_cast<NUMERIC>(*reinterpret_cast<const uint32_t *>(raw_value));
        break;
      case GRN_DB_INT64:
        value =
          static_cast<NUMERIC>(*reinterpret_cast<const int64_t *>(raw_value));
        break;
      case GRN_DB_UINT64:
        value =
          static_cast<NUMERIC>(*reinterpret_cast<const uint64_t *>(raw_value));
        break;
      case GRN_DB_FLOAT32:
        value = cast_value<NUMERIC, float>(raw_value);
        break;
      case GRN_DB_FLOAT:
        value = cast_value<NUMERIC, double>(raw_value);
        break;
      default:
        break;
      }
    }

    GRN_API_RETURN(value);
  }
} // namespace grn

extern "C" {
bool
grn_vector_get_element_bool(grn_ctx *ctx,
                            grn_obj *vector,
                            uint32_t offset,
                            bool default_value)
{
  return grn::vector_get_element<bool>(ctx, vector, offset, default_value);
}

int8_t
grn_vector_get_element_int8(grn_ctx *ctx,
                            grn_obj *vector,
                            uint32_t offset,
                            int8_t default_value)
{
  return grn::vector_get_element<int8_t>(ctx, vector, offset, default_value);
}

uint8_t
grn_vector_get_element_uint8(grn_ctx *ctx,
                             grn_obj *vector,
                             uint32_t offset,
                             uint8_t default_value)
{
  return grn::vector_get_element<uint8_t>(ctx, vector, offset, default_value);
}

int16_t
grn_vector_get_element_int16(grn_ctx *ctx,
                             grn_obj *vector,
                             uint32_t offset,
                             int16_t default_value)
{
  return grn::vector_get_element<int16_t>(ctx, vector, offset, default_value);
}

uint16_t
grn_vector_get_element_uint16(grn_ctx *ctx,
                              grn_obj *vector,
                              uint32_t offset,
                              uint16_t default_value)
{
  return grn::vector_get_element<uint16_t>(ctx, vector, offset, default_value);
}

int32_t
grn_vector_get_element_int32(grn_ctx *ctx,
                             grn_obj *vector,
                             uint32_t offset,
                             int32_t default_value)
{
  return grn::vector_get_element<int32_t>(ctx, vector, offset, default_value);
}

uint32_t
grn_vector_get_element_uint32(grn_ctx *ctx,
                              grn_obj *vector,
                              uint32_t offset,
                              uint32_t default_value)
{
  return grn::vector_get_element<uint32_t>(ctx, vector, offset, default_value);
}

int64_t
grn_vector_get_element_int64(grn_ctx *ctx,
                             grn_obj *vector,
                             uint32_t offset,
                             int64_t default_value)
{
  return grn::vector_get_element<int64_t>(ctx, vector, offset, default_value);
}

uint64_t
grn_vector_get_element_uint64(grn_ctx *ctx,
                              grn_obj *vector,
                              uint32_t offset,
                              uint64_t default_value)
{
  return grn::vector_get_element<uint64_t>(ctx, vector, offset, default_value);
}

float
grn_vector_get_element_float32(grn_ctx *ctx,
                               grn_obj *vector,
                               uint32_t offset,
                               float default_value)
{
  return grn::vector_get_element<float>(ctx, vector, offset, default_value);
}

double
grn_vector_get_element_float64(grn_ctx *ctx,
                               grn_obj *vector,
                               uint32_t offset,
                               double default_value)
{
  return grn::vector_get_element<double>(ctx, vector, offset, default_value);
}

static uint32_t
grn_uvector_element_size_internal(grn_ctx *ctx, grn_obj *uvector)
{
  size_t element_size = grn_type_id_size(ctx, uvector->header.domain);
  if (grn_obj_is_weight_uvector(ctx, uvector)) {
#ifdef GRN_HAVE_BFLOAT16
    if (uvector->header.flags & GRN_OBJ_WEIGHT_BFLOAT16) {
      element_size += sizeof(grn_bfloat16);
    } else {
      element_size += sizeof(float);
    }
#else
    element_size += sizeof(float);
#endif
  }
  return static_cast<uint32_t>(element_size);
}

static uint32_t
grn_uvector_size_internal(grn_ctx *ctx, grn_obj *uvector)
{
  uint32_t element_size = grn_uvector_element_size_internal(ctx, uvector);
  return static_cast<uint32_t>(GRN_BULK_VSIZE(uvector) / element_size);
}

uint32_t
grn_vector_size(grn_ctx *ctx, grn_obj *vector)
{
  uint32_t size;
  if (!vector) {
    ERR(GRN_INVALID_ARGUMENT, "[vector][size] vector is null");
    return 0;
  }
  GRN_API_ENTER;
  switch (vector->header.type) {
  case GRN_BULK:
    size = static_cast<uint32_t>(GRN_BULK_VSIZE(vector));
    break;
  case GRN_UVECTOR:
    size = grn_uvector_size_internal(ctx, vector);
    break;
  case GRN_PVECTOR:
    size = static_cast<uint32_t>(GRN_BULK_VSIZE(vector) / sizeof(grn_obj *));
    break;
  case GRN_VECTOR:
    size = vector->u.v.n_sections;
    break;
  default:
    {
      grn_obj inspected;
      GRN_TEXT_INIT(&inspected, 0);
      grn_inspect(ctx, &inspected, vector);
      ERR(GRN_INVALID_ARGUMENT,
          "[vector][size] not vector: %.*s",
          (int)GRN_TEXT_LEN(&inspected),
          GRN_TEXT_VALUE(&inspected));
      GRN_OBJ_FIN(ctx, &inspected);
    }
    size = 0;
    break;
  }
  GRN_API_RETURN(size);
}

grn_obj *
grn_vector_body(grn_ctx *ctx, grn_obj *v)
{
  if (!v) {
    ERR(GRN_INVALID_ARGUMENT, "invalid argument");
    return NULL;
  }
  switch (v->header.type) {
  case GRN_VECTOR:
    if (!v->u.v.body) {
      v->u.v.body = grn_obj_open(ctx, GRN_BULK, 0, v->header.domain);
    }
    return v->u.v.body;
  case GRN_BULK:
  case GRN_UVECTOR:
    return v;
  default:
    return NULL;
  }
}

uint32_t
grn_vector_get_element(grn_ctx *ctx,
                       grn_obj *vector,
                       uint32_t offset,
                       const char **str,
                       uint32_t *weight,
                       grn_id *domain)
{
  float weight_float;
  uint32_t length = grn_vector_get_element_float(ctx,
                                                 vector,
                                                 offset,
                                                 str,
                                                 &weight_float,
                                                 domain);
  if (weight) {
    *weight = static_cast<uint32_t>(weight_float);
  }
  return length;
}

uint32_t
grn_vector_get_element_float(grn_ctx *ctx,
                             grn_obj *vector,
                             uint32_t offset,
                             const char **str,
                             float *weight,
                             grn_id *domain)
{
  uint32_t length = 0;
  GRN_API_ENTER;
  if (!vector || vector->header.type != GRN_VECTOR) {
    ERR(GRN_INVALID_ARGUMENT, "invalid vector");
    goto exit;
  }
  if (vector->u.v.n_sections <= offset) {
    ERR(GRN_RANGE_ERROR, "offset out of range");
    goto exit;
  }
  {
    grn_section *vp = &vector->u.v.sections[offset];
    grn_obj *body = grn_vector_body(ctx, vector);
    *str = GRN_BULK_HEAD(body) + vp->offset;
    if (weight) {
      *weight = vp->weight;
    }
    if (domain) {
      *domain = vp->domain;
    }
    length = vp->length;
  }
exit:
  GRN_API_RETURN(length);
}

uint32_t
grn_vector_pop_element(grn_ctx *ctx,
                       grn_obj *vector,
                       const char **str,
                       uint32_t *weight,
                       grn_id *domain)
{
  float weight_float;
  uint32_t length =
    grn_vector_pop_element_float(ctx, vector, str, &weight_float, domain);
  if (weight) {
    *weight = static_cast<uint32_t>(weight_float);
  }
  return length;
}

uint32_t
grn_vector_pop_element_float(grn_ctx *ctx,
                             grn_obj *vector,
                             const char **str,
                             float *weight,
                             grn_id *domain)
{
  uint32_t offset, length = 0;
  GRN_API_ENTER;
  if (!vector || vector->header.type != GRN_VECTOR) {
    ERR(GRN_INVALID_ARGUMENT, "invalid vector");
    goto exit;
  }
  if (!vector->u.v.n_sections) {
    ERR(GRN_RANGE_ERROR, "offset out of range");
    goto exit;
  }
  offset = --vector->u.v.n_sections;
  {
    grn_section *vp = &vector->u.v.sections[offset];
    grn_obj *body = grn_vector_body(ctx, vector);
    *str = GRN_BULK_HEAD(body) + vp->offset;
    if (weight) {
      *weight = vp->weight;
    }
    if (domain) {
      *domain = vp->domain;
    }
    length = vp->length;
    grn_bulk_truncate(ctx, body, vp->offset);
  }
exit:
  GRN_API_RETURN(length);
}

#define W_SECTIONS_UNIT 8
#define S_SECTIONS_UNIT (1 << W_SECTIONS_UNIT)
#define M_SECTIONS_UNIT (S_SECTIONS_UNIT - 1)

grn_rc
grn_vector_delimit(grn_ctx *ctx, grn_obj *v, float weight, grn_id domain)
{
  if (v->header.type != GRN_VECTOR) {
    return GRN_INVALID_ARGUMENT;
  }
  if (!(v->u.v.n_sections & M_SECTIONS_UNIT)) {
    size_t size = sizeof(grn_section) * (v->u.v.n_sections + S_SECTIONS_UNIT);
    grn_section *vp =
      static_cast<grn_section *>(GRN_REALLOC(v->u.v.sections, size));
    if (!vp) {
      return GRN_NO_MEMORY_AVAILABLE;
    }
    v->u.v.sections = vp;
  }
  {
    grn_obj *body = grn_vector_body(ctx, v);
    grn_section *vp = &v->u.v.sections[v->u.v.n_sections];
    vp->offset = v->u.v.n_sections ? vp[-1].offset + vp[-1].length : 0;
    vp->length = static_cast<uint32_t>(GRN_BULK_VSIZE(body) - vp->offset);
    vp->weight = weight;
    vp->domain = domain;
  }
  v->u.v.n_sections++;
  return GRN_SUCCESS;
}

grn_obj *
grn_vector_pack(grn_ctx *ctx,
                grn_obj *vector,
                uint32_t offset,
                uint32_t n,
                grn_vector_pack_flags flags,
                grn_obj *header,
                grn_obj *footer)
{
  bool need_footer = false;
  grn_text_benc(ctx, header, n);
  for (uint32_t i = 0; i < n; ++i) {
    grn_section *section = &(vector->u.v.sections[i + offset]);
    grn_text_benc(ctx, header, section->length);
    if (section->weight > 0 || section->domain != GRN_ID_NIL) {
      need_footer = true;
    }
  }
  if (need_footer) {
    for (uint32_t i = 0; i < n; ++i) {
      grn_section *section = &(vector->u.v.sections[i + offset]);
      if (flags & GRN_VECTOR_PACK_WEIGHT_FLOAT32) {
        GRN_FLOAT32_PUT(ctx, footer, section->weight);
      } else if (flags & GRN_VECTOR_PACK_WEIGHT_BFLOAT16) {
#ifdef GRN_HAVE_BFLOAT16
        GRN_BFLOAT16_PUT(ctx,
                         footer,
                         grn::numeric::to_bfloat16(section->weight));
#else
        GRN_FLOAT32_PUT(ctx, footer, section->weight);
#endif
      } else {
        grn_text_benc(ctx, footer, static_cast<unsigned int>(section->weight));
      }
      grn_text_benc(ctx, footer, section->domain);
    }
  }
  return grn_vector_body(ctx, vector);
}

grn_rc
grn_vector_unpack(grn_ctx *ctx,
                  grn_obj *vector,
                  const uint8_t *data,
                  uint32_t data_size,
                  grn_vector_pack_flags flags,
                  uint32_t *used_size)
{
  auto start = data;
  auto p = start;
  auto pe = p + data_size;
  uint32_t n0 = vector->u.v.n_sections;
  uint32_t n;
  GRN_B_DEC(n, p);
  if (((n0 + M_SECTIONS_UNIT) >> W_SECTIONS_UNIT) !=
      ((n0 + n + M_SECTIONS_UNIT) >> W_SECTIONS_UNIT)) {
    size_t size =
      sizeof(grn_section) * ((n0 + n + M_SECTIONS_UNIT) & ~M_SECTIONS_UNIT);
    grn_section *sections =
      static_cast<grn_section *>(GRN_REALLOC(vector->u.v.sections, size));
    if (!sections) {
      return GRN_NO_MEMORY_AVAILABLE;
    }
    vector->u.v.sections = sections;
  }
  {
    grn_obj *body = grn_vector_body(ctx, vector);
    uint32_t offset = static_cast<uint32_t>(GRN_BULK_VSIZE(body));
    uint32_t o = 0;
    for (uint32_t i = 0; i < n; ++i) {
      grn_section *section = vector->u.v.sections + n0 + i;
      if (pe <= p) {
        return GRN_INVALID_ARGUMENT;
      }
      uint32_t length;
      GRN_B_DEC(length, p);
      section->length = length;
      section->offset = offset + o;
      section->weight = 0;
      section->domain = 0;
      o += length;
    }
    if (pe < p + o) {
      return GRN_INVALID_ARGUMENT;
    }
    grn_bulk_write(ctx, body, (char *)p, o);
    p += o;
    if (p < pe) {
      for (uint32_t i = 0; i < n; ++i) {
        grn_section *section = vector->u.v.sections + n0 + i;
        if (pe <= p) {
          return GRN_INVALID_ARGUMENT;
        }
        if (flags & GRN_VECTOR_PACK_WEIGHT_FLOAT32) {
          grn_memcpy(&(section->weight), p, sizeof(float));
          p += sizeof(float);
        } else if (flags & GRN_VECTOR_PACK_WEIGHT_BFLOAT16) {
#ifdef GRN_HAVE_BFLOAT16
          grn_bfloat16 weight_bfloat16;
          grn_memcpy(&weight_bfloat16, p, sizeof(grn_bfloat16));
          p += sizeof(grn_bfloat16);
          section->weight = grn_bfloat16_to_float32(weight_bfloat16);
#else
          grn_memcpy(&(section->weight), p, sizeof(float));
          p += sizeof(float);
#endif
        } else {
          uint32_t weight;
          GRN_B_DEC(weight, p);
          section->weight = static_cast<float>(weight);
        }
        GRN_B_DEC(section->domain, p);
      }
    }
  }
  vector->u.v.n_sections += n;
  if (used_size) {
    *used_size = p - start;
  }
  return GRN_SUCCESS;
}

grn_rc
grn_vector_add_element(grn_ctx *ctx,
                       grn_obj *vector,
                       const char *str,
                       uint32_t str_len,
                       uint32_t weight,
                       grn_id domain)
{
  return grn_vector_add_element_float(ctx,
                                      vector,
                                      str,
                                      str_len,
                                      static_cast<float>(weight),
                                      domain);
}

grn_rc
grn_vector_add_element_float(grn_ctx *ctx,
                             grn_obj *vector,
                             const char *str,
                             uint32_t str_len,
                             float weight,
                             grn_id domain)
{
  grn_obj *body;
  GRN_API_ENTER;
  if (!vector) {
    ERR(GRN_INVALID_ARGUMENT, "vector is null");
    goto exit;
  }
  if ((body = grn_vector_body(ctx, vector))) {
    grn_bulk_write(ctx, body, str, str_len);
    grn_vector_delimit(ctx, vector, weight, domain);
  }
exit:
  GRN_API_RETURN(ctx->rc);
}

grn_rc
grn_vector_copy(grn_ctx *ctx, grn_obj *src, grn_obj *dest)
{
  GRN_API_ENTER;
  uint32_t n_elements = grn_vector_size(ctx, src);
  uint32_t i;
  for (i = 0; i < n_elements; i++) {
    const char *content;
    float weight;
    grn_id domain;
    uint32_t content_size =
      grn_vector_get_element_float(ctx, src, i, &content, &weight, &domain);
    grn_vector_add_element_float(ctx,
                                 dest,
                                 content,
                                 content_size,
                                 weight,
                                 domain);
  }
  GRN_API_RETURN(ctx->rc);
}

grn_obj *
grn_vector_join(grn_ctx *ctx,
                grn_obj *vector,
                const char *separator,
                int separator_length,
                grn_obj *destination)
{
  GRN_API_ENTER;
  if (!vector) {
    ERR(GRN_INVALID_ARGUMENT, "[vector][join] vector is NULL");
    GRN_API_RETURN(NULL);
  }
  if (vector->header.type != GRN_VECTOR) {
    grn_obj type_name;
    GRN_TEXT_INIT(&type_name, 0);
    grn_inspect_type(ctx, &type_name, vector->header.type);
    ERR(GRN_INVALID_ARGUMENT,
        "[vector][join] must be GRN_UVECTOR: %.*s",
        (int)GRN_TEXT_LEN(&type_name),
        GRN_TEXT_VALUE(&type_name));
    GRN_OBJ_FIN(ctx, &type_name);
    GRN_API_RETURN(NULL);
  }
  if (separator_length < 0) {
    separator_length = (int)strlen(separator);
  }
  if (!destination) {
    destination = grn_obj_open(ctx, GRN_BULK, 0, GRN_DB_TEXT);
  }
  uint32_t n_elements = grn_vector_size(ctx, vector);
  uint32_t i;
  grn_obj element;
  GRN_TEXT_INIT(&element, GRN_OBJ_DO_SHALLOW_COPY);
  for (i = 0; i < n_elements; i++) {
    if (i > 0 && separator_length > 0) {
      GRN_TEXT_PUT(ctx, destination, separator, separator_length);
    }
    const char *content;
    grn_id domain;
    uint32_t content_size =
      grn_vector_get_element_float(ctx, vector, i, &content, NULL, &domain);
    GRN_TEXT_SET(ctx, &element, content, content_size);
    element.header.domain = domain;
    grn_obj_cast(ctx, &element, destination, false);
    element.header.domain = GRN_DB_TEXT;
  }
  GRN_OBJ_FIN(ctx, &element);
  GRN_API_RETURN(destination);
}

uint32_t
grn_uvector_size(grn_ctx *ctx, grn_obj *uvector)
{
  if (!uvector) {
    ERR(GRN_INVALID_ARGUMENT, "uvector must not be NULL");
    return 0;
  }

  if (uvector->header.type != GRN_UVECTOR) {
    grn_obj type_name;
    GRN_TEXT_INIT(&type_name, 0);
    grn_inspect_type(ctx, &type_name, uvector->header.type);
    ERR(GRN_INVALID_ARGUMENT,
        "must be GRN_UVECTOR: %.*s",
        (int)GRN_TEXT_LEN(&type_name),
        GRN_TEXT_VALUE(&type_name));
    GRN_OBJ_FIN(ctx, &type_name);
    return 0;
  }

  GRN_API_ENTER;
  size_t size = grn_uvector_size_internal(ctx, uvector);
  GRN_API_RETURN(static_cast<uint32_t>(size));
}

uint32_t
grn_uvector_element_size(grn_ctx *ctx, grn_obj *uvector)
{
  if (!uvector) {
    ERR(GRN_INVALID_ARGUMENT, "uvector must not be NULL");
    return 0;
  }

  if (uvector->header.type != GRN_UVECTOR) {
    grn_obj type_name;
    GRN_TEXT_INIT(&type_name, 0);
    grn_inspect_type(ctx, &type_name, uvector->header.type);
    ERR(GRN_INVALID_ARGUMENT,
        "must be GRN_UVECTOR: %.*s",
        (int)GRN_TEXT_LEN(&type_name),
        GRN_TEXT_VALUE(&type_name));
    GRN_OBJ_FIN(ctx, &type_name);
    return 0;
  }

  GRN_API_ENTER;
  uint32_t element_size = grn_uvector_element_size_internal(ctx, uvector);
  GRN_API_RETURN(element_size);
}

/* For reference uvector. We can't use this for integer family uvector
   such as Int64 uvector. */
grn_rc
grn_uvector_add_element(grn_ctx *ctx,
                        grn_obj *uvector,
                        grn_id id,
                        uint32_t weight)
{
  return grn_uvector_add_element_record(ctx,
                                        uvector,
                                        id,
                                        static_cast<float>(weight));
}

grn_rc
grn_uvector_add_element_record(grn_ctx *ctx,
                               grn_obj *uvector,
                               grn_id id,
                               float weight)
{
  GRN_API_ENTER;
  if (!uvector) {
    ERR(GRN_INVALID_ARGUMENT, "[uvector][add-element][record] uvector is null");
    goto exit;
  }
  GRN_RECORD_PUT(ctx, uvector, id);
  if (grn_obj_is_weight_uvector(ctx, uvector)) {
    if (uvector->header.flags & GRN_OBJ_WEIGHT_BFLOAT16) {
#ifdef GRN_HAVE_BFLOAT16
      GRN_BFLOAT16_PUT(ctx, uvector, grn::numeric::to_bfloat16(weight));
#else
      GRN_FLOAT32_PUT(ctx, uvector, weight);
#endif
    } else {
      GRN_FLOAT32_PUT(ctx, uvector, weight);
    }
  }
exit:
  GRN_API_RETURN(ctx->rc);
}

/* For reference uvector. We can't use this for integer family uvector
   such as Int64 uvector. */
grn_id
grn_uvector_get_element(grn_ctx *ctx,
                        grn_obj *uvector,
                        uint32_t offset,
                        uint32_t *weight)
{
  float weight_float;
  grn_id id =
    grn_uvector_get_element_record(ctx, uvector, offset, &weight_float);
  if (weight) {
    *weight = static_cast<uint32_t>(weight_float);
  }
  return id;
}

grn_id
grn_uvector_get_element_record(grn_ctx *ctx,
                               grn_obj *uvector,
                               uint32_t offset,
                               float *weight)
{
  grn_id id = GRN_ID_NIL;

  GRN_API_ENTER;
  if (!uvector) {
    ERR(GRN_INVALID_ARGUMENT, "[uvector][get-element][record] uvector is null");
    goto exit;
  }
  if (uvector->header.type != GRN_UVECTOR) {
    ERR(GRN_INVALID_ARGUMENT, "[uvector][get-element][record] invalid uvector");
    goto exit;
  }

  {
    size_t element_value_size = sizeof(grn_id);
    size_t element_size = element_value_size;
    if (grn_obj_is_weight_uvector(ctx, uvector)) {
      if (uvector->header.flags & GRN_OBJ_WEIGHT_BFLOAT16) {
#ifdef GRN_HAVE_BFLOAT16
        element_size += sizeof(grn_bfloat16);
#else
        element_size += sizeof(float);
#endif
      } else {
        element_size += sizeof(float);
      }
    }
    const char *elements_start = GRN_BULK_HEAD(uvector);
    const char *elements_end = GRN_BULK_CURR(uvector);
    if (offset > elements_end - elements_start) {
      ERR(GRN_RANGE_ERROR,
          "[uvector][get-element][record] "
          "offset out of range: <%u>/<%" GRN_FMT_SIZE ">",
          offset,
          elements_end - elements_start);
      goto exit;
    }

    id = *((grn_id *)(elements_start + (element_size * offset)));
    if (weight) {
      if (grn_obj_is_weight_uvector(ctx, uvector)) {
        if (uvector->header.flags & GRN_OBJ_WEIGHT_BFLOAT16) {
#ifdef GRN_HAVE_BFLOAT16
          grn_bfloat16 weight_bfloat16 =
            *reinterpret_cast<const grn_bfloat16 *>(
              elements_start + (element_size * offset) + element_value_size);
          *weight = grn_bfloat16_to_float32(weight_bfloat16);
#else
          *weight = *reinterpret_cast<const float *>(
            elements_start + (element_size * offset) + element_value_size);
#endif
        } else {
          *weight = *reinterpret_cast<const float *>(
            elements_start + (element_size * offset) + element_value_size);
        }
      } else {
        *weight = 0.0;
      }
    }
  }
exit:
  GRN_API_RETURN(id);
}

grn_rc
grn_uvector_copy(grn_ctx *ctx, grn_obj *src, grn_obj *dest)
{
  GRN_API_ENTER;
  if (src->header.domain != dest->header.domain) {
    ERR(GRN_INVALID_ARGUMENT,
        "[uvector][copy] different domain: "
        "source=<%u> "
        "destination=<%u>",
        src->header.domain,
        dest->header.domain);
    GRN_API_RETURN(ctx->rc);
  }
  bool src_have_weight = grn_obj_is_weight_uvector(ctx, src);
  bool dest_have_weight = grn_obj_is_weight_uvector(ctx, dest);
  if (src_have_weight != dest_have_weight) {
    ERR(GRN_INVALID_ARGUMENT,
        "[uvector][copy] different weight availability: "
        "source=<%s> "
        "destination=<%s>",
        src_have_weight ? "true" : "false",
        dest_have_weight ? "true" : "false");
    GRN_API_RETURN(ctx->rc);
  }
  grn_bulk_write(ctx, dest, GRN_BULK_HEAD(src), GRN_BULK_VSIZE(src));
  GRN_API_RETURN(ctx->rc);
}

grn_obj *
grn_uvector_join(grn_ctx *ctx,
                 grn_obj *uvector,
                 const char *separator,
                 int separator_length,
                 grn_obj *destination)
{
  GRN_API_ENTER;
  if (!uvector) {
    ERR(GRN_INVALID_ARGUMENT, "[uvector][join] uvector is NULL");
    GRN_API_RETURN(NULL);
  }
  if (uvector->header.type != GRN_UVECTOR) {
    grn_obj type_name;
    GRN_TEXT_INIT(&type_name, 0);
    grn_inspect_type(ctx, &type_name, uvector->header.type);
    ERR(GRN_INVALID_ARGUMENT,
        "[uvector][join] must be GRN_UVECTOR: %.*s",
        (int)GRN_TEXT_LEN(&type_name),
        GRN_TEXT_VALUE(&type_name));
    GRN_OBJ_FIN(ctx, &type_name);
    GRN_API_RETURN(NULL);
  }
  if (separator_length < 0) {
    separator_length = (int)strlen(separator);
  }
  if (!destination) {
    destination = grn_obj_open(ctx, GRN_BULK, 0, GRN_DB_TEXT);
  }
  uint32_t element_size = grn_uvector_element_size_internal(ctx, uvector);
  uint32_t element_content_size = element_size;
  if (grn_obj_is_weight_uvector(ctx, uvector)) {
    if (uvector->header.flags & GRN_OBJ_WEIGHT_BFLOAT16) {
#ifdef GRN_HAVE_BFLOAT16
      element_content_size -= static_cast<uint32_t>(sizeof(grn_bfloat16));
#else
      element_content_size -= static_cast<uint32_t>(sizeof(float));
#endif
    } else {
      element_content_size -= static_cast<uint32_t>(sizeof(float));
    }
  }
  uint32_t n_elements = grn_uvector_size_internal(ctx, uvector);
  uint32_t i;
  grn_obj element;
  GRN_VALUE_FIX_SIZE_INIT(&element,
                          GRN_OBJ_DO_SHALLOW_COPY,
                          uvector->header.domain);
  for (i = 0; i < n_elements; i++) {
    if (i > 0 && separator_length > 0) {
      GRN_TEXT_PUT(ctx, destination, separator, separator_length);
    }
    GRN_TEXT_SET(ctx,
                 &element,
                 GRN_BULK_HEAD(uvector) + (element_size * i),
                 element_content_size);
    grn_obj_cast(ctx, &element, destination, false);
  }
  GRN_OBJ_FIN(ctx, &element);
  GRN_API_RETURN(destination);
}

grn_obj *
grn_pvector_join(grn_ctx *ctx,
                 grn_obj *pvector,
                 const char *separator,
                 int separator_length,
                 grn_obj *destination)
{
  GRN_API_ENTER;
  if (!pvector) {
    ERR(GRN_INVALID_ARGUMENT, "[pvector][join] pvector is NULL");
    GRN_API_RETURN(NULL);
  }
  if (pvector->header.type != GRN_PVECTOR) {
    grn_obj type_name;
    GRN_TEXT_INIT(&type_name, 0);
    grn_inspect_type(ctx, &type_name, pvector->header.type);
    ERR(GRN_INVALID_ARGUMENT,
        "[pvector][join] must be GRN_PVECTOR: %.*s",
        (int)GRN_TEXT_LEN(&type_name),
        GRN_TEXT_VALUE(&type_name));
    GRN_OBJ_FIN(ctx, &type_name);
    GRN_API_RETURN(NULL);
  }
  if (separator_length < 0) {
    separator_length = (int)strlen(separator);
  }
  if (!destination) {
    destination = grn_obj_open(ctx, GRN_BULK, 0, GRN_DB_TEXT);
  }
  size_t n_elements = GRN_PTR_VECTOR_SIZE(pvector);
  size_t i;
  for (i = 0; i < n_elements; i++) {
    if (i > 0 && separator_length > 0) {
      GRN_TEXT_PUT(ctx, destination, separator, separator_length);
    }
    grn_obj *element = GRN_PTR_VALUE_AT(pvector, i);
    grn_obj_cast(ctx, element, destination, false);
  }
  GRN_API_RETURN(destination);
}
}
