// Copyright (c) 2013 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/browser/media/capture/audio_mirroring_manager.h"

#include <algorithm>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/lazy_instance.h"

namespace content {

namespace {

base::LazyInstance<AudioMirroringManager>::Leaky g_audio_mirroring_manager =
    LAZY_INSTANCE_INITIALIZER;

}  // namespace

// static
AudioMirroringManager* AudioMirroringManager::GetInstance() {
  return g_audio_mirroring_manager.Pointer();
}

AudioMirroringManager::AudioMirroringManager() {
  // Only *after* construction, check that AudioMirroringManager is being
  // invoked on the same single thread.
  thread_checker_.DetachFromThread();
}

AudioMirroringManager::~AudioMirroringManager() {}

void AudioMirroringManager::AddDiverter(
    int render_process_id, int render_frame_id, Diverter* diverter) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(diverter);

  // DCHECK(diverter not already in routes_)
#ifndef NDEBUG
  for (StreamRoutes::const_iterator it = routes_.begin();
       it != routes_.end(); ++it) {
    DCHECK_NE(diverter, it->diverter);
  }
#endif
  routes_.push_back(StreamRoutingState(
      SourceFrameRef(render_process_id, render_frame_id),
      diverter));

  // Query existing destinations to see whether to immediately start diverting
  // the stream.
  std::set<SourceFrameRef> candidates;
  candidates.insert(routes_.back().source_render_frame);
  InitiateQueriesToFindNewDestination(NULL, candidates);
}

void AudioMirroringManager::RemoveDiverter(Diverter* diverter) {
  DCHECK(thread_checker_.CalledOnValidThread());

  // Find and remove the entry from the routing table.  If the stream is being
  // diverted, it is stopped.
  for (StreamRoutes::iterator it = routes_.begin(); it != routes_.end(); ++it) {
    if (it->diverter == diverter) {
      ChangeRoute(&(*it), NULL);
      routes_.erase(it);
      return;
    }
  }
  NOTREACHED();
}

void AudioMirroringManager::StartMirroring(MirroringDestination* destination) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(destination);

  // Insert an entry into the set of active mirroring sessions, if this is a
  // previously-unknown destination.
  if (std::find(sessions_.begin(), sessions_.end(), destination) ==
          sessions_.end()) {
    sessions_.push_back(destination);
  }

  // Query the MirroringDestination to see which of the audio streams should be
  // diverted.
  std::set<SourceFrameRef> candidates;
  for (StreamRoutes::const_iterator it = routes_.begin(); it != routes_.end();
       ++it) {
    if (!it->destination || it->destination == destination)
      candidates.insert(it->source_render_frame);
  }
  if (!candidates.empty()) {
    destination->QueryForMatches(
        candidates,
        base::Bind(&AudioMirroringManager::UpdateRoutesToDestination,
                   base::Unretained(this),
                   destination,
                   false));
  }
}

void AudioMirroringManager::StopMirroring(MirroringDestination* destination) {
  DCHECK(thread_checker_.CalledOnValidThread());

  // Stop diverting each audio stream in the mirroring session being stopped.
  // Each stopped stream becomes a candidate to be diverted to another
  // destination.
  std::set<SourceFrameRef> redivert_candidates;
  for (StreamRoutes::iterator it = routes_.begin(); it != routes_.end(); ++it) {
    if (it->destination == destination) {
      ChangeRoute(&(*it), NULL);
      redivert_candidates.insert(it->source_render_frame);
    }
  }
  if (!redivert_candidates.empty())
    InitiateQueriesToFindNewDestination(destination, redivert_candidates);

  // Remove the entry from the set of active mirroring sessions.
  const Destinations::iterator dest_it =
      std::find(sessions_.begin(), sessions_.end(), destination);
  if (dest_it == sessions_.end()) {
    NOTREACHED();
    return;
  }
  sessions_.erase(dest_it);
}

void AudioMirroringManager::InitiateQueriesToFindNewDestination(
    MirroringDestination* old_destination,
    const std::set<SourceFrameRef>& candidates) {
  DCHECK(thread_checker_.CalledOnValidThread());

  for (Destinations::const_iterator it = sessions_.begin();
       it != sessions_.end(); ++it) {
    if (*it != old_destination) {
      (*it)->QueryForMatches(
          candidates,
          base::Bind(&AudioMirroringManager::UpdateRoutesToDestination,
                     base::Unretained(this),
                     *it,
                     true));
    }
  }
}

void AudioMirroringManager::UpdateRoutesToDestination(
    MirroringDestination* destination,
    bool add_only,
    const std::set<SourceFrameRef>& matches) {
  DCHECK(thread_checker_.CalledOnValidThread());

  if (std::find(sessions_.begin(), sessions_.end(), destination) ==
          sessions_.end()) {
    return;  // Query result callback invoked after StopMirroring().
  }

  DVLOG(1) << (add_only ? "Add " : "Replace with ") << matches.size()
           << " routes to MirroringDestination@" << destination;

  // Start/stop diverting based on |matches|.  Any stopped stream becomes a
  // candidate to be diverted to another destination.
  std::set<SourceFrameRef> redivert_candidates;
  for (StreamRoutes::iterator it = routes_.begin(); it != routes_.end(); ++it) {
    if (matches.find(it->source_render_frame) != matches.end()) {
      // Only change the route if the stream is not already being diverted.
      if (!it->destination)
        ChangeRoute(&(*it), destination);
    } else if (!add_only) {
      // Only stop diverting if the stream is currently routed to |destination|.
      if (it->destination == destination) {
        ChangeRoute(&(*it), NULL);
        redivert_candidates.insert(it->source_render_frame);
      }
    }
  }
  if (!redivert_candidates.empty())
    InitiateQueriesToFindNewDestination(destination, redivert_candidates);
}

// static
void AudioMirroringManager::ChangeRoute(
    StreamRoutingState* route, MirroringDestination* new_destination) {
  if (route->destination == new_destination)
    return;  // No change.

  if (route->destination) {
    DVLOG(1) << "Stop diverting render_process_id:render_frame_id="
             << route->source_render_frame.first << ':'
             << route->source_render_frame.second
             << " --> MirroringDestination@" << route->destination;
    route->diverter->StopDiverting();
    route->destination = NULL;
  }

  if (new_destination) {
      DVLOG(1) << "Start diverting of render_process_id:render_frame_id="
               << route->source_render_frame.first << ':'
               << route->source_render_frame.second
               << " --> MirroringDestination@" << new_destination;
    route->diverter->StartDiverting(
        new_destination->AddInput(route->diverter->GetAudioParameters()));
    route->destination = new_destination;
  }
}

AudioMirroringManager::StreamRoutingState::StreamRoutingState(
    const SourceFrameRef& source_frame, Diverter* stream_diverter)
  : source_render_frame(source_frame),
    diverter(stream_diverter),
    destination(NULL) {}

AudioMirroringManager::StreamRoutingState::~StreamRoutingState() {}

}  // namespace content
