// Copyright 2010-2021, 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 "net/jsonpath.h"

#include <string>
#include <vector>

#include "testing/base/public/gunit.h"

namespace mozc {
namespace net {

class JsonPathTest : public testing::Test {
 public:
  void SetUp() override {}

  std::string Parse(const std::string &json, const std::string &jsonpath) {
    Json::Reader reader;
    Json::Value root;
    EXPECT_TRUE(reader.parse(json, root))
        << reader.getFormattedErrorMessages() << " " << json;
    std::vector<const Json::Value *> output;
    if (!JsonPath::Parse(root, jsonpath, &output)) {
      return "ERROR";
    }
    std::string result;
    for (size_t i = 0; i < output.size(); ++i) {
      const Json::Value *value = output[i];
      if (!value->isNull() && !value->isArray() && !value->isObject()) {
        if (!result.empty()) {
          result.append(" ");
        }
        result.append(value->asString());
      }
    }
    return result;
  }

  void TearDown() override {}
};

TEST_F(JsonPathTest, BasicTest) {
  const char kInput[] =
      "{"
      "\"books\": ["
      "    {\"title\":\"foo1\",\"author\":\"bar1\"},"
      "    {\"title\":\"foo2\",\"author\":\"bar2\"},"
      "    {\"title\":\"foo3\",\"author\":\"bar3\"}"
      "],"
      "\"papers\": {"
      "   \"year\": 2011,"
      "   \"month\": 11,"
      "   \"date\": 10,"
      "   \"list\" : ["
      "     {\"title\":\"Foo1\",\"author\":\"Bar2\"},"
      "     {\"title\":\"Foo2\",\"author\":\"Bar2\"},"
      "     {\"title\":\"Foo3\",\"author\":\"Bar3\"}"
      "   ]"
      "},"
      "\"title\": \"hello\""
      "}";

  // Parse error
  EXPECT_EQ("ERROR", Parse(kInput, ""));
  EXPECT_EQ("ERROR", Parse(kInput, "$"));
  EXPECT_EQ("ERROR", Parse(kInput, "$.foo...bar"));
  EXPECT_EQ("ERROR", Parse(kInput, "$."));
  EXPECT_EQ("ERROR", Parse(kInput, "."));
  EXPECT_EQ("ERROR", Parse(kInput, "$.."));
  EXPECT_EQ("ERROR", Parse(kInput, "$.*."));
  EXPECT_EQ("ERROR", Parse(kInput, "$.foo]["));
  EXPECT_EQ("ERROR", Parse(kInput, "$.foo[."));
  EXPECT_EQ("ERROR", Parse(kInput, "$.foo[]"));
  EXPECT_EQ("ERROR", Parse(kInput, "$.foo[10]]"));

  // Normal expression
  EXPECT_EQ("hello", Parse(kInput, "$.title"));
  EXPECT_EQ("", Parse(kInput, "$.books.title"));
  EXPECT_EQ("", Parse(kInput, "$.books[0]"));
  EXPECT_EQ("foo1", Parse(kInput, "$.books[0].title"));
  EXPECT_EQ("foo1 foo2 foo3", Parse(kInput, "$.books[*].title"));
  EXPECT_EQ("foo2", Parse(kInput, "$.books[1].title"));
  EXPECT_EQ("foo2", Parse(kInput, "$.*[1].title"));
  EXPECT_EQ("foo1 foo2 foo3", Parse(kInput, "$.*[*].title"));
  // Object names are fetched by alphabetical order.
  EXPECT_EQ("bar1 foo1", Parse(kInput, "$.books[0].*"));
  EXPECT_EQ("bar1 foo1 bar3 foo3", Parse(kInput, "$.books[0,2].*"));
  EXPECT_EQ("bar3 foo3", Parse(kInput, "$.books[-1].*"));

  EXPECT_EQ("Foo1", Parse(kInput, "$.papers.list[0].title"));
  EXPECT_EQ("Foo1 Foo2 Foo3", Parse(kInput, "$.papers.list[*].title"));
  EXPECT_EQ("Foo1", Parse(kInput, "$.papers.*[0].title"));
  EXPECT_EQ("10 11 2011", Parse(kInput, "$.papers.*"));
  EXPECT_EQ("10", Parse(kInput, "$.papers.date"));

  EXPECT_EQ("hello foo1 foo2 foo3 Foo1 Foo2 Foo3", Parse(kInput, "$..title"));
  EXPECT_EQ("Foo1 Foo2 Foo3", Parse(kInput, "$.papers..title"));
  EXPECT_EQ("bar1 foo1 bar2 foo2 bar3 foo3", Parse(kInput, "$.books..*"));

  // Bracket expression
  EXPECT_EQ("hello", Parse(kInput, "$.['title']"));
  EXPECT_EQ("", Parse(kInput, "$['books']['title']"));
  EXPECT_EQ("", Parse(kInput, "$['books'][0]"));
  EXPECT_EQ("foo1", Parse(kInput, "$['books'][0]['title']"));
  EXPECT_EQ("foo1 foo2 foo3", Parse(kInput, "$['books'][*]['title']"));
  EXPECT_EQ("foo2", Parse(kInput, "$['books'][1]['title']"));
  EXPECT_EQ("foo2", Parse(kInput, "$[*][1]['title']"));
  EXPECT_EQ("foo1 foo2 foo3", Parse(kInput, "$[*][*]['title']"));
  // Object names are fetched by alphabetical order.
  EXPECT_EQ("bar1 foo1", Parse(kInput, "$['books'][0][*]"));
  EXPECT_EQ("bar1 foo1 bar3 foo3", Parse(kInput, "$['books'][0,2][*]"));
  EXPECT_EQ("bar3 foo3", Parse(kInput, "$['books'][-1][*]"));

  EXPECT_EQ("Foo1", Parse(kInput, "$['papers']['list'][0]['title']"));
  EXPECT_EQ("Foo1 Foo2 Foo3", Parse(kInput, "$['papers']['list'][*]['title']"));
  EXPECT_EQ("Foo1", Parse(kInput, "$['papers'][*][0]['title']"));
  EXPECT_EQ("10 11 2011", Parse(kInput, "$['papers'][*]"));
  EXPECT_EQ("10", Parse(kInput, "$['papers']['date']"));

  EXPECT_EQ("hello foo1 foo2 foo3 Foo1 Foo2 Foo3",
            Parse(kInput, "$..['title']"));
  EXPECT_EQ("Foo1 Foo2 Foo3", Parse(kInput, "$['papers']..['title']"));
  EXPECT_EQ("bar1 foo1 bar2 foo2 bar3 foo3", Parse(kInput, "$['books']..[*]"));

  EXPECT_EQ("2011 11 10", Parse(kInput, "$['papers']['year','month','date']"));
  EXPECT_EQ("11", Parse(kInput, "$['papers']['foo','month','bar']"));
}

TEST_F(JsonPathTest, SliceTest) {
  const char kInput[] = "{ \"a\": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] }";
  EXPECT_EQ("ERROR", Parse(kInput, "$.a[]"));
  EXPECT_EQ("0", Parse(kInput, "$.a[0]"));
  EXPECT_EQ("3", Parse(kInput, "$.a[3]"));
  EXPECT_EQ("", Parse(kInput, "$.a[100]"));
  EXPECT_EQ("", Parse(kInput, "$.a[-100]"));
  EXPECT_EQ("0", Parse(kInput, "$.a[0:1]"));
  EXPECT_EQ("0 1", Parse(kInput, "$.a[0:2]"));
  EXPECT_EQ("5 6", Parse(kInput, "$.a[5:7]"));
  EXPECT_EQ("9", Parse(kInput, "$.a[-1]"));
  EXPECT_EQ("0 1 2 3 4 5 6 7 8", Parse(kInput, "$.a[:-1]"));
  EXPECT_EQ("9", Parse(kInput, "$.a[-1:]"));
  EXPECT_EQ("7 8 9", Parse(kInput, "$.a[-3:]"));
  EXPECT_EQ("6 7", Parse(kInput, "$.a[6:-2]"));
  EXPECT_EQ("", Parse(kInput, "$.a[5:2]"));
  EXPECT_EQ("", Parse(kInput, "$.a[-2:-3]"));
  EXPECT_EQ("0 2", Parse(kInput, "$.a[0:4:2]"));
  EXPECT_EQ("8 6 4", Parse(kInput, "$.a[8:2:-2]"));
  EXPECT_EQ("", Parse(kInput, "$.a[0:4:-2]"));
  EXPECT_EQ("4 3", Parse(kInput, "$.a[4:2:-1]"));
  EXPECT_EQ("", Parse(kInput, "$.a[4:2:1]"));
  EXPECT_EQ("4 5 6", Parse(kInput, "$.a[4:-3:1]"));
  EXPECT_EQ("0 1 2 3 4 2 3 4 5 6", Parse(kInput, "$.a[0:3,3:5,2:-3]"));
}

}  // namespace net
}  // namespace mozc
