// 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 "content/renderer/p2p/filtering_network_manager.h"

#include <utility>

#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/thread_task_runner_handle.h"
#include "media/base/media_permission.h"

namespace content {

FilteringNetworkManager::FilteringNetworkManager(
    rtc::NetworkManager* network_manager,
    const GURL& requesting_origin,
    scoped_ptr<media::MediaPermission> media_permission)
    : network_manager_(network_manager),
      media_permission_(std::move(media_permission)),
      requesting_origin_(requesting_origin),
      weak_ptr_factory_(this) {
  thread_checker_.DetachFromThread();
  set_enumeration_permission(ENUMERATION_BLOCKED);

  // If the feature is not enabled, just return ALLOWED as it's requested.
  if (!media_permission_) {
    initialized_ = true;
    set_enumeration_permission(ENUMERATION_ALLOWED);
    VLOG(3) << "media_permission is not passed, granting permission";
    return;
  }
}

void FilteringNetworkManager::Initialize() {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(!initialized_);

  initialized_ = true;
  pending_permission_checks_ = 2;

  // Request for media permission asynchronously.
  media_permission_->HasPermission(
      media::MediaPermission::AUDIO_CAPTURE, requesting_origin_,
      base::Bind(&FilteringNetworkManager::OnPermissionStatus, GetWeakPtr()));
  media_permission_->HasPermission(
      media::MediaPermission::VIDEO_CAPTURE, requesting_origin_,
      base::Bind(&FilteringNetworkManager::OnPermissionStatus, GetWeakPtr()));
}

FilteringNetworkManager::~FilteringNetworkManager() {
  DCHECK(thread_checker_.CalledOnValidThread());
  // This helps to catch the case if permission never comes back.
  if (!start_updating_time_.is_null())
    ReportMetrics(false);
}

base::WeakPtr<FilteringNetworkManager> FilteringNetworkManager::GetWeakPtr() {
  return weak_ptr_factory_.GetWeakPtr();
}

void FilteringNetworkManager::StartUpdating() {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(initialized_);

  if (start_updating_time_.is_null()) {
    start_updating_time_ = base::TimeTicks::Now();
    network_manager_->SignalNetworksChanged.connect(
        this, &FilteringNetworkManager::OnNetworksChanged);
  }

  network_manager_->StartUpdating();
  ++start_count_;

  if (sent_first_update_ || should_fire_event())
    FireEventIfStarted();
}

void FilteringNetworkManager::StopUpdating() {
  DCHECK(thread_checker_.CalledOnValidThread());
  network_manager_->StopUpdating();
  DCHECK_GT(start_count_, 0);
  --start_count_;
}

void FilteringNetworkManager::GetNetworks(NetworkList* networks) const {
  DCHECK(thread_checker_.CalledOnValidThread());
  networks->clear();

  if (enumeration_permission() == ENUMERATION_ALLOWED)
    network_manager_->GetNetworks(networks);

  VLOG(3) << "GetNetworks() returns " << networks->size() << " networks.";
}

bool FilteringNetworkManager::GetDefaultLocalAddress(
    int family,
    rtc::IPAddress* ipaddress) const {
  return network_manager_->GetDefaultLocalAddress(family, ipaddress);
}

void FilteringNetworkManager::OnPermissionStatus(bool granted) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK_GT(pending_permission_checks_, 0);
  VLOG(3) << "OnPermissionStatus: " << granted;

  --pending_permission_checks_;

  if (granted)
    set_enumeration_permission(ENUMERATION_ALLOWED);

  if (should_fire_event())
    FireEventIfStarted();
}

void FilteringNetworkManager::OnNetworksChanged() {
  DCHECK(thread_checker_.CalledOnValidThread());
  received_networks_changed_since_last_firing_ = true;
  if (enumeration_permission() == ENUMERATION_ALLOWED)
    FireEventIfStarted();
}

void FilteringNetworkManager::ReportMetrics(bool report_start_latency) {
  if (sent_first_update_)
    return;

  if (report_start_latency)
    ReportTimeToUpdateNetworkList(base::TimeTicks::Now() -
                                  start_updating_time_);

  ReportIPPermissionStatus(GetIPPermissionStatus());
}

IPPermissionStatus FilteringNetworkManager::GetIPPermissionStatus() const {
  if (enumeration_permission() == ENUMERATION_ALLOWED) {
    return media_permission_ ? PERMISSION_GRANTED_WITH_CHECKING
                             : PERMISSION_GRANTED_WITHOUT_CHECKING;
  }

  if (!pending_permission_checks_ &&
      enumeration_permission() == ENUMERATION_BLOCKED) {
    return PERMISSION_DENIED;
  }

  return PERMISSION_UNKNOWN;
}

bool FilteringNetworkManager::should_fire_event() const {
  // We should signal network changes when the permisison is denied and we have
  // never fired any SignalNetworksChanged. Or 2) permission is granted and we
  // have received new SignalNetworksChanged from |network_manager_| yet to
  // be broadcast.
  bool permission_blocked_but_never_fired =
      (GetIPPermissionStatus() == PERMISSION_DENIED) && !sent_first_update_;
  bool permission_allowed_pending_networks_update =
      enumeration_permission() == ENUMERATION_ALLOWED &&
      received_networks_changed_since_last_firing_;

  return permission_blocked_but_never_fired ||
         permission_allowed_pending_networks_update;
}

void FilteringNetworkManager::FireEventIfStarted() {
  if (!start_count_)
    return;

  ReportMetrics(true);

  // Post a task to avoid reentrancy.
  base::ThreadTaskRunnerHandle::Get()->PostTask(
      FROM_HERE, base::Bind(&FilteringNetworkManager::SendNetworksChangedSignal,
                            GetWeakPtr()));

  sent_first_update_ = true;
  received_networks_changed_since_last_firing_ = false;
}

void FilteringNetworkManager::SendNetworksChangedSignal() {
  SignalNetworksChanged();
}

}  // namespace content
