/*
 * 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.
 */

/* -- sphinx-include-start -- */
/* Reports the maximum nested depth of a BSON document. */
#include <bson/bson.h>

#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>

typedef struct {
   uint32_t depth;
   uint32_t max_depth;
   bool valid;
} check_depth_t;

bool
_check_depth_document(const bson_iter_t *iter, const char *key, const bson_t *v_document, void *data);

static const bson_visitor_t check_depth_funcs = {
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   _check_depth_document,
   _check_depth_document,
   NULL,
};

bool
_check_depth_document(const bson_iter_t *iter, const char *key, const bson_t *v_document, void *data)
{
   check_depth_t *state = (check_depth_t *)data;
   bson_iter_t child;

   BSON_UNUSED(iter);
   BSON_UNUSED(key);

   if (!bson_iter_init(&child, v_document)) {
      fprintf(stderr, "corrupt\n");
      return true; /* cancel */
   }

   state->depth++;
   if (state->depth > state->max_depth) {
      state->valid = false;
      return true; /* cancel */
   }

   bson_iter_visit_all(&child, &check_depth_funcs, state);
   state->depth--;
   return false; /* continue */
}

void
check_depth(const bson_t *bson, uint32_t max_depth)
{
   bson_iter_t iter;
   check_depth_t state = {0};

   if (!bson_iter_init(&iter, bson)) {
      fprintf(stderr, "corrupt\n");
   }

   state.valid = true;
   state.max_depth = max_depth;
   _check_depth_document(&iter, NULL, bson, &state);
   if (!state.valid) {
      printf("document exceeds maximum depth of %" PRIu32 "\n", state.max_depth);
   } else {
      char *as_json = bson_as_canonical_extended_json(bson, NULL);
      printf("document %s ", as_json);
      printf("is valid\n");
      bson_free(as_json);
   }
}

int
main(int argc, char **argv)
{
   bson_reader_t *bson_reader;
   const bson_t *bson;
   bool reached_eof;
   bson_error_t error;

   if (argc != 3) {
      fprintf(stderr, "usage: %s FILE MAX_DEPTH\n", argv[0]);
      fprintf(stderr, "Checks that the depth of the BSON contained in FILE\n");
      fprintf(stderr, "does not exceed MAX_DEPTH\n");
   }

   const char *const filename = argv[1];
   const int max_depth = atoi(argv[2]);

   bson_reader = bson_reader_new_from_file(filename, &error);
   if (!bson_reader) {
      printf("could not read %s: %s\n", filename, error.message);
      return 1;
   }

   BSON_ASSERT(max_depth >= 0 && (uint64_t)max_depth <= UINT32_MAX);

   while ((bson = bson_reader_read(bson_reader, &reached_eof))) {
      check_depth(bson, (uint32_t)max_depth);
   }

   if (!reached_eof) {
      printf("error reading BSON\n");
   }

   bson_reader_destroy(bson_reader);
   return 0;
}
