#include "perf_data_converter.h"
#include "quipper/perf_parser.h"
#include <map>

using std::map;

namespace wireless_android_logging_awp {

struct RangeTarget {
  RangeTarget(uint64 start, uint64 end, uint64 to)
      : start(start), end(end), to(to) {}

  bool operator<(const RangeTarget &r) const {
    if (start != r.start) {
      return start < r.start;
    } else if (end != r.end) {
      return end < r.end;
    } else {
      return to < r.to;
    }
  }
  uint64 start;
  uint64 end;
  uint64 to;
};

struct BinaryProfile {
  map<uint64, uint64> address_count_map;
  map<RangeTarget, uint64> range_count_map;
};

wireless_android_play_playlog::AndroidPerfProfile
RawPerfDataToAndroidPerfProfile(const string &perf_file) {
  wireless_android_play_playlog::AndroidPerfProfile ret;
  quipper::PerfParser parser;
  if (!parser.ReadFile(perf_file) || !parser.ParseRawEvents()) {
    return ret;
  }

  typedef map<string, BinaryProfile> ModuleProfileMap;
  typedef map<string, ModuleProfileMap> ProgramProfileMap;
  ProgramProfileMap name_profile_map;
  uint64 total_samples = 0;
  for (const auto &event : parser.parsed_events()) {
    if (!event.raw_event ||
        event.raw_event->header.type != PERF_RECORD_SAMPLE) {
      continue;
    }
    string dso_name = event.dso_and_offset.dso_name();
    string program_name;
    if (dso_name == "[kernel.kallsyms]_text") {
      program_name = "kernel";
      dso_name = "[kernel.kallsyms]";
    } else if (event.command() == "") {
      program_name = "unknown_program";
    } else {
      program_name = event.command();
    }
    name_profile_map[program_name][dso_name].address_count_map[
        event.dso_and_offset.offset()]++;
    total_samples++;
    for (size_t i = 1; i < event.branch_stack.size(); i++) {
      if (dso_name == event.branch_stack[i - 1].to.dso_name()) {
        uint64 start = event.branch_stack[i].to.offset();
        uint64 end = event.branch_stack[i - 1].from.offset();
        uint64 to = event.branch_stack[i - 1].to.offset();
        // The interval between two taken branches should not be too large.
        if (end < start || end - start > (1 << 20)) {
          LOG(WARNING) << "Bogus LBR data: " << start << "->" << end;
          continue;
        }
        name_profile_map[program_name][dso_name].range_count_map[
            RangeTarget(start, end, to)]++;
      }
    }
  }

  map<string, int> name_id_map;
  for (const auto &program_profile : name_profile_map) {
    for (const auto &module_profile : program_profile.second) {
      name_id_map[module_profile.first] = 0;
    }
  }
  int current_index = 0;
  for (auto iter = name_id_map.begin(); iter != name_id_map.end(); ++iter) {
    iter->second = current_index++;
  }

  map<string, string> name_buildid_map;
  parser.GetFilenamesToBuildIDs(&name_buildid_map);
  ret.set_total_samples(total_samples);
  for (const auto &name_id : name_id_map) {
    auto load_module = ret.add_load_modules();
    load_module->set_name(name_id.first);
    auto nbmi = name_buildid_map.find(name_id.first);
    if (nbmi != name_buildid_map.end()) {
      const std::string &build_id = nbmi->second;
      if (build_id.size() == 40 && build_id.substr(32) == "00000000") {
        load_module->set_build_id(build_id.substr(0, 32));
      } else {
        load_module->set_build_id(build_id);
      }
    }
  }
  for (const auto &program_profile : name_profile_map) {
    auto program = ret.add_programs();
    program->set_name(program_profile.first);
    for (const auto &module_profile : program_profile.second) {
      int32 module_id = name_id_map[module_profile.first];
      auto module = program->add_modules();
      module->set_load_module_id(module_id);
      for (const auto &addr_count : module_profile.second.address_count_map) {
        auto address_samples = module->add_address_samples();
        address_samples->add_address(addr_count.first);
        address_samples->set_count(addr_count.second);
      }
      for (const auto &range_count : module_profile.second.range_count_map) {
        auto range_samples = module->add_range_samples();
        range_samples->set_start(range_count.first.start);
        range_samples->set_end(range_count.first.end);
        range_samples->set_to(range_count.first.to);
        range_samples->set_count(range_count.second);
      }
    }
  }
  return ret;
}

}  // namespace wireless_android_logging_awp