/*
 *
 * Copyright 2015-2016 gRPC authors.
 *
 * 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 <string.h>

#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
#include "src/core/lib/gpr/string.h"
#include "src/core/lib/gpr/useful.h"
#include "src/core/lib/json/json.h"

#include "test/core/util/test_config.h"

typedef struct testing_pair {
  const char* input;
  const char* output;
} testing_pair;

static testing_pair testing_pairs[] = {
    /* Testing valid parsing. */
    /* Testing trivial parses, with de-indentation. */
    {" 0 ", "0"},
    {" 1 ", "1"},
    {" \"    \" ", "\"    \""},
    {" \"a\" ", "\"a\""},
    {" true ", "true"},
    /* Testing the parser's ability to decode trivial UTF-16. */
    {"\"\\u0020\\\\\\u0010\\u000a\\u000D\"", "\" \\\\\\u0010\\n\\r\""},
    /* Testing various UTF-8 sequences. */
    {"\"ßâñć௵⇒\"", "\"\\u00df\\u00e2\\u00f1\\u0107\\u0bf5\\u21d2\""},
    {"\"\\u00df\\u00e2\\u00f1\\u0107\\u0bf5\\u21d2\"",
     "\"\\u00df\\u00e2\\u00f1\\u0107\\u0bf5\\u21d2\""},
    /* Testing UTF-8 character "𝄞", U+11D1E. */
    {"\"\xf0\x9d\x84\x9e\"", "\"\\ud834\\udd1e\""},
    {"\"\\ud834\\udd1e\"", "\"\\ud834\\udd1e\""},
    {"{\"\\ud834\\udd1e\":0}", "{\"\\ud834\\udd1e\":0}"},
    /* Testing nested empty containers. */
    {
        " [ [ ] , { } , [ ] ] ",
        "[[],{},[]]",
    },
    /* Testing escapes and control chars in key strings. */
    {" { \"\\u007f\x7f\\n\\r\\\"\\f\\b\\\\a , b\": 1, \"\": 0 } ",
     "{\"\\u007f\\u007f\\n\\r\\\"\\f\\b\\\\a , b\":1,\"\":0}"},
    /* Testing the writer's ability to cut off invalid UTF-8 sequences. */
    {"\"abc\xf0\x9d\x24\"", "\"abc\""},
    {"\"\xff\"", "\"\""},
    /* Testing valid number parsing. */
    {"[0, 42 , 0.0123, 123.456]", "[0,42,0.0123,123.456]"},
    {"[1e4,-53.235e-31, 0.3e+3]", "[1e4,-53.235e-31,0.3e+3]"},
    /* Testing keywords parsing. */
    {"[true, false, null]", "[true,false,null]"},

    /* Testing invalid parsing. */

    /* Testing plain invalid things, exercising the state machine. */
    {"\\", nullptr},
    {"nu ll", nullptr},
    {"{\"foo\": bar}", nullptr},
    {"{\"foo\": bar\"x\"}", nullptr},
    {"fals", nullptr},
    {"0,0 ", nullptr},
    {"\"foo\",[]", nullptr},
    /* Testing unterminated string. */
    {"\"\\x", nullptr},
    /* Testing invalid UTF-16 number. */
    {"\"\\u123x", nullptr},
    {"{\"\\u123x", nullptr},
    /* Testing imbalanced surrogate pairs. */
    {"\"\\ud834f", nullptr},
    {"{\"\\ud834f\":0}", nullptr},
    {"\"\\ud834\\n", nullptr},
    {"{\"\\ud834\\n\":0}", nullptr},
    {"\"\\udd1ef", nullptr},
    {"{\"\\udd1ef\":0}", nullptr},
    {"\"\\ud834\\ud834\"", nullptr},
    {"{\"\\ud834\\ud834\"\":0}", nullptr},
    {"\"\\ud834\\u1234\"", nullptr},
    {"{\"\\ud834\\u1234\"\":0}", nullptr},
    {"\"\\ud834]\"", nullptr},
    {"{\"\\ud834]\"\":0}", nullptr},
    {"\"\\ud834 \"", nullptr},
    {"{\"\\ud834 \"\":0}", nullptr},
    {"\"\\ud834\\\\\"", nullptr},
    {"{\"\\ud834\\\\\"\":0}", nullptr},
    /* Testing embedded invalid whitechars. */
    {"\"\n\"", nullptr},
    {"\"\t\"", nullptr},
    /* Testing empty json data. */
    {"", nullptr},
    /* Testing extra characters after end of parsing. */
    {"{},", nullptr},
    /* Testing imbalanced containers. */
    {"{}}", nullptr},
    {"[]]", nullptr},
    {"{{}", nullptr},
    {"[[]", nullptr},
    {"[}", nullptr},
    {"{]", nullptr},
    /* Testing bad containers. */
    {"{x}", nullptr},
    {"{x=0,y}", nullptr},
    /* Testing trailing comma. */
    {"{,}", nullptr},
    {"[1,2,3,4,]", nullptr},
    {"{\"a\": 1, }", nullptr},
    /* Testing after-ending characters. */
    {"{}x", nullptr},
    /* Testing having a key syntax in an array. */
    {"[\"x\":0]", nullptr},
    /* Testing invalid numbers. */
    {"1.", nullptr},
    {"1e", nullptr},
    {".12", nullptr},
    {"1.x", nullptr},
    {"1.12x", nullptr},
    {"1ex", nullptr},
    {"1e12x", nullptr},
    {".12x", nullptr},
    {"000", nullptr},
};

static void test_pairs() {
  unsigned i;

  for (i = 0; i < GPR_ARRAY_SIZE(testing_pairs); i++) {
    testing_pair* pair = testing_pairs + i;
    char* scratchpad = gpr_strdup(pair->input);
    grpc_json* json;

    gpr_log(GPR_INFO, "parsing string %i - should %s", i,
            pair->output ? "succeed" : "fail");
    json = grpc_json_parse_string(scratchpad);

    if (pair->output) {
      char* output;

      GPR_ASSERT(json);
      output = grpc_json_dump_to_string(json, 0);
      GPR_ASSERT(output);
      gpr_log(GPR_INFO, "succeeded with output = %s", output);
      GPR_ASSERT(strcmp(output, pair->output) == 0);

      grpc_json_destroy(json);
      gpr_free(output);
    } else {
      gpr_log(GPR_INFO, "failed");
      GPR_ASSERT(!json);
    }

    gpr_free(scratchpad);
  }
}

static void test_atypical() {
  char* scratchpad = gpr_strdup("[[],[],[]]");
  grpc_json* json = grpc_json_parse_string(scratchpad);
  grpc_json* brother;

  GPR_ASSERT(json);
  GPR_ASSERT(json->child);
  brother = json->child->next;
  grpc_json_destroy(json->child);
  GPR_ASSERT(json->child == brother);
  grpc_json_destroy(json->child->next);
  grpc_json_destroy(json);
  gpr_free(scratchpad);
}

int main(int argc, char** argv) {
  grpc::testing::TestEnvironment env(argc, argv);
  test_pairs();
  test_atypical();
  gpr_log(GPR_INFO, "json_test success");
  return 0;
}
