/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 * Contains some contributions under the Thrift Software License.
 * Please see doc/old-thrift-license.txt in the Thrift distribution for
 * details.
 */

#include <map>
#include <thrift/protocol/TDebugProtocol.h>
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/transport/TBufferTransports.h>
#include "gen-cpp/OptionalRequiredTest_types.h"

#define BOOST_TEST_MODULE OptionalRequiredTest
#include <boost/test/unit_test.hpp>

using namespace thrift::test;
using namespace apache::thrift;
using namespace apache::thrift::transport;
using namespace apache::thrift::protocol;

/*
template<typename Struct>
void trywrite(const Struct& s, bool should_work) {
  bool worked;
  try {
    TBinaryProtocol protocol(std::shared_ptr<TTransport>(new TMemoryBuffer));
    s.write(&protocol);
    worked = true;
  } catch (TProtocolException & ex) {
    worked = false;
  }
  BOOST_CHECK(worked == should_work);
}
*/

template <typename Struct1, typename Struct2>
void write_to_read(const Struct1& w, Struct2& r) {
  TBinaryProtocol protocol(std::shared_ptr<TTransport>(new TMemoryBuffer));
  w.write(&protocol);
  r.read(&protocol);
}

BOOST_AUTO_TEST_CASE(test_optional_required_1) {
  OldSchool o;

  const std::string expected_result(
    "OldSchool {\n"
    "  01: im_int (i16) = 0,\n"
    "  02: im_str (string) = \"\",\n"
    "  03: im_big (list) = list<map>[0] {\n"
    "  },\n"
    "}");
  const std::string result(apache::thrift::ThriftDebugString(o));

  BOOST_CHECK_MESSAGE(!expected_result.compare(result),
    "Expected:\n" << expected_result << "\nGotten:\n" << result);
}

BOOST_AUTO_TEST_CASE(test_optional_required_2_1) {
  Simple s;

  const std::string expected_result(
    "Simple {\n"
    "  01: im_default (i16) = 0,\n"
    "  02: im_required (i16) = 0,\n"
    "}");
  const std::string result(apache::thrift::ThriftDebugString(s));

  BOOST_CHECK_MESSAGE(!expected_result.compare(result),
    "Expected:\n" << expected_result << "\nGotten:\n" << result);
}

BOOST_AUTO_TEST_CASE(test_optional_required_2_2) {
  Simple s;
  s.im_optional = 10;

  const std::string expected_result(
    "Simple {\n"
    "  01: im_default (i16) = 0,\n"
    "  02: im_required (i16) = 0,\n"
    "}");
  const std::string result(apache::thrift::ThriftDebugString(s));

  BOOST_CHECK_MESSAGE(!expected_result.compare(result),
    "Expected:\n" << expected_result << "\nGotten:\n" << result);
}

BOOST_AUTO_TEST_CASE(test_optional_required_2_3) {
  Simple s;
  s.im_optional = 10;
  s.__isset.im_optional = true;

  const std::string expected_result(
    "Simple {\n"
    "  01: im_default (i16) = 0,\n"
    "  02: im_required (i16) = 0,\n"
    "  03: im_optional (i16) = 10,\n"
    "}");
  const std::string result(apache::thrift::ThriftDebugString(s));

  BOOST_CHECK_MESSAGE(!expected_result.compare(result),
    "Expected:\n" << expected_result << "\nGotten:\n" << result);
}

BOOST_AUTO_TEST_CASE(test_optional_required_2_4) {
  Simple s;
  s.__isset.im_optional = true;

  const std::string expected_result(
    "Simple {\n"
    "  01: im_default (i16) = 0,\n"
    "  02: im_required (i16) = 0,\n"
    "  03: im_optional (i16) = 0,\n"
    "}");
  const std::string result(apache::thrift::ThriftDebugString(s));

  BOOST_CHECK_MESSAGE(!expected_result.compare(result),
    "Expected:\n" << expected_result << "\nGotten:\n" << result);
}

BOOST_AUTO_TEST_CASE(test_optional_required_2_5) {
  Simple s;
  s.__isset.im_optional = true;
  s.im_optional = 10;

  const std::string expected_result(
    "Simple {\n"
    "  01: im_default (i16) = 0,\n"
    "  02: im_required (i16) = 0,\n"
    "  03: im_optional (i16) = 10,\n"
    "}");
  const std::string result(apache::thrift::ThriftDebugString(s));

  BOOST_CHECK_MESSAGE(!expected_result.compare(result),
    "Expected:\n" << expected_result << "\nGotten:\n" << result);
}

BOOST_AUTO_TEST_CASE(test_optional_required_3) {
  // assign/copy-construct with non-required fields

  Simple s1, s2;
  s1.__isset.im_default = true;
  s1.__set_im_optional(10);
  BOOST_CHECK(s1.__isset.im_default);
  BOOST_CHECK(s1.__isset.im_optional);

  s2 = s1;

  BOOST_CHECK(s2.__isset.im_default);
  BOOST_CHECK(s2.__isset.im_optional);

  Simple s3(s1);

  BOOST_CHECK(s3.__isset.im_default);
  BOOST_CHECK(s3.__isset.im_optional);
}

BOOST_AUTO_TEST_CASE(test_optional_required_4) {
  // Write-to-read with optional fields.

  Simple s1, s2, s3;
  s1.im_optional = 10;
  BOOST_CHECK(!s1.__isset.im_default);
  // BOOST_CHECK(!s1.__isset.im_required);  // Compile error.
  BOOST_CHECK(!s1.__isset.im_optional);

  write_to_read(s1, s2);

  BOOST_CHECK(s2.__isset.im_default);
  // BOOST_CHECK( s2.__isset.im_required);  // Compile error.
  BOOST_CHECK(!s2.__isset.im_optional);
  BOOST_CHECK(s3.im_optional == 0);

  s1.__isset.im_optional = true;
  write_to_read(s1, s3);

  BOOST_CHECK(s3.__isset.im_default);
  // BOOST_CHECK( s3.__isset.im_required);  // Compile error.
  BOOST_CHECK(s3.__isset.im_optional);
  BOOST_CHECK(s3.im_optional == 10);
}

BOOST_AUTO_TEST_CASE(test_optional_required_5) {
  // Writing between optional and default.

  Tricky1 t1;
  Tricky2 t2;

  t2.im_optional = 10;
  write_to_read(t2, t1);
  write_to_read(t1, t2);
  BOOST_CHECK(!t1.__isset.im_default);
  BOOST_CHECK(t2.__isset.im_optional);
  BOOST_CHECK(t1.im_default == t2.im_optional);
  BOOST_CHECK(t1.im_default == 0);
}

BOOST_AUTO_TEST_CASE(test_optional_required_6) {
  // Writing between default and required.

  Tricky1 t1;
  Tricky3 t3;
  write_to_read(t1, t3);
  write_to_read(t3, t1);
  BOOST_CHECK(t1.__isset.im_default);
}

BOOST_AUTO_TEST_CASE(test_optional_required_7) {
  // Writing between optional and required.

  Tricky2 t2;
  Tricky3 t3;
  t2.__isset.im_optional = true;
  write_to_read(t2, t3);
  write_to_read(t3, t2);
}

BOOST_AUTO_TEST_CASE(test_optional_required_8) {
  // Mu-hu-ha-ha-ha!

  Tricky2 t2;
  Tricky3 t3;
  try {
    write_to_read(t2, t3);
    abort();
  } catch (const TProtocolException&) {
  }

  write_to_read(t3, t2);
  BOOST_CHECK(t2.__isset.im_optional);
}

BOOST_AUTO_TEST_CASE(test_optional_required_9) {
  Complex c;

  const std::string expected_result(
    "Complex {\n"
    "  01: cp_default (i16) = 0,\n"
    "  02: cp_required (i16) = 0,\n"
    "  04: the_map (map) = map<i16,struct>[0] {\n"
    "  },\n"
    "  05: req_simp (struct) = Simple {\n"
    "    01: im_default (i16) = 0,\n"
    "    02: im_required (i16) = 0,\n"
    "  },\n"
    "}");
  const std::string result(apache::thrift::ThriftDebugString(c));

  BOOST_CHECK_MESSAGE(!expected_result.compare(result),
    "Expected:\n" << expected_result << "\nGotten:\n" << result);
}

BOOST_AUTO_TEST_CASE(test_optional_required_10) {
  Tricky1 t1;
  Tricky2 t2;
  // Compile error.
  //(void)(t1 == t2);
}

BOOST_AUTO_TEST_CASE(test_optional_required_11) {
  OldSchool o1, o2, o3;
  BOOST_CHECK(o1 == o2);
  o1.im_int = o2.im_int = 10;
  BOOST_CHECK(o1 == o2);
  o1.__isset.im_int = true;
  o2.__isset.im_int = false;
  BOOST_CHECK(o1 == o2);
  o1.im_int = 20;
  o1.__isset.im_int = false;
  BOOST_CHECK(o1 != o2);
  o1.im_int = 10;
  BOOST_CHECK(o1 == o2);
  o1.im_str = o2.im_str = "foo";
  BOOST_CHECK(o1 == o2);
  o1.__isset.im_str = o2.__isset.im_str = true;
  BOOST_CHECK(o1 == o2);
  std::map<int32_t, std::string> mymap;
  mymap[1] = "bar";
  mymap[2] = "baz";
  o1.im_big.push_back(std::map<int32_t, std::string>());
  BOOST_CHECK(o1 != o2);
  o2.im_big.push_back(std::map<int32_t, std::string>());
  BOOST_CHECK(o1 == o2);
  o2.im_big.push_back(mymap);
  BOOST_CHECK(o1 != o2);
  o1.im_big.push_back(mymap);
  BOOST_CHECK(o1 == o2);

  TBinaryProtocol protocol(std::shared_ptr<TTransport>(new TMemoryBuffer));
  o1.write(&protocol);

  o1.im_big.push_back(mymap);
  mymap[3] = "qux";
  o2.im_big.push_back(mymap);
  BOOST_CHECK(o1 != o2);
  o1.im_big.back()[3] = "qux";
  BOOST_CHECK(o1 == o2);
  
  o3.read(&protocol);
  o3.im_big.push_back(mymap);
  BOOST_CHECK(o1 == o3);

  const std::string expected_result(
    "OldSchool {\n"
    "  01: im_int (i16) = 10,\n"
    "  02: im_str (string) = \"foo\",\n"
    "  03: im_big (list) = list<map>[3] {\n"
    "    [0] = map<i32,string>[0] {\n"
    "    },\n"
    "    [1] = map<i32,string>[2] {\n"
    "      1 -> \"bar\",\n"
    "      2 -> \"baz\",\n"
    "    },\n"
    "    [2] = map<i32,string>[3] {\n"
    "      1 -> \"bar\",\n"
    "      2 -> \"baz\",\n"
    "      3 -> \"qux\",\n"
    "    },\n"
    "  },\n"
    "}");
  const std::string result(apache::thrift::ThriftDebugString(o3));

  BOOST_CHECK_MESSAGE(!expected_result.compare(result),
    "Expected:\n" << expected_result << "\nGotten:\n" << result);
}

BOOST_AUTO_TEST_CASE(test_optional_required_12) {
  Tricky2 t1, t2;
  BOOST_CHECK(t1.__isset.im_optional == false);
  BOOST_CHECK(t2.__isset.im_optional == false);
  BOOST_CHECK(t1 == t2);
  t1.im_optional = 5;
  BOOST_CHECK(t1 == t2);
  t2.im_optional = 5;
  BOOST_CHECK(t1 == t2);
  t1.__isset.im_optional = true;
  BOOST_CHECK(t1 != t2);
  t2.__isset.im_optional = true;
  BOOST_CHECK(t1 == t2);
  t1.im_optional = 10;
  BOOST_CHECK(t1 != t2);
  t2.__isset.im_optional = false;
  BOOST_CHECK(t1 != t2);
}

BOOST_AUTO_TEST_CASE(test_optional_required_13) {
  OptionalDefault t1, t2;

  BOOST_CHECK(t1.__isset.opt_int == true);
  BOOST_CHECK(t1.__isset.opt_str == true);
  BOOST_CHECK(t1.opt_int == t2.opt_int);
  BOOST_CHECK(t1.opt_str == t2.opt_str);

  write_to_read(t1, t2);
  BOOST_CHECK(t2.__isset.opt_int == true);
  BOOST_CHECK(t2.__isset.opt_str == true);
  BOOST_CHECK(t1.opt_int == t2.opt_int);
  BOOST_CHECK(t1.opt_str == t2.opt_str);

  const std::string expected_result(
    "OptionalDefault {\n"
    "  01: opt_int (i16) = 1234,\n"
    "  02: opt_str (string) = \"default\",\n"
    "}");
  const std::string result(apache::thrift::ThriftDebugString(t2));

  BOOST_CHECK_MESSAGE(!expected_result.compare(result),
    "Expected:\n" << expected_result << "\nGotten:\n" << result);
}
