/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * 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
 *
 *      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 <gtest/gtest.h>

#include "BranchListFile.h"
#include "get_test_data.h"

using namespace simpleperf;

// @CddTest = 6.1/C-0-2
TEST(BranchListFile, etm_branch_to_proto_string) {
  std::vector<bool> branch;
  for (size_t i = 0; i < 100; i++) {
    branch.push_back(i % 2 == 0);
    std::string s = ETMBranchToProtoString(branch);
    for (size_t j = 0; j <= i; j++) {
      bool b = s[j >> 3] & (1 << (j & 7));
      ASSERT_EQ(b, branch[j]);
    }
    std::vector<bool> branch2 = ProtoStringToETMBranch(s, branch.size());
    ASSERT_EQ(branch, branch2);
  }
}

static bool IsETMDataEqual(ETMBinaryMap& data1, ETMBinaryMap& data2) {
  if (data1.size() != data2.size()) {
    return false;
  }
  for (const auto& [key, binary1] : data1) {
    auto it = data2.find(key);
    if (it == data2.end()) {
      return false;
    }
    ETMBinary& binary2 = it->second;
    if (binary1.dso_type != binary2.dso_type) {
      return false;
    }
    const UnorderedETMBranchMap& branch_map1 = binary1.branch_map;
    const UnorderedETMBranchMap& branch_map2 = binary2.branch_map;
    if (branch_map1.size() != branch_map2.size()) {
      return false;
    }
    for (const auto& [addr, b_map1] : branch_map1) {
      auto it2 = branch_map2.find(addr);
      if (it2 == branch_map2.end()) {
        return false;
      }
      const auto& b_map2 = it2->second;
      if (b_map1.size() != b_map2.size()) {
        return false;
      }
      for (const auto& [branch, count1] : b_map1) {
        auto it3 = b_map2.find(branch);
        if (it3 == b_map2.end()) {
          return false;
        }
        if (count1 != it3->second) {
          return false;
        }
      }
    }
  }
  return true;
}

static bool IsLBRDataEqual(const LBRData& data1, const LBRData& data2) {
  if (data1.samples.size() != data2.samples.size()) {
    return false;
  }
  for (size_t i = 0; i < data1.samples.size(); i++) {
    const LBRSample& sample1 = data1.samples[i];
    const LBRSample& sample2 = data2.samples[i];
    if (sample1.binary_id != sample2.binary_id) {
      return false;
    }
    if (sample1.vaddr_in_file != sample2.vaddr_in_file) {
      return false;
    }
    if (sample1.branches.size() != sample2.branches.size()) {
      return false;
    }
    for (size_t j = 0; j < sample1.branches.size(); j++) {
      const LBRBranch& b1 = sample1.branches[j];
      const LBRBranch& b2 = sample2.branches[j];
      if (b1.from_binary_id != b2.from_binary_id || b1.to_binary_id != b2.to_binary_id ||
          b1.from_vaddr_in_file != b2.from_vaddr_in_file ||
          b1.to_vaddr_in_file != b2.to_vaddr_in_file) {
        return false;
      }
    }
  }
  return data1.binaries == data2.binaries;
}

// @CddTest = 6.1/C-0-2
TEST(BranchListProtoReaderWriter, smoke) {
  ETMBinaryMap etm_data;
  ETMBinary& binary = etm_data[BinaryKey("fake_binary", BuildId())];
  binary.dso_type = DSO_ELF_FILE;
  UnorderedETMBranchMap& branch_map = binary.branch_map;
  for (size_t addr = 0; addr <= 100; addr++) {
    auto& b_map = branch_map[addr];
    size_t branch_count = addr;
    for (size_t i = 1; i <= branch_count; i++) {
      std::vector<bool> branch(i, false);
      for (size_t j = 0; j < branch.size(); j += 2) {
        branch[j] = true;
      }
      b_map[binary.GetBranch(branch)] = i;
    }
  }
  LBRData lbr_data;
  lbr_data.binaries.emplace_back(BinaryKey("binary1", BuildId()));
  lbr_data.binaries.emplace_back(BinaryKey("binary2", BuildId()));
  for (uint64_t from_addr = 0; from_addr <= 10; from_addr++) {
    for (uint64_t to_addr = 100; to_addr <= 110; to_addr++) {
      LBRBranch branch = {0, 1, from_addr, to_addr};
      LBRSample sample = {0, from_addr, {branch}};
      lbr_data.samples.emplace_back(sample);
    }
  }

  TemporaryFile tmpfile;
  close(tmpfile.fd);
  for (size_t max_branches_per_message : {100}) {
    // for (size_t max_branches_per_message : {100, 100000000}) {
    for (bool compress : {false, true}) {
      auto writer =
          BranchListProtoWriter::CreateForFile(tmpfile.path, compress, max_branches_per_message);
      ASSERT_TRUE(writer);
      ASSERT_TRUE(writer->Write(etm_data));
      ASSERT_TRUE(writer->Write(lbr_data));
      writer = nullptr;
      auto reader = BranchListProtoReader::CreateForFile(tmpfile.path);
      ASSERT_TRUE(reader);
      reader->CalculateMaxBranchesPerMessage();
      ETMBinaryMap new_etm_data;
      LBRData new_lbr_data;
      ASSERT_TRUE(reader->Read(new_etm_data, new_lbr_data));
      ASSERT_TRUE(IsETMDataEqual(etm_data, new_etm_data));
      ASSERT_TRUE(IsLBRDataEqual(lbr_data, new_lbr_data));
      ASSERT_LE(reader->GetMaxBranchesPerMessage(), max_branches_per_message);
    }
  }

  for (size_t max_branches_per_message : {100, 100000000}) {
    for (bool compress : {false, true}) {
      std::string s;
      auto writer = BranchListProtoWriter::CreateForString(&s, compress, max_branches_per_message);
      ASSERT_TRUE(writer);
      ASSERT_TRUE(writer->Write(etm_data));
      ASSERT_TRUE(writer->Write(lbr_data));
      writer = nullptr;
      auto reader = BranchListProtoReader::CreateForString(s);
      ASSERT_TRUE(reader);
      reader->CalculateMaxBranchesPerMessage();
      ETMBinaryMap new_etm_data;
      LBRData new_lbr_data;
      ASSERT_TRUE(reader->Read(new_etm_data, new_lbr_data));
      ASSERT_TRUE(IsETMDataEqual(etm_data, new_etm_data));
      ASSERT_TRUE(IsLBRDataEqual(lbr_data, new_lbr_data));
      ASSERT_LE(reader->GetMaxBranchesPerMessage(), max_branches_per_message);
    }
  }
}

// @CddTest = 6.1/C-0-2
TEST(BranchListProtoReaderWriter, read_old_branch_list_file) {
  std::string path = GetTestData("etm/old_branch_list.data");
  auto reader = BranchListProtoReader::CreateForFile(path);
  ASSERT_TRUE(reader);
  ETMBinaryMap etm_data;
  LBRData lbr_data;
  ASSERT_TRUE(reader->Read(etm_data, lbr_data));
  ASSERT_EQ(etm_data.size(), 1u);
}
