#include "vsync_service.h"

#include <hardware/hwcomposer.h>
#include <log/log.h>
#include <poll.h>
#include <sys/prctl.h>
#include <time.h>
#include <utils/Trace.h>

#include <dvr/dvr_display_types.h>
#include <pdx/default_transport/service_endpoint.h>
#include <private/dvr/clock_ns.h>
#include <private/dvr/display_protocol.h>

using android::dvr::display::VSyncProtocol;
using android::dvr::display::VSyncSchedInfo;
using android::pdx::Channel;
using android::pdx::Message;
using android::pdx::MessageInfo;
using android::pdx::default_transport::Endpoint;
using android::pdx::rpc::DispatchRemoteMethod;

namespace android {
namespace dvr {

VSyncService::VSyncService()
    : BASE("VSyncService", Endpoint::Create(VSyncProtocol::kClientPath)),
      last_vsync_(0),
      current_vsync_(0),
      compositor_time_ns_(0),
      current_vsync_count_(0) {}

VSyncService::~VSyncService() {}

void VSyncService::VSyncEvent(int64_t timestamp_ns,
                              int64_t compositor_time_ns,
                              uint32_t vsync_count) {
  ATRACE_NAME("VSyncService::VSyncEvent");
  std::lock_guard<std::mutex> autolock(mutex_);

  last_vsync_ = current_vsync_;
  current_vsync_ = timestamp_ns;
  compositor_time_ns_ = compositor_time_ns;
  current_vsync_count_ = vsync_count;

  NotifyWaiters();
  UpdateClients();
}

std::shared_ptr<Channel> VSyncService::OnChannelOpen(pdx::Message& message) {
  const MessageInfo& info = message.GetInfo();

  auto client = std::make_shared<VSyncChannel>(*this, info.pid, info.cid);
  AddClient(client);

  return client;
}

void VSyncService::OnChannelClose(pdx::Message& /*message*/,
                                  const std::shared_ptr<Channel>& channel) {
  auto client = std::static_pointer_cast<VSyncChannel>(channel);
  if (!client) {
    ALOGW("WARNING: VSyncChannel was NULL!!!\n");
    return;
  }

  RemoveClient(client);
}

void VSyncService::AddWaiter(pdx::Message& message) {
  std::lock_guard<std::mutex> autolock(mutex_);
  std::unique_ptr<VSyncWaiter> waiter(new VSyncWaiter(message));
  waiters_.push_back(std::move(waiter));
}

void VSyncService::AddClient(const std::shared_ptr<VSyncChannel>& client) {
  std::lock_guard<std::mutex> autolock(mutex_);
  clients_.push_back(client);
}

void VSyncService::RemoveClient(const std::shared_ptr<VSyncChannel>& client) {
  std::lock_guard<std::mutex> autolock(mutex_);
  clients_.remove(client);
}

// Private. Assumes mutex is held.
void VSyncService::NotifyWaiters() {
  ATRACE_NAME("VSyncService::NotifyWaiters");
  auto first = waiters_.begin();
  auto last = waiters_.end();

  while (first != last) {
    (*first)->Notify(current_vsync_);
    waiters_.erase(first++);
  }
}

// Private. Assumes mutex is held.
void VSyncService::UpdateClients() {
  ATRACE_NAME("VSyncService::UpdateClients");
  auto first = clients_.begin();
  auto last = clients_.end();

  while (first != last) {
    (*first)->Signal();
    first++;
  }
}

pdx::Status<void> VSyncService::HandleMessage(pdx::Message& message) {
  ATRACE_NAME("VSyncService::HandleMessage");
  switch (message.GetOp()) {
    case VSyncProtocol::Wait::Opcode:
      AddWaiter(message);
      return {};

    case VSyncProtocol::GetLastTimestamp::Opcode:
      DispatchRemoteMethod<VSyncProtocol::GetLastTimestamp>(
          *this, &VSyncService::OnGetLastTimestamp, message);
      return {};

    case VSyncProtocol::GetSchedInfo::Opcode:
      DispatchRemoteMethod<VSyncProtocol::GetSchedInfo>(
          *this, &VSyncService::OnGetSchedInfo, message);
      return {};

    case VSyncProtocol::Acknowledge::Opcode:
      DispatchRemoteMethod<VSyncProtocol::Acknowledge>(
          *this, &VSyncService::OnAcknowledge, message);
      return {};

    default:
      return Service::HandleMessage(message);
  }
}

pdx::Status<int64_t> VSyncService::OnGetLastTimestamp(pdx::Message& message) {
  auto client = std::static_pointer_cast<VSyncChannel>(message.GetChannel());
  std::lock_guard<std::mutex> autolock(mutex_);

  // Getting the timestamp has the side effect of ACKing.
  client->Ack();
  return {current_vsync_};
}

pdx::Status<VSyncSchedInfo> VSyncService::OnGetSchedInfo(
    pdx::Message& message) {
  auto client = std::static_pointer_cast<VSyncChannel>(message.GetChannel());
  std::lock_guard<std::mutex> autolock(mutex_);

  // Getting the timestamp has the side effect of ACKing.
  client->Ack();

  uint32_t next_vsync_count = current_vsync_count_ + 1;
  int64_t current_time = GetSystemClockNs();
  int64_t vsync_period_ns = 0;
  int64_t next_warp;
  if (current_vsync_ == 0 || last_vsync_ == 0) {
    // Handle startup when current_vsync_ or last_vsync_ are 0.
    // Normally should not happen because vsync_service is running before
    // applications, but in case it does a sane time prevents applications
    // from malfunctioning.
    vsync_period_ns = 20000000;
    next_warp = current_time;
  } else {
    // TODO(jbates) When we have an accurate reading of the true vsync
    // period, use that instead of this estimated value.
    vsync_period_ns = current_vsync_ - last_vsync_;
    // Clamp the period, because when there are no surfaces the last_vsync_
    // value will get stale. Note this is temporary and goes away as soon
    // as we have an accurate vsync period reported by the system.
    vsync_period_ns = std::min(vsync_period_ns, INT64_C(20000000));
    next_warp = current_vsync_ + vsync_period_ns - compositor_time_ns_;
    // If the request missed the present window, move up to the next vsync.
    if (current_time > next_warp) {
      next_warp += vsync_period_ns;
      ++next_vsync_count;
    }
  }

  return {{vsync_period_ns, next_warp, next_vsync_count}};
}

pdx::Status<void> VSyncService::OnAcknowledge(pdx::Message& message) {
  auto client = std::static_pointer_cast<VSyncChannel>(message.GetChannel());
  std::lock_guard<std::mutex> autolock(mutex_);
  client->Ack();
  return {};
}

void VSyncWaiter::Notify(int64_t timestamp) {
  timestamp_ = timestamp;
  DispatchRemoteMethod<VSyncProtocol::Wait>(*this, &VSyncWaiter::OnWait,
                                            message_);
}

pdx::Status<int64_t> VSyncWaiter::OnWait(pdx::Message& /*message*/) {
  return {timestamp_};
}

void VSyncChannel::Ack() {
  ALOGD_IF(TRACE > 1, "VSyncChannel::Ack: pid=%d cid=%d\n", pid_, cid_);
  service_.ModifyChannelEvents(cid_, POLLPRI, 0);
}

void VSyncChannel::Signal() {
  ALOGD_IF(TRACE > 1, "VSyncChannel::Signal: pid=%d cid=%d\n", pid_, cid_);
  service_.ModifyChannelEvents(cid_, 0, POLLPRI);
}

}  // namespace dvr
}  // namespace android
