/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/Maybe.h"  // mozilla::Maybe

#include <string>

#include "js/AllocPolicy.h"  // js::SystemAllocPolicy
#include "js/JSON.h"
#include "js/Vector.h"  // js::Vector
#include "jsapi-tests/tests.h"

using namespace JS;

BEGIN_FRONTEND_TEST(testIsValidJSONLatin1) {
  const char* source;

  source = "true";
  CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                    strlen(source)));

  source = "false";
  CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                    strlen(source)));

  source = "null";
  CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                    strlen(source)));

  source = "0";
  CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                    strlen(source)));

  source = "1";
  CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                    strlen(source)));

  source = "-1";
  CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                    strlen(source)));

  source = "1.75";
  CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                    strlen(source)));

  source = "9000000000";
  CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                    strlen(source)));

  source = "\"foo\"";
  CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                    strlen(source)));

  source = "[]";
  CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                    strlen(source)));

  source = "[1, true]";
  CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                    strlen(source)));

  source = "{}";
  CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                    strlen(source)));

  source = "{\"key\": 10}";
  CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                    strlen(source)));

  source = "{\"key\": 10, \"prop\": 20}";
  CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                    strlen(source)));

  source = "1 ";
  CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                    strlen(source)));

  // Invalid cases.

  source = "";
  CHECK(!IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                     strlen(source)));

  source = "1 1";
  CHECK(!IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                     strlen(source)));

  source = ".1";
  CHECK(!IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                     strlen(source)));

  source = "undefined";
  CHECK(!IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                     strlen(source)));

  source = "TRUE";
  CHECK(!IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                     strlen(source)));

  source = "'foo'";
  CHECK(!IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                     strlen(source)));

  source = "[";
  CHECK(!IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                     strlen(source)));

  source = "{";
  CHECK(!IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                     strlen(source)));

  source = "/a/";
  CHECK(!IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
                     strlen(source)));

  return true;
}

END_TEST(testIsValidJSONLatin1)

BEGIN_FRONTEND_TEST(testIsValidJSONTwoBytes) {
  const char16_t* source;

  source = u"true";
  CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  source = u"false";
  CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  source = u"null";
  CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  source = u"0";
  CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  source = u"1";
  CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  source = u"-1";
  CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  source = u"1.75";
  CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  source = u"9000000000";
  CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  source = u"\"foo\"";
  CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  source = u"[]";
  CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  source = u"[1, true]";
  CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  source = u"{}";
  CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  source = u"{\"key\": 10}";
  CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  source = u"{\"key\": 10, \"prop\": 20}";
  CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  source = u"1 ";
  CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  // Invalid cases.

  source = u"";
  CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  source = u"1 1";
  CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  source = u".1";
  CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  source = u"undefined";
  CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  source = u"TRUE";
  CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  source = u"'foo'";
  CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  source = u"[";
  CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  source = u"{";
  CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  source = u"/a/";
  CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));

  return true;
}

END_TEST(testIsValidJSONTwoBytes)

BEGIN_FRONTEND_TEST(testParseJSONWithHandler) {
  {
    MyHandler handler;

    const char* source =
        "{ \"prop1\": 10.5, \"prop\\uff12\": [true, false, null, \"Ascii\", "
        "\"\\u3042\\u3044\\u3046\", \"\\u0020\", \"\\u0080\"] }";
    CHECK(JS::ParseJSONWithHandler((const JS::Latin1Char*)source,
                                   std::char_traits<char>::length(source),
                                   &handler));

    size_t i = 0;

    CHECK(handler.events[i++] == MyHandler::Event::StartObject);

    // Non-escaped ASCII property name in Latin1 input should be passed with
    // Latin1.
    CHECK(handler.events[i++] == MyHandler::Event::Latin1Prop1);

    CHECK(handler.events[i++] == MyHandler::Event::Number);

    // Escaped non-Latin1 property name in Latin1 input should be passed with
    // TwoBytes.
    CHECK(handler.events[i++] == MyHandler::Event::TwoBytesProp2);
    CHECK(handler.events[i++] == MyHandler::Event::StartArray);
    CHECK(handler.events[i++] == MyHandler::Event::True);
    CHECK(handler.events[i++] == MyHandler::Event::False);
    CHECK(handler.events[i++] == MyHandler::Event::Null);

    // Non-escaped ASCII string in Latin1 input should be passed with Latin1.
    CHECK(handler.events[i++] == MyHandler::Event::Latin1Str1);

    // Escaped non-Latin1 string in Latin1 input should be passed with TwoBytes.
    CHECK(handler.events[i++] == MyHandler::Event::TwoBytesStr2);

    // Escaped ASCII-range string in Latin1 input should be passed with Latin1.
    CHECK(handler.events[i++] == MyHandler::Event::Latin1Str3);

    // Escaped Latin1-range string in Latin1 input should be passed with Latin1.
    CHECK(handler.events[i++] == MyHandler::Event::Latin1Str4);

    CHECK(handler.events[i++] == MyHandler::Event::EndArray);
    CHECK(handler.events[i++] == MyHandler::Event::EndObject);
    CHECK(handler.events.length() == i);
  }
  {
    MyHandler handler;

    const char* source = "{";
    CHECK(!JS::ParseJSONWithHandler((const JS::Latin1Char*)source,
                                    std::char_traits<char>::length(source),
                                    &handler));

    size_t i = 0;

    CHECK(handler.events[i++] == MyHandler::Event::StartObject);
    CHECK(handler.events[i++] == MyHandler::Event::Error);
    CHECK(handler.events.length() == i);
  }

  {
    MyHandler handler;

    const char16_t* source =
        u"{ \"prop1\": 10.5, \"prop\uff12\": [true, false, null, \"Ascii\", "
        u"\"\\u3042\\u3044\\u3046\", \"\\u0020\", \"\\u0080\"] }";
    CHECK(JS::ParseJSONWithHandler(
        source, std::char_traits<char16_t>::length(source), &handler));

    size_t i = 0;

    CHECK(handler.events[i++] == MyHandler::Event::StartObject);

    // Non-escaped ASCII property name in TwoBytes input should be passed with
    // TwoBytes.
    CHECK(handler.events[i++] == MyHandler::Event::TwoBytesProp1);

    CHECK(handler.events[i++] == MyHandler::Event::Number);

    // Escaped non-Latin1 property name in TwoBytes input should be passed with
    // TwoBytes.
    CHECK(handler.events[i++] == MyHandler::Event::TwoBytesProp2);

    CHECK(handler.events[i++] == MyHandler::Event::StartArray);
    CHECK(handler.events[i++] == MyHandler::Event::True);
    CHECK(handler.events[i++] == MyHandler::Event::False);
    CHECK(handler.events[i++] == MyHandler::Event::Null);

    // Non-escaped ASCII string in TwoBytes input should be passed with
    // TwoBytes.
    CHECK(handler.events[i++] == MyHandler::Event::TwoBytesStr1);

    // Escaped non-Latin1 string in TwoBytes input should be passed with
    // TwoBytes.
    CHECK(handler.events[i++] == MyHandler::Event::TwoBytesStr2);

    // Escaped ASCII-range string in TwoBytes input should be passed with
    // Latin1.
    CHECK(handler.events[i++] == MyHandler::Event::Latin1Str3);

    // Escaped Latin1-range string in TwoBytes input should be passed with
    // Latin1.
    CHECK(handler.events[i++] == MyHandler::Event::Latin1Str4);

    CHECK(handler.events[i++] == MyHandler::Event::EndArray);
    CHECK(handler.events[i++] == MyHandler::Event::EndObject);
    CHECK(handler.events.length() == i);
  }

  {
    MyHandler handler;

    const char16_t* source = u"{";
    CHECK(!JS::ParseJSONWithHandler(
        source, std::char_traits<char16_t>::length(source), &handler));

    size_t i = 0;

    CHECK(handler.events[i++] == MyHandler::Event::StartObject);
    CHECK(handler.events[i++] == MyHandler::Event::Error);
    CHECK(handler.events.length() == i);
  }

  // Verify the failure case is handled properly and no methods are called
  // after the failure.

  bool checkedLast = false;
  for (size_t failAt = 1; !checkedLast; failAt++) {
    MyHandler handler;
    handler.failAt.emplace(failAt);

    const char* source =
        "{ \"prop1\": 10.5, \"prop\\uff12\": [true, false, null, \"Ascii\", "
        "\"\\u3042\\u3044\\u3046\", \"\\u0020\", \"\\u0080\"] }";
    CHECK(!JS::ParseJSONWithHandler((const JS::Latin1Char*)source,
                                    std::char_traits<char>::length(source),
                                    &handler));

    CHECK(handler.events.length() == failAt);

    size_t i = 0;

    CHECK(handler.events[i++] == MyHandler::Event::StartObject);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::Latin1Prop1);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::Number);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::TwoBytesProp2);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::StartArray);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::True);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::False);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::Null);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::Latin1Str1);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::TwoBytesStr2);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::Latin1Str3);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::Latin1Str4);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::EndArray);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::EndObject);
    checkedLast = true;
  }

  checkedLast = false;
  for (size_t failAt = 1; !checkedLast; failAt++) {
    MyHandler handler;
    handler.failAt.emplace(failAt);

    const char16_t* source =
        u"{ \"prop1\": 10.5, \"prop\uff12\": [true, false, null, \"Ascii\", "
        u"\"\\u3042\\u3044\\u3046\", \"\\u0020\", \"\\u0080\"] }";
    CHECK(!JS::ParseJSONWithHandler(
        source, std::char_traits<char16_t>::length(source), &handler));

    CHECK(handler.events.length() == failAt);

    size_t i = 0;

    CHECK(handler.events[i++] == MyHandler::Event::StartObject);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::TwoBytesProp1);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::Number);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::TwoBytesProp2);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::StartArray);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::True);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::False);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::Null);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::TwoBytesStr1);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::TwoBytesStr2);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::Latin1Str3);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::Latin1Str4);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::EndArray);
    if (i >= failAt) {
      continue;
    }
    CHECK(handler.events[i++] == MyHandler::Event::EndObject);
    checkedLast = true;
  }

  {
    const size_t failAt = 1;
    MyHandler handler;
    const char16_t* source;

#define IMMEDIATE_FAIL(json)                                          \
  handler.failAt.emplace(failAt);                                     \
  source = json;                                                      \
  CHECK(!JS::ParseJSONWithHandler(                                    \
      source, std::char_traits<char16_t>::length(source), &handler)); \
  CHECK(handler.events.length() == failAt);                           \
  handler.events.clear();

    IMMEDIATE_FAIL(u"{");
    IMMEDIATE_FAIL(u"[");
    IMMEDIATE_FAIL(u"\"string\"");
    IMMEDIATE_FAIL(u"1");
    IMMEDIATE_FAIL(u"true");
    IMMEDIATE_FAIL(u"null");

#undef IMMEDIATE_FAIL
  }

  return true;
}

class MyHandler : public JS::JSONParseHandler {
 public:
  enum class Event {
    Uninitialized = 0,

    StartObject,
    Latin1Prop1,
    TwoBytesProp1,
    Number,
    TwoBytesProp2,
    StartArray,
    True,
    False,
    Null,
    Latin1Str1,
    TwoBytesStr1,
    TwoBytesStr2,
    Latin1Str3,
    Latin1Str4,
    EndArray,
    EndObject,
    Error,
    UnexpectedNumber,
    UnexpectedLatin1Prop,
    UnexpectedTwoBytesProp,
    UnexpectedLatin1String,
    UnexpectedTwoBytesString,
  };
  js::Vector<Event, 0, js::SystemAllocPolicy> events;
  mozilla::Maybe<size_t> failAt;

  MyHandler() {}
  virtual ~MyHandler() {}

  bool startObject() override {
    MOZ_ALWAYS_TRUE(events.append(Event::StartObject));
    if (failAt.isSome() && events.length() == *failAt) {
      failAt.reset();
      return false;
    }
    return true;
  }
  bool propertyName(const JS::Latin1Char* name, size_t length) override {
    if (length == 5 && name[0] == 'p' && name[1] == 'r' && name[2] == 'o' &&
        name[3] == 'p' && name[4] == '1') {
      MOZ_ALWAYS_TRUE(events.append(Event::Latin1Prop1));
    } else {
      MOZ_ALWAYS_TRUE(events.append(Event::UnexpectedLatin1Prop));
    }
    if (failAt.isSome() && events.length() == *failAt) {
      failAt.reset();
      return false;
    }
    return true;
  }
  bool propertyName(const char16_t* name, size_t length) override {
    if (length == 5 && name[0] == 'p' && name[1] == 'r' && name[2] == 'o' &&
        name[3] == 'p' && name[4] == '1') {
      MOZ_ALWAYS_TRUE(events.append(Event::TwoBytesProp1));
    } else if (length == 5 && name[0] == 'p' && name[1] == 'r' &&
               name[2] == 'o' && name[3] == 'p' && name[4] == 0xff12) {
      MOZ_ALWAYS_TRUE(events.append(Event::TwoBytesProp2));
    } else {
      MOZ_ALWAYS_TRUE(events.append(Event::UnexpectedTwoBytesProp));
    }
    if (failAt.isSome() && events.length() == *failAt) {
      failAt.reset();
      return false;
    }
    return true;
  }
  bool endObject() override {
    MOZ_ALWAYS_TRUE(events.append(Event::EndObject));
    if (failAt.isSome() && events.length() == *failAt) {
      failAt.reset();
      return false;
    }
    return true;
  }

  bool startArray() override {
    MOZ_ALWAYS_TRUE(events.append(Event::StartArray));
    if (failAt.isSome() && events.length() == *failAt) {
      failAt.reset();
      return false;
    }
    return true;
  }
  bool endArray() override {
    MOZ_ALWAYS_TRUE(events.append(Event::EndArray));
    if (failAt.isSome() && events.length() == *failAt) {
      failAt.reset();
      return false;
    }
    return true;
  }

  bool stringValue(const JS::Latin1Char* name, size_t length) override {
    if (length == 5 && name[0] == 'A' && name[1] == 's' && name[2] == 'c' &&
        name[3] == 'i' && name[4] == 'i') {
      MOZ_ALWAYS_TRUE(events.append(Event::Latin1Str1));
    } else if (length == 1 && name[0] == ' ') {
      MOZ_ALWAYS_TRUE(events.append(Event::Latin1Str3));
    } else if (length == 1 && name[0] == 0x80) {
      MOZ_ALWAYS_TRUE(events.append(Event::Latin1Str4));
    } else {
      MOZ_ALWAYS_TRUE(events.append(Event::UnexpectedLatin1String));
    }
    if (failAt.isSome() && events.length() == *failAt) {
      failAt.reset();
      return false;
    }
    return true;
  }
  bool stringValue(const char16_t* name, size_t length) override {
    if (length == 5 && name[0] == 'A' && name[1] == 's' && name[2] == 'c' &&
        name[3] == 'i' && name[4] == 'i') {
      MOZ_ALWAYS_TRUE(events.append(Event::TwoBytesStr1));
    } else if (length == 3 && name[0] == 0x3042 && name[1] == 0x3044 &&
               name[2] == 0x3046) {
      MOZ_ALWAYS_TRUE(events.append(Event::TwoBytesStr2));
    } else {
      MOZ_ALWAYS_TRUE(events.append(Event::UnexpectedTwoBytesString));
    }
    if (failAt.isSome() && events.length() == *failAt) {
      failAt.reset();
      return false;
    }
    return true;
  }
  bool numberValue(double d) override {
    if (d == 10.5) {
      MOZ_ALWAYS_TRUE(events.append(Event::Number));
    } else {
      MOZ_ALWAYS_TRUE(events.append(Event::UnexpectedNumber));
    }
    if (failAt.isSome() && events.length() == *failAt) {
      failAt.reset();
      return false;
    }
    return true;
  }
  bool booleanValue(bool v) override {
    if (v) {
      MOZ_ALWAYS_TRUE(events.append(Event::True));
    } else {
      MOZ_ALWAYS_TRUE(events.append(Event::False));
    }
    if (failAt.isSome() && events.length() == *failAt) {
      failAt.reset();
      return false;
    }
    return true;
  }
  bool nullValue() override {
    MOZ_ALWAYS_TRUE(events.append(Event::Null));
    if (failAt.isSome() && events.length() == *failAt) {
      failAt.reset();
      return false;
    }
    return true;
  }

  void error(const char* msg, uint32_t line, uint32_t column) override {
    MOZ_ALWAYS_TRUE(events.append(Event::Error));
  }
};

END_TEST(testParseJSONWithHandler)
