/*
  Copyright (C) 2018  Brazil
  Copyright (C) 2021-2022  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_db.h"
#include "grn_msgpack.h"
#include "grn_options.h"
#include "grn_util.h"

#include <stdio.h>

#ifdef GRN_WITH_MESSAGE_PACK
# include <groonga/msgpack.h>
#endif /* GRN_WITH_MESSAGE_PACK */

struct _grn_options {
  grn_ja *values;
};

static const char *GRN_OPTIONS_PATH_FORMAT = "%s.options";
static const unsigned int GRN_OPTIONS_MAX_VALUE_SIZE = 65536;

grn_options *
grn_options_create(grn_ctx *ctx,
                   const char *path,
                   const char *context_tag)
{
  char *options_path;
  char options_path_buffer[PATH_MAX];
  grn_options *options;
  uint32_t flags = 0;

  if (path) {
    grn_snprintf(options_path_buffer,
                 PATH_MAX,
                 PATH_MAX,
                 GRN_OPTIONS_PATH_FORMAT,
                 path);
    options_path = options_path_buffer;
  } else {
    options_path = NULL;
  }

  options = GRN_CALLOC(sizeof(grn_options));
  if (!options) {
    ERR(GRN_NO_MEMORY_AVAILABLE,
        "%s failed to allocate memory for options: <%s>",
        context_tag,
        options_path ? options_path : "(temporary)");
    return NULL;
  }

  options->values = grn_ja_create(ctx,
                                  options_path,
                                  GRN_OPTIONS_MAX_VALUE_SIZE,
                                  flags);
  if (!options->values) {
    GRN_FREE(options);
    ERR(GRN_NO_MEMORY_AVAILABLE,
        "%s failed to create data store for options: <%s>",
        context_tag,
        options_path ? options_path : "(temporary)");
    return NULL;
  }

  return options;
}

grn_options *
grn_options_open(grn_ctx *ctx,
                 const char *path,
                 const char *context_tag)
{
  char options_path[PATH_MAX];
  grn_options *options;

  grn_snprintf(options_path,
               PATH_MAX,
               PATH_MAX,
               GRN_OPTIONS_PATH_FORMAT,
               path);
  if (!grn_path_exist(options_path)) {
    return grn_options_create(ctx, path, context_tag);
  }

  options = GRN_CALLOC(sizeof(grn_options));
  if (!options) {
    ERR(GRN_NO_MEMORY_AVAILABLE,
        "%s failed to allocate memory for options: <%s>",
        context_tag,
        options_path);
    return NULL;
  }

  options->values = grn_ja_open(ctx, options_path);
  if (!options->values) {
    GRN_FREE(options);
    ERR(GRN_NO_MEMORY_AVAILABLE,
        "%s failed to open data store for options: <%s>",
        context_tag,
        options_path);
    return NULL;
  }

  return options;
}

grn_rc
grn_options_close(grn_ctx *ctx, grn_options *options)
{
  grn_rc rc;

  if (!options) {
    return GRN_SUCCESS;
  }

  rc = grn_ja_close(ctx, options->values);
  GRN_FREE(options);

  return rc;
}

grn_rc
grn_options_remove(grn_ctx *ctx, const char *path)
{
  char options_path[PATH_MAX];

  grn_snprintf(options_path,
               PATH_MAX,
               PATH_MAX,
               GRN_OPTIONS_PATH_FORMAT,
               path);
  return grn_ja_remove(ctx, options_path);
}

bool
grn_options_is_locked(grn_ctx *ctx, grn_options *options)
{
  return grn_obj_is_locked(ctx, (grn_obj *)(options->values));
}

grn_rc
grn_options_clear_lock(grn_ctx *ctx, grn_options *options)
{
  return grn_obj_clear_lock(ctx, (grn_obj *)(options->values));
}

bool
grn_options_is_corrupt(grn_ctx *ctx, grn_options *options)
{
  return grn_obj_is_corrupt(ctx, (grn_obj *)(options->values));
}

grn_rc
grn_options_flush(grn_ctx *ctx, grn_options *options)
{
  return grn_obj_flush(ctx, (grn_obj *)(options->values));
}

#ifdef GRN_WITH_MESSAGE_PACK
typedef struct {
  grn_ctx *ctx;
  grn_obj *buffer;
} grn_options_msgpack_write_data;

static int
grn_options_msgpack_write(void *data, const char *buf, msgpack_size_t len)
{
  grn_options_msgpack_write_data *write_data = data;
  return grn_bulk_write(write_data->ctx,
                        write_data->buffer,
                        buf,
                        len);
}
#endif /* GRN_WITH_MESSAGE_PACK */

grn_rc
grn_options_set(grn_ctx *ctx,
                grn_options *options,
                grn_id id,
                const char *name,
                int name_length,
                grn_obj *values)
{
#ifdef GRN_WITH_MESSAGE_PACK
  msgpack_packer packer;
  grn_options_msgpack_write_data data;
  grn_obj buffer;
  grn_io_win iw;
  void *raw_value;
  uint32_t length;
  unsigned int i, n;

  if (name_length < 0) {
    name_length = (int)strlen(name);
  }

  GRN_TEXT_INIT(&buffer, 0);
  data.ctx = ctx;
  data.buffer = &buffer;

  msgpack_packer_init(&packer, &data, grn_options_msgpack_write);

  raw_value = grn_ja_ref(ctx, options->values, id, &iw, &length);
  if (raw_value) {
    msgpack_unpacker unpacker;
    msgpack_unpacked unpacked;

    msgpack_unpacker_init(&unpacker, MSGPACK_UNPACKER_INIT_BUFFER_SIZE);
    msgpack_unpacked_init(&unpacked);

    msgpack_unpacker_reserve_buffer(&unpacker, length);
    grn_memcpy(msgpack_unpacker_buffer(&unpacker), raw_value, length);
    msgpack_unpacker_buffer_consumed(&unpacker, length);
    while (MSGPACK_UNPACKER_NEXT(&unpacker, &unpacked)) {
      msgpack_object *object = &(unpacked.data);
      if (object->type == MSGPACK_OBJECT_MAP) {
        msgpack_object_map *map = &(object->via.map);
        uint32_t i;
        bool have_same_key = false;
        uint32_t same_key_index = 0;

        for (i = 0; i < map->size; i++) {
          msgpack_object_kv *kv = &(map->ptr[i]);
          msgpack_object *key = &(kv->key);
          if (key->type == MSGPACK_OBJECT_STR &&
              MSGPACK_OBJECT_STR_SIZE(key) == (size_t)name_length &&
              memcmp(name, MSGPACK_OBJECT_STR_PTR(key), (size_t)name_length) == 0) {
            have_same_key = true;
            same_key_index = i;
            break;
          }
        }
        if (have_same_key) {
          msgpack_pack_map(&packer, map->size);
        } else {
          msgpack_pack_map(&packer, map->size + 1);
        }
        for (i = 0; i < map->size; i++) {
          msgpack_object_kv *kv = &(map->ptr[i]);
          if (have_same_key && i == same_key_index) {
            continue;
          }
          msgpack_pack_object(&packer, kv->key);
          msgpack_pack_object(&packer, kv->val);
        }
      }
    }

    msgpack_unpacked_destroy(&unpacked);
    msgpack_unpacker_destroy(&unpacker);

    grn_ja_unref(ctx, &iw);
  } else {
    msgpack_pack_map(&packer, 1);
  }

  msgpack_pack_str(&packer, (size_t)name_length);
  msgpack_pack_str_body(&packer, name, (size_t)name_length);

  n = grn_vector_size(ctx, values);
  msgpack_pack_array(&packer, n);
  for (i = 0; i < n; i++) {
    const char *value;
    unsigned int value_size;
    grn_id value_domain;

    value_size = grn_vector_get_element(ctx,
                                        values,
                                        i,
                                        &value,
                                        NULL,
                                        &value_domain);
    grn_msgpack_pack_raw_internal(ctx, &packer, value, value_size, value_domain);
    if (ctx->rc != GRN_SUCCESS) {
      break;
    }
  }

  if (ctx->rc == GRN_SUCCESS) {
    grn_ja_put(ctx,
               options->values,
               id,
               GRN_TEXT_VALUE(&buffer),
               (uint32_t)GRN_TEXT_LEN(&buffer),
               GRN_OBJ_SET,
               NULL);
  }
  GRN_OBJ_FIN(ctx, &buffer);
#else /* GRN_WITH_MESSAGE_PACK */
  ERR(GRN_FUNCTION_NOT_IMPLEMENTED, "[options] MessagePack is required");
#endif /* GRN_WITH_MESSAGE_PACK */
  return ctx->rc;
}

grn_option_revision
grn_options_get(grn_ctx *ctx,
                grn_options *options,
                grn_id id,
                const char *name,
                int name_length,
                grn_option_revision revision,
                grn_obj *value)
{
#ifdef GRN_WITH_MESSAGE_PACK
  bool found = false;
  grn_io_win iw;
  void *raw_value;
  uint32_t length;
  msgpack_unpacker unpacker;
  msgpack_unpacked unpacked;

  raw_value = grn_ja_ref(ctx, options->values, id, &iw, &length);
  if (!raw_value) {
    return GRN_OPTION_REVISION_NONE;
  }

  if (raw_value == revision) {
    grn_ja_unref(ctx, &iw);
    return GRN_OPTION_REVISION_UNCHANGED;
  }

  if (name_length < 0) {
    name_length = (int)strlen(name);
  }

  msgpack_unpacker_init(&unpacker, MSGPACK_UNPACKER_INIT_BUFFER_SIZE);
  msgpack_unpacker_reserve_buffer(&unpacker, length);
  msgpack_unpacked_init(&unpacked);
  grn_memcpy(msgpack_unpacker_buffer(&unpacker),
             iw.addr,
             length);
  msgpack_unpacker_buffer_consumed(&unpacker, length);
  while (MSGPACK_UNPACKER_NEXT(&unpacker, &unpacked)) {
    msgpack_object *object = &(unpacked.data);
    if (object->type == MSGPACK_OBJECT_MAP) {
      msgpack_object_map *map = &(object->via.map);
      uint32_t i;
      for (i = 0; i < map->size; i++) {
        msgpack_object_kv *kv = &(map->ptr[i]);
        if (kv->key.type == MSGPACK_OBJECT_STR &&
            MSGPACK_OBJECT_STR_SIZE(&(kv->key)) == (size_t)name_length &&
            memcmp(MSGPACK_OBJECT_STR_PTR(&(kv->key)), name,
                   (size_t)name_length) == 0) {
          if (kv->val.type == MSGPACK_OBJECT_ARRAY) {
            grn_msgpack_unpack_array_internal(ctx,
                                              &(kv->val.via.array),
                                              value);
            found = true;
          }
          break;
        }
      }
    }
  }
  msgpack_unpacked_destroy(&unpacked);
  msgpack_unpacker_destroy(&unpacker);

  grn_ja_unref(ctx, &iw);

  return raw_value;
#else /* GRN_WITH_MESSAGE_PACK */
  return GRN_OPTION_REVISION_NONE;
#endif /* GRN_WITH_MESSAGE_PACK */
}

grn_rc
grn_options_clear(grn_ctx *ctx,
                  grn_options *options,
                  grn_id id)
{
  return grn_ja_put(ctx, options->values, id, NULL, 0, GRN_OBJ_SET, NULL);
}

grn_rc
grn_options_wal_recover(grn_ctx *ctx, grn_options *options)
{
  return grn_ja_wal_recover(ctx, options->values);
}

grn_rc
grn_options_warm(grn_ctx *ctx, grn_options *options)
{
  return grn_ja_warm(ctx, options->values);
}
