/*
 * Copyright (C) 2016 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 "VtsHidlHalReplayer.h"

#include <fcntl.h>
#include <unistd.h>
#include <fstream>
#include <iostream>
#include <string>

#include <android-base/logging.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <google/protobuf/text_format.h>

#include "VtsProfilingUtil.h"
#include "driver_base/DriverBase.h"
#include "utils/InterfaceSpecUtil.h"
#include "utils/StringUtil.h"

using namespace std;

static constexpr const char* kErrorString = "error";
static constexpr const char* kVoidString = "void";
static constexpr const int kInvalidDriverId = -1;

namespace android {
namespace vts {

void VtsHidlHalReplayer::ListTestServices(const string& trace_file) {
  // Parse the trace file to get the sequence of function calls.
  int fd = open(trace_file.c_str(), O_RDONLY);
  if (fd < 0) {
    LOG(ERROR) << "Can not open trace file: " << trace_file
               << " error: " << std::strerror(errno);
    return;
  }

  google::protobuf::io::FileInputStream input(fd);

  VtsProfilingRecord msg;
  set<string> registeredHalServices;
  while (readOneDelimited(&msg, &input)) {
    string package_name = msg.package();
    float version = msg.version();
    string interface_name = msg.interface();
    string service_fq_name =
        GetInterfaceFQName(package_name, version, interface_name);
    registeredHalServices.insert(service_fq_name);
  }
  for (string service : registeredHalServices) {
    cout << "hal_service: " << service << endl;
  }
}

bool VtsHidlHalReplayer::ReplayTrace(
    const string& trace_file, map<string, string>& hal_service_instances) {
  // Parse the trace file to get the sequence of function calls.
  int fd = open(trace_file.c_str(), O_RDONLY);
  if (fd < 0) {
    LOG(ERROR) << "Can not open trace file: " << trace_file
               << "error: " << std::strerror(errno);
    return false;
  }

  google::protobuf::io::FileInputStream input(fd);

  VtsProfilingRecord call_msg;
  VtsProfilingRecord expected_result_msg;
  while (readOneDelimited(&call_msg, &input) &&
         readOneDelimited(&expected_result_msg, &input)) {
    if (call_msg.event() != InstrumentationEventType::SERVER_API_ENTRY &&
        call_msg.event() != InstrumentationEventType::CLIENT_API_ENTRY &&
        call_msg.event() != InstrumentationEventType::SYNC_CALLBACK_ENTRY &&
        call_msg.event() != InstrumentationEventType::ASYNC_CALLBACK_ENTRY &&
        call_msg.event() != InstrumentationEventType::PASSTHROUGH_ENTRY) {
      LOG(WARNING) << "Expected a call message but got message with event: "
                   << call_msg.event();
      continue;
    }
    if (expected_result_msg.event() !=
            InstrumentationEventType::SERVER_API_EXIT &&
        expected_result_msg.event() !=
            InstrumentationEventType::CLIENT_API_EXIT &&
        expected_result_msg.event() !=
            InstrumentationEventType::SYNC_CALLBACK_EXIT &&
        expected_result_msg.event() !=
            InstrumentationEventType::ASYNC_CALLBACK_EXIT &&
        expected_result_msg.event() !=
            InstrumentationEventType::PASSTHROUGH_EXIT) {
      LOG(WARNING) << "Expected a result message but got message with event: "
                   << call_msg.event();
      continue;
    }

    string package_name = call_msg.package();
    float version = call_msg.version();
    string interface_name = call_msg.interface();
    string instance_name =
        GetInterfaceFQName(package_name, version, interface_name);
    string hal_service_name = "default";

    if (hal_service_instances.find(instance_name) ==
        hal_service_instances.end()) {
      LOG(WARNING) << "Does not find service name for " << instance_name
                   << "; this could be a nested interface.";
    } else {
      hal_service_name = hal_service_instances[instance_name];
    }

    cout << "Replay function: " << call_msg.func_msg().name() << endl;
    LOG(DEBUG) << "Replay function: " << call_msg.func_msg().DebugString();

    int32_t driver_id = driver_manager_->GetDriverIdForHidlHalInterface(
        package_name, version, interface_name, hal_service_name);
    if (driver_id == kInvalidDriverId) {
      LOG(ERROR) << "Couldn't get a driver base class";
      return false;
    }

    vts::FunctionCallMessage func_call_msg;
    func_call_msg.set_component_class(HAL_HIDL);
    func_call_msg.set_hal_driver_id(driver_id);
    *func_call_msg.mutable_api() = call_msg.func_msg();
    const string& result = driver_manager_->CallFunction(&func_call_msg);
    if (result == kVoidString || result == kErrorString) {
      LOG(ERROR) << "Replay function fail. Failed function call: "
                 << func_call_msg.DebugString();
      return false;
    }
    vts::FunctionSpecificationMessage result_msg;
    if (!google::protobuf::TextFormat::ParseFromString(result, &result_msg)) {
      LOG(ERROR) << "Failed to parse result msg.";
      return false;
    }
    if (!driver_manager_->VerifyResults(
            driver_id, expected_result_msg.func_msg(), result_msg)) {
      // Verification is not strict, i.e. if fail, output error message and
      // continue the process.
      LOG(WARNING) << "Verification fail. Expected: "
                   << expected_result_msg.func_msg().DebugString()
                   << " Actual: " << result_msg.DebugString();
    }
    call_msg.Clear();
    expected_result_msg.Clear();
  }
  return true;
}

}  // namespace vts
}  // namespace android