//
// Copyright (C) 2015 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 "shill/dbus/chromeos_power_manager_proxy.h"

#include <base/bind.h>
#include <google/protobuf/message_lite.h>

#include "power_manager/proto_bindings/suspend.pb.h"
#include "shill/event_dispatcher.h"
#include "shill/logging.h"

using std::string;
using std::vector;

namespace shill {

namespace {

// Serializes |protobuf| to |out| and returns true on success.
bool SerializeProtocolBuffer(const google::protobuf::MessageLite& protobuf,
                             vector<uint8_t>* out) {
  CHECK(out);
  out->clear();
  string serialized_protobuf;
  if (!protobuf.SerializeToString(&serialized_protobuf))
    return false;
  out->assign(serialized_protobuf.begin(), serialized_protobuf.end());
  return true;
}

// Deserializes |serialized_protobuf| to |protobuf_out| and returns true on
// success.
bool DeserializeProtocolBuffer(const vector<uint8_t>& serialized_protobuf,
                               google::protobuf::MessageLite* protobuf_out) {
  CHECK(protobuf_out);
  if (serialized_protobuf.empty())
    return false;
  return protobuf_out->ParseFromArray(&serialized_protobuf.front(),
                                      serialized_protobuf.size());
}

}  // namespace

ChromeosPowerManagerProxy::ChromeosPowerManagerProxy(
      EventDispatcher* dispatcher,
      const scoped_refptr<dbus::Bus>& bus,
      PowerManagerProxyDelegate* delegate,
      const base::Closure& service_appeared_callback,
      const base::Closure& service_vanished_callback)
    : proxy_(new org::chromium::PowerManagerProxy(bus)),
      dispatcher_(dispatcher),
      delegate_(delegate),
      service_appeared_callback_(service_appeared_callback),
      service_vanished_callback_(service_vanished_callback) {
  // Register signal handlers.
  proxy_->RegisterSuspendImminentSignalHandler(
      base::Bind(&ChromeosPowerManagerProxy::SuspendImminent,
                 weak_factory_.GetWeakPtr()),
      base::Bind(&ChromeosPowerManagerProxy::OnSignalConnected,
                 weak_factory_.GetWeakPtr()));
  proxy_->RegisterSuspendDoneSignalHandler(
      base::Bind(&ChromeosPowerManagerProxy::SuspendDone,
                 weak_factory_.GetWeakPtr()),
      base::Bind(&ChromeosPowerManagerProxy::OnSignalConnected,
                 weak_factory_.GetWeakPtr()));
  proxy_->RegisterDarkSuspendImminentSignalHandler(
      base::Bind(&ChromeosPowerManagerProxy::DarkSuspendImminent,
                 weak_factory_.GetWeakPtr()),
      base::Bind(&ChromeosPowerManagerProxy::OnSignalConnected,
                 weak_factory_.GetWeakPtr()));

  // One time callback when service becomes available.
  proxy_->GetObjectProxy()->WaitForServiceToBeAvailable(
      base::Bind(&ChromeosPowerManagerProxy::OnServiceAvailable,
                 weak_factory_.GetWeakPtr()));
}

ChromeosPowerManagerProxy::~ChromeosPowerManagerProxy() {}

bool ChromeosPowerManagerProxy::RegisterSuspendDelay(
    base::TimeDelta timeout,
    const string& description,
    int* delay_id_out) {
  if (!service_available_) {
    LOG(ERROR) << "PowerManager service not available";
    return false;
  }
  return RegisterSuspendDelayInternal(false,
                                      timeout,
                                      description,
                                      delay_id_out);
}

bool ChromeosPowerManagerProxy::UnregisterSuspendDelay(int delay_id) {
  if (!service_available_) {
    LOG(ERROR) << "PowerManager service not available";
    return false;
  }
  return UnregisterSuspendDelayInternal(false, delay_id);
}

bool ChromeosPowerManagerProxy::ReportSuspendReadiness(int delay_id,
                                                       int suspend_id) {
  if (!service_available_) {
    LOG(ERROR) << "PowerManager service not available";
    return false;
  }
  return ReportSuspendReadinessInternal(false, delay_id, suspend_id);
}

bool ChromeosPowerManagerProxy::RegisterDarkSuspendDelay(
    base::TimeDelta timeout,
    const string& description,
    int* delay_id_out) {
  if (!service_available_) {
    LOG(ERROR) << "PowerManager service not available";
    return false;
  }
  return RegisterSuspendDelayInternal(true,
                                      timeout,
                                      description,
                                      delay_id_out);
}

bool ChromeosPowerManagerProxy::UnregisterDarkSuspendDelay(int delay_id) {
  if (!service_available_) {
    LOG(ERROR) << "PowerManager service not available";
    return false;
  }
  return UnregisterSuspendDelayInternal(true, delay_id);
}

bool ChromeosPowerManagerProxy::ReportDarkSuspendReadiness(int delay_id,
                                                           int suspend_id ) {
  if (!service_available_) {
    LOG(ERROR) << "PowerManager service not available";
    return false;
  }
  return ReportSuspendReadinessInternal(true, delay_id, suspend_id);
}

bool ChromeosPowerManagerProxy::RecordDarkResumeWakeReason(
    const string& wake_reason) {
  LOG(INFO) << __func__;

  if (!service_available_) {
    LOG(ERROR) << "PowerManager service not available";
    return false;
  }

  power_manager::DarkResumeWakeReason proto;
  proto.set_wake_reason(wake_reason);
  vector<uint8_t> serialized_proto;
  CHECK(SerializeProtocolBuffer(proto, &serialized_proto));

  brillo::ErrorPtr error;
  if (!proxy_->RecordDarkResumeWakeReason(serialized_proto, &error)) {
    LOG(ERROR) << "Failed tp record dark resume wake reason: "
               << error->GetCode() << " " << error->GetMessage();
    return false;
  }
  return true;
}

bool ChromeosPowerManagerProxy::RegisterSuspendDelayInternal(
    bool is_dark,
    base::TimeDelta timeout,
    const string& description,
    int* delay_id_out) {
  const string is_dark_arg = (is_dark ? "dark=true" : "dark=false");
  LOG(INFO) << __func__ << "(" << timeout.InMilliseconds()
            << ", " << is_dark_arg <<")";

  power_manager::RegisterSuspendDelayRequest request_proto;
  request_proto.set_timeout(timeout.ToInternalValue());
  request_proto.set_description(description);
  vector<uint8_t> serialized_request;
  CHECK(SerializeProtocolBuffer(request_proto, &serialized_request));

  vector<uint8_t> serialized_reply;
  brillo::ErrorPtr error;
  if (is_dark) {
    proxy_->RegisterDarkSuspendDelay(serialized_request,
                                     &serialized_reply,
                                     &error);
  } else {
    proxy_->RegisterSuspendDelay(serialized_request, &serialized_reply, &error);
  }
  if (error) {
    LOG(ERROR) << "Failed to register suspend delay: "
               << error->GetCode() << " " << error->GetMessage();
    return false;
  }

  power_manager::RegisterSuspendDelayReply reply_proto;
  if (!DeserializeProtocolBuffer(serialized_reply, &reply_proto)) {
    LOG(ERROR) << "Failed to register "
               << (is_dark ? "dark " : "")
               << "suspend delay.  Couldn't parse response.";
    return false;
  }
  *delay_id_out = reply_proto.delay_id();
  return true;
}

bool ChromeosPowerManagerProxy::UnregisterSuspendDelayInternal(bool is_dark,
                                                               int delay_id) {
  const string is_dark_arg = (is_dark ? "dark=true" : "dark=false");
  LOG(INFO) << __func__ << "(" << delay_id << ", " << is_dark_arg << ")";

  power_manager::UnregisterSuspendDelayRequest request_proto;
  request_proto.set_delay_id(delay_id);
  vector<uint8_t> serialized_request;
  CHECK(SerializeProtocolBuffer(request_proto, &serialized_request));

  brillo::ErrorPtr error;
  if (is_dark) {
    proxy_->UnregisterDarkSuspendDelay(serialized_request, &error);
  } else {
    proxy_->UnregisterSuspendDelay(serialized_request, &error);
  }
  if (error) {
    LOG(ERROR) << "Failed to unregister suspend delay: "
               << error->GetCode() << " " << error->GetMessage();
    return false;
  }
  return true;
}

bool ChromeosPowerManagerProxy::ReportSuspendReadinessInternal(
    bool is_dark, int delay_id, int suspend_id) {
  const string is_dark_arg = (is_dark ? "dark=true" : "dark=false");
  LOG(INFO) << __func__
            << "(" << delay_id
            << ", " << suspend_id
            << ", " << is_dark_arg << ")";

  power_manager::SuspendReadinessInfo proto;
  proto.set_delay_id(delay_id);
  proto.set_suspend_id(suspend_id);
  vector<uint8_t> serialized_proto;
  CHECK(SerializeProtocolBuffer(proto, &serialized_proto));

  brillo::ErrorPtr error;
  if (is_dark) {
    proxy_->HandleDarkSuspendReadiness(serialized_proto, &error);
  } else {
    proxy_->HandleSuspendReadiness(serialized_proto, &error);
  }
  if (error) {
    LOG(ERROR) << "Failed to report suspend readiness: "
               << error->GetCode() << " " << error->GetMessage();
    return false;
  }
  return true;
}

void ChromeosPowerManagerProxy::SuspendImminent(
    const vector<uint8_t>& serialized_proto) {
  LOG(INFO) << __func__;
  power_manager::SuspendImminent proto;
  if (!DeserializeProtocolBuffer(serialized_proto, &proto)) {
    LOG(ERROR) << "Failed to parse SuspendImminent signal.";
    return;
  }
  delegate_->OnSuspendImminent(proto.suspend_id());
}

void ChromeosPowerManagerProxy::SuspendDone(
    const vector<uint8_t>& serialized_proto) {
  LOG(INFO) << __func__;
  power_manager::SuspendDone proto;
  if (!DeserializeProtocolBuffer(serialized_proto, &proto)) {
    LOG(ERROR) << "Failed to parse SuspendDone signal.";
    return;
  }
  delegate_->OnSuspendDone(proto.suspend_id());
}

void ChromeosPowerManagerProxy::DarkSuspendImminent(
    const vector<uint8_t>& serialized_proto) {
  LOG(INFO) << __func__;
  power_manager::SuspendImminent proto;
  if (!DeserializeProtocolBuffer(serialized_proto, &proto)) {
    LOG(ERROR) << "Failed to parse DarkSuspendImminent signal.";
    return;
  }
  delegate_->OnDarkSuspendImminent(proto.suspend_id());
}

void ChromeosPowerManagerProxy::OnServiceAvailable(bool available) {
  // The only time this function will ever be invoked with |available| set to
  // false is when we failed to connect the signals, either bus is not setup
  // yet or we failed to add match rules, and both of these errors are
  // considered fatal.
  CHECK(available);

  // Service is available now, continuously monitor the service owner changes.
  proxy_->GetObjectProxy()->SetNameOwnerChangedCallback(
      base::Bind(&ChromeosPowerManagerProxy::OnServiceOwnerChanged,
                 weak_factory_.GetWeakPtr()));

  // The callback might invoke calls to the ObjectProxy, so defer the callback
  // to event loop.
  if (!service_appeared_callback_.is_null()) {
    dispatcher_->PostTask(service_appeared_callback_);
  }

  service_available_ = true;
}

void ChromeosPowerManagerProxy::OnServiceOwnerChanged(
    const string& old_owner, const string& new_owner) {
  LOG(INFO) << __func__ << "old: " << old_owner << " new: " << new_owner;

  if (new_owner.empty()) {
    // The callback might invoke calls to the ObjectProxy, so defer the
    // callback to event loop.
    if (!service_vanished_callback_.is_null()) {
        dispatcher_->PostTask(service_vanished_callback_);
    }
    service_available_ = false;
  } else {
    // The callback might invoke calls to the ObjectProxy, so defer the
    // callback to event loop.
    if (!service_appeared_callback_.is_null()) {
      dispatcher_->PostTask(service_appeared_callback_);
    }
    service_available_ = true;
  }
}

void ChromeosPowerManagerProxy::OnSignalConnected(
    const string& interface_name, const string& signal_name, bool success) {
  LOG(INFO) << __func__ << " interface: " << interface_name
            << " signal: " << signal_name << "success: " << success;
  if (!success) {
    LOG(ERROR) << "Failed to connect signal " << signal_name
        << " to interface " << interface_name;
  }
}

}  // namespace shill