/* vim: set et ts=3 sw=3 sts=3 ft=c:
 *
 * Copyright (C) 2014 James McLaughlin.  All rights reserved.
 * https://github.com/udp/json-builder
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "json-builder.h"

#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <math.h>

#ifdef _MSC_VER
    #define snprintf _snprintf
#endif

const static json_serialize_opts default_opts =
{
   json_serialize_mode_single_line,
   0,
   3  /* indent_size */
};

typedef struct json_builder_value
{
   json_value value;

   int is_builder_value;

   size_t additional_length_allocated;
   size_t length_iterated;

} json_builder_value;

static int builderize (json_value * value)
{
   if (((json_builder_value *) value)->is_builder_value)
      return 1;
   
   if (value->type == json_object)
   {
      unsigned int i;

      /* Values straight out of the parser have the names of object entries
       * allocated in the same allocation as the values array itself.  This is
       * not desirable when manipulating values because the names would be easy
       * to clobber.
       */
      for (i = 0; i < value->u.object.length; ++ i)
      {
         json_char * name_copy;
         json_object_entry * entry = &value->u.object.values [i];

         if (! (name_copy = (json_char *) malloc ((entry->name_length + 1) * sizeof (json_char))))
            return 0;

         memcpy (name_copy, entry->name, entry->name_length + 1);
         entry->name = name_copy;
      }
   }

   ((json_builder_value *) value)->is_builder_value = 1;

   return 1;
}

const size_t json_builder_extra = sizeof(json_builder_value) - sizeof(json_value);

/* These flags are set up from the opts before serializing to make the
 * serializer conditions simpler.
 */
const int f_spaces_around_brackets = (1 << 0);
const int f_spaces_after_commas    = (1 << 1);
const int f_spaces_after_colons    = (1 << 2);
const int f_tabs                   = (1 << 3);

int get_serialize_flags (json_serialize_opts opts)
{
   int flags = 0;

   if (opts.mode == json_serialize_mode_packed)
      return 0;

   if (opts.mode == json_serialize_mode_multiline)
   {
      if (opts.opts & json_serialize_opt_use_tabs)
         flags |= f_tabs;
   }
   else
   {
      if (! (opts.opts & json_serialize_opt_pack_brackets))
         flags |= f_spaces_around_brackets;

      if (! (opts.opts & json_serialize_opt_no_space_after_comma))
         flags |= f_spaces_after_commas;
   }

   if (! (opts.opts & json_serialize_opt_no_space_after_colon))
      flags |= f_spaces_after_colons;

   return flags;
}

json_value * json_array_new (size_t length)
{
    json_value * value = (json_value *) calloc (1, sizeof (json_builder_value));

    if (!value)
       return NULL;

    ((json_builder_value *) value)->is_builder_value = 1;

    value->type = json_array;

    if (! (value->u.array.values = (json_value **) malloc (length * sizeof (json_value *))))
    {
       free (value);
       return NULL;
    }

    ((json_builder_value *) value)->additional_length_allocated = length;

    return value;
}

json_value * json_array_push (json_value * array, json_value * value)
{
   assert (array->type == json_array);

   if (!builderize (array) || !builderize (value))
      return NULL;

   if (((json_builder_value *) array)->additional_length_allocated > 0)
   {
      -- ((json_builder_value *) array)->additional_length_allocated;
   }
   else
   {
      json_value ** values_new = (json_value **) realloc
            (array->u.array.values, sizeof (json_value *) * (array->u.array.length + 1));

      if (!values_new)
         return NULL;

      array->u.array.values = values_new;
   }

   array->u.array.values [array->u.array.length] = value;
   ++ array->u.array.length;

   value->parent = array;

   return value;
}

json_value * json_object_new (size_t length)
{
    json_value * value = (json_value *) calloc (1, sizeof (json_builder_value));

    if (!value)
       return NULL;

    ((json_builder_value *) value)->is_builder_value = 1;

    value->type = json_object;

    if (! (value->u.object.values = (json_object_entry *) calloc
           (length, sizeof (*value->u.object.values))))
    {
       free (value);
       return NULL;
    }

    ((json_builder_value *) value)->additional_length_allocated = length;

    return value;
}

json_value * json_object_push (json_value * object,
                               const json_char * name,
                               json_value * value)
{
   return json_object_push_length (object, (unsigned int) strlen (name), name, value);
}

json_value * json_object_push_length (json_value * object,
                                      unsigned int name_length, const json_char * name,
                                      json_value * value)
{
   json_char * name_copy;

   assert (object->type == json_object);

   if (! (name_copy = (json_char *) malloc ((name_length + 1) * sizeof (json_char))))
      return NULL;
   
   memcpy (name_copy, name, name_length * sizeof (json_char));
   name_copy [name_length] = 0;

   if (!json_object_push_nocopy (object, name_length, name_copy, value))
   {
      free (name_copy);
      return NULL;
   }

   return value;
}

json_value * json_object_push_nocopy (json_value * object,
                                      unsigned int name_length, json_char * name,
                                      json_value * value)
{
   json_object_entry * entry;

   assert (object->type == json_object);

   if (!builderize (object) || !builderize (value))
      return NULL;

   if (((json_builder_value *) object)->additional_length_allocated > 0)
   {
      -- ((json_builder_value *) object)->additional_length_allocated;
   }
   else
   {
      json_object_entry * values_new = (json_object_entry *)
            realloc (object->u.object.values, sizeof (*object->u.object.values)
                            * (object->u.object.length + 1));

      if (!values_new)
         return NULL;

      object->u.object.values = values_new;
   }

   entry = object->u.object.values + object->u.object.length;

   entry->name_length = name_length;
   entry->name = name;
   entry->value = value;

   ++ object->u.object.length;

   value->parent = object;

   return value;
}

json_value * json_string_new (const json_char * buf)
{
   return json_string_new_length ((unsigned int) strlen (buf), buf);
}

json_value * json_string_new_length (unsigned int length, const json_char * buf)
{
   json_value * value;
   json_char * copy = (json_char *) malloc ((length + 1) * sizeof (json_char));

   if (!copy)
      return NULL;
   
   memcpy (copy, buf, length * sizeof (json_char));
   copy [length] = 0;

   if (! (value = json_string_new_nocopy (length, copy)))
   {
      free (copy);
      return NULL;
   }

   return value;
}

json_value * json_string_new_nocopy (unsigned int length, json_char * buf)
{
   json_value * value = (json_value *) calloc (1, sizeof (json_builder_value));
   
   if (!value)
      return NULL;

   ((json_builder_value *) value)->is_builder_value = 1;

   value->type = json_string;
   value->u.string.length = length;
   value->u.string.ptr = buf;

   return value;
}

json_value * json_integer_new (json_int_t integer)
{
   json_value * value = (json_value *) calloc (1, sizeof (json_builder_value));
   
   if (!value)
      return NULL;

   ((json_builder_value *) value)->is_builder_value = 1;

   value->type = json_integer;
   value->u.integer = integer;

   return value;
}

json_value * json_double_new (double dbl)
{
   json_value * value = (json_value *) calloc (1, sizeof (json_builder_value));
   
   if (!value)
      return NULL;

   ((json_builder_value *) value)->is_builder_value = 1;

   value->type = json_double;
   value->u.dbl = dbl;

   return value;
}

json_value * json_boolean_new (int b)
{
   json_value * value = (json_value *) calloc (1, sizeof (json_builder_value));
   
   if (!value)
      return NULL;

   ((json_builder_value *) value)->is_builder_value = 1;

   value->type = json_boolean;
   value->u.boolean = b;

   return value;
}

json_value * json_null_new ()
{
   json_value * value = (json_value *) calloc (1, sizeof (json_builder_value));
   
   if (!value)
      return NULL;

   ((json_builder_value *) value)->is_builder_value = 1;

   value->type = json_null;

   return value;
}

void json_object_sort (json_value * object, json_value * proto)
{
   unsigned int i, out_index = 0;

   if (!builderize (object))
      return;  /* TODO error */

   assert (object->type == json_object);
   assert (proto->type == json_object);

   for (i = 0; i < proto->u.object.length; ++ i)
   {
      unsigned int j;
      json_object_entry proto_entry = proto->u.object.values [i];

      for (j = 0; j < object->u.object.length; ++ j)
      {
         json_object_entry entry = object->u.object.values [j];

         if (entry.name_length != proto_entry.name_length)
            continue;

         if (memcmp (entry.name, proto_entry.name, entry.name_length) != 0)
            continue;

         object->u.object.values [j] = object->u.object.values [out_index];
         object->u.object.values [out_index] = entry;

         ++ out_index;
      }
   }
}

json_value * json_object_merge (json_value * objectA, json_value * objectB)
{
   unsigned int i;

   assert (objectA->type == json_object);
   assert (objectB->type == json_object);
   assert (objectA != objectB);

   if (!builderize (objectA) || !builderize (objectB))
      return NULL;

   if (objectB->u.object.length <=
        ((json_builder_value *) objectA)->additional_length_allocated)
   {
      ((json_builder_value *) objectA)->additional_length_allocated
          -= objectB->u.object.length;
   }
   else
   {
      json_object_entry * values_new;

      unsigned int alloc = (unsigned int)
          (objectA->u.object.length
              + ((json_builder_value *) objectA)->additional_length_allocated
              + objectB->u.object.length);

      if (! (values_new = (json_object_entry *)
            realloc (objectA->u.object.values, sizeof (json_object_entry) * alloc)))
      {
          return NULL;
      }

      objectA->u.object.values = values_new;
   }

   for (i = 0; i < objectB->u.object.length; ++ i)
   {
      json_object_entry * entry = &objectA->u.object.values[objectA->u.object.length + i];

      *entry = objectB->u.object.values[i];
      entry->value->parent = objectA;
   }

   objectA->u.object.length += objectB->u.object.length;

   free (objectB->u.object.values);
   free (objectB);

   return objectA;
}

static size_t measure_string (unsigned int length,
                              const json_char * str)
{
   unsigned int i;
   size_t measured_length = 0;

   for(i = 0; i < length; ++ i)
   {
      json_char c = str [i];

      switch (c)
      {
      case '"':
      case '\\':
      case '\b':
      case '\f':
      case '\n':
      case '\r':
      case '\t':

         measured_length += 2;
         break;

      default:

         ++ measured_length;
         break;
      };
   };

   return measured_length;
}

#define PRINT_ESCAPED(c) do {  \
   *buf ++ = '\\';             \
   *buf ++ = (c);              \
} while(0);                    \

static size_t serialize_string (json_char * buf,
                                unsigned int length,
                                const json_char * str)
{
   json_char * orig_buf = buf;
   unsigned int i;

   for(i = 0; i < length; ++ i)
   {
      json_char c = str [i];

      switch (c)
      {
      case '"':   PRINT_ESCAPED ('\"');  continue;
      case '\\':  PRINT_ESCAPED ('\\');  continue;
      case '\b':  PRINT_ESCAPED ('b');   continue;
      case '\f':  PRINT_ESCAPED ('f');   continue;
      case '\n':  PRINT_ESCAPED ('n');   continue;
      case '\r':  PRINT_ESCAPED ('r');   continue;
      case '\t':  PRINT_ESCAPED ('t');   continue;

      default:

         *buf ++ = c;
         break;
      };
   };

   return buf - orig_buf;
}

size_t json_measure (json_value * value)
{
   return json_measure_ex (value, default_opts);
}

#define MEASURE_NEWLINE() do {                     \
   ++ newlines;                                    \
   indents += depth;                               \
} while(0);                                        \

size_t json_measure_ex (json_value * value, json_serialize_opts opts)
{
   size_t total = 1;  /* null terminator */
   size_t newlines = 0;
   size_t depth = 0;
   size_t indents = 0;
   int flags;
   int bracket_size, comma_size, colon_size;

   flags = get_serialize_flags (opts);

   /* to reduce branching
    */
   bracket_size = flags & f_spaces_around_brackets ? 2 : 1;
   comma_size = flags & f_spaces_after_commas ? 2 : 1;
   colon_size = flags & f_spaces_after_colons ? 2 : 1;

   while (value)
   {
      json_int_t integer;
      json_object_entry * entry;

      switch (value->type)
      {
         case json_array:

            if (((json_builder_value *) value)->length_iterated == 0)
            {
               if (value->u.array.length == 0)
               {
                  total += 2;  /* `[]` */
                  break;
               }

               total += bracket_size;  /* `[` */

               ++ depth;
               MEASURE_NEWLINE(); /* \n after [ */
            }

            if (((json_builder_value *) value)->length_iterated == value->u.array.length)
            {
               -- depth;
               MEASURE_NEWLINE();
               total += bracket_size;  /* `]` */

               ((json_builder_value *) value)->length_iterated = 0;
               break;
            }

            if (((json_builder_value *) value)->length_iterated > 0)
            {
               total += comma_size;  /* `, ` */

               MEASURE_NEWLINE();
            }

            ((json_builder_value *) value)->length_iterated++;
            value = value->u.array.values [((json_builder_value *) value)->length_iterated - 1];
            continue;

         case json_object:

            if (((json_builder_value *) value)->length_iterated == 0)
            {
               if (value->u.object.length == 0)
               {
                  total += 2;  /* `{}` */
                  break;
               }

               total += bracket_size;  /* `{` */

               ++ depth;
               MEASURE_NEWLINE(); /* \n after { */
            }

            if (((json_builder_value *) value)->length_iterated == value->u.object.length)
            {
               -- depth;
               MEASURE_NEWLINE();
               total += bracket_size;  /* `}` */

               ((json_builder_value *) value)->length_iterated = 0;
               break;
            }

            if (((json_builder_value *) value)->length_iterated > 0)
            {
               total += comma_size;  /* `, ` */
               MEASURE_NEWLINE();
            }

            entry = value->u.object.values + (((json_builder_value *) value)->length_iterated ++);

            total += 2 + colon_size;  /* `"": ` */
            total += measure_string (entry->name_length, entry->name);

            value = entry->value;
            continue;

         case json_string:

            total += 2;  /* `""` */
            total += measure_string (value->u.string.length, value->u.string.ptr);
            break;

         case json_integer:

            integer = value->u.integer;

            if (integer < 0)
            {
               total += 1;  /* `-` */
               integer = - integer;
            }

            ++ total;  /* first digit */

            while (integer >= 10)
            {
               ++ total;  /* another digit */
               integer /= 10;
            }

            break;

         case json_double:

            total += snprintf (NULL, 0, "%g", value->u.dbl);

            if (value->u.dbl - floor (value->u.dbl) < 0.001)
                total += 2;

            break;

         case json_boolean:

            total += value->u.boolean ? 
               4:  /* `true` */
               5;  /* `false` */

            break;

         case json_null:

            total += 4;  /* `null` */
            break;

         default:
            break;
      };

      value = value->parent;
   }

   if (opts.mode == json_serialize_mode_multiline)
   {
      total += newlines * (((opts.opts & json_serialize_opt_CRLF) ? 2 : 1) + opts.indent_size);
      total += indents * opts.indent_size;
   }

   return total;
}

void json_serialize (json_char * buf, json_value * value)
{
   json_serialize_ex (buf, value, default_opts);
}

#define PRINT_NEWLINE() do {                          \
   if (opts.mode == json_serialize_mode_multiline) {  \
      if (opts.opts & json_serialize_opt_CRLF)        \
         *buf ++ = '\r';                              \
      *buf ++ = '\n';                                 \
      for(i = 0; i < indent; ++ i)                    \
         *buf ++ = indent_char;                       \
   }                                                  \
} while(0);                                           \

#define PRINT_OPENING_BRACKET(c) do {                 \
   *buf ++ = (c);                                     \
   if (flags & f_spaces_around_brackets)              \
      *buf ++ = ' ';                                  \
} while(0);                                           \

#define PRINT_CLOSING_BRACKET(c) do {                 \
   if (flags & f_spaces_around_brackets)              \
      *buf ++ = ' ';                                  \
   *buf ++ = (c);                                     \
} while(0);                                           \

void json_serialize_ex (json_char * buf, json_value * value, json_serialize_opts opts)
{
   json_int_t integer, orig_integer;
   json_object_entry * entry;
   json_char * ptr, * dot;
   int indent = 0;
   char indent_char;
   int i;
   int flags;

   flags = get_serialize_flags (opts);

   indent_char = flags & f_tabs ? '\t' : ' ';

   while (value)
   {
      switch (value->type)
      {
         case json_array:

            if (((json_builder_value *) value)->length_iterated == 0)
            {
               if (value->u.array.length == 0)
               {
                  *buf ++ = '[';
                  *buf ++ = ']';

                  break;
               }

               PRINT_OPENING_BRACKET ('[');

               indent += opts.indent_size;
               PRINT_NEWLINE();
            }

            if (((json_builder_value *) value)->length_iterated == value->u.array.length)
            {
               indent -= opts.indent_size;
               PRINT_NEWLINE();
               PRINT_CLOSING_BRACKET (']');

               ((json_builder_value *) value)->length_iterated = 0;
               break;
            }

            if (((json_builder_value *) value)->length_iterated > 0)
            {
               *buf ++ = ',';

               if (flags & f_spaces_after_commas)
                  *buf ++ = ' ';

               PRINT_NEWLINE();
            }

            ((json_builder_value *) value)->length_iterated++;
            value = value->u.array.values [((json_builder_value *) value)->length_iterated - 1];
            continue;

         case json_object:

            if (((json_builder_value *) value)->length_iterated == 0)
            {
               if (value->u.object.length == 0)
               {
                  *buf ++ = '{';
                  *buf ++ = '}';

                  break;
               }

               PRINT_OPENING_BRACKET ('{');

               indent += opts.indent_size;
               PRINT_NEWLINE();
            }

            if (((json_builder_value *) value)->length_iterated == value->u.object.length)
            {
               indent -= opts.indent_size;
               PRINT_NEWLINE();
               PRINT_CLOSING_BRACKET ('}');

               ((json_builder_value *) value)->length_iterated = 0;
               break;
            }

            if (((json_builder_value *) value)->length_iterated > 0)
            {
               *buf ++ = ',';

               if (flags & f_spaces_after_commas)
                  *buf ++ = ' ';

               PRINT_NEWLINE();
            }

            entry = value->u.object.values + (((json_builder_value *) value)->length_iterated ++);

            *buf ++ = '\"';
            buf += serialize_string (buf, entry->name_length, entry->name);
            *buf ++ = '\"';
            *buf ++ = ':';

            if (flags & f_spaces_after_colons)
               *buf ++ = ' ';

            value = entry->value;
            continue;

         case json_string:

            *buf ++ = '\"';
            buf += serialize_string (buf, value->u.string.length, value->u.string.ptr);
            *buf ++ = '\"';
            break;

         case json_integer:

            integer = value->u.integer;

            if (integer < 0)
            {
               *buf ++ = '-';
               integer = - integer;
            }

            orig_integer = integer;

            ++ buf;

            while (integer >= 10)
            {
               ++ buf;
               integer /= 10;
            }

            integer = orig_integer;
            ptr = buf;

            do
            {
               *-- ptr = "0123456789"[integer % 10];

            } while ((integer /= 10) > 0);

            break;

         case json_double:

            ptr = buf;

            buf += sprintf (buf, "%g", value->u.dbl);

            if ((dot = strchr (ptr, ',')))
            {
               *dot = '.';
            }
            else if (!strchr (ptr, '.'))
            {
               *buf ++ = '.';
               *buf ++ = '0';
            }

            break;

         case json_boolean:

            if (value->u.boolean)
            {
               memcpy (buf, "true", 4);
               buf += 4;
            }
            else
            {
               memcpy (buf, "false", 5);
               buf += 5;
            }

            break;

         case json_null:

            memcpy (buf, "null", 4);
            buf += 4;
            break;

         default:
            break;
      };

      value = value->parent;
   }

   *buf = 0;
}

void json_builder_free (json_value * value)
{
   json_value * cur_value;

   if (!value)
      return;

   value->parent = 0;

   while (value)
   {
      switch (value->type)
      {
         case json_array:

            if (!value->u.array.length)
            {
               free (value->u.array.values);
               break;
            }

            value = value->u.array.values [-- value->u.array.length];
            continue;

         case json_object:

            if (!value->u.object.length)
            {
               free (value->u.object.values);
               break;
            }

            -- value->u.object.length;

            if (((json_builder_value *) value)->is_builder_value)
            {
               /* Names are allocated separately for builder values.  In parser
                * values, they are part of the same allocation as the values array
                * itself.
                */
               free (value->u.object.values [value->u.object.length].name);
            }

            value = value->u.object.values [value->u.object.length].value;
            continue;

         case json_string:

            free (value->u.string.ptr);
            break;

         default:
            break;
      };

      cur_value = value;
      value = value->parent;
      free (cur_value);
   }
}