/*
 * Copyright 2019 Google
 *
 * 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.
 */

#include "Firestore/Protos/nanopb/firestore/local/maybe_document.nanopb.h"
#include "Firestore/Protos/nanopb/firestore/local/mutation.nanopb.h"
#include "Firestore/Protos/nanopb/firestore/local/target.nanopb.h"
#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h"
#include "Firestore/Protos/nanopb/google/firestore/v1/firestore.nanopb.h"
#include "Firestore/Protos/nanopb/google/firestore/v1/write.nanopb.h"
#include "Firestore/core/src/nanopb/message.h"
#include "Firestore/core/src/nanopb/nanopb_util.h"
#include "absl/strings/str_cat.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"

namespace firebase {
namespace firestore {
namespace nanopb {
namespace {

using ::testing::MatchesRegex;

// Checks that the printed form of the given argument is of the expected form.
// The printed form is generated by calling `ToString` on the argument. The
// expected form is the following regex (assume that the parameters given to
// this macro are substituted for the placeholders below):
//
// <$(proto_name) 0x[0-9A-Fa-f]+>: {$(contents)}"
//
// The printed form of a message includes its address; the regex takes care of
// that variating value, as well as avoids the need to explicitly provide the
// header and tail of the printed form. Note, however, that the regex does not
// contain any newlines; the appropriate newlines must be included in
// `contents`.
MATCHER_P2(IsPrintedAs, proto_name, contents, "") {
#if GTEST_USES_POSIX_RE
  std::string hex = "[0-9A-Fa-f]+";
#else
  // On Windows, validate more loosely because GoogleTest doesn't support
  // matching with brackets.
  std::string hex = "\\w+";
#endif

  // NOLINTNEXTLINE(whitespace/braces)
  std::string header = absl::StrCat("<", proto_name, " 0x", hex, ">: \\{");
  std::string expected = absl::StrCat(header, contents, "\\}");
  return testing::Value(arg, MatchesRegex(expected));
}

TEST(PrettyPrintingTest, PrintsInt) {
  Message<firestore_client_WriteBatch> m;
  m->batch_id = 123;

  EXPECT_THAT(m.ToString(), IsPrintedAs("WriteBatch", R"(
  batch_id: 123
)"));
}

TEST(PrettyPrintingTest, PrintsBool) {
  Message<firestore_client_MaybeDocument> m;
  m->has_committed_mutations = true;

  EXPECT_THAT(m.ToString(), IsPrintedAs("MaybeDocument", R"(
  has_committed_mutations: true
)"));
}

TEST(PrettyPrintingTest, PrintsString) {
  Message<firestore_client_MutationQueue> m;
  m->last_stream_token = MakeBytesArray("Abc123");

  EXPECT_THAT(m.ToString(), IsPrintedAs("MutationQueue", R"(
  last_stream_token: "Abc123"
)"));
}

TEST(PrettyPrintingTest, PrintsBytes) {
  Message<firestore_client_MutationQueue> m;
  m->last_stream_token = MakeBytesArray("\001\002\003");

  EXPECT_THAT(m.ToString(), IsPrintedAs("MutationQueue", R"(
  last_stream_token: "\\001\\002\\003"
)"));
}

TEST(PrettyPrintingTest, PrintsEnums) {
  Message<google_firestore_v1_TargetChange> m;
  m->target_change_type =
      google_firestore_v1_TargetChange_TargetChangeType_CURRENT;

  EXPECT_THAT(m.ToString(), IsPrintedAs("TargetChange", R"(
  target_change_type: CURRENT
)"));
}

TEST(PrettyPrintingTest, PrintsSubmessages) {
  Message<firestore_client_Target> m;
  m->snapshot_version.seconds = 123;
  m->snapshot_version.nanos = 456;

  EXPECT_THAT(m.ToString(), IsPrintedAs("Target", R"(
  snapshot_version \{
    seconds: 123
    nanos: 456
  \}
)"));
}

TEST(PrettyPrintingTest, PrintsArraysOfPrimitives) {
  Message<google_firestore_v1_Target_DocumentsTarget> m;

  m->documents_count = 2;
  m->documents = MakeArray<pb_bytes_array_t*>(m->documents_count);
  m->documents[0] = MakeBytesArray("doc1");
  m->documents[1] = MakeBytesArray("doc2");

  EXPECT_THAT(m.ToString(), IsPrintedAs("DocumentsTarget", R"(
  documents: "doc1"
  documents: "doc2"
)"));
}

TEST(PrettyPrintingTest, PrintsArraysOfObjects) {
  Message<google_firestore_v1_ListenRequest> m;

  m->labels_count = 2;
  m->labels =
      MakeArray<google_firestore_v1_ListenRequest_LabelsEntry>(m->labels_count);

  m->labels[0].key = MakeBytesArray("key1");
  m->labels[0].value = MakeBytesArray("value1");
  m->labels[1].key = MakeBytesArray("key2");
  m->labels[1].value = MakeBytesArray("value2");

  EXPECT_THAT(m.ToString(), IsPrintedAs("ListenRequest", R"(
  labels \{
    key: "key1"
    value: "value1"
  \}
  labels \{
    key: "key2"
    value: "value2"
  \}
)"));
}

TEST(PrettyPrintingTest, PrintsPrimitivesInOneofs) {
  Message<google_firestore_v1_Write> m;
  m->which_operation = google_firestore_v1_Write_delete_tag;
  // Also checks for the special case with `delete` being a keyword in C++.
  m->delete_ = MakeBytesArray("abc");

  EXPECT_THAT(m.ToString(), IsPrintedAs("Write", R"(
  delete: "abc"
)"));
}

TEST(PrettyPrintingTest, PrintsMessagesInOneofs) {
  // This test also exercises deeply-nested messages.
  Message<google_firestore_v1_Write> m;
  m->which_operation = google_firestore_v1_Write_update_tag;

  auto& doc = m->update;
  doc.name = MakeBytesArray("some name");

  doc.fields_count = 2;
  doc.fields =
      MakeArray<google_firestore_v1_Document_FieldsEntry>(doc.fields_count);

  // Also checks that even fields with default values are printed if they're the
  // active member of a oneof.
  doc.fields[0].key = MakeBytesArray("key1");
  doc.fields[0].value.which_value_type =
      google_firestore_v1_Value_boolean_value_tag;
  doc.fields[0].value.boolean_value = false;

  doc.fields[1].key = MakeBytesArray("key2");
  doc.fields[1].value.which_value_type =
      google_firestore_v1_Value_timestamp_value_tag;

  EXPECT_THAT(m.ToString(), IsPrintedAs("Write", R"(
  update \{
    name: "some name"
    fields \{
      key: "key1"
      value \{
        boolean_value: false
      \}
    \}
    fields \{
      key: "key2"
      value \{
        timestamp_value \{
        \}
      \}
    \}
  \}
)"));
}

TEST(PrettyPrintingTest, PrintsNonAnonymousOneofs) {
  Message<google_firestore_v1_RunQueryRequest> m;

  m->which_consistency_selector =
      google_firestore_v1_RunQueryRequest_read_time_tag;
  m->consistency_selector.read_time.seconds = 123;
  m->consistency_selector.read_time.nanos = 456;
  EXPECT_THAT(m.ToString(), IsPrintedAs("RunQueryRequest", R"(
  read_time \{
    seconds: 123
    nanos: 456
  \}
)"));
}

TEST(PrettyPrintingTest, PrintsOptionals) {
  Message<google_firestore_v1_Write> m;

  auto& mask = m->update_mask;
  mask.field_paths_count = 2;
  mask.field_paths = MakeArray<pb_bytes_array_t*>(mask.field_paths_count);
  mask.field_paths[0] = MakeBytesArray("abc");
  mask.field_paths[1] = MakeBytesArray("def");

  // `has_update_mask` is false, so `update_mask` shouldn't be printed.
  // Note that normally setting `update_mask` without setting `has_update_mask`
  // to true shouldn't happen.
  EXPECT_THAT(m.ToString(), IsPrintedAs("Write", "\n"));

  m->has_update_mask = true;
  EXPECT_THAT(m.ToString(), IsPrintedAs("Write", R"(
  update_mask \{
    field_paths: "abc"
    field_paths: "def"
  \}
)"));
}

TEST(PrettyPrintingTest, PrintsEmptyOptionals) {
  Message<google_firestore_v1_Write> m;
  m->has_update_mask = true;

  // When set, an optional submessage should always be printed, even if it's
  // "empty".
  EXPECT_THAT(m.ToString(), IsPrintedAs("Write", R"(
  update_mask \{
  \}
)"));
}

TEST(PrettyPrintingTest, PrintsEmptyArrayElements) {
  Message<google_firestore_v1_Target_DocumentsTarget> m;

  m->documents_count = 2;
  m->documents = MakeArray<pb_bytes_array_t*>(m->documents_count);
  m->documents[0] = MakeBytesArray("");
  m->documents[1] = MakeBytesArray("");

  EXPECT_THAT(m.ToString(), IsPrintedAs("DocumentsTarget", R"(
  documents: ""
  documents: ""
)"));
}

TEST(PrettyPrintingTest, PrintsEmptyMessageIfRoot) {
  Message<google_firestore_v1_Write> m;
  EXPECT_THAT(m.ToString(), IsPrintedAs("Write", "\n"));
}

}  //  namespace
}  //  namespace nanopb
}  //  namespace firestore
}  //  namespace firebase
