// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
// 2. 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.
//
// 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 "schema-parser.h"
#include <gtest/gtest.h>
#include "test-util.h"
#include <kj/debug.h>
#include <map>

namespace capnp {
namespace {

class FakeFileReader final: public SchemaFile::FileReader {
public:
  void add(kj::StringPtr name, kj::StringPtr content) {
    files[name] = content;
  }

  bool exists(kj::StringPtr path) const override {
    return files.count(path) > 0;
  }

  kj::Array<const char> read(kj::StringPtr path) const override {
    auto iter = files.find(path);
    KJ_ASSERT(iter != files.end(), "FakeFileReader has no such file.", path);
    auto result = kj::heapArray<char>(iter->second.size());
    memcpy(result.begin(), iter->second.begin(), iter->second.size());
    return kj::mv(result);
  }

private:
  std::map<kj::StringPtr, kj::StringPtr> files;
};

TEST(SchemaParser, Basic) {
  SchemaParser parser;
  FakeFileReader reader;

  reader.add("src/foo/bar.capnp",
      "@0x8123456789abcdef;\n"
      "struct Bar {\n"
      "  baz @0: import \"baz.capnp\".Baz;\n"
      "  corge @1: import \"../qux/corge.capnp\".Corge;\n"
      "  grault @2: import \"/grault.capnp\".Grault;\n"
      "  garply @3: import \"/garply.capnp\".Garply;\n"
      "}\n");
  reader.add("src/foo/baz.capnp",
      "@0x823456789abcdef1;\n"
      "struct Baz {}\n");
  reader.add("src/qux/corge.capnp",
      "@0x83456789abcdef12;\n"
      "struct Corge {}\n");
  reader.add("/usr/include/grault.capnp",
      "@0x8456789abcdef123;\n"
      "struct Grault {}\n");
  reader.add("/opt/include/grault.capnp",
      "@0x8000000000000001;\n"
      "struct WrongGrault {}\n");
  reader.add("/usr/local/include/garply.capnp",
      "@0x856789abcdef1234;\n"
      "struct Garply {}\n");

  kj::StringPtr importPath[] = {
    "/usr/include", "/usr/local/include", "/opt/include"
  };

  ParsedSchema barSchema = parser.parseFile(SchemaFile::newDiskFile(
      kj::str("foo2/bar2.capnp"), kj::str("src/foo/bar.capnp"), importPath, reader));

  auto barProto = barSchema.getProto();
  EXPECT_EQ(0x8123456789abcdefull, barProto.getId());
  EXPECT_EQ("foo2/bar2.capnp", barProto.getDisplayName());
  auto barImports = barProto.getBody().getFileNode().getImports();
  ASSERT_EQ(4, barImports.size());
  EXPECT_EQ("../qux/corge.capnp", barImports[0].getName());
  EXPECT_EQ(0x83456789abcdef12ull, barImports[0].getId());
  EXPECT_EQ("/garply.capnp", barImports[1].getName());
  EXPECT_EQ(0x856789abcdef1234ull, barImports[1].getId());
  EXPECT_EQ("/grault.capnp", barImports[2].getName());
  EXPECT_EQ(0x8456789abcdef123ull, barImports[2].getId());
  EXPECT_EQ("baz.capnp", barImports[3].getName());
  EXPECT_EQ(0x823456789abcdef1ull, barImports[3].getId());

  auto barStruct = barSchema.getNested("Bar");
  auto barMembers = barStruct.asStruct().getMembers();
  ASSERT_EQ(4, barMembers.size());
  EXPECT_EQ("baz", barMembers[0].getProto().getName());
  EXPECT_EQ("corge", barMembers[1].getProto().getName());
  EXPECT_EQ("grault", barMembers[2].getProto().getName());
  EXPECT_EQ("garply", barMembers[3].getProto().getName());

  auto bazSchema = parser.parseFile(SchemaFile::newDiskFile(
      kj::str("not/used/because/already/loaded"),
      kj::str("src/foo/baz.capnp"), importPath, reader));
  EXPECT_EQ(0x823456789abcdef1ull, bazSchema.getProto().getId());
  EXPECT_EQ("foo2/baz.capnp", bazSchema.getProto().getDisplayName());
  auto bazStruct = bazSchema.getNested("Baz").asStruct();
  EXPECT_EQ(bazStruct, barStruct.getDependency(bazStruct.getProto().getId()));

  auto corgeSchema = parser.parseFile(SchemaFile::newDiskFile(
      kj::str("not/used/because/already/loaded"),
      kj::str("src/qux/corge.capnp"), importPath, reader));
  EXPECT_EQ(0x83456789abcdef12ull, corgeSchema.getProto().getId());
  EXPECT_EQ("qux/corge.capnp", corgeSchema.getProto().getDisplayName());
  auto corgeStruct = corgeSchema.getNested("Corge").asStruct();
  EXPECT_EQ(corgeStruct, barStruct.getDependency(corgeStruct.getProto().getId()));

  auto graultSchema = parser.parseFile(SchemaFile::newDiskFile(
      kj::str("not/used/because/already/loaded"),
      kj::str("/usr/include/grault.capnp"), importPath, reader));
  EXPECT_EQ(0x8456789abcdef123ull, graultSchema.getProto().getId());
  EXPECT_EQ("grault.capnp", graultSchema.getProto().getDisplayName());
  auto graultStruct = graultSchema.getNested("Grault").asStruct();
  EXPECT_EQ(graultStruct, barStruct.getDependency(graultStruct.getProto().getId()));

  // Try importing the other grault.capnp directly.  It'll get the display name we specify since
  // it wasn't imported before.
  auto wrongGraultSchema = parser.parseFile(SchemaFile::newDiskFile(
      kj::str("weird/display/name.capnp"),
      kj::str("/opt/include/grault.capnp"), importPath, reader));
  EXPECT_EQ(0x8000000000000001ull, wrongGraultSchema.getProto().getId());
  EXPECT_EQ("weird/display/name.capnp", wrongGraultSchema.getProto().getDisplayName());
}

}  // namespace
}  // namespace capnp