/*
 *
 * Copyright 2015, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * 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.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
 * OWNER 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 <string.h>

#include <grpc/support/port_platform.h>

#include "src/core/json/json_reader.h"

static void json_reader_string_clear(grpc_json_reader* reader) {
  reader->vtable->string_clear(reader->userdata);
}

static void json_reader_string_add_char(grpc_json_reader* reader,
                                             gpr_uint32 c) {
  reader->vtable->string_add_char(reader->userdata, c);
}

static void json_reader_string_add_utf32(grpc_json_reader* reader,
                                              gpr_uint32 utf32) {
  reader->vtable->string_add_utf32(reader->userdata, utf32);
}

static gpr_uint32
    grpc_json_reader_read_char(grpc_json_reader* reader) {
  return reader->vtable->read_char(reader->userdata);
}

static void json_reader_container_begins(grpc_json_reader* reader,
                                              grpc_json_type type) {
  reader->vtable->container_begins(reader->userdata, type);
}

static grpc_json_type
    grpc_json_reader_container_ends(grpc_json_reader* reader) {
  return reader->vtable->container_ends(reader->userdata);
}

static void json_reader_set_key(grpc_json_reader* reader) {
  reader->vtable->set_key(reader->userdata);
}

static void json_reader_set_string(grpc_json_reader* reader) {
  reader->vtable->set_string(reader->userdata);
}

static int json_reader_set_number(grpc_json_reader* reader) {
  return reader->vtable->set_number(reader->userdata);
}

static void json_reader_set_true(grpc_json_reader* reader) {
  reader->vtable->set_true(reader->userdata);
}

static void json_reader_set_false(grpc_json_reader* reader) {
  reader->vtable->set_false(reader->userdata);
}

static void json_reader_set_null(grpc_json_reader* reader) {
  reader->vtable->set_null(reader->userdata);
}

/* Call this function to initialize the reader structure. */
void grpc_json_reader_init(grpc_json_reader* reader,
                           grpc_json_reader_vtable* vtable, void* userdata) {
  memset(reader, 0, sizeof(*reader));
  reader->vtable = vtable;
  reader->userdata = userdata;
  json_reader_string_clear(reader);
  reader->state = GRPC_JSON_STATE_VALUE_BEGIN;
}

int grpc_json_reader_is_complete(grpc_json_reader* reader) {
  return ((reader->depth == 0) && ((reader->state == GRPC_JSON_STATE_END) ||
          (reader->state == GRPC_JSON_STATE_VALUE_END)));
}

grpc_json_reader_status grpc_json_reader_run(grpc_json_reader* reader) {
  gpr_uint32 c, success;

  /* This state-machine is a strict implementation of ECMA-404 */
  for (;;) {
    c = grpc_json_reader_read_char(reader);
    switch (c) {
      /* Let's process the error cases first. */
      case GRPC_JSON_READ_CHAR_ERROR:
        return GRPC_JSON_READ_ERROR;

      case GRPC_JSON_READ_CHAR_EAGAIN:
        return GRPC_JSON_EAGAIN;

      case GRPC_JSON_READ_CHAR_EOF:
        if (grpc_json_reader_is_complete(reader)) {
          return GRPC_JSON_DONE;
        } else {
          return GRPC_JSON_PARSE_ERROR;
        }
        break;

      /* Processing whitespaces. */
      case ' ':
      case '\t':
      case '\n':
      case '\r':
        switch (reader->state) {
          case GRPC_JSON_STATE_OBJECT_KEY_BEGIN:
          case GRPC_JSON_STATE_OBJECT_KEY_END:
          case GRPC_JSON_STATE_VALUE_BEGIN:
          case GRPC_JSON_STATE_VALUE_END:
          case GRPC_JSON_STATE_END:
            break;

          case GRPC_JSON_STATE_OBJECT_KEY_STRING:
          case GRPC_JSON_STATE_VALUE_STRING:
            if (c != ' ') return GRPC_JSON_PARSE_ERROR;
            if (reader->unicode_high_surrogate != 0) return GRPC_JSON_PARSE_ERROR;
            json_reader_string_add_char(reader, c);
            break;

          case GRPC_JSON_STATE_VALUE_NUMBER:
          case GRPC_JSON_STATE_VALUE_NUMBER_WITH_DECIMAL:
          case GRPC_JSON_STATE_VALUE_NUMBER_ZERO:
          case GRPC_JSON_STATE_VALUE_NUMBER_EPM:
            success = json_reader_set_number(reader);
            if (!success) return GRPC_JSON_PARSE_ERROR;
            json_reader_string_clear(reader);
            reader->state = GRPC_JSON_STATE_VALUE_END;
            break;

          default:
            return GRPC_JSON_PARSE_ERROR;
        }
        break;

      /* Value, object or array terminations. */
      case ',':
      case '}':
      case ']':
        switch (reader->state) {
          case GRPC_JSON_STATE_OBJECT_KEY_STRING:
          case GRPC_JSON_STATE_VALUE_STRING:
            if (reader->unicode_high_surrogate != 0) return GRPC_JSON_PARSE_ERROR;
            json_reader_string_add_char(reader, c);
            break;

          case GRPC_JSON_STATE_VALUE_NUMBER:
          case GRPC_JSON_STATE_VALUE_NUMBER_WITH_DECIMAL:
          case GRPC_JSON_STATE_VALUE_NUMBER_ZERO:
          case GRPC_JSON_STATE_VALUE_NUMBER_EPM:
            success = json_reader_set_number(reader);
            if (!success) return GRPC_JSON_PARSE_ERROR;
            json_reader_string_clear(reader);
            reader->state = GRPC_JSON_STATE_VALUE_END;
          /* The missing break here is intentional. */

          case GRPC_JSON_STATE_VALUE_END:
          case GRPC_JSON_STATE_OBJECT_KEY_BEGIN:
          case GRPC_JSON_STATE_VALUE_BEGIN:
            if (c == ',') {
              if (reader->state != GRPC_JSON_STATE_VALUE_END) {
                return GRPC_JSON_PARSE_ERROR;
              }
              if (reader->in_object) {
                reader->state = GRPC_JSON_STATE_OBJECT_KEY_BEGIN;
              } else {
                reader->state = GRPC_JSON_STATE_VALUE_BEGIN;
              }
            } else {
              if (reader->depth-- == 0) return GRPC_JSON_PARSE_ERROR;
              if ((c == '}') && !reader->in_object) {
                return GRPC_JSON_PARSE_ERROR;
              }
              if ((c == '}') &&
                  (reader->state == GRPC_JSON_STATE_OBJECT_KEY_BEGIN) &&
                  !reader->container_just_begun) {
                return GRPC_JSON_PARSE_ERROR;
              }
              if ((c == ']') && !reader->in_array) return GRPC_JSON_PARSE_ERROR;
              if ((c == ']') &&
                  (reader->state == GRPC_JSON_STATE_VALUE_BEGIN) &&
                  !reader->container_just_begun) {
                return GRPC_JSON_PARSE_ERROR;
              }
              reader->state = GRPC_JSON_STATE_VALUE_END;
              switch (grpc_json_reader_container_ends(reader)) {
                case GRPC_JSON_OBJECT:
                  reader->in_object = 1;
                  reader->in_array = 0;
                  break;
                case GRPC_JSON_ARRAY:
                  reader->in_object = 0;
                  reader->in_array = 1;
                  break;
                case GRPC_JSON_TOP_LEVEL:
                  if (reader->depth != 0) return GRPC_JSON_INTERNAL_ERROR;
                  reader->in_object = 0;
                  reader->in_array = 0;
                  reader->state = GRPC_JSON_STATE_END;
                  break;
                default:
                  return GRPC_JSON_INTERNAL_ERROR;
              }
            }
            break;

          default:
            return GRPC_JSON_PARSE_ERROR;
        }
        break;

      /* In-string escaping. */
      case '\\':
        switch (reader->state) {
          case GRPC_JSON_STATE_OBJECT_KEY_STRING:
            reader->escaped_string_was_key = 1;
            reader->state = GRPC_JSON_STATE_STRING_ESCAPE;
            break;

          case GRPC_JSON_STATE_VALUE_STRING:
            reader->escaped_string_was_key = 0;
            reader->state = GRPC_JSON_STATE_STRING_ESCAPE;
            break;

          /* This is the \\ case. */
          case GRPC_JSON_STATE_STRING_ESCAPE:
            if (reader->unicode_high_surrogate != 0) return GRPC_JSON_PARSE_ERROR;
            json_reader_string_add_char(reader, '\\');
            if (reader->escaped_string_was_key) {
              reader->state = GRPC_JSON_STATE_OBJECT_KEY_STRING;
            } else {
              reader->state = GRPC_JSON_STATE_VALUE_STRING;
            }
            break;

          default:
            return GRPC_JSON_PARSE_ERROR;
        }
        break;

      default:
        reader->container_just_begun = 0;
        switch (reader->state) {
          case GRPC_JSON_STATE_OBJECT_KEY_BEGIN:
            if (c != '"') return GRPC_JSON_PARSE_ERROR;
            reader->state = GRPC_JSON_STATE_OBJECT_KEY_STRING;
            break;

          case GRPC_JSON_STATE_OBJECT_KEY_STRING:
            if (reader->unicode_high_surrogate != 0) return GRPC_JSON_PARSE_ERROR;
            if (c == '"') {
              reader->state = GRPC_JSON_STATE_OBJECT_KEY_END;
              json_reader_set_key(reader);
              json_reader_string_clear(reader);
            } else {
              if (c <= 0x001f) return GRPC_JSON_PARSE_ERROR;
              json_reader_string_add_char(reader, c);
            }
            break;

          case GRPC_JSON_STATE_VALUE_STRING:
            if (reader->unicode_high_surrogate != 0) return GRPC_JSON_PARSE_ERROR;
            if (c == '"') {
              reader->state = GRPC_JSON_STATE_VALUE_END;
              json_reader_set_string(reader);
              json_reader_string_clear(reader);
            } else {
              if (c < 32) return GRPC_JSON_PARSE_ERROR;
              json_reader_string_add_char(reader, c);
            }
            break;

          case GRPC_JSON_STATE_OBJECT_KEY_END:
            if (c != ':') return GRPC_JSON_PARSE_ERROR;
            reader->state = GRPC_JSON_STATE_VALUE_BEGIN;
            break;

          case GRPC_JSON_STATE_VALUE_BEGIN:
            switch (c) {
              case 't':
                reader->state = GRPC_JSON_STATE_VALUE_TRUE_R;
                break;

              case 'f':
                reader->state = GRPC_JSON_STATE_VALUE_FALSE_A;
                break;

              case 'n':
                reader->state = GRPC_JSON_STATE_VALUE_NULL_U;
                break;

              case '"':
                reader->state = GRPC_JSON_STATE_VALUE_STRING;
                break;

              case '0':
                json_reader_string_add_char(reader, c);
                reader->state = GRPC_JSON_STATE_VALUE_NUMBER_ZERO;
                break;

              case '1':
              case '2':
              case '3':
              case '4':
              case '5':
              case '6':
              case '7':
              case '8':
              case '9':
              case '-':
                json_reader_string_add_char(reader, c);
                reader->state = GRPC_JSON_STATE_VALUE_NUMBER;
                break;

              case '{':
                reader->container_just_begun = 1;
                json_reader_container_begins(reader, GRPC_JSON_OBJECT);
                reader->depth++;
                reader->state = GRPC_JSON_STATE_OBJECT_KEY_BEGIN;
                reader->in_object = 1;
                reader->in_array = 0;
                break;

              case '[':
                reader->container_just_begun = 1;
                json_reader_container_begins(reader, GRPC_JSON_ARRAY);
                reader->depth++;
                reader->in_object = 0;
                reader->in_array = 1;
                break;
            }
            break;

          case GRPC_JSON_STATE_STRING_ESCAPE:
            if (reader->escaped_string_was_key) {
              reader->state = GRPC_JSON_STATE_OBJECT_KEY_STRING;
            } else {
              reader->state = GRPC_JSON_STATE_VALUE_STRING;
            }
            if (reader->unicode_high_surrogate && c != 'u')
              return GRPC_JSON_PARSE_ERROR;
            switch (c) {
              case '"':
              case '/':
                json_reader_string_add_char(reader, c);
                break;
              case 'b':
                json_reader_string_add_char(reader, '\b');
                break;
              case 'f':
                json_reader_string_add_char(reader, '\f');
                break;
              case 'n':
                json_reader_string_add_char(reader, '\n');
                break;
              case 'r':
                json_reader_string_add_char(reader, '\r');
                break;
              case 't':
                json_reader_string_add_char(reader, '\t');
                break;
              case 'u':
                reader->state = GRPC_JSON_STATE_STRING_ESCAPE_U1;
                reader->unicode_char = 0;
                break;
              default:
                return GRPC_JSON_PARSE_ERROR;
            }
            break;

          case GRPC_JSON_STATE_STRING_ESCAPE_U1:
          case GRPC_JSON_STATE_STRING_ESCAPE_U2:
          case GRPC_JSON_STATE_STRING_ESCAPE_U3:
          case GRPC_JSON_STATE_STRING_ESCAPE_U4:
            if ((c >= '0') && (c <= '9')) {
              c -= '0';
            } else if ((c >= 'A') && (c <= 'F')) {
              c -= 'A' - 10;
            } else if ((c >= 'a') && (c <= 'f')) {
              c -= 'a' - 10;
            } else {
              return GRPC_JSON_PARSE_ERROR;
            }
            reader->unicode_char <<= 4;
            reader->unicode_char |= c;

            switch (reader->state) {
              case GRPC_JSON_STATE_STRING_ESCAPE_U1:
                reader->state = GRPC_JSON_STATE_STRING_ESCAPE_U2;
                break;
              case GRPC_JSON_STATE_STRING_ESCAPE_U2:
                reader->state = GRPC_JSON_STATE_STRING_ESCAPE_U3;
                break;
              case GRPC_JSON_STATE_STRING_ESCAPE_U3:
                reader->state = GRPC_JSON_STATE_STRING_ESCAPE_U4;
                break;
              case GRPC_JSON_STATE_STRING_ESCAPE_U4:
                /* See grpc_json_writer_escape_string to have a description
                 * of what's going on here.
                 */
                if ((reader->unicode_char & 0xfc00) == 0xd800) {
                  /* high surrogate utf-16 */
                  if (reader->unicode_high_surrogate != 0)
                    return GRPC_JSON_PARSE_ERROR;
                  reader->unicode_high_surrogate = reader->unicode_char;
                } else if ((reader->unicode_char & 0xfc00) == 0xdc00) {
                  /* low surrogate utf-16 */
                  gpr_uint32 utf32;
                  if (reader->unicode_high_surrogate == 0)
                    return GRPC_JSON_PARSE_ERROR;
                  utf32 = 0x10000;
                  utf32 += (reader->unicode_high_surrogate - 0xd800) * 0x400;
                  utf32 += reader->unicode_char - 0xdc00;
                  json_reader_string_add_utf32(reader, utf32);
                  reader->unicode_high_surrogate = 0;
                } else {
                  /* anything else */
                  if (reader->unicode_high_surrogate != 0)
                    return GRPC_JSON_PARSE_ERROR;
                  json_reader_string_add_utf32(reader, reader->unicode_char);
                }
                if (reader->escaped_string_was_key) {
                  reader->state = GRPC_JSON_STATE_OBJECT_KEY_STRING;
                } else {
                  reader->state = GRPC_JSON_STATE_VALUE_STRING;
                }
                break;
              default:
                return GRPC_JSON_INTERNAL_ERROR;
            }
            break;

          case GRPC_JSON_STATE_VALUE_NUMBER:
            json_reader_string_add_char(reader, c);
            switch (c) {
              case '0':
              case '1':
              case '2':
              case '3':
              case '4':
              case '5':
              case '6':
              case '7':
              case '8':
              case '9':
                break;
              case 'e':
              case 'E':
                reader->state = GRPC_JSON_STATE_VALUE_NUMBER_E;
                break;
              case '.':
                reader->state = GRPC_JSON_STATE_VALUE_NUMBER_DOT;
                break;
              default:
                return GRPC_JSON_PARSE_ERROR;
            }
            break;

          case GRPC_JSON_STATE_VALUE_NUMBER_WITH_DECIMAL:
            json_reader_string_add_char(reader, c);
            switch (c) {
              case '0':
              case '1':
              case '2':
              case '3':
              case '4':
              case '5':
              case '6':
              case '7':
              case '8':
              case '9':
                break;
              case 'e':
              case 'E':
                reader->state = GRPC_JSON_STATE_VALUE_NUMBER_E;
                break;
              default:
                return GRPC_JSON_PARSE_ERROR;
            }
            break;

          case GRPC_JSON_STATE_VALUE_NUMBER_ZERO:
            if (c != '.') return GRPC_JSON_PARSE_ERROR;
            json_reader_string_add_char(reader, c);
            reader->state = GRPC_JSON_STATE_VALUE_NUMBER_DOT;
            break;

          case GRPC_JSON_STATE_VALUE_NUMBER_DOT:
            json_reader_string_add_char(reader, c);
            switch (c) {
              case '0':
              case '1':
              case '2':
              case '3':
              case '4':
              case '5':
              case '6':
              case '7':
              case '8':
              case '9':
                reader->state = GRPC_JSON_STATE_VALUE_NUMBER_WITH_DECIMAL;
                break;
              default:
                return GRPC_JSON_PARSE_ERROR;
            }
            break;

          case GRPC_JSON_STATE_VALUE_NUMBER_E:
            json_reader_string_add_char(reader, c);
            switch (c) {
              case '0':
              case '1':
              case '2':
              case '3':
              case '4':
              case '5':
              case '6':
              case '7':
              case '8':
              case '9':
              case '+':
              case '-':
                reader->state = GRPC_JSON_STATE_VALUE_NUMBER_EPM;
                break;
              default:
                return GRPC_JSON_PARSE_ERROR;
            }
            break;

          case GRPC_JSON_STATE_VALUE_NUMBER_EPM:
            json_reader_string_add_char(reader, c);
            switch (c) {
              case '0':
              case '1':
              case '2':
              case '3':
              case '4':
              case '5':
              case '6':
              case '7':
              case '8':
              case '9':
                break;
              default:
                return GRPC_JSON_PARSE_ERROR;
            }
            break;

          case GRPC_JSON_STATE_VALUE_TRUE_R:
            if (c != 'r') return GRPC_JSON_PARSE_ERROR;
            reader->state = GRPC_JSON_STATE_VALUE_TRUE_U;
            break;

          case GRPC_JSON_STATE_VALUE_TRUE_U:
            if (c != 'u') return GRPC_JSON_PARSE_ERROR;
            reader->state = GRPC_JSON_STATE_VALUE_TRUE_E;
            break;

          case GRPC_JSON_STATE_VALUE_TRUE_E:
            if (c != 'e') return GRPC_JSON_PARSE_ERROR;
            json_reader_set_true(reader);
            reader->state = GRPC_JSON_STATE_VALUE_END;
            break;

          case GRPC_JSON_STATE_VALUE_FALSE_A:
            if (c != 'a') return GRPC_JSON_PARSE_ERROR;
            reader->state = GRPC_JSON_STATE_VALUE_FALSE_L;
            break;

          case GRPC_JSON_STATE_VALUE_FALSE_L:
            if (c != 'l') return GRPC_JSON_PARSE_ERROR;
            reader->state = GRPC_JSON_STATE_VALUE_FALSE_S;
            break;

          case GRPC_JSON_STATE_VALUE_FALSE_S:
            if (c != 's') return GRPC_JSON_PARSE_ERROR;
            reader->state = GRPC_JSON_STATE_VALUE_FALSE_E;
            break;

          case GRPC_JSON_STATE_VALUE_FALSE_E:
            if (c != 'e') return GRPC_JSON_PARSE_ERROR;
            json_reader_set_false(reader);
            reader->state = GRPC_JSON_STATE_VALUE_END;
            break;

          case GRPC_JSON_STATE_VALUE_NULL_U:
            if (c != 'u') return GRPC_JSON_PARSE_ERROR;
            reader->state = GRPC_JSON_STATE_VALUE_NULL_L1;
            break;

          case GRPC_JSON_STATE_VALUE_NULL_L1:
            if (c != 'l') return GRPC_JSON_PARSE_ERROR;
            reader->state = GRPC_JSON_STATE_VALUE_NULL_L2;
            break;

          case GRPC_JSON_STATE_VALUE_NULL_L2:
            if (c != 'l') return GRPC_JSON_PARSE_ERROR;
            json_reader_set_null(reader);
            reader->state = GRPC_JSON_STATE_VALUE_END;
            break;

          /* All of the VALUE_END cases are handled in the specialized case
           * above. */
          case GRPC_JSON_STATE_VALUE_END:
            switch (c) {
              case ',':
              case '}':
              case ']':
                return GRPC_JSON_INTERNAL_ERROR;
                break;

              default:
                return GRPC_JSON_PARSE_ERROR;
            }
            break;

          case GRPC_JSON_STATE_END:
            return GRPC_JSON_PARSE_ERROR;
        }
    }
  }

  return GRPC_JSON_INTERNAL_ERROR;
}