/* bson-inspect.c - BSON inspector, example application.
 * Copyright 2011 Gergely Nagy <algernon@balabit.hu>
 *
 * 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 <glib.h>
#include <bson.h>

#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static void
_indent (gint level, gboolean verbose)
{
  gint i;

  if (!verbose)
    return;

  for (i = 1; i <= level; i++)
    printf ("  ");
}

static void
bson_dump (bson *b, gint ilevel, gboolean verbose, gboolean as_array)
{
  bson_cursor *c;
  gboolean first = TRUE;

  c = bson_cursor_new (b);
  while (bson_cursor_next (c))
    {
      if (!first)
	{
	  printf (", ");
	  if (verbose)
	    printf ("\n");
	}
      first = FALSE;
      if (verbose)
	{
	  _indent (ilevel, verbose);
	  printf ("/* type='%s'; */\n",
		  bson_cursor_type_as_string (c) + 10);
	}
      _indent (ilevel, verbose);
      if (!as_array)
	{
	  printf ("\"%s\" : ", bson_cursor_key (c));
	}
      switch (bson_cursor_type (c))
	{
	case BSON_TYPE_DOUBLE:
	  {
	    gdouble d;
	    bson_cursor_get_double (c, &d);
	    printf ("%f", d);
	    break;
	  }
	case BSON_TYPE_STRING:
	  {
	    const gchar *s;
	    gchar *s2;
	    bson_cursor_get_string (c, &s);
	    s2 = g_strescape (s, NULL);
	    printf ("\"%s\"", s2);
	    g_free (s2);
	    break;
	  }
	case BSON_TYPE_OID:
	  {
	    const guint8 *oid;
	    gint j;
	    bson_cursor_get_oid (c, &oid);
	    printf ("ObjectId( \"");
	    for (j = 0; j < 12; j++)
	      printf ("%02x", oid[j]);
	    printf ("\" )");
	    break;
	  }
	case BSON_TYPE_BOOLEAN:
	  {
	    gboolean b;
	    bson_cursor_get_boolean (c, &b);
	    printf ((b) ? "true" : "false");
	    break;
	  }
	case BSON_TYPE_REGEXP:
	  {
	    const gchar *r, *o;
	    gchar *r2, *o2;
	    bson_cursor_get_regex (c, &r, &o);
	    r2 = g_strescape (r, NULL);
	    o2 = g_strescape (o, NULL);
	    printf ("Regex(\"/%s/%s\")", r2, o2);
	    g_free (r2);
	    g_free (o2);
	    break;
	  }
	case BSON_TYPE_NULL:
	  {
	    printf ("null");
	    break;
	  }
	case BSON_TYPE_JS_CODE:
	  {
	    const gchar *js;
	    gchar *js2;
	    bson_cursor_get_javascript (c, &js);
	    js2 = g_strescape (js, NULL);
	    printf ("%s", js2);
	    g_free (js2);
	    break;
	  }
	case BSON_TYPE_SYMBOL:
	  {
	    const gchar *s;
	    gchar *s2;
	    bson_cursor_get_symbol (c, &s);
	    s2 = g_strescape (s, NULL);
	    printf ("%s", s2);
	    g_free (s2);
	    break;
	  }
	case BSON_TYPE_INT32:
	  {
	    gint32 l32;
	    bson_cursor_get_int32 (c, &l32);
	    printf ("%d", l32);
	    break;
	  }
	case BSON_TYPE_INT64:
	  {
	    gint64 l64;
	    bson_cursor_get_int64 (c, &l64);
	    printf ("%" G_GINT64_FORMAT, l64);
	    break;
	  }
	case BSON_TYPE_DOCUMENT:
	  {
	    bson *sd;
	    bson_cursor_get_document (c, &sd);
	    printf ("{ ");
	    if (verbose)
	      printf ("/* size='%d' */\n", bson_size (sd));
	    bson_dump (sd, ilevel + 1, verbose, FALSE);
	    if (verbose)
	      {
		printf ("\n");
		_indent (ilevel, verbose);
		printf ("}");
	      }
	    else
	      printf (" }");
	    bson_free (sd);
	    break;
	  }
	case BSON_TYPE_ARRAY:
	  {
	    bson *sa;

	    bson_cursor_get_array (c, &sa);
	    printf ("[ ");
	    if (verbose)
	      printf ("/* size='%d' */\n", bson_size (sa));
	    bson_dump (sa, ilevel + 1, verbose, TRUE);
	    if (verbose)
	      {
		printf ("\n");
		_indent (ilevel, verbose);
		printf ("]");
	      }
	    else
	      printf (" ]");
	    bson_free (sa);
	    break;
	  }
	case BSON_TYPE_BINARY:
	  {
	    const guint8 *data;
	    gint32 size;
	    bson_binary_subtype t;
	    gchar *b64;

	    bson_cursor_get_binary (c, &t, &data, &size);
	    b64 = g_base64_encode (data, size);
	    printf ("{ ");
	    if (verbose)
	      {
		printf ("/* size='%d' */\n", size);
		_indent (ilevel + 1, verbose);
	      }
	    printf ("\"$binary\" : \"%s\",", b64);
	    if (verbose)
	      {
		printf ("\n");
		_indent (ilevel + 1, verbose);
	      }
	    else
	      printf (" ");
	    printf ("\"$type\" : \"%02d\"", t);
	    if (verbose)
	      {
		printf ("\n");
		_indent (ilevel, verbose);
	      }
	    else
	      printf (" ");
	    printf ("}");
	    g_free (b64);
	    break;
	  }
	case BSON_TYPE_JS_CODE_W_SCOPE:
	case BSON_TYPE_UNDEFINED:
	case BSON_TYPE_UTC_DATETIME:
	case BSON_TYPE_DBPOINTER:
	case BSON_TYPE_TIMESTAMP:
	case BSON_TYPE_MIN:
	case BSON_TYPE_MAX:
	default:
	  printf ("\"<unimplemented>\"");
	  break;
	}
    }
  bson_cursor_free (c);
}

int
main (int argc, char *argv[])
{
  int fd;
  off_t offs = 0;
  bson *b;
  guint8 *data;
  struct stat st;
  gint64 i = 1;
  GOptionContext *context;
  gboolean verbose = FALSE;
  GError *error = NULL;

  GOptionEntry entries[] =
    {
      { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,
	"Be verbose", NULL },
      { NULL, 0, 0, 0, NULL, NULL, NULL }
    };

  context = g_option_context_new ("- inspect a BSON dump");
  g_option_context_add_main_entries (context, entries, "bson-inspect");
  if (!g_option_context_parse (context, &argc, &argv, &error))
    {
      g_print ("option parsing failed: %s\n", error->message);
      exit (1);
    }

  if (argc < 2)
    {
      gchar **nargv;
      argc = 2;

      nargv = g_new (gchar *, 3);
      nargv[0] = argv[0];
      nargv[1] = "--help";
      nargv[2] = NULL;

      g_option_context_parse (context, &argc, (gchar ***)&nargv, &error);

      exit (1);
    }

  fd = open (argv[1], O_RDONLY);
  if (fd == -1)
    {
      fprintf (stderr, "Error opening file '%s': %s\n",
	       argv[1], strerror (errno));
      exit (1);
    }
  if (fstat (fd, &st) != 0)
    {
      fprintf (stderr, "Error fstat()ing file '%s': %s\n",
	       argv[1], strerror (errno));
      close (fd);
      exit (1);
    }

  data = mmap (NULL, (size_t)st.st_size, PROT_READ, MAP_SHARED, fd, 0);
  if (data == MAP_FAILED)
    {
      fprintf (stderr, "Error mmap()ing file '%s': %s\n",
	       argv[1], strerror (errno));
      close (fd);
      exit (1);
    }

  while (offs < st.st_size)
    {
      b = bson_new_from_data ((const guint8 *)(data + offs),
			      bson_stream_doc_size (data, offs) - 1);
      bson_finish (b);
      offs += bson_size (b);

      if (verbose)
	printf ("/* Document #%" G_GUINT64_FORMAT "; size='%d' */\n", i,
		bson_size (b));
      printf ("{ ");
      if (verbose)
	printf ("\n");
      bson_dump (b, 1, verbose, FALSE);
      if (verbose)
	printf ("\n}\n");
      else
	printf (" }\n");
      if (verbose)
	printf ("\n");

      bson_free (b);
      i++;
    }
  munmap (data, st.st_size);
  close (fd);

  return 0;
}
