/*
 * Copyright 2009-present MongoDB, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <mongoc/mcd-nsinfo.h>

#include <mongoc/mongoc-buffer-private.h>
#include <mongoc/mongoc-error-private.h>

#include <mongoc/uthash.h>

#include <mlib/config.h>

typedef struct {
   char *ns; // Hash key.
   int32_t index;
   UT_hash_handle hh;
} ns_to_index_t;

struct _mcd_nsinfo_t {
   ns_to_index_t *n2i;
   int32_t count;
   mongoc_buffer_t payload;
};

mcd_nsinfo_t *
mcd_nsinfo_new(void)
{
   mcd_nsinfo_t *self = bson_malloc0(sizeof(*self));
   _mongoc_buffer_init(&self->payload, NULL, 0, NULL, NULL);
   return self;
}

void
mcd_nsinfo_destroy(mcd_nsinfo_t *self)
{
   if (!self) {
      return;
   }
   // Delete hash table.
   ns_to_index_t *entry, *tmp;
   HASH_ITER(hh, self->n2i, entry, tmp)
   {
      HASH_DEL(self->n2i, entry);
      bson_free(entry->ns);
      bson_free(entry);
   }
   _mongoc_buffer_destroy(&self->payload);
   bson_free(self);
}

mlib_diagnostic_push();
mlib_msvc_warning(disable : 4702); // HASH_ADD_KEYPTR
int32_t
mcd_nsinfo_append(mcd_nsinfo_t *self, const char *ns, bson_error_t *error)
{
   BSON_ASSERT_PARAM(self);
   BSON_ASSERT_PARAM(ns);
   BSON_OPTIONAL_PARAM(error);

   const int32_t ns_index = self->count;
   if (self->count == INT32_MAX) {
      _mongoc_set_error(error,
                        MONGOC_ERROR_COMMAND,
                        MONGOC_ERROR_COMMAND_INVALID_ARG,
                        "Only %" PRId32 " distinct collections may be used",
                        INT32_MAX);
      return -1;
   }
   self->count++;

   // Add to hash table.
   ns_to_index_t *entry = bson_malloc(sizeof(*entry));
   *entry = (ns_to_index_t){.index = ns_index, .ns = bson_strdup(ns), .hh = {0}};

   mlib_diagnostic_push();
   mlib_disable_constant_conditional_expression_warnings();
   HASH_ADD_KEYPTR(hh, self->n2i, entry->ns, strlen(entry->ns), entry);
   mlib_diagnostic_pop();

   // Append to buffer.
   bson_t mcd_nsinfo_bson = BSON_INITIALIZER;
   BSON_ASSERT(bson_append_utf8(&mcd_nsinfo_bson, "ns", 2, ns, -1));
   BSON_ASSERT(_mongoc_buffer_append(&self->payload, bson_get_data(&mcd_nsinfo_bson), mcd_nsinfo_bson.len));
   bson_destroy(&mcd_nsinfo_bson);
   return ns_index;
}
mlib_diagnostic_pop();

int32_t
mcd_nsinfo_find(const mcd_nsinfo_t *self, const char *ns)
{
   BSON_ASSERT_PARAM(self);
   BSON_ASSERT_PARAM(ns);

   ns_to_index_t *found;

   mlib_diagnostic_push();
   mlib_disable_constant_conditional_expression_warnings();
   HASH_FIND_STR(self->n2i, ns, found);
   mlib_diagnostic_pop();

   if (found == NULL) {
      return -1;
   }

   return found->index;
}

uint32_t
mcd_nsinfo_get_bson_size(const char *ns)
{
   BSON_ASSERT_PARAM(ns);

   // Calculate overhead of the BSON document { "ns": "<ns>" }. See BSON specification.
   bson_t as_bson = BSON_INITIALIZER;
   BSON_ASSERT(bson_append_utf8(&as_bson, "ns", 2, ns, -1));
   uint32_t size = as_bson.len;
   bson_destroy(&as_bson);
   return size;
}

const mongoc_buffer_t *
mcd_nsinfo_as_document_sequence(const mcd_nsinfo_t *self)
{
   BSON_ASSERT_PARAM(self);

   return &self->payload;
}
