/*
 * libjingle
 * Copyright 2004--2005, Google Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *  3. The name of the author may not be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "talk/p2p/base/session.h"
#include "talk/base/common.h"
#include "talk/base/logging.h"
#include "talk/base/helpers.h"
#include "talk/base/scoped_ptr.h"
#include "talk/base/sslstreamadapter.h"
#include "talk/xmpp/constants.h"
#include "talk/xmpp/jid.h"
#include "talk/p2p/base/dtlstransport.h"
#include "talk/p2p/base/p2ptransport.h"
#include "talk/p2p/base/sessionclient.h"
#include "talk/p2p/base/transport.h"
#include "talk/p2p/base/transportchannelproxy.h"
#include "talk/p2p/base/transportinfo.h"

#include "talk/p2p/base/constants.h"

namespace cricket {

bool BadMessage(const buzz::QName type,
                const std::string& text,
                MessageError* err) {
  err->SetType(type);
  err->SetText(text);
  return false;
}

TransportProxy::~TransportProxy() {
  for (ChannelMap::iterator iter = channels_.begin();
       iter != channels_.end(); ++iter) {
    iter->second->SignalDestroyed(iter->second);
    delete iter->second;
  }
}

std::string TransportProxy::type() const {
  return transport_->get()->type();
}

TransportChannel* TransportProxy::GetChannel(int component) {
  return GetChannelProxy(component);
}

TransportChannel* TransportProxy::CreateChannel(
    const std::string& name, int component) {
  ASSERT(GetChannel(component) == NULL);
  ASSERT(!transport_->get()->HasChannel(component));

  // We always create a proxy in case we need to change out the transport later.
  TransportChannelProxy* channel =
      new TransportChannelProxy(content_name(), name, component);
  channels_[component] = channel;

  // If we're already negotiated, create an impl and hook it up to the proxy
  // channel. If we're connecting, create an impl but don't hook it up yet.
  if (negotiated_) {
    SetChannelProxyImpl(component, channel);
  } else if (connecting_) {
    GetOrCreateChannelProxyImpl(component);
  }
  return channel;
}

bool TransportProxy::HasChannel(int component) {
  return transport_->get()->HasChannel(component);
}

void TransportProxy::DestroyChannel(int component) {
  TransportChannel* channel = GetChannel(component);
  if (channel) {
    // If the state of TransportProxy is not NEGOTIATED
    // then TransportChannelProxy and its impl are not
    // connected. Both must be connected before
    // deletion.
    if (!negotiated_) {
      SetChannelProxyImpl(component, GetChannelProxy(component));
    }

    channels_.erase(component);
    channel->SignalDestroyed(channel);
    delete channel;
  }
}

void TransportProxy::ConnectChannels() {
  if (!connecting_) {
    if (!negotiated_) {
      for (ChannelMap::iterator iter = channels_.begin();
           iter != channels_.end(); ++iter) {
        GetOrCreateChannelProxyImpl(iter->first);
      }
    }
    connecting_ = true;
  }
  // TODO(juberti): Right now Transport::ConnectChannels doesn't work if we
  // don't have any channels yet, so we need to allow this method to be called
  // multiple times. Once we fix Transport, we can move this call inside the
  // if (!connecting_) block.
  transport_->get()->ConnectChannels();
}

void TransportProxy::CompleteNegotiation() {
  if (!negotiated_) {
    for (ChannelMap::iterator iter = channels_.begin();
         iter != channels_.end(); ++iter) {
      SetChannelProxyImpl(iter->first, iter->second);
    }
    negotiated_ = true;
  }
}

void TransportProxy::AddSentCandidates(const Candidates& candidates) {
  for (Candidates::const_iterator cand = candidates.begin();
       cand != candidates.end(); ++cand) {
    sent_candidates_.push_back(*cand);
  }
}

void TransportProxy::AddUnsentCandidates(const Candidates& candidates) {
  for (Candidates::const_iterator cand = candidates.begin();
       cand != candidates.end(); ++cand) {
    unsent_candidates_.push_back(*cand);
  }
}

bool TransportProxy::GetChannelNameFromComponent(
    int component, std::string* channel_name) const {
  const TransportChannelProxy* channel = GetChannelProxy(component);
  if (channel == NULL) {
    return false;
  }

  *channel_name = channel->name();
  return true;
}

bool TransportProxy::GetComponentFromChannelName(
    const std::string& channel_name, int* component) const {
  const TransportChannelProxy* channel = GetChannelProxyByName(channel_name);
  if (channel == NULL) {
    return false;
  }

  *component = channel->component();
  return true;
}

TransportChannelProxy* TransportProxy::GetChannelProxy(int component) const {
  ChannelMap::const_iterator iter = channels_.find(component);
  return (iter != channels_.end()) ? iter->second : NULL;
}

TransportChannelProxy* TransportProxy::GetChannelProxyByName(
    const std::string& name) const {
  for (ChannelMap::const_iterator iter = channels_.begin();
       iter != channels_.end();
       ++iter) {
    if (iter->second->name() == name) {
      return iter->second;
    }
  }
  return NULL;
}

TransportChannelImpl* TransportProxy::GetOrCreateChannelProxyImpl(
    int component) {
  TransportChannelImpl* impl = transport_->get()->GetChannel(component);
  if (impl == NULL) {
    impl = transport_->get()->CreateChannel(component);
  }
  return impl;
}

void TransportProxy::SetChannelProxyImpl(
    int component, TransportChannelProxy* transproxy) {
  TransportChannelImpl* impl = GetOrCreateChannelProxyImpl(component);
  ASSERT(impl != NULL);
  transproxy->SetImplementation(impl);
}

// This function muxes |this| onto |target| by repointing |this| at
// |target|'s transport and setting our TransportChannelProxies
// to point to |target|'s underlying implementations.
bool TransportProxy::SetupMux(TransportProxy* target) {
  // Bail out if there's nothing to do.
  if (transport_ == target->transport_) {
    return true;
  }

  // Run through all channels and remove any non-rtp transport channels before
  // setting target transport channels.
  for (ChannelMap::const_iterator iter = channels_.begin();
       iter != channels_.end(); ++iter) {
    if (!target->transport_->get()->HasChannel(iter->first)) {
      // Remove if channel doesn't exist in |transport_|.
      iter->second->SetImplementation(NULL);
    } else {
      // Replace the impl for all the TransportProxyChannels with the channels
      // from |target|'s transport. Fail if there's not an exact match.
      iter->second->SetImplementation(
          target->transport_->get()->CreateChannel(iter->first));
    }
  }

  // Now replace our transport. Must happen afterwards because
  // it deletes all impls as a side effect.
  transport_ = target->transport_;
  transport_->get()->SignalCandidatesReady.connect(
      this, &TransportProxy::OnTransportCandidatesReady);
  set_candidates_allocated(target->candidates_allocated());
  return true;
}

void TransportProxy::SetIceRole(IceRole role) {
  transport_->get()->SetIceRole(role);
}

bool TransportProxy::SetLocalTransportDescription(
    const TransportDescription& description, ContentAction action) {
  // If this is an answer, finalize the negotiation.
  if (action == CA_ANSWER) {
    CompleteNegotiation();
  }
  return transport_->get()->SetLocalTransportDescription(description, action);
}

bool TransportProxy::SetRemoteTransportDescription(
    const TransportDescription& description, ContentAction action) {
  // If this is an answer, finalize the negotiation.
  if (action == CA_ANSWER) {
    CompleteNegotiation();
  }
  return transport_->get()->SetRemoteTransportDescription(description, action);
}

void TransportProxy::OnSignalingReady() {
  // If we're starting a new allocation sequence, reset our state.
  set_candidates_allocated(false);
  transport_->get()->OnSignalingReady();
}

bool TransportProxy::OnRemoteCandidates(const Candidates& candidates,
                                        std::string* error) {
  // Ensure the transport is negotiated before handling candidates.
  // TODO(juberti): Remove this once everybody calls SetLocalTD.
  CompleteNegotiation();

  // Verify each candidate before passing down to transport layer.
  for (Candidates::const_iterator cand = candidates.begin();
       cand != candidates.end(); ++cand) {
    if (!transport_->get()->VerifyCandidate(*cand, error))
      return false;
    if (!HasChannel(cand->component())) {
      *error = "Candidate has unknown component: " + cand->ToString() +
               " for content: " + content_name_;
      return false;
    }
  }
  transport_->get()->OnRemoteCandidates(candidates);
  return true;
}

void TransportProxy::SetIdentity(
    talk_base::SSLIdentity* identity) {
  transport_->get()->SetIdentity(identity);
}

std::string BaseSession::StateToString(State state) {
  switch (state) {
    case Session::STATE_INIT:
      return "STATE_INIT";
    case Session::STATE_SENTINITIATE:
      return "STATE_SENTINITIATE";
    case Session::STATE_RECEIVEDINITIATE:
      return "STATE_RECEIVEDINITIATE";
    case Session::STATE_SENTPRACCEPT:
      return "STATE_SENTPRACCEPT";
    case Session::STATE_SENTACCEPT:
      return "STATE_SENTACCEPT";
    case Session::STATE_RECEIVEDPRACCEPT:
      return "STATE_RECEIVEDPRACCEPT";
    case Session::STATE_RECEIVEDACCEPT:
      return "STATE_RECEIVEDACCEPT";
    case Session::STATE_SENTMODIFY:
      return "STATE_SENTMODIFY";
    case Session::STATE_RECEIVEDMODIFY:
      return "STATE_RECEIVEDMODIFY";
    case Session::STATE_SENTREJECT:
      return "STATE_SENTREJECT";
    case Session::STATE_RECEIVEDREJECT:
      return "STATE_RECEIVEDREJECT";
    case Session::STATE_SENTREDIRECT:
      return "STATE_SENTREDIRECT";
    case Session::STATE_SENTTERMINATE:
      return "STATE_SENTTERMINATE";
    case Session::STATE_RECEIVEDTERMINATE:
      return "STATE_RECEIVEDTERMINATE";
    case Session::STATE_INPROGRESS:
      return "STATE_INPROGRESS";
    case Session::STATE_DEINIT:
      return "STATE_DEINIT";
    default:
      break;
  }
  return "STATE_" + talk_base::ToString(state);
}

BaseSession::BaseSession(talk_base::Thread* signaling_thread,
                         talk_base::Thread* worker_thread,
                         PortAllocator* port_allocator,
                         const std::string& sid,
                         const std::string& content_type,
                         bool initiator)
    : state_(STATE_INIT),
      error_(ERROR_NONE),
      signaling_thread_(signaling_thread),
      worker_thread_(worker_thread),
      port_allocator_(port_allocator),
      sid_(sid),
      content_type_(content_type),
      transport_type_(NS_GINGLE_P2P),
      initiator_(initiator),
      identity_(NULL),
      local_description_(NULL),
      remote_description_(NULL),
      ice_tiebreaker_(talk_base::CreateRandomId64()),
      role_switch_(false) {
  ASSERT(signaling_thread->IsCurrent());
}

BaseSession::~BaseSession() {
  ASSERT(signaling_thread()->IsCurrent());

  ASSERT(state_ != STATE_DEINIT);
  LogState(state_, STATE_DEINIT);
  state_ = STATE_DEINIT;
  SignalState(this, state_);

  for (TransportMap::iterator iter = transports_.begin();
       iter != transports_.end(); ++iter) {
    delete iter->second;
  }

  delete remote_description_;
  delete local_description_;
}

bool BaseSession::SetIdentity(talk_base::SSLIdentity* identity) {
  if (identity_)
    return false;
  identity_ = identity;
  for (TransportMap::iterator iter = transports_.begin();
       iter != transports_.end(); ++iter) {
    iter->second->SetIdentity(identity_);
  }
  return true;
}

bool BaseSession::PushdownTransportDescription(ContentSource source,
                                               ContentAction action) {
  if (source == CS_LOCAL) {
    return PushdownLocalTransportDescription(local_description_, action);
  }
  return PushdownRemoteTransportDescription(remote_description_, action);
}

bool BaseSession::PushdownLocalTransportDescription(
    const SessionDescription* sdesc,
    ContentAction action) {
  // Update the Transports with the right information, and trigger them to
  // start connecting.
  for (TransportMap::iterator iter = transports_.begin();
       iter != transports_.end(); ++iter) {
    // If no transport info was in this session description, ret == false
    // and we just skip this one.
    TransportDescription tdesc;
    bool ret = GetTransportDescription(
        sdesc, iter->second->content_name(), &tdesc);
    if (ret) {
      if (!iter->second->SetLocalTransportDescription(tdesc, action)) {
        return false;
      }

      iter->second->ConnectChannels();
    }
  }

  return true;
}

bool BaseSession::PushdownRemoteTransportDescription(
    const SessionDescription* sdesc,
    ContentAction action) {
  // Update the Transports with the right information.
  for (TransportMap::iterator iter = transports_.begin();
       iter != transports_.end(); ++iter) {
    TransportDescription tdesc;

    // If no transport info was in this session description, ret == false
    // and we just skip this one.
    bool ret = GetTransportDescription(
        sdesc, iter->second->content_name(), &tdesc);
    if (ret) {
      if (!iter->second->SetRemoteTransportDescription(tdesc, action)) {
        return false;
      }
    }
  }

  return true;
}

TransportChannel* BaseSession::CreateChannel(const std::string& content_name,
                                             const std::string& channel_name,
                                             int component) {
  // We create the proxy "on demand" here because we need to support
  // creating channels at any time, even before we send or receive
  // initiate messages, which is before we create the transports.
  TransportProxy* transproxy = GetOrCreateTransportProxy(content_name);
  return transproxy->CreateChannel(channel_name, component);
}

TransportChannel* BaseSession::GetChannel(const std::string& content_name,
                                          int component) {
  TransportProxy* transproxy = GetTransportProxy(content_name);
  if (transproxy == NULL)
    return NULL;
  else
    return transproxy->GetChannel(component);
}

void BaseSession::DestroyChannel(const std::string& content_name,
                                 int component) {
  TransportProxy* transproxy = GetTransportProxy(content_name);
  ASSERT(transproxy != NULL);
  transproxy->DestroyChannel(component);
}

TransportProxy* BaseSession::GetOrCreateTransportProxy(
    const std::string& content_name) {
  TransportProxy* transproxy = GetTransportProxy(content_name);
  if (transproxy)
    return transproxy;

  Transport* transport = CreateTransport(content_name);
  transport->SetIceRole(initiator_ ? ICEROLE_CONTROLLING : ICEROLE_CONTROLLED);
  transport->SetIceTiebreaker(ice_tiebreaker_);
  // TODO: Connect all the Transport signals to TransportProxy
  // then to the BaseSession.
  transport->SignalConnecting.connect(
      this, &BaseSession::OnTransportConnecting);
  transport->SignalWritableState.connect(
      this, &BaseSession::OnTransportWritable);
  transport->SignalRequestSignaling.connect(
      this, &BaseSession::OnTransportRequestSignaling);
  transport->SignalTransportError.connect(
      this, &BaseSession::OnTransportSendError);
  transport->SignalRouteChange.connect(
      this, &BaseSession::OnTransportRouteChange);
  transport->SignalCandidatesAllocationDone.connect(
      this, &BaseSession::OnTransportCandidatesAllocationDone);
  transport->SignalRoleConflict.connect(
      this, &BaseSession::OnRoleConflict);

  transproxy = new TransportProxy(sid_, content_name,
                                  new TransportWrapper(transport));
  transproxy->SignalCandidatesReady.connect(
      this, &BaseSession::OnTransportProxyCandidatesReady);
  transports_[content_name] = transproxy;

  return transproxy;
}

Transport* BaseSession::GetTransport(const std::string& content_name) {
  TransportProxy* transproxy = GetTransportProxy(content_name);
  if (transproxy == NULL)
    return NULL;
  return transproxy->impl();
}

TransportProxy* BaseSession::GetTransportProxy(
    const std::string& content_name) {
  TransportMap::iterator iter = transports_.find(content_name);
  return (iter != transports_.end()) ? iter->second : NULL;
}

TransportProxy* BaseSession::GetTransportProxy(const Transport* transport) {
  for (TransportMap::iterator iter = transports_.begin();
       iter != transports_.end(); ++iter) {
    TransportProxy* transproxy = iter->second;
    if (transproxy->impl() == transport) {
      return transproxy;
    }
  }
  return NULL;
}

TransportProxy* BaseSession::GetFirstTransportProxy() {
  if (transports_.empty())
    return NULL;
  return transports_.begin()->second;
}

void BaseSession::DestroyTransportProxy(
    const std::string& content_name) {
  TransportMap::iterator iter = transports_.find(content_name);
  if (iter != transports_.end()) {
    delete iter->second;
    transports_.erase(content_name);
  }
}

cricket::Transport* BaseSession::CreateTransport(
    const std::string& content_name) {
  ASSERT(transport_type_ == NS_GINGLE_P2P);
  return new cricket::DtlsTransport<P2PTransport>(
      signaling_thread(), worker_thread(), content_name,
      port_allocator(), identity_);
}

bool BaseSession::GetStats(SessionStats* stats) {
  for (TransportMap::iterator iter = transports_.begin();
       iter != transports_.end(); ++iter) {
    std::string proxy_id = iter->second->content_name();
    // We are ignoring not-yet-instantiated transports.
    if (iter->second->impl()) {
      std::string transport_id = iter->second->impl()->content_name();
      stats->proxy_to_transport[proxy_id] = transport_id;
      if (stats->transport_stats.find(transport_id)
          == stats->transport_stats.end()) {
        TransportStats subinfos;
        if (!iter->second->impl()->GetStats(&subinfos)) {
          return false;
        }
        stats->transport_stats[transport_id] = subinfos;
      }
    }
  }
  return true;
}

void BaseSession::SetState(State state) {
  ASSERT(signaling_thread_->IsCurrent());
  if (state != state_) {
    LogState(state_, state);
    state_ = state;
    SignalState(this, state_);
    signaling_thread_->Post(this, MSG_STATE);
  }
  SignalNewDescription();
}

void BaseSession::SetError(Error error) {
  ASSERT(signaling_thread_->IsCurrent());
  if (error != error_) {
    error_ = error;
    SignalError(this, error);
  }
}

void BaseSession::OnSignalingReady() {
  ASSERT(signaling_thread()->IsCurrent());
  for (TransportMap::iterator iter = transports_.begin();
       iter != transports_.end(); ++iter) {
    iter->second->OnSignalingReady();
  }
}

// TODO(juberti): Since PushdownLocalTD now triggers the connection process to
// start, remove this method once everyone calls PushdownLocalTD.
void BaseSession::SpeculativelyConnectAllTransportChannels() {
  // Put all transports into the connecting state.
  for (TransportMap::iterator iter = transports_.begin();
       iter != transports_.end(); ++iter) {
    iter->second->ConnectChannels();
  }
}

bool BaseSession::OnRemoteCandidates(const std::string& content_name,
                                     const Candidates& candidates,
                                     std::string* error) {
  // Give candidates to the appropriate transport, and tell that transport
  // to start connecting, if it's not already doing so.
  TransportProxy* transproxy = GetTransportProxy(content_name);
  if (!transproxy) {
    *error = "Unknown content name " + content_name;
    return false;
  }
  if (!transproxy->OnRemoteCandidates(candidates, error)) {
    return false;
  }
  // TODO(juberti): Remove this call once we can be sure that we always have
  // a local transport description (which will trigger the connection).
  transproxy->ConnectChannels();
  return true;
}

bool BaseSession::MaybeEnableMuxingSupport() {
  // We need both a local and remote description to decide if we should mux.
  if ((state_ == STATE_SENTINITIATE ||
      state_ == STATE_RECEIVEDINITIATE) &&
      ((local_description_ == NULL) ||
      (remote_description_ == NULL))) {
    return false;
  }

  // In order to perform the multiplexing, we need all proxies to be in the
  // negotiated state, i.e. to have implementations underneath.
  // Ensure that this is the case, regardless of whether we are going to mux.
  for (TransportMap::iterator iter = transports_.begin();
       iter != transports_.end(); ++iter) {
    ASSERT(iter->second->negotiated());
    if (!iter->second->negotiated())
      return false;
  }

  // If both sides agree to BUNDLE, mux all the specified contents onto the
  // transport belonging to the first content name in the BUNDLE group.
  // If the contents are already muxed, this will be a no-op.
  // TODO(juberti): Should this check that local and remote have configured
  // BUNDLE the same way?
  bool candidates_allocated = IsCandidateAllocationDone();
  const ContentGroup* local_bundle_group =
      local_description()->GetGroupByName(GROUP_TYPE_BUNDLE);
  const ContentGroup* remote_bundle_group =
      remote_description()->GetGroupByName(GROUP_TYPE_BUNDLE);
  if (local_bundle_group && remote_bundle_group &&
      local_bundle_group->FirstContentName()) {
    const std::string* content_name = local_bundle_group->FirstContentName();
    const ContentInfo* content =
        local_description_->GetContentByName(*content_name);
    ASSERT(content != NULL);
    if (!SetSelectedProxy(content->name, local_bundle_group)) {
      LOG(LS_WARNING) << "Failed to set up BUNDLE";
      return false;
    }

    // If we weren't done gathering before, we might be done now, as a result
    // of enabling mux.
    LOG(LS_INFO) << "Enabling BUNDLE, bundling onto transport: "
                 << *content_name;
    if (!candidates_allocated) {
      MaybeCandidateAllocationDone();
    }
  } else {
    LOG(LS_INFO) << "No BUNDLE information, not bundling.";
  }
  return true;
}

bool BaseSession::SetSelectedProxy(const std::string& content_name,
                                   const ContentGroup* muxed_group) {
  TransportProxy* selected_proxy = GetTransportProxy(content_name);
  if (!selected_proxy) {
    return false;
  }

  ASSERT(selected_proxy->negotiated());
  for (TransportMap::iterator iter = transports_.begin();
       iter != transports_.end(); ++iter) {
    // If content is part of the mux group, then repoint its proxy at the
    // transport object that we have chosen to mux onto. If the proxy
    // is already pointing at the right object, it will be a no-op.
    if (muxed_group->HasContentName(iter->first) &&
        !iter->second->SetupMux(selected_proxy)) {
      return false;
    }
  }
  return true;
}

void BaseSession::OnTransportCandidatesAllocationDone(Transport* transport) {
  // TODO(juberti): This is a clunky way of processing the done signal. Instead,
  // TransportProxy should receive the done signal directly, set its allocated
  // flag internally, and then reissue the done signal to Session.
  // Overall we should make TransportProxy receive *all* the signals from
  // Transport, since this removes the need to manually iterate over all
  // the transports, as is needed to make sure signals are handled properly
  // when BUNDLEing.
#if 0
  ASSERT(!IsCandidateAllocationDone());
#endif
  for (TransportMap::iterator iter = transports_.begin();
       iter != transports_.end(); ++iter) {
    if (iter->second->impl() == transport) {
      iter->second->set_candidates_allocated(true);
    }
  }
  MaybeCandidateAllocationDone();
}

bool BaseSession::IsCandidateAllocationDone() const {
  for (TransportMap::const_iterator iter = transports_.begin();
       iter != transports_.end(); ++iter) {
    if (!iter->second->candidates_allocated())
      return false;
  }
  return true;
}

void BaseSession::MaybeCandidateAllocationDone() {
  if (IsCandidateAllocationDone()) {
    LOG(LS_INFO) << "Candidate gathering is complete.";
    OnCandidatesAllocationDone();
  }
}

void BaseSession::OnRoleConflict() {
  if (role_switch_) {
    LOG(LS_WARNING) << "Repeat of role conflict signal from Transport.";
    return;
  }

  role_switch_ = true;
  for (TransportMap::iterator iter = transports_.begin();
       iter != transports_.end(); ++iter) {
    // Role will be reverse of initial role setting.
    IceRole role = initiator_ ? ICEROLE_CONTROLLED : ICEROLE_CONTROLLING;
    iter->second->SetIceRole(role);
  }
}

void BaseSession::LogState(State old_state, State new_state) {
  LOG(LS_INFO) << "Session:" << id()
               << " Old state:" << StateToString(old_state)
               << " New state:" << StateToString(new_state)
               << " Type:" << content_type()
               << " Transport:" << transport_type();
}

bool BaseSession::GetTransportDescription(const SessionDescription* description,
                                          const std::string& content_name,
                                          TransportDescription* tdesc) {
  if (!description || !tdesc) {
    return false;
  }
  const TransportInfo* transport_info =
      description->GetTransportInfoByName(content_name);
  if (!transport_info) {
    return false;
  }
  *tdesc = transport_info->description;
  return true;
}

void BaseSession::SignalNewDescription() {
  ContentAction action;
  ContentSource source;
  if (!GetContentAction(&action, &source)) {
    return;
  }
  if (source == CS_LOCAL) {
    SignalNewLocalDescription(this, action);
  } else {
    SignalNewRemoteDescription(this, action);
  }
}

bool BaseSession::GetContentAction(ContentAction* action,
                                   ContentSource* source) {
  switch (state_) {
    // new local description
    case STATE_SENTINITIATE:
      *action = CA_OFFER;
      *source = CS_LOCAL;
      break;
    case STATE_SENTPRACCEPT:
      *action = CA_PRANSWER;
      *source = CS_LOCAL;
      break;
    case STATE_SENTACCEPT:
      *action = CA_ANSWER;
      *source = CS_LOCAL;
      break;
    // new remote description
    case STATE_RECEIVEDINITIATE:
      *action = CA_OFFER;
      *source = CS_REMOTE;
      break;
    case STATE_RECEIVEDPRACCEPT:
      *action = CA_PRANSWER;
      *source = CS_REMOTE;
      break;
    case STATE_RECEIVEDACCEPT:
      *action = CA_ANSWER;
      *source = CS_REMOTE;
      break;
    default:
      return false;
  }
  return true;
}

void BaseSession::OnMessage(talk_base::Message *pmsg) {
  switch (pmsg->message_id) {
  case MSG_TIMEOUT:
    // Session timeout has occured.
    SetError(ERROR_TIME);
    break;

  case MSG_STATE:
    switch (state_) {
    case STATE_SENTACCEPT:
    case STATE_RECEIVEDACCEPT:
      SetState(STATE_INPROGRESS);
      break;

    default:
      // Explicitly ignoring some states here.
      break;
    }
    break;
  }
}

Session::Session(SessionManager* session_manager,
                 const std::string& local_name,
                 const std::string& initiator_name,
                 const std::string& sid,
                 const std::string& content_type,
                 SessionClient* client)
    : BaseSession(session_manager->signaling_thread(),
                  session_manager->worker_thread(),
                  session_manager->port_allocator(),
                  sid, content_type, initiator_name == local_name) {
  ASSERT(client != NULL);
  session_manager_ = session_manager;
  local_name_ = local_name;
  initiator_name_ = initiator_name;
  transport_parser_ = new P2PTransportParser();
  client_ = client;
  initiate_acked_ = false;
  current_protocol_ = PROTOCOL_HYBRID;
}

Session::~Session() {
  delete transport_parser_;
}

bool Session::Initiate(const std::string &to,
                       const SessionDescription* sdesc) {
  ASSERT(signaling_thread()->IsCurrent());
  SessionError error;

  // Only from STATE_INIT
  if (state() != STATE_INIT)
    return false;

  // Setup for signaling.
  set_remote_name(to);
  set_local_description(sdesc);
  if (!CreateTransportProxies(GetEmptyTransportInfos(sdesc->contents()),
                              &error)) {
    LOG(LS_ERROR) << "Could not create transports: " << error.text;
    return false;
  }

  if (!SendInitiateMessage(sdesc, &error)) {
    LOG(LS_ERROR) << "Could not send initiate message: " << error.text;
    return false;
  }

  // We need to connect transport proxy and impl here so that we can process
  // the TransportDescriptions.
  SpeculativelyConnectAllTransportChannels();

  PushdownTransportDescription(CS_LOCAL, CA_OFFER);
  SetState(Session::STATE_SENTINITIATE);
  return true;
}

bool Session::Accept(const SessionDescription* sdesc) {
  ASSERT(signaling_thread()->IsCurrent());

  // Only if just received initiate
  if (state() != STATE_RECEIVEDINITIATE)
    return false;

  // Setup for signaling.
  set_local_description(sdesc);

  SessionError error;
  if (!SendAcceptMessage(sdesc, &error)) {
    LOG(LS_ERROR) << "Could not send accept message: " << error.text;
    return false;
  }
  // TODO(juberti): Add BUNDLE support to transport-info messages.
  PushdownTransportDescription(CS_LOCAL, CA_ANSWER);
  MaybeEnableMuxingSupport();  // Enable transport channel mux if supported.
  SetState(Session::STATE_SENTACCEPT);
  return true;
}

bool Session::Reject(const std::string& reason) {
  ASSERT(signaling_thread()->IsCurrent());

  // Reject is sent in response to an initiate or modify, to reject the
  // request
  if (state() != STATE_RECEIVEDINITIATE && state() != STATE_RECEIVEDMODIFY)
    return false;

  SessionError error;
  if (!SendRejectMessage(reason, &error)) {
    LOG(LS_ERROR) << "Could not send reject message: " << error.text;
    return false;
  }

  SetState(STATE_SENTREJECT);
  return true;
}

bool Session::TerminateWithReason(const std::string& reason) {
  ASSERT(signaling_thread()->IsCurrent());

  // Either side can terminate, at any time.
  switch (state()) {
    case STATE_SENTTERMINATE:
    case STATE_RECEIVEDTERMINATE:
      return false;

    case STATE_SENTREJECT:
    case STATE_RECEIVEDREJECT:
      // We don't need to send terminate if we sent or received a reject...
      // it's implicit.
      break;

    default:
      SessionError error;
      if (!SendTerminateMessage(reason, &error)) {
        LOG(LS_ERROR) << "Could not send terminate message: " << error.text;
        return false;
      }
      break;
  }

  SetState(STATE_SENTTERMINATE);
  return true;
}

bool Session::SendInfoMessage(const XmlElements& elems) {
  ASSERT(signaling_thread()->IsCurrent());
  SessionError error;
  if (!SendMessage(ACTION_SESSION_INFO, elems, &error)) {
    LOG(LS_ERROR) << "Could not send info message " << error.text;
    return false;
  }
  return true;
}

bool Session::SendDescriptionInfoMessage(const ContentInfos& contents) {
  XmlElements elems;
  WriteError write_error;
  if (!WriteDescriptionInfo(current_protocol_,
                            contents,
                            GetContentParsers(),
                            &elems, &write_error)) {
    LOG(LS_ERROR) << "Could not write description info message: "
                  << write_error.text;
    return false;
  }
  SessionError error;
  if (!SendMessage(ACTION_DESCRIPTION_INFO, elems, &error)) {
    LOG(LS_ERROR) << "Could not send description info message: "
                  << error.text;
    return false;
  }
  return true;
}

TransportInfos Session::GetEmptyTransportInfos(
    const ContentInfos& contents) const {
  TransportInfos tinfos;
  for (ContentInfos::const_iterator content = contents.begin();
       content != contents.end(); ++content) {
    tinfos.push_back(TransportInfo(content->name,
                                   TransportDescription(transport_type(),
                                                        std::string(),
                                                        std::string())));
  }
  return tinfos;
}

bool Session::OnRemoteCandidates(
    const TransportInfos& tinfos, ParseError* error) {
  for (TransportInfos::const_iterator tinfo = tinfos.begin();
       tinfo != tinfos.end(); ++tinfo) {
    std::string str_error;
    if (!BaseSession::OnRemoteCandidates(
        tinfo->content_name, tinfo->description.candidates, &str_error)) {
      return BadParse(str_error, error);
    }
  }
  return true;
}

bool Session::CreateTransportProxies(const TransportInfos& tinfos,
                                     SessionError* error) {
  for (TransportInfos::const_iterator tinfo = tinfos.begin();
       tinfo != tinfos.end(); ++tinfo) {
    if (tinfo->description.transport_type != transport_type()) {
      error->SetText("No supported transport in offer.");
      return false;
    }

    GetOrCreateTransportProxy(tinfo->content_name);
  }
  return true;
}

TransportParserMap Session::GetTransportParsers() {
  TransportParserMap parsers;
  parsers[transport_type()] = transport_parser_;
  return parsers;
}

CandidateTranslatorMap Session::GetCandidateTranslators() {
  CandidateTranslatorMap translators;
  // NOTE: This technique makes it impossible to parse G-ICE
  // candidates in session-initiate messages because the channels
  // aren't yet created at that point.  Since we don't use candidates
  // in session-initiate messages, we should be OK.  Once we switch to
  // ICE, this translation shouldn't be necessary.
  for (TransportMap::const_iterator iter = transport_proxies().begin();
       iter != transport_proxies().end(); ++iter) {
    translators[iter->first] = iter->second;
  }
  return translators;
}

ContentParserMap Session::GetContentParsers() {
  ContentParserMap parsers;
  parsers[content_type()] = client_;
  // We need to be able parse both RTP-based and SCTP-based Jingle
  // with the same client.
  if (content_type() == NS_JINGLE_RTP) {
    parsers[NS_JINGLE_DRAFT_SCTP] = client_;
  }
  return parsers;
}

void Session::OnTransportRequestSignaling(Transport* transport) {
  ASSERT(signaling_thread()->IsCurrent());
  TransportProxy* transproxy = GetTransportProxy(transport);
  ASSERT(transproxy != NULL);
  if (transproxy) {
    // Reset candidate allocation status for the transport proxy.
    transproxy->set_candidates_allocated(false);
  }
  SignalRequestSignaling(this);
}

void Session::OnTransportConnecting(Transport* transport) {
  // This is an indication that we should begin watching the writability
  // state of the transport.
  OnTransportWritable(transport);
}

void Session::OnTransportWritable(Transport* transport) {
  ASSERT(signaling_thread()->IsCurrent());

  // If the transport is not writable, start a timer to make sure that it
  // becomes writable within a reasonable amount of time.  If it does not, we
  // terminate since we can't actually send data.  If the transport is writable,
  // cancel the timer.  Note that writability transitions may occur repeatedly
  // during the lifetime of the session.
  signaling_thread()->Clear(this, MSG_TIMEOUT);
  if (transport->HasChannels() && !transport->writable()) {
    signaling_thread()->PostDelayed(
        session_manager_->session_timeout() * 1000, this, MSG_TIMEOUT);
  }
}

void Session::OnTransportProxyCandidatesReady(TransportProxy* transproxy,
                                              const Candidates& candidates) {
  ASSERT(signaling_thread()->IsCurrent());
  if (transproxy != NULL) {
    if (initiator() && !initiate_acked_) {
      // TODO: This is to work around server re-ordering
      // messages.  We send the candidates once the session-initiate
      // is acked.  Once we have fixed the server to guarantee message
      // order, we can remove this case.
      transproxy->AddUnsentCandidates(candidates);
    } else {
      if (!transproxy->negotiated()) {
        transproxy->AddSentCandidates(candidates);
      }
      SessionError error;
      if (!SendTransportInfoMessage(transproxy, candidates, &error)) {
        LOG(LS_ERROR) << "Could not send transport info message: "
                      << error.text;
        return;
      }
    }
  }
}

void Session::OnTransportSendError(Transport* transport,
                                   const buzz::XmlElement* stanza,
                                   const buzz::QName& name,
                                   const std::string& type,
                                   const std::string& text,
                                   const buzz::XmlElement* extra_info) {
  ASSERT(signaling_thread()->IsCurrent());
  SignalErrorMessage(this, stanza, name, type, text, extra_info);
}

void Session::OnIncomingMessage(const SessionMessage& msg) {
  ASSERT(signaling_thread()->IsCurrent());
  ASSERT(state() == STATE_INIT || msg.from == remote_name());

  if (current_protocol_== PROTOCOL_HYBRID) {
    if (msg.protocol == PROTOCOL_GINGLE) {
      current_protocol_ = PROTOCOL_GINGLE;
    } else {
      current_protocol_ = PROTOCOL_JINGLE;
    }
  }

  bool valid = false;
  MessageError error;
  switch (msg.type) {
    case ACTION_SESSION_INITIATE:
      valid = OnInitiateMessage(msg, &error);
      break;
    case ACTION_SESSION_INFO:
      valid = OnInfoMessage(msg);
      break;
    case ACTION_SESSION_ACCEPT:
      valid = OnAcceptMessage(msg, &error);
      break;
    case ACTION_SESSION_REJECT:
      valid = OnRejectMessage(msg, &error);
      break;
    case ACTION_SESSION_TERMINATE:
      valid = OnTerminateMessage(msg, &error);
      break;
    case ACTION_TRANSPORT_INFO:
      valid = OnTransportInfoMessage(msg, &error);
      break;
    case ACTION_TRANSPORT_ACCEPT:
      valid = OnTransportAcceptMessage(msg, &error);
      break;
    case ACTION_DESCRIPTION_INFO:
      valid = OnDescriptionInfoMessage(msg, &error);
      break;
    default:
      valid = BadMessage(buzz::QN_STANZA_BAD_REQUEST,
                         "unknown session message type",
                         &error);
  }

  if (valid) {
    SendAcknowledgementMessage(msg.stanza);
  } else {
    SignalErrorMessage(this, msg.stanza, error.type,
                       "modify", error.text, NULL);
  }
}

void Session::OnIncomingResponse(const buzz::XmlElement* orig_stanza,
                                 const buzz::XmlElement* response_stanza,
                                 const SessionMessage& msg) {
  ASSERT(signaling_thread()->IsCurrent());

  if (msg.type == ACTION_SESSION_INITIATE) {
    OnInitiateAcked();
  }
}

void Session::OnInitiateAcked() {
    // TODO: This is to work around server re-ordering
    // messages.  We send the candidates once the session-initiate
    // is acked.  Once we have fixed the server to guarantee message
    // order, we can remove this case.
  if (!initiate_acked_) {
    initiate_acked_ = true;
    SessionError error;
    SendAllUnsentTransportInfoMessages(&error);
  }
}

void Session::OnFailedSend(const buzz::XmlElement* orig_stanza,
                           const buzz::XmlElement* error_stanza) {
  ASSERT(signaling_thread()->IsCurrent());

  SessionMessage msg;
  ParseError parse_error;
  if (!ParseSessionMessage(orig_stanza, &msg, &parse_error)) {
    LOG(LS_ERROR) << "Error parsing failed send: " << parse_error.text
                  << ":" << orig_stanza;
    return;
  }

  // If the error is a session redirect, call OnRedirectError, which will
  // continue the session with a new remote JID.
  SessionRedirect redirect;
  if (FindSessionRedirect(error_stanza, &redirect)) {
    SessionError error;
    if (!OnRedirectError(redirect, &error)) {
      // TODO: Should we send a message back?  The standard
      // says nothing about it.
      LOG(LS_ERROR) << "Failed to redirect: " << error.text;
      SetError(ERROR_RESPONSE);
    }
    return;
  }

  std::string error_type = "cancel";

  const buzz::XmlElement* error = error_stanza->FirstNamed(buzz::QN_ERROR);
  if (error) {
    error_type = error->Attr(buzz::QN_TYPE);

    LOG(LS_ERROR) << "Session error:\n" << error->Str() << "\n"
                  << "in response to:\n" << orig_stanza->Str();
  } else {
    // don't crash if <error> is missing
    LOG(LS_ERROR) << "Session error without <error/> element, ignoring";
    return;
  }

  if (msg.type == ACTION_TRANSPORT_INFO) {
    // Transport messages frequently generate errors because they are sent right
    // when we detect a network failure.  For that reason, we ignore such
    // errors, because if we do not establish writability again, we will
    // terminate anyway.  The exceptions are transport-specific error tags,
    // which we pass on to the respective transport.
  } else if ((error_type != "continue") && (error_type != "wait")) {
    // We do not set an error if the other side said it is okay to continue
    // (possibly after waiting).  These errors can be ignored.
    SetError(ERROR_RESPONSE);
  }
}

bool Session::OnInitiateMessage(const SessionMessage& msg,
                                MessageError* error) {
  if (!CheckState(STATE_INIT, error))
    return false;

  SessionInitiate init;
  if (!ParseSessionInitiate(msg.protocol, msg.action_elem,
                            GetContentParsers(), GetTransportParsers(),
                            GetCandidateTranslators(),
                            &init, error))
    return false;

  SessionError session_error;
  if (!CreateTransportProxies(init.transports, &session_error)) {
    return BadMessage(buzz::QN_STANZA_NOT_ACCEPTABLE,
                      session_error.text, error);
  }

  set_remote_name(msg.from);
  set_initiator_name(msg.initiator);
  set_remote_description(new SessionDescription(init.ClearContents(),
                                                init.transports,
                                                init.groups));
  // Updating transport with TransportDescription.
  PushdownTransportDescription(CS_REMOTE, CA_OFFER);
  SetState(STATE_RECEIVEDINITIATE);

  // Users of Session may listen to state change and call Reject().
  if (state() != STATE_SENTREJECT) {
    if (!OnRemoteCandidates(init.transports, error))
      return false;

    // TODO(juberti): Auto-generate and push down the local transport answer.
    // This is necessary for trickling to work with RFC 5245 ICE.
  }
  return true;
}

bool Session::OnAcceptMessage(const SessionMessage& msg, MessageError* error) {
  if (!CheckState(STATE_SENTINITIATE, error))
    return false;

  SessionAccept accept;
  if (!ParseSessionAccept(msg.protocol, msg.action_elem,
                          GetContentParsers(), GetTransportParsers(),
                          GetCandidateTranslators(),
                          &accept, error)) {
    return false;
  }

  // If we get an accept, we can assume the initiate has been
  // received, even if we haven't gotten an IQ response.
  OnInitiateAcked();

  set_remote_description(new SessionDescription(accept.ClearContents(),
                                                accept.transports,
                                                accept.groups));
  // Updating transport with TransportDescription.
  PushdownTransportDescription(CS_REMOTE, CA_ANSWER);
  MaybeEnableMuxingSupport();  // Enable transport channel mux if supported.
  SetState(STATE_RECEIVEDACCEPT);

  if (!OnRemoteCandidates(accept.transports, error))
    return false;

  return true;
}

bool Session::OnRejectMessage(const SessionMessage& msg, MessageError* error) {
  if (!CheckState(STATE_SENTINITIATE, error))
    return false;

  SetState(STATE_RECEIVEDREJECT);
  return true;
}

bool Session::OnInfoMessage(const SessionMessage& msg) {
  SignalInfoMessage(this, msg.action_elem);
  return true;
}

bool Session::OnTerminateMessage(const SessionMessage& msg,
                                 MessageError* error) {
  SessionTerminate term;
  if (!ParseSessionTerminate(msg.protocol, msg.action_elem, &term, error))
    return false;

  SignalReceivedTerminateReason(this, term.reason);
  if (term.debug_reason != buzz::STR_EMPTY) {
    LOG(LS_VERBOSE) << "Received error on call: " << term.debug_reason;
  }

  SetState(STATE_RECEIVEDTERMINATE);
  return true;
}

bool Session::OnTransportInfoMessage(const SessionMessage& msg,
                                     MessageError* error) {
  TransportInfos tinfos;
  if (!ParseTransportInfos(msg.protocol, msg.action_elem,
                           initiator_description()->contents(),
                           GetTransportParsers(), GetCandidateTranslators(),
                           &tinfos, error))
    return false;

  if (!OnRemoteCandidates(tinfos, error))
    return false;

  return true;
}

bool Session::OnTransportAcceptMessage(const SessionMessage& msg,
                                       MessageError* error) {
  // TODO: Currently here only for compatibility with
  // Gingle 1.1 clients (notably, Google Voice).
  return true;
}

bool Session::OnDescriptionInfoMessage(const SessionMessage& msg,
                              MessageError* error) {
  if (!CheckState(STATE_INPROGRESS, error))
    return false;

  DescriptionInfo description_info;
  if (!ParseDescriptionInfo(msg.protocol, msg.action_elem,
                            GetContentParsers(), GetTransportParsers(),
                            GetCandidateTranslators(),
                            &description_info, error)) {
    return false;
  }

  ContentInfos& updated_contents = description_info.contents;

  // TODO: Currently, reflector sends back
  // video stream updates even for an audio-only call, which causes
  // this to fail.  Put this back once reflector is fixed.
  //
  // ContentInfos::iterator it;
  // First, ensure all updates are valid before modifying remote_description_.
  // for (it = updated_contents.begin(); it != updated_contents.end(); ++it) {
  //   if (remote_description()->GetContentByName(it->name) == NULL) {
  //     return false;
  //   }
  // }

  // TODO: We used to replace contents from an update, but
  // that no longer works with partial updates.  We need to figure out
  // a way to merge patial updates into contents.  For now, users of
  // Session should listen to SignalRemoteDescriptionUpdate and handle
  // updates.  They should not expect remote_description to be the
  // latest value.
  //
  // for (it = updated_contents.begin(); it != updated_contents.end(); ++it) {
  //     remote_description()->RemoveContentByName(it->name);
  //     remote_description()->AddContent(it->name, it->type, it->description);
  //   }
  // }

  SignalRemoteDescriptionUpdate(this, updated_contents);
  return true;
}

bool BareJidsEqual(const std::string& name1,
                   const std::string& name2) {
  buzz::Jid jid1(name1);
  buzz::Jid jid2(name2);

  return jid1.IsValid() && jid2.IsValid() && jid1.BareEquals(jid2);
}

bool Session::OnRedirectError(const SessionRedirect& redirect,
                              SessionError* error) {
  MessageError message_error;
  if (!CheckState(STATE_SENTINITIATE, &message_error)) {
    return BadWrite(message_error.text, error);
  }

  if (!BareJidsEqual(remote_name(), redirect.target))
    return BadWrite("Redirection not allowed: must be the same bare jid.",
                    error);

  // When we receive a redirect, we point the session at the new JID
  // and resend the candidates.
  set_remote_name(redirect.target);
  return (SendInitiateMessage(local_description(), error) &&
          ResendAllTransportInfoMessages(error));
}

bool Session::CheckState(State expected, MessageError* error) {
  if (state() != expected) {
    // The server can deliver messages out of order/repeated for various
    // reasons. For example, if the server does not recive our iq response,
    // it could assume that the iq it sent was lost, and will then send
    // it again. Ideally, we should implement reliable messaging with
    // duplicate elimination.
    return BadMessage(buzz::QN_STANZA_NOT_ALLOWED,
                      "message not allowed in current state",
                      error);
  }
  return true;
}

void Session::SetError(Error error) {
  BaseSession::SetError(error);
  if (error != ERROR_NONE)
    signaling_thread()->Post(this, MSG_ERROR);
}

void Session::OnMessage(talk_base::Message* pmsg) {
  // preserve this because BaseSession::OnMessage may modify it
  State orig_state = state();

  BaseSession::OnMessage(pmsg);

  switch (pmsg->message_id) {
  case MSG_ERROR:
    TerminateWithReason(STR_TERMINATE_ERROR);
    break;

  case MSG_STATE:
    switch (orig_state) {
    case STATE_SENTREJECT:
    case STATE_RECEIVEDREJECT:
      // Assume clean termination.
      Terminate();
      break;

    case STATE_SENTTERMINATE:
    case STATE_RECEIVEDTERMINATE:
      session_manager_->DestroySession(this);
      break;

    default:
      // Explicitly ignoring some states here.
      break;
    }
    break;
  }
}

bool Session::SendInitiateMessage(const SessionDescription* sdesc,
                                  SessionError* error) {
  SessionInitiate init;
  init.contents = sdesc->contents();
  init.transports = GetEmptyTransportInfos(init.contents);
  init.groups = sdesc->groups();
  return SendMessage(ACTION_SESSION_INITIATE, init, error);
}

bool Session::WriteSessionAction(
    SignalingProtocol protocol, const SessionInitiate& init,
    XmlElements* elems, WriteError* error) {
  return WriteSessionInitiate(protocol, init.contents, init.transports,
                              GetContentParsers(), GetTransportParsers(),
                              GetCandidateTranslators(), init.groups,
                              elems, error);
}

bool Session::SendAcceptMessage(const SessionDescription* sdesc,
                                SessionError* error) {
  XmlElements elems;
  if (!WriteSessionAccept(current_protocol_,
                          sdesc->contents(),
                          GetEmptyTransportInfos(sdesc->contents()),
                          GetContentParsers(), GetTransportParsers(),
                          GetCandidateTranslators(), sdesc->groups(),
                          &elems, error)) {
    return false;
  }
  return SendMessage(ACTION_SESSION_ACCEPT, elems, error);
}

bool Session::SendRejectMessage(const std::string& reason,
                                SessionError* error) {
  SessionTerminate term(reason);
  return SendMessage(ACTION_SESSION_REJECT, term, error);
}

bool Session::SendTerminateMessage(const std::string& reason,
                                   SessionError* error) {
  SessionTerminate term(reason);
  return SendMessage(ACTION_SESSION_TERMINATE, term, error);
}

bool Session::WriteSessionAction(SignalingProtocol protocol,
                                 const SessionTerminate& term,
                                 XmlElements* elems, WriteError* error) {
  WriteSessionTerminate(protocol, term, elems);
  return true;
}

bool Session::SendTransportInfoMessage(const TransportInfo& tinfo,
                                       SessionError* error) {
  return SendMessage(ACTION_TRANSPORT_INFO, tinfo, error);
}

bool Session::SendTransportInfoMessage(const TransportProxy* transproxy,
                                       const Candidates& candidates,
                                       SessionError* error) {
  return SendTransportInfoMessage(TransportInfo(transproxy->content_name(),
      TransportDescription(transproxy->type(), std::vector<std::string>(),
                           std::string(), std::string(), ICEMODE_FULL,
                           CONNECTIONROLE_NONE, NULL, candidates)), error);
}

bool Session::WriteSessionAction(SignalingProtocol protocol,
                                 const TransportInfo& tinfo,
                                 XmlElements* elems, WriteError* error) {
  TransportInfos tinfos;
  tinfos.push_back(tinfo);
  return WriteTransportInfos(protocol, tinfos,
                             GetTransportParsers(), GetCandidateTranslators(),
                             elems, error);
}

bool Session::ResendAllTransportInfoMessages(SessionError* error) {
  for (TransportMap::const_iterator iter = transport_proxies().begin();
       iter != transport_proxies().end(); ++iter) {
    TransportProxy* transproxy = iter->second;
    if (transproxy->sent_candidates().size() > 0) {
      if (!SendTransportInfoMessage(
              transproxy, transproxy->sent_candidates(), error)) {
        LOG(LS_ERROR) << "Could not resend transport info messages: "
                      << error->text;
        return false;
      }
      transproxy->ClearSentCandidates();
    }
  }
  return true;
}

bool Session::SendAllUnsentTransportInfoMessages(SessionError* error) {
  for (TransportMap::const_iterator iter = transport_proxies().begin();
       iter != transport_proxies().end(); ++iter) {
    TransportProxy* transproxy = iter->second;
    if (transproxy->unsent_candidates().size() > 0) {
      if (!SendTransportInfoMessage(
              transproxy, transproxy->unsent_candidates(), error)) {
        LOG(LS_ERROR) << "Could not send unsent transport info messages: "
                      << error->text;
        return false;
      }
      transproxy->ClearUnsentCandidates();
    }
  }
  return true;
}

bool Session::SendMessage(ActionType type, const XmlElements& action_elems,
                          SessionError* error) {
  talk_base::scoped_ptr<buzz::XmlElement> stanza(
      new buzz::XmlElement(buzz::QN_IQ));

  SessionMessage msg(current_protocol_, type, id(), initiator_name());
  msg.to = remote_name();
  WriteSessionMessage(msg, action_elems, stanza.get());

  SignalOutgoingMessage(this, stanza.get());
  return true;
}

template <typename Action>
bool Session::SendMessage(ActionType type, const Action& action,
                          SessionError* error) {
  talk_base::scoped_ptr<buzz::XmlElement> stanza(
      new buzz::XmlElement(buzz::QN_IQ));
  if (!WriteActionMessage(type, action, stanza.get(), error))
    return false;

  SignalOutgoingMessage(this, stanza.get());
  return true;
}

template <typename Action>
bool Session::WriteActionMessage(ActionType type, const Action& action,
                                 buzz::XmlElement* stanza,
                                 WriteError* error) {
  if (current_protocol_ == PROTOCOL_HYBRID) {
    if (!WriteActionMessage(PROTOCOL_JINGLE, type, action, stanza, error))
      return false;
    if (!WriteActionMessage(PROTOCOL_GINGLE, type, action, stanza, error))
      return false;
  } else {
    if (!WriteActionMessage(current_protocol_, type, action, stanza, error))
      return false;
  }
  return true;
}

template <typename Action>
bool Session::WriteActionMessage(SignalingProtocol protocol,
                                 ActionType type, const Action& action,
                                 buzz::XmlElement* stanza, WriteError* error) {
  XmlElements action_elems;
  if (!WriteSessionAction(protocol, action, &action_elems, error))
    return false;

  SessionMessage msg(protocol, type, id(), initiator_name());
  msg.to = remote_name();

  WriteSessionMessage(msg, action_elems, stanza);
  return true;
}

void Session::SendAcknowledgementMessage(const buzz::XmlElement* stanza) {
  talk_base::scoped_ptr<buzz::XmlElement> ack(
      new buzz::XmlElement(buzz::QN_IQ));
  ack->SetAttr(buzz::QN_TO, remote_name());
  ack->SetAttr(buzz::QN_ID, stanza->Attr(buzz::QN_ID));
  ack->SetAttr(buzz::QN_TYPE, "result");

  SignalOutgoingMessage(this, ack.get());
}

}  // namespace cricket
