/*
 * Copyright (C) 2020 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 <stdio.h>

#include <memory>
#include <regex>
#include <string>

#include <android-base/macros.h>
#include <android-base/strings.h>

#include "command.h"
#include "event_attr.h"
#include "record_file.h"
#include "thread_tree.h"
#include "utils.h"

namespace simpleperf {
namespace {

class MergedFileFeature {
 public:
  MergedFileFeature(FileFeature& file)
      : path_(file.path),
        type_(file.type),
        min_vaddr_(file.min_vaddr),
        file_offset_of_min_vaddr_(file.file_offset_of_min_vaddr),
        dex_file_offsets_(std::move(file.dex_file_offsets)) {
    for (auto& symbol : file.symbols) {
      symbol_map_.emplace(symbol.addr, std::move(symbol));
    }
  }

  bool Merge(FileFeature& file) {
    if (file.type != type_ || file.min_vaddr != min_vaddr_ ||
        file.file_offset_of_min_vaddr != file_offset_of_min_vaddr_ ||
        file.dex_file_offsets != dex_file_offsets_) {
      return false;
    }
    for (auto& symbol : file.symbols) {
      auto it = symbol_map_.lower_bound(symbol.addr);
      if (it != symbol_map_.end()) {
        const auto& found = it->second;
        if (found.addr == symbol.addr && found.len == symbol.len &&
            strcmp(found.Name(), symbol.Name()) == 0) {
          // The symbol already exists in symbol_map.
          continue;
        }
        if (symbol.addr + symbol.len > found.addr) {
          // an address conflict with the next symbol
          return false;
        }
      }
      if (it != symbol_map_.begin()) {
        --it;
        if (it->second.addr + it->second.len > symbol.addr) {
          // an address conflict with the previous symbol
          return false;
        }
      }
      symbol_map_.emplace(symbol.addr, std::move(symbol));
    }
    return true;
  }

  void ToFileFeature(FileFeature* file) const {
    file->path = path_;
    file->type = type_;
    file->min_vaddr = min_vaddr_;
    file->file_offset_of_min_vaddr = file_offset_of_min_vaddr_;
    file->symbol_ptrs.clear();
    for (const auto& [_, symbol] : symbol_map_) {
      file->symbol_ptrs.emplace_back(&symbol);
    }
    file->dex_file_offsets = dex_file_offsets_;
  }

 private:
  std::string path_;
  DsoType type_;
  uint64_t min_vaddr_;
  uint64_t file_offset_of_min_vaddr_;
  std::map<uint64_t, Symbol> symbol_map_;
  std::vector<uint64_t> dex_file_offsets_;

  DISALLOW_COPY_AND_ASSIGN(MergedFileFeature);
};

class MergeCommand : public Command {
 public:
  MergeCommand()
      : Command("merge", "merge multiple perf.data into one",
                // clang-format off
"Usage: simpleperf merge [options]\n"
"       Merge multiple perf.data into one. The input files should be recorded on the same\n"
"       device using the same event types.\n"
"-i <file1>,<file2>,...       Input recording files separated by comma\n"
"-o <file>                    output recording file\n"
"\n"
"Examples:\n"
"$ simpleperf merge -i perf1.data,perf2.data -o perf.data\n"
                // clang-format on
        ) {}

  bool Run(const std::vector<std::string>& args) override {
    // 1. Parse options.
    if (!ParseOptions(args)) {
      return false;
    }

    // 2. Open input files and check if they are mergeable.
    for (const auto& file : input_files_) {
      readers_.emplace_back(RecordFileReader::CreateInstance(file));
      if (!readers_.back()) {
        return false;
      }
    }
    if (!IsMergeable()) {
      return false;
    }

    // 3. Merge files.
    writer_ = RecordFileWriter::CreateInstance(output_file_);
    if (!writer_) {
      return false;
    }
    if (!MergeAttrSection() || !MergeDataSection() || !MergeFeatureSection()) {
      return false;
    }
    return writer_->Close();
  }

 private:
  bool ParseOptions(const std::vector<std::string>& args) {
    const OptionFormatMap option_formats = {
        {"-i", {OptionValueType::STRING, OptionType::MULTIPLE}},
        {"-o", {OptionValueType::STRING, OptionType::SINGLE}},
    };
    OptionValueMap options;
    std::vector<std::pair<OptionName, OptionValue>> ordered_options;
    if (!PreprocessOptions(args, option_formats, &options, &ordered_options, nullptr)) {
      return false;
    }
    for (const OptionValue& value : options.PullValues("-i")) {
      auto files = android::base::Split(*value.str_value, ",");
      input_files_.insert(input_files_.end(), files.begin(), files.end());
    }
    options.PullStringValue("-o", &output_file_);

    CHECK(options.values.empty());

    if (input_files_.empty()) {
      LOG(ERROR) << "missing input files";
      return false;
    }
    if (output_file_.empty()) {
      LOG(ERROR) << "missing output file";
      return false;
    }
    return true;
  }

  bool IsMergeable() { return CheckFeatureSection() && CheckAttrSection(); }

  // Check feature sections to know if the recording environments are the same.
  bool CheckFeatureSection() {
    auto get_arch = [](std::unique_ptr<RecordFileReader>& reader) {
      return reader->ReadFeatureString(PerfFileFormat::FEAT_ARCH);
    };
    auto get_kernel_version = [](std::unique_ptr<RecordFileReader>& reader) {
      return reader->ReadFeatureString(PerfFileFormat::FEAT_OSRELEASE);
    };
    auto get_meta_info = [](std::unique_ptr<RecordFileReader>& reader, const char* key) {
      auto it = reader->GetMetaInfoFeature().find(key);
      return it == reader->GetMetaInfoFeature().end() ? "" : it->second;
    };
    auto get_simpleperf_version = [&](std::unique_ptr<RecordFileReader>& reader) {
      return get_meta_info(reader, "simpleperf_version");
    };
    auto get_trace_offcpu = [&](std::unique_ptr<RecordFileReader>& reader) {
      return get_meta_info(reader, "trace_offcpu");
    };
    auto get_event_types = [&](std::unique_ptr<RecordFileReader>& reader) {
      std::string s = get_meta_info(reader, "event_type_info");
      std::vector<std::string> v = android::base::Split(s, "\n");
      std::sort(v.begin(), v.end());
      return android::base::Join(v, ";");
    };
    auto get_android_device = [&](std::unique_ptr<RecordFileReader>& reader) {
      return get_meta_info(reader, "product_props");
    };
    auto get_android_version = [&](std::unique_ptr<RecordFileReader>& reader) {
      return get_meta_info(reader, "android_version");
    };
    auto get_app_package_name = [&](std::unique_ptr<RecordFileReader>& reader) {
      return get_meta_info(reader, "app_package_name");
    };
    auto get_clockid = [&](std::unique_ptr<RecordFileReader>& reader) {
      return get_meta_info(reader, "clockid");
    };
    auto get_used_features = [](std::unique_ptr<RecordFileReader>& reader) {
      std::string s;
      for (const auto& [key, _] : reader->FeatureSectionDescriptors()) {
        s += std::to_string(key) + ",";
      }
      return s;
    };

    using value_func_t = std::function<std::string(std::unique_ptr<RecordFileReader>&)>;
    std::vector<std::pair<std::string, value_func_t>> check_entries = {
        std::make_pair("arch", get_arch),
        std::make_pair("kernel_version", get_kernel_version),
        std::make_pair("simpleperf_version", get_simpleperf_version),
        std::make_pair("trace_offcpu", get_trace_offcpu),
        std::make_pair("event_types", get_event_types),
        std::make_pair("android_device", get_android_device),
        std::make_pair("android_version", get_android_version),
        std::make_pair("app_package_name", get_app_package_name),
        std::make_pair("clockid", get_clockid),
        std::make_pair("used_features", get_used_features),
    };

    for (const auto& [name, get_value] : check_entries) {
      std::string value0 = get_value(readers_[0]);
      for (size_t i = 1; i < readers_.size(); i++) {
        std::string value = get_value(readers_[i]);
        if (value != value0) {
          LOG(ERROR) << input_files_[0] << " and " << input_files_[i] << " are not mergeable for "
                     << name << " difference: " << value0 << " vs " << value;
          return false;
        }
      }
    }

    if (readers_[0]->HasFeature(PerfFileFormat::FEAT_AUXTRACE)) {
      LOG(ERROR) << "merging of recording files with auxtrace feature isn't supported";
      return false;
    }
    return true;
  }

  // Check attr sections to know if recorded event types are the same.
  bool CheckAttrSection() {
    std::vector<EventAttrWithId> attrs0 = readers_[0]->AttrSection();
    for (size_t i = 1; i < readers_.size(); i++) {
      std::vector<EventAttrWithId> attrs = readers_[i]->AttrSection();
      if (attrs.size() != attrs0.size()) {
        LOG(ERROR) << input_files_[0] << " and " << input_files_[i]
                   << " are not mergeable for recording different event types";
        return false;
      }
      for (size_t attr_id = 0; attr_id < attrs.size(); attr_id++) {
        if (memcmp(attrs[attr_id].attr, attrs0[attr_id].attr, sizeof(perf_event_attr)) != 0) {
          LOG(ERROR) << input_files_[0] << " and " << input_files_[i]
                     << " are not mergeable for recording different event types";
          return false;
        }
      }
    }
    return true;
  }

  bool MergeAttrSection() { return writer_->WriteAttrSection(readers_[0]->AttrSection()); }

  bool MergeDataSection() {
    for (size_t i = 0; i < readers_.size(); i++) {
      if (i != 0) {
        if (!WriteGapInDataSection(i - 1, i)) {
          return false;
        }
      }
      auto callback = [this](std::unique_ptr<Record> record) {
        return ProcessRecord(record.get());
      };
      if (!readers_[i]->ReadDataSection(callback)) {
        return false;
      }
    }
    return true;
  }

  bool ProcessRecord(Record* record) { return writer_->WriteRecord(*record); }

  bool WriteGapInDataSection(size_t prev_reader_id, size_t next_reader_id) {
    // MergeAttrSection() only maps event_ids in readers_[0] to event attrs. So we need to
    // map event_ids in readers_[next_read_id] to event attrs. The map info is put into an
    // EventIdRecord.
    const std::unordered_map<uint64_t, size_t>& cur_map = readers_[prev_reader_id]->EventIdMap();
    std::vector<EventAttrWithId> attrs = readers_[next_reader_id]->AttrSection();
    std::vector<uint64_t> event_id_data;
    for (size_t attr_id = 0; attr_id < attrs.size(); attr_id++) {
      for (size_t event_id : attrs[attr_id].ids) {
        if (auto it = cur_map.find(event_id); it == cur_map.end() || it->second != attr_id) {
          event_id_data.push_back(attr_id);
          event_id_data.push_back(event_id);
        }
      }
    }
    if (!event_id_data.empty()) {
      EventIdRecord record(event_id_data);
      if (!ProcessRecord(&record)) {
        return false;
      }
    }
    return true;
  }

  bool MergeFeatureSection() {
    std::vector<int> features;
    for (const auto& [key, _] : readers_[0]->FeatureSectionDescriptors()) {
      features.push_back(key);
    }
    if (!writer_->BeginWriteFeatures(features.size())) {
      return false;
    }
    for (int feature : features) {
      if (feature == PerfFileFormat::FEAT_OSRELEASE || feature == PerfFileFormat::FEAT_ARCH ||
          feature == PerfFileFormat::FEAT_BRANCH_STACK ||
          feature == PerfFileFormat::FEAT_META_INFO || feature == PerfFileFormat::FEAT_CMDLINE) {
        std::vector<char> data;
        if (!readers_[0]->ReadFeatureSection(feature, &data) ||
            !writer_->WriteFeature(feature, data.data(), data.size())) {
          return false;
        }
      } else if (feature == PerfFileFormat::FEAT_BUILD_ID) {
        WriteBuildIdFeature();
      } else if (feature == PerfFileFormat::FEAT_FILE || feature == PerfFileFormat::FEAT_FILE2) {
        WriteFileFeature();
      } else {
        LOG(WARNING) << "Drop feature " << feature << ", which isn't supported in the merge cmd.";
      }
    }
    return writer_->EndWriteFeatures();
  }

  bool WriteBuildIdFeature() {
    std::map<std::string, BuildIdRecord> build_ids;
    std::unordered_set<std::string> files_to_drop;
    for (auto& reader : readers_) {
      for (auto& record : reader->ReadBuildIdFeature()) {
        auto it = build_ids.find(record.filename);
        if (it == build_ids.end()) {
          build_ids.emplace(record.filename, std::move(record));
        } else if (it->second.build_id != record.build_id) {
          if (files_to_drop.count(record.filename) == 0) {
            files_to_drop.emplace(record.filename);
            LOG(WARNING)
                << record.filename
                << " has different build ids in different record files. So drop its build ids.";
          }
        }
      }
    }
    std::vector<BuildIdRecord> records;
    for (auto& [filename, record] : build_ids) {
      if (files_to_drop.count(filename) == 0) {
        records.emplace_back(std::move(record));
      }
    }
    return writer_->WriteBuildIdFeature(records);
  }

  bool WriteFileFeature() {
    std::map<std::string, MergedFileFeature> file_map;
    std::unordered_set<std::string> files_to_drop;

    // Read file features.
    for (auto& reader : readers_) {
      FileFeature file;
      size_t read_pos = 0;
      while (reader->ReadFileFeature(read_pos, &file)) {
        if (files_to_drop.count(file.path) != 0) {
          continue;
        }
        if (auto it = file_map.find(file.path); it == file_map.end()) {
          file_map.emplace(file.path, file);
        } else if (!it->second.Merge(file)) {
          LOG(WARNING)
              << file.path
              << " has address-conflict symbols in different record files. So drop its symbols.";
          files_to_drop.emplace(file.path);
        }
      }
    }
    // Write file features.
    for (const auto& [file_path, file] : file_map) {
      if (files_to_drop.count(file_path) != 0) {
        continue;
      }
      FileFeature file_feature;
      file.ToFileFeature(&file_feature);
      if (!writer_->WriteFileFeature(file_feature)) {
        return false;
      }
    }
    return true;
  }

  std::vector<std::string> input_files_;
  std::vector<std::unique_ptr<RecordFileReader>> readers_;
  std::string output_file_;
  std::unique_ptr<RecordFileWriter> writer_;
};

}  // namespace

void RegisterMergeCommand() {
  return RegisterCommand("merge", [] { return std::unique_ptr<Command>(new MergeCommand); });
}

}  // namespace simpleperf
