// 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.

// 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 "dynamic.h"
#include "message.h"
#include <kj/debug.h>
#include <gtest/gtest.h>
#include "test-util.h"

namespace capnp {
namespace _ {  // private
namespace {

template <typename Element, typename T>
void checkList(T reader, std::initializer_list<ReaderFor<Element>> expected) {
  auto list = reader.template as<DynamicList>();
  ASSERT_EQ(expected.size(), list.size());
  for (uint i = 0; i < expected.size(); i++) {
    EXPECT_EQ(expected.begin()[i], list[i].template as<Element>());
  }

  auto typed = reader.template as<List<Element>>();
  ASSERT_EQ(expected.size(), typed.size());
  for (uint i = 0; i < expected.size(); i++) {
    EXPECT_EQ(expected.begin()[i], typed[i]);
  }
}

TEST(DynamicApi, Build) {
  MallocMessageBuilder builder;
  auto root = builder.initRoot<DynamicStruct>(Schema::from<TestAllTypes>());

  initDynamicTestMessage(root);
  checkTestMessage(root.asReader().as<TestAllTypes>());

  checkDynamicTestMessage(root.asReader());
  checkDynamicTestMessage(root);
}

TEST(DynamicApi, Read) {
  MallocMessageBuilder builder;
  auto root = builder.initRoot<TestAllTypes>();

  initTestMessage(root);

  checkDynamicTestMessage(toDynamic(root.asReader()));
  checkDynamicTestMessage(toDynamic(root).asReader());
  checkDynamicTestMessage(toDynamic(root));
}

TEST(DynamicApi, Defaults) {
  AlignedData<1> nullRoot = {{0, 0, 0, 0, 0, 0, 0, 0}};
  kj::ArrayPtr<const word> segments[1] = {kj::arrayPtr(nullRoot.words, 1)};
  SegmentArrayMessageReader reader(kj::arrayPtr(segments, 1));
  auto root = reader.getRoot<DynamicStruct>(Schema::from<TestDefaults>());
  checkDynamicTestMessage(root);
}

TEST(DynamicApi, DefaultsBuilder) {
  MallocMessageBuilder builder;
  auto root = builder.initRoot<DynamicStruct>(Schema::from<TestDefaults>());

  checkTestMessage(root.asReader().as<TestDefaults>());
  checkDynamicTestMessage(root.asReader());

  // This will initialize the whole message, replacing null pointers with copies of defaults.
  checkDynamicTestMessage(root);

  // Check again now that the message is initialized.
  checkTestMessage(root.asReader().as<TestDefaults>());
  checkDynamicTestMessage(root.asReader());
  checkDynamicTestMessage(root);
}

TEST(DynamicApi, Zero) {
  MallocMessageBuilder builder;
  auto root = builder.initRoot<DynamicStruct>(Schema::from<TestAllTypes>());

  checkDynamicTestMessageAllZero(root.asReader());
  checkTestMessageAllZero(root.asReader().as<TestAllTypes>());
  checkDynamicTestMessageAllZero(root);
  checkTestMessageAllZero(root.asReader().as<TestAllTypes>());
}

TEST(DynamicApi, ListListsBuild) {
  MallocMessageBuilder builder;
  auto root = builder.initRoot<DynamicStruct>(Schema::from<TestListDefaults>());

  initDynamicTestLists(root);
  checkTestMessage(root.asReader().as<TestListDefaults>());

  checkDynamicTestLists(root.asReader());
  checkDynamicTestLists(root);
}

TEST(DynamicApi, ListListsRead) {
  MallocMessageBuilder builder;
  auto root = builder.initRoot<TestListDefaults>();

  initTestMessage(root);

  checkDynamicTestLists(toDynamic(root.asReader()));
  checkDynamicTestLists(toDynamic(root).asReader());
  checkDynamicTestLists(toDynamic(root));
}

TEST(DynamicApi, GenericObjects) {
  MallocMessageBuilder builder;
  auto root = builder.getRoot<test::TestObject>();

  initDynamicTestMessage(root.initObjectField<DynamicStruct>(Schema::from<TestAllTypes>()));
  checkTestMessage(root.asReader().getObjectField<TestAllTypes>());

  checkDynamicTestMessage(
      root.asReader().getObjectField<DynamicStruct>(Schema::from<TestAllTypes>()));
  checkDynamicTestMessage(root.getObjectField<DynamicStruct>(Schema::from<TestAllTypes>()));

  {
    {
      auto list = root.initObjectField<DynamicList>(Schema::from<List<uint32_t>>(), 4);
      list.set(0, 123);
      list.set(1, 456);
      list.set(2, 789);
      list.set(3, 123456789);
    }

    {
      auto list = root.asReader().getObjectField<List<uint32_t>>();
      ASSERT_EQ(4u, list.size());
      EXPECT_EQ(123u, list[0]);
      EXPECT_EQ(456u, list[1]);
      EXPECT_EQ(789u, list[2]);
      EXPECT_EQ(123456789u, list[3]);
    }

    checkList<uint32_t>(root.asReader().getObjectField<DynamicList>(Schema::from<List<uint32_t>>()),
                        {123u, 456u, 789u, 123456789u});
    checkList<uint32_t>(root.getObjectField<DynamicList>(Schema::from<List<uint32_t>>()),
                        {123u, 456u, 789u, 123456789u});
  }
}

TEST(DynamicApi, DynamicGenericObjects) {
  MallocMessageBuilder builder;
  auto root = builder.getRoot<DynamicStruct>(Schema::from<test::TestObject>());

  initDynamicTestMessage(root.initObject("objectField", Schema::from<TestAllTypes>()));
  checkTestMessage(root.asReader().as<test::TestObject>().getObjectField<TestAllTypes>());

  checkDynamicTestMessage(
      root.asReader().get("objectField").as<DynamicObject>().as(Schema::from<TestAllTypes>()));
  checkDynamicTestMessage(
      root.get("objectField").as<DynamicObject>().as(Schema::from<TestAllTypes>()));
  checkDynamicTestMessage(
      root.getObject("objectField", Schema::from<TestAllTypes>()));

  {
    {
      auto list = root.initObject("objectField", Schema::from<List<uint32_t>>(), 4);
      list.set(0, 123);
      list.set(1, 456);
      list.set(2, 789);
      list.set(3, 123456789);
    }

    {
      auto list = root.asReader().as<test::TestObject>().getObjectField<List<uint32_t>>();
      ASSERT_EQ(4u, list.size());
      EXPECT_EQ(123u, list[0]);
      EXPECT_EQ(456u, list[1]);
      EXPECT_EQ(789u, list[2]);
      EXPECT_EQ(123456789u, list[3]);
    }

    checkList<uint32_t>(
        root.asReader().get("objectField").as<DynamicObject>().as(Schema::from<List<uint32_t>>()),
        {123u, 456u, 789u, 123456789u});
    checkList<uint32_t>(
        root.get("objectField").as<DynamicObject>().as(Schema::from<List<uint32_t>>()),
        {123u, 456u, 789u, 123456789u});
    checkList<uint32_t>(
        root.getObject("objectField", Schema::from<List<uint32_t>>()),
        {123u, 456u, 789u, 123456789u});
  }
}

#define EXPECT_MAYBE_EQ(name, exp, expected, actual) \
  KJ_IF_MAYBE(name, exp) { \
    EXPECT_EQ(expected, actual); \
  } else { \
    FAIL() << "Maybe was empty."; \
  }

TEST(DynamicApi, UnionsRead) {
  MallocMessageBuilder builder;
  auto root = builder.initRoot<TestUnion>();

  root.getUnion0().setU0f1s32(1234567);
  root.getUnion1().setU1f1sp("foo");
  root.getUnion2().setU2f0s1(true);
  root.getUnion3().setU3f0s64(1234567890123456789ll);

  {
    auto dynamic = toDynamic(root.asReader());
    {
      auto u = dynamic.get("union0").as<DynamicUnion>();
      EXPECT_MAYBE_EQ(w, u.which(), "u0f1s32", w->getProto().getName());
      EXPECT_EQ(1234567, u.get().as<int32_t>());
    }
    {
      auto u = dynamic.get("union1").as<DynamicUnion>();
      EXPECT_MAYBE_EQ(w, u.which(), "u1f1sp", w->getProto().getName());
      EXPECT_EQ("foo", u.get().as<Text>());
    }
    {
      auto u = dynamic.get("union2").as<DynamicUnion>();
      EXPECT_MAYBE_EQ(w, u.which(), "u2f0s1", w->getProto().getName());
      EXPECT_TRUE(u.get().as<bool>());
    }
    {
      auto u = dynamic.get("union3").as<DynamicUnion>();
      EXPECT_MAYBE_EQ(w, u.which(), "u3f0s64", w->getProto().getName());
      EXPECT_EQ(1234567890123456789ll, u.get().as<int64_t>());
    }
  }

  {
    // Again as a builder.
    auto dynamic = toDynamic(root);
    {
      auto u = dynamic.get("union0").as<DynamicUnion>();
      EXPECT_MAYBE_EQ(w, u.which(), "u0f1s32", w->getProto().getName());
      EXPECT_EQ(1234567, u.get().as<int32_t>());
    }
    {
      auto u = dynamic.get("union1").as<DynamicUnion>();
      EXPECT_MAYBE_EQ(w, u.which(), "u1f1sp", w->getProto().getName());
      EXPECT_EQ("foo", u.get().as<Text>());
    }
    {
      auto u = dynamic.get("union2").as<DynamicUnion>();
      EXPECT_MAYBE_EQ(w, u.which(), "u2f0s1", w->getProto().getName());
      EXPECT_TRUE(u.get().as<bool>());
    }
    {
      auto u = dynamic.get("union3").as<DynamicUnion>();
      EXPECT_MAYBE_EQ(w, u.which(), "u3f0s64", w->getProto().getName());
      EXPECT_EQ(1234567890123456789ll, u.get().as<int64_t>());
    }
  }
}

TEST(DynamicApi, UnionsWrite) {
  MallocMessageBuilder builder;
  auto root = builder.initRoot<DynamicStruct>(Schema::from<TestUnion>());

  root.get("union0").as<DynamicUnion>().set("u0f1s32", 1234567);
  root.get("union1").as<DynamicUnion>().set("u1f1sp", "foo");
  root.get("union2").as<DynamicUnion>().set("u2f0s1", true);
  root.get("union3").as<DynamicUnion>().set("u3f0s64", 1234567890123456789ll);

  auto reader = root.asReader().as<TestUnion>();
  ASSERT_EQ(TestUnion::Union0::U0F1S32, reader.getUnion0().which());
  EXPECT_EQ(1234567, reader.getUnion0().getU0f1s32());

  ASSERT_EQ(TestUnion::Union1::U1F1SP, reader.getUnion1().which());
  EXPECT_EQ("foo", reader.getUnion1().getU1f1sp());

  ASSERT_EQ(TestUnion::Union2::U2F0S1, reader.getUnion2().which());
  EXPECT_TRUE(reader.getUnion2().getU2f0s1());

  ASSERT_EQ(TestUnion::Union3::U3F0S64, reader.getUnion3().which());
  EXPECT_EQ(1234567890123456789ll, reader.getUnion3().getU3f0s64());
}

#if KJ_NO_EXCEPTIONS
#undef EXPECT_ANY_THROW
// All exceptions should be non-fatal, so when exceptions are disabled the code should return.
#define EXPECT_ANY_THROW(code) code
#endif

TEST(DynamicApi, ConversionFailures) {
  MallocMessageBuilder builder;
  auto root = builder.initRoot<DynamicStruct>(Schema::from<TestAllTypes>());

  root.set("int8Field", 123);
  EXPECT_ANY_THROW(root.set("int8Field", 1234));

  root.set("uInt32Field", 1);
  EXPECT_ANY_THROW(root.set("uInt32Field", -1));

  root.set("int16Field", 5);
  EXPECT_ANY_THROW(root.set("int16Field", 0.5));

  root.set("boolField", true);
  EXPECT_ANY_THROW(root.set("boolField", 1));
}

TEST(DynamicApi, LateUnion) {
  MallocMessageBuilder builder;
  auto root = builder.initRoot<DynamicStruct>(Schema::from<test::TestLateUnion>());

  root.get("theUnion").as<DynamicUnion>().set("qux", "hello");
  EXPECT_EQ("hello", root.as<test::TestLateUnion>().getTheUnion().getQux());
}

TEST(DynamicApi, Has) {
  MallocMessageBuilder builder;
  auto root = builder.initRoot<DynamicStruct>(Schema::from<TestDefaults>());

  EXPECT_FALSE(root.has("int32Field"));
  root.set("int32Field", 123);
  EXPECT_TRUE(root.has("int32Field"));
  root.set("int32Field", -12345678);
  EXPECT_FALSE(root.has("int32Field"));

  EXPECT_FALSE(root.has("structField"));
  root.init("structField");
  EXPECT_TRUE(root.has("structField"));
}

TEST(DynamicApi, HasWhenEmpty) {
  AlignedData<1> nullRoot = {{0, 0, 0, 0, 0, 0, 0, 0}};
  kj::ArrayPtr<const word> segments[1] = {kj::arrayPtr(nullRoot.words, 1)};
  SegmentArrayMessageReader reader(kj::arrayPtr(segments, 1));
  auto root = reader.getRoot<DynamicStruct>(Schema::from<TestDefaults>());

  EXPECT_FALSE(root.has("voidField"));
  EXPECT_FALSE(root.has("int32Field"));
  EXPECT_FALSE(root.has("structField"));
  EXPECT_FALSE(root.has("int32List"));
}

TEST(DynamicApi, SetEnumFromNative) {
  MallocMessageBuilder builder;
  auto root = builder.initRoot<DynamicStruct>(Schema::from<TestAllTypes>());

  root.set("enumField", TestEnum::BAZ);
  root.set("enumList", {TestEnum::BAR, TestEnum::FOO});
  EXPECT_EQ(TestEnum::BAZ, root.get("enumField").as<TestEnum>());
  checkList<TestEnum>(root.get("enumList"), {TestEnum::BAR, TestEnum::FOO});
}

}  // namespace
}  // namespace _ (private)
}  // namespace capnp