/*
 * This file Copyright (C) Mnemosyne LLC
 *
 * This file is licensed by the GPL version 2. Works owned by the
 * Transmission project are granted a special exemption to clause 2 (b)
 * so that the bulk of its code can remain under the MIT license.
 * This exemption does not extend to derived works not owned by
 * the Transmission project.
 *
 * $Id: json.c 13625 2012-12-05 17:29:46Z jordan $
 */

#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <errno.h> /* EILSEQ, EINVAL */

#include <event2/util.h> /* evutil_strtoll () */

#define JSONSL_STATE_USER_FIELDS /* no fields */
#include "jsonsl.h"
#include "jsonsl.c"

#include "transmission.h"
#include "ConvertUTF.h"
#include "bencode.h"
#include "json.h"
#include "ptrarray.h"
#include "utils.h"

/* arbitrary value... this is much deeper than our code goes */
#define MAX_DEPTH 64

struct json_wrapper_data
{
  int error;
  bool has_content;
  tr_benc * top;
  char * key;
  const char * source;
  tr_ptrArray stack;
};

static tr_benc*
get_node (struct jsonsl_st * jsn)
{
  tr_benc * parent;
  tr_benc * node = NULL;
  struct json_wrapper_data * data = jsn->data;

  parent = tr_ptrArrayEmpty (&data->stack)
         ? NULL
         : tr_ptrArrayBack (&data->stack);

  if (!parent)
    {
      node = data->top;
    }
  else if (tr_bencIsList (parent))
    {
      node = tr_bencListAdd (parent);
    }
  else if (tr_bencIsDict (parent) && (data->key!=NULL))
    {
      node = tr_bencDictAdd (parent, data->key);
      tr_free (data->key);
      data->key = NULL;
    }

  return node;
}


static void
error_handler (jsonsl_t                  jsn,
               jsonsl_error_t            error,
               struct jsonsl_state_st  * state   UNUSED,
               const jsonsl_char_t     * buf)
{
  struct json_wrapper_data * data = jsn->data;

  if (data->source)
    {
      tr_err ("JSON parse failed in %s at pos %zu: %s -- remaining text \"%.16s\"",
              data->source,
              jsn->pos,
              jsonsl_strerror (error),
              buf);
    }
  else
    {
      tr_err ("JSON parse failed at pos %zu: %s -- remaining text \"%.16s\"",
              jsn->pos,
              jsonsl_strerror (error),
              buf);
    }

  data->error = EILSEQ;
}

static int
error_callback (jsonsl_t                  jsn,
                jsonsl_error_t            error,
                struct jsonsl_state_st  * state,
                jsonsl_char_t           * at)
{
  error_handler (jsn, error, state, at);
  return 0; /* bail */
}

static void
action_callback_PUSH (jsonsl_t                  jsn,
                      jsonsl_action_t           action  UNUSED,
                      struct jsonsl_state_st  * state,
                      const jsonsl_char_t     * buf     UNUSED)
{
  tr_benc * node;
  struct json_wrapper_data * data = jsn->data;

  switch (state->type)
    {
      case JSONSL_T_LIST:
        data->has_content = true;
        node = get_node (jsn);
        tr_bencInitList (node, 0);
        tr_ptrArrayAppend (&data->stack, node);
        break;

      case JSONSL_T_OBJECT:
        data->has_content = true;
        node = get_node (jsn);
        tr_bencInitDict (node, 0);
        tr_ptrArrayAppend (&data->stack, node);
        break;

      default:
        /* nothing else interesting on push */
        break;
    }
}

static char*
extract_string (jsonsl_t jsn, struct jsonsl_state_st * state, size_t * len)
{
  const char * in_begin;
  const char * in_end;
  const char * in_it;
  size_t out_buflen;
  char * out_buf;
  char * out_it;

  in_begin = jsn->base + state->pos_begin;
  if (*in_begin == '"')
    in_begin++;
  in_end = jsn->base + state->pos_cur;

  out_buflen = (in_end-in_begin)*3 + 1;
  out_buf = tr_new0 (char, out_buflen);
  out_it = out_buf;

  for (in_it=in_begin; in_it!=in_end;)
    {
      bool unescaped = false;

      if (*in_it=='\\' && in_end-in_it>=2)
        {
          switch (in_it[1])
            {
              case 'b' : *out_it++ = '\b'; in_it+=2; unescaped = true; break;
              case 'f' : *out_it++ = '\f'; in_it+=2; unescaped = true; break;
              case 'n' : *out_it++ = '\n'; in_it+=2; unescaped = true; break;
              case 'r' : *out_it++ = '\r'; in_it+=2; unescaped = true; break;
              case 't' : *out_it++ = '\t'; in_it+=2; unescaped = true; break;
              case '/' : *out_it++ = '/' ; in_it+=2; unescaped = true; break;
              case '"' : *out_it++ = '"' ; in_it+=2; unescaped = true; break;
              case '\\': *out_it++ = '\\'; in_it+=2; unescaped = true; break;
              case 'u':
                {
                  if (in_end - in_it >= 6)
                    {
                      unsigned int val = 0;
                      if (sscanf (in_it+2, "%4x", &val) == 1)
                        {
                          UTF32 str32_buf[2] = { val, 0 };
                          const UTF32 * str32_walk = str32_buf;
                          const UTF32 * str32_end = str32_buf + 1;
                          UTF8 str8_buf[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
                          UTF8 * str8_walk = str8_buf;
                          UTF8 * str8_end = str8_buf + 8;

                          if (ConvertUTF32toUTF8 (&str32_walk, str32_end, &str8_walk, str8_end, 0) == 0)
                            {
                              const size_t len = str8_walk - str8_buf;
                              memcpy (out_it, str8_buf, len);
                              out_it += len;
                              unescaped = true;
                            }

                          in_it += 6;
                          break;
                        }
                    }
                }
            }
        }

      if (!unescaped)
        *out_it++ = *in_it++;
    }

  if (len != NULL)
    *len = out_it - out_buf;

  return out_buf;
}

static void
action_callback_POP (jsonsl_t                  jsn,
                     jsonsl_action_t           action  UNUSED,
                     struct jsonsl_state_st  * state,
                     const jsonsl_char_t     * buf     UNUSED)
{
  struct json_wrapper_data * data = jsn->data;

  if (state->type == JSONSL_T_STRING)
    {
      size_t len = 0;
      char * str = extract_string (jsn, state, &len);
      tr_bencInitStr (get_node (jsn), str, len);
      data->has_content = true;
      tr_free (str);
    }
  else if (state->type == JSONSL_T_HKEY)
    {
      char * str = extract_string (jsn, state, NULL);
      data->has_content = true;
      data->key = str;
    }
  else if ((state->type == JSONSL_T_LIST) || (state->type == JSONSL_T_OBJECT))
    {
      tr_ptrArrayPop (&data->stack);
    }
  else if (state->type == JSONSL_T_SPECIAL)
    {
      if (state->special_flags & JSONSL_SPECIALf_NUMNOINT)
        {
          const char * begin = jsn->base + state->pos_begin;
          data->has_content = true;
          tr_bencInitReal (get_node (jsn), strtod (begin, NULL));
        }
      else if (state->special_flags & JSONSL_SPECIALf_NUMERIC)
        {
          const char * begin = jsn->base + state->pos_begin;
          data->has_content = true;
          tr_bencInitInt (get_node (jsn), evutil_strtoll (begin, NULL, 10));
        }
      else if (state->special_flags & JSONSL_SPECIALf_BOOLEAN)
        {
          const bool b = (state->special_flags & JSONSL_SPECIALf_TRUE) != 0;
          data->has_content = true;
          tr_bencInitBool (get_node (jsn), b);
        }
      else if (state->special_flags & JSONSL_SPECIALf_NULL)
        {
          data->has_content = true;
          tr_bencInitStr (get_node (jsn), "", 0);
        }
    }
}

int
tr_jsonParse (const char     * source,
              const void     * vbuf,
              size_t           len,
              tr_benc        * setme_benc,
              const uint8_t ** setme_end)
{
  int error;
  jsonsl_t jsn;
  struct json_wrapper_data data;

  jsn = jsonsl_new (MAX_DEPTH);
  jsn->action_callback_PUSH = action_callback_PUSH;
  jsn->action_callback_POP = action_callback_POP;
  jsn->error_callback = error_callback;
  jsn->data = &data;
  jsonsl_enable_all_callbacks (jsn);

  data.error = 0;
  data.has_content = false;
  data.key = NULL;
  data.top = setme_benc;
  data.stack = TR_PTR_ARRAY_INIT;
  data.source = source;

  /* parse it */
  jsonsl_feed (jsn, vbuf, len);

  /* EINVAL if there was no content */
  if (!data.error && !data.has_content)
    data.error = EINVAL;

  /* maybe set the end ptr */
  if (setme_end)
    *setme_end = ((const uint8_t*)vbuf) + jsn->pos;

  /* cleanup */
  error = data.error;
  tr_ptrArrayDestruct (&data.stack, NULL);
  jsonsl_destroy (jsn);
  return error;
}
