// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/display/manager/chromeos/apply_content_protection_task.h"

#include "ui/display/manager/chromeos/display_layout_manager.h"
#include "ui/display/types/display_snapshot.h"
#include "ui/display/types/native_display_delegate.h"

namespace display {

namespace {

bool GetHDCPCapableDisplays(
    const DisplayLayoutManager& layout_manager,
    std::vector<DisplaySnapshot*>* hdcp_capable_displays) {
  for (DisplaySnapshot* display : layout_manager.GetDisplayStates()) {
    switch (display->type()) {
      case DISPLAY_CONNECTION_TYPE_UNKNOWN:
        return false;
      // DisplayPort, DVI, and HDMI all support HDCP.
      case DISPLAY_CONNECTION_TYPE_DISPLAYPORT:
      case DISPLAY_CONNECTION_TYPE_DVI:
      case DISPLAY_CONNECTION_TYPE_HDMI:
        hdcp_capable_displays->push_back(display);
        break;
      case DISPLAY_CONNECTION_TYPE_INTERNAL:
      case DISPLAY_CONNECTION_TYPE_VGA:
      case DISPLAY_CONNECTION_TYPE_NETWORK:
        // No protections for these types. Do nothing.
        break;
      case DISPLAY_CONNECTION_TYPE_NONE:
        NOTREACHED();
        break;
    }
  }

  return true;
}

}  // namespace

ApplyContentProtectionTask::ApplyContentProtectionTask(
    DisplayLayoutManager* layout_manager,
    NativeDisplayDelegate* native_display_delegate,
    const DisplayConfigurator::ContentProtections& requests,
    const ResponseCallback& callback)
    : layout_manager_(layout_manager),
      native_display_delegate_(native_display_delegate),
      requests_(requests),
      callback_(callback),
      query_status_(true),
      pending_requests_(0),
      weak_ptr_factory_(this) {}

ApplyContentProtectionTask::~ApplyContentProtectionTask() {}

void ApplyContentProtectionTask::Run() {
  std::vector<DisplaySnapshot*> hdcp_capable_displays;
  if (!GetHDCPCapableDisplays(*layout_manager_, &hdcp_capable_displays)) {
    callback_.Run(false);
    return;
  }

  pending_requests_ = hdcp_capable_displays.size();
  if (pending_requests_ == 0) {
    callback_.Run(true);
    return;
  }

  // Need to poll the driver for updates since other applications may have
  // updated the state.
  for (DisplaySnapshot* display : hdcp_capable_displays) {
    native_display_delegate_->GetHDCPState(
        *display,
        base::Bind(&ApplyContentProtectionTask::OnHDCPStateUpdate,
                   weak_ptr_factory_.GetWeakPtr(), display->display_id()));
  }
}

void ApplyContentProtectionTask::OnHDCPStateUpdate(int64_t display_id,
                                                   bool success,
                                                   HDCPState state) {
  query_status_ &= success;
  display_hdcp_state_map_[display_id] = state;
  pending_requests_--;

  // Wait for all the requests before continuing.
  if (pending_requests_ != 0)
    return;

  if (!query_status_) {
    callback_.Run(false);
    return;
  }

  ApplyProtections();
}

void ApplyContentProtectionTask::ApplyProtections() {
  std::vector<DisplaySnapshot*> hdcp_capable_displays;
  if (!GetHDCPCapableDisplays(*layout_manager_, &hdcp_capable_displays)) {
    callback_.Run(false);
    return;
  }

  std::vector<std::pair<DisplaySnapshot*, HDCPState>> hdcp_requests;
  // Figure out which displays need to have their HDCP state changed.
  for (DisplaySnapshot* display : hdcp_capable_displays) {
    uint32_t desired_mask = GetDesiredProtectionMask(display->display_id());

    auto it = display_hdcp_state_map_.find(display->display_id());
    // If the display can't be found, the display configuration changed.
    if (it == display_hdcp_state_map_.end()) {
      callback_.Run(false);
      return;
    }

    bool hdcp_enabled = it->second != HDCP_STATE_UNDESIRED;
    bool hdcp_requested = desired_mask & CONTENT_PROTECTION_METHOD_HDCP;
    if (hdcp_enabled != hdcp_requested) {
      hdcp_requests.push_back(std::make_pair(
          display, hdcp_requested ? HDCP_STATE_DESIRED : HDCP_STATE_UNDESIRED));
    }
  }

  pending_requests_ = hdcp_requests.size();
  // All the requested changes are the same as the current HDCP state. Nothing
  // to do anymore, just ack the content protection change.
  if (pending_requests_ == 0) {
    callback_.Run(true);
    return;
  }

  for (const auto& pair : hdcp_requests) {
    native_display_delegate_->SetHDCPState(
        *pair.first, pair.second,
        base::Bind(&ApplyContentProtectionTask::OnHDCPStateApplied,
                   weak_ptr_factory_.GetWeakPtr()));
  }
}

void ApplyContentProtectionTask::OnHDCPStateApplied(bool success) {
  query_status_ &= success;
  pending_requests_--;

  if (pending_requests_ == 0)
    callback_.Run(query_status_);
}

uint32_t ApplyContentProtectionTask::GetDesiredProtectionMask(
    int64_t display_id) const {
  uint32_t desired_mask = 0;
  // In mirror mode, protection request of all displays need to be fulfilled.
  // In non-mirror mode, only request of client's display needs to be
  // fulfilled.
  if (layout_manager_->IsMirroring()) {
    for (auto pair : requests_)
      desired_mask |= pair.second;
  } else {
    auto it = requests_.find(display_id);
    if (it != requests_.end())
      desired_mask = it->second;
  }

  return desired_mask;
}

}  // namespace display
