// Copyright 2022 The Pigweed Authors
//
// Licensed 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
//
//     https://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 "pw_log_rpc_private/test_utils.h"

#include <cstdint>

#include "pw_bytes/span.h"
#include "pw_containers/vector.h"
#include "pw_log/log.h"
#include "pw_log/proto/log.pwpb.h"
#include "pw_log_tokenized/metadata.h"
#include "pw_protobuf/bytes_utils.h"
#include "pw_protobuf/decoder.h"
#include "pw_unit_test/framework.h"

namespace pw::log_rpc {
namespace {
void VerifyOptionallyTokenizedField(protobuf::Decoder& entry_decoder,
                                    log::pwpb::LogEntry::Fields field_number,
                                    ConstByteSpan expected_data) {
  if (expected_data.empty()) {
    return;
  }
  ConstByteSpan tokenized_data;
  ASSERT_EQ(entry_decoder.Next(), OkStatus());
  ASSERT_EQ(entry_decoder.FieldNumber(), static_cast<uint32_t>(field_number));
  ASSERT_EQ(entry_decoder.ReadBytes(&tokenized_data), OkStatus());
  std::string_view data_as_string(
      reinterpret_cast<const char*>(tokenized_data.data()),
      tokenized_data.size());
  std::string_view expected_data_as_string(
      reinterpret_cast<const char*>(expected_data.data()),
      expected_data.size());
  EXPECT_EQ(data_as_string, expected_data_as_string);
}
}  // namespace

// Unpacks a `LogEntry` proto buffer to compare it with the expected data and
// updates the total drop count found.
void VerifyLogEntry(protobuf::Decoder& entry_decoder,
                    const TestLogEntry& expected_entry,
                    uint32_t& drop_count_out) {
  VerifyOptionallyTokenizedField(entry_decoder,
                                 log::pwpb::LogEntry::Fields::kMessage,
                                 expected_entry.tokenized_data);
  if (expected_entry.metadata.level()) {
    ASSERT_EQ(entry_decoder.Next(), OkStatus());
    ASSERT_EQ(entry_decoder.FieldNumber(),
              static_cast<uint32_t>(log::pwpb::LogEntry::Fields::kLineLevel));
    uint32_t line_level;
    ASSERT_TRUE(entry_decoder.ReadUint32(&line_level).ok());
    EXPECT_EQ(expected_entry.metadata.level(),
              line_level & PW_LOG_LEVEL_BITMASK);
    EXPECT_EQ(expected_entry.metadata.line_number(),
              (line_level & ~PW_LOG_LEVEL_BITMASK) >> PW_LOG_LEVEL_BITS);
  }
  if (expected_entry.metadata.flags()) {
    ASSERT_EQ(entry_decoder.Next(), OkStatus());
    ASSERT_EQ(entry_decoder.FieldNumber(),
              static_cast<uint32_t>(log::pwpb::LogEntry::Fields::kFlags));
    uint32_t flags;
    ASSERT_TRUE(entry_decoder.ReadUint32(&flags).ok());
    EXPECT_EQ(expected_entry.metadata.flags(), flags);
  }
  if (expected_entry.timestamp) {
    ASSERT_EQ(entry_decoder.Next(), OkStatus());
    ASSERT_TRUE(
        entry_decoder.FieldNumber() ==
            static_cast<uint32_t>(log::pwpb::LogEntry::Fields::kTimestamp) ||
        entry_decoder.FieldNumber() ==
            static_cast<uint32_t>(
                log::pwpb::LogEntry::Fields::kTimeSinceLastEntry));
    int64_t timestamp;
    ASSERT_TRUE(entry_decoder.ReadInt64(&timestamp).ok());
    EXPECT_EQ(expected_entry.timestamp, timestamp);
  }
  if (expected_entry.dropped) {
    ASSERT_EQ(entry_decoder.Next(), OkStatus());
    ASSERT_EQ(entry_decoder.FieldNumber(),
              static_cast<uint32_t>(log::pwpb::LogEntry::Fields::kDropped));
    uint32_t dropped = 0;
    ASSERT_TRUE(entry_decoder.ReadUint32(&dropped).ok());
    EXPECT_EQ(expected_entry.dropped, dropped);
    drop_count_out += dropped;
  }
  if (expected_entry.metadata.module()) {
    ASSERT_EQ(entry_decoder.Next(), OkStatus());
    ASSERT_EQ(entry_decoder.FieldNumber(),
              static_cast<uint32_t>(log::pwpb::LogEntry::Fields::kModule));
    const Result<uint32_t> module =
        protobuf::DecodeBytesToUint32(entry_decoder);
    ASSERT_EQ(module.status(), OkStatus());
    EXPECT_EQ(expected_entry.metadata.module(), module.value());
  }
  VerifyOptionallyTokenizedField(
      entry_decoder, log::pwpb::LogEntry::Fields::kFile, expected_entry.file);
  VerifyOptionallyTokenizedField(entry_decoder,
                                 log::pwpb::LogEntry::Fields::kThread,
                                 expected_entry.thread);
}

// Compares an encoded LogEntry's fields against the expected sequence ID and
// LogEntries, and updates the total entry and drop counts. Starts comparing at
// `expected_entries[entries_count_out]`. `expected_entries` must be in the same
// order that messages were added to the MultiSink.
void VerifyLogEntries(protobuf::Decoder& entries_decoder,
                      const Vector<TestLogEntry>& expected_entries,
                      uint32_t expected_first_entry_sequence_id,
                      size_t& entries_count_out,
                      uint32_t& drop_count_out) {
  size_t entry_index = entries_count_out;
  while (entries_decoder.Next().ok()) {
    if (static_cast<log::pwpb::LogEntries::Fields>(
            entries_decoder.FieldNumber()) ==
        log::pwpb::LogEntries::Fields::kEntries) {
      ConstByteSpan entry;
      EXPECT_EQ(entries_decoder.ReadBytes(&entry), OkStatus());
      protobuf::Decoder entry_decoder(entry);
      if (expected_entries.empty()) {
        break;
      }

      ASSERT_LT(entry_index, expected_entries.size());

      // Keep track of entries and drops respective counts.
      uint32_t current_drop_count = 0;
      VerifyLogEntry(
          entry_decoder, expected_entries[entry_index], current_drop_count);
      ++entry_index;
      drop_count_out += current_drop_count;
      if (current_drop_count == 0) {
        ++entries_count_out;
      }
    } else if (static_cast<log::pwpb::LogEntries::Fields>(
                   entries_decoder.FieldNumber()) ==
               log::pwpb::LogEntries::Fields::kFirstEntrySequenceId) {
      uint32_t first_entry_sequence_id = 0;
      EXPECT_EQ(entries_decoder.ReadUint32(&first_entry_sequence_id),
                OkStatus());
      EXPECT_EQ(expected_first_entry_sequence_id, first_entry_sequence_id);
    }
  }
}

size_t CountLogEntries(protobuf::Decoder& entries_decoder) {
  size_t entries_found = 0;
  while (entries_decoder.Next().ok()) {
    if (static_cast<log::pwpb::LogEntries::Fields>(
            entries_decoder.FieldNumber()) ==
        log::pwpb::LogEntries::Fields::kEntries) {
      ++entries_found;
    }
  }
  return entries_found;
}

}  // namespace pw::log_rpc
