/*
 * Copyright 2017 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 <android/hidl/manager/1.0/IServiceManager.h>
#include <cutils/properties.h>
#include <hidl/ServiceManagement.h>
#include <iostream>

using namespace std;
using namespace android;

const string kSysPropHalCoverage = "hal.coverage.enable";

// Print usage directions.
void usage() {
  cout << "usage:\n";
  cout << "vts_coverage_configure flush\t\t\t\t: to flush coverage on all "
          "HALs\n";
  cout << "vts_coverage_configure flush <hal name>@<hal version>\t: to flush "
          "coverage on one HAL name/version instance"
       << std::endl;
}

// Parse the fully-qualified instance name and call the func with the interface
// name, instance name, and HAL name.
template <typename Lambda>
bool parseFqInstaceName(string fqInstanceName, Lambda &&func) {
  string::size_type n = fqInstanceName.find("/");
  if (n == std::string::npos || fqInstanceName.size() == n + 1) return false;

  string fqInterfaceName = fqInstanceName.substr(0, n);
  string instanceName = fqInstanceName.substr(n + 1, std::string::npos);

  n = fqInstanceName.find("::");
  if (n == std::string::npos || fqInstanceName.size() == n + 1) return false;
  string halName = fqInstanceName.substr(0, n);
  std::forward<Lambda>(func)(fqInterfaceName, instanceName, halName);
  return true;
}

// Flush coverage on all HAL processes, or just the provided HAL name if
// provided.
bool FlushHALCoverage(string flushHal = "") {
  using ::android::hidl::base::V1_0::IBase;
  using ::android::hidl::manager::V1_0::IServiceManager;
  using ::android::hardware::hidl_string;
  using ::android::hardware::Return;

  sp<IServiceManager> sm = ::android::hardware::defaultServiceManager();

  if (sm == nullptr) {
    cerr << "failed to get IServiceManager to poke HAL services." << std::endl;
    return false;
  }
  property_set(kSysPropHalCoverage.c_str(), "true");
  auto listRet = sm->list([&](const auto &interfaces) {
    for (const string &fqInstanceName : interfaces) {
      hidl_string fqInterfaceName;
      hidl_string instanceName;
      string halName;

      auto cb = [&](string fqIface, string instance, string hal) {
        fqInterfaceName = fqIface;
        instanceName = instance;
        halName = hal;
      };
      if (!parseFqInstaceName(fqInstanceName, cb)) continue;
      if (halName.find("android.hidl") == 0) continue;
      if (flushHal == "" || !flushHal.compare(halName)) {
        Return<sp<IBase>> interfaceRet = sm->get(fqInterfaceName, instanceName);
        if (!interfaceRet.isOk()) {
          cerr << "failed to get service " << fqInstanceName << ": "
               << interfaceRet.description() << std::endl;
          continue;
        }
        sp<IBase> interface = interfaceRet;
        auto notifyRet = interface->notifySyspropsChanged();
        if (!notifyRet.isOk()) {
          cerr << "failed to notifySyspropsChanged on service "
               << fqInstanceName << ": " << notifyRet.description()
               << std::endl;
        }
        cout << "- flushed the coverage for HAL " << fqInstanceName
             << std::endl;
      }
    }
  });
  property_set(kSysPropHalCoverage.c_str(), "false");
  if (!listRet.isOk()) {
    cerr << "failed to list services: " << listRet.description() << std::endl;
    return false;
  }
  return true;
}

// The provided binary can be used to flush coverage on one or all HALs.
// Usage examples:
//   To flush gcov and/or sancov coverage data on all hals: <binary> flush
//   To flush gcov and/or sancov coverage data on one hal: <binary> flush <hal
//   name>@<hal version>
int main(int argc, char *argv[]) {
  bool flush_coverage = false;
  if (argc < 2) {
    usage();
    return -1;
  }
  if (!strcmp(argv[1], "flush")) {
    flush_coverage = true;
    string halString = "";
    if (argc == 3) {
      halString = string(argv[2]);
    }
    cout << "* flush coverage" << std::endl;
    if (!FlushHALCoverage(halString)) {
      cerr << "failed to flush coverage" << std::endl;
    }
  } else {
    usage();
  }
  return 0;
}