/*
 * 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/sessionmanager.h"
#include "talk/base/common.h"
#include "talk/base/helpers.h"
#include "talk/p2p/base/constants.h"
#include "talk/xmpp/constants.h"
#include "talk/xmpp/jid.h"

namespace cricket {

SessionManager::SessionManager(PortAllocator *allocator, 
                               talk_base::Thread *worker) {
  allocator_ = allocator;
  signaling_thread_ = talk_base::Thread::Current();
  if (worker == NULL) {
    worker_thread_ = talk_base::Thread::Current();
  } else {
    worker_thread_ = worker;
  }
  timeout_ = 50;
}

SessionManager::~SessionManager() {
  // Note: Session::Terminate occurs asynchronously, so it's too late to
  // delete them now.  They better be all gone.
  ASSERT(session_map_.empty());
  //TerminateAll();
}

void SessionManager::AddClient(const std::string& session_type,
                               SessionClient* client) {
  ASSERT(client_map_.find(session_type) == client_map_.end());
  client_map_[session_type] = client;
}

void SessionManager::RemoveClient(const std::string& session_type) {
  ClientMap::iterator iter = client_map_.find(session_type);
  ASSERT(iter != client_map_.end());
  client_map_.erase(iter);
}

SessionClient* SessionManager::GetClient(const std::string& session_type) {
  ClientMap::iterator iter = client_map_.find(session_type);
  return (iter != client_map_.end()) ? iter->second : NULL;
}

Session *SessionManager::CreateSession(const std::string& name,
                                       const std::string& session_type) {
  return CreateSession(name, SessionID(name, CreateRandomId()), session_type,
                       false);
}

Session *SessionManager::CreateSession(
    const std::string &name, const SessionID& id,
    const std::string& session_type, bool received_initiate) {
  SessionClient* client = GetClient(session_type);
  ASSERT(client != NULL);

  Session *session = new Session(this, name, id, session_type, client);
  session_map_[session->id()] = session;
  session->SignalRequestSignaling.connect(
      this, &SessionManager::OnRequestSignaling);
  session->SignalOutgoingMessage.connect(
      this, &SessionManager::OnOutgoingMessage);
  session->SignalErrorMessage.connect(this, &SessionManager::OnErrorMessage);
  SignalSessionCreate(session, received_initiate);
  session->client()->OnSessionCreate(session, received_initiate);
  return session;
}

void SessionManager::DestroySession(Session *session) {
  if (session != NULL) {
    SessionMap::iterator it = session_map_.find(session->id());
    if (it != session_map_.end()) {
      SignalSessionDestroy(session);
      session->client()->OnSessionDestroy(session);
      session_map_.erase(it);
      delete session;
    }
  }
}

Session *SessionManager::GetSession(const SessionID& id) {
  SessionMap::iterator it = session_map_.find(id);
  if (it != session_map_.end())
    return it->second;
  return NULL;
}

void SessionManager::TerminateAll() {
  while (session_map_.begin() != session_map_.end()) {
    Session *session = session_map_.begin()->second;
    session->Terminate();
  }
}

bool SessionManager::IsSessionMessage(const buzz::XmlElement* stanza) {
  if (stanza->Name() != buzz::QN_IQ)
    return false;
  if (!stanza->HasAttr(buzz::QN_TYPE))
    return false;
  if (stanza->Attr(buzz::QN_TYPE) != buzz::STR_SET)
    return false;

  const buzz::XmlElement* session = stanza->FirstNamed(QN_SESSION);
  if (!session)
    return false;
  if (!session->HasAttr(buzz::QN_TYPE))
    return false;
  if (!session->HasAttr(buzz::QN_ID) || !session->HasAttr(QN_INITIATOR))
    return false;

  return true;
}

Session* SessionManager::FindSessionForStanza(const buzz::XmlElement* stanza, 
                                              bool incoming) {
  const buzz::XmlElement* session_xml = stanza->FirstNamed(QN_SESSION);
  ASSERT(session_xml != NULL);

  SessionID id;
  id.set_id_str(session_xml->Attr(buzz::QN_ID));
  id.set_initiator(session_xml->Attr(QN_INITIATOR));

  // Pass this message to the session in question.
  SessionMap::iterator iter = session_map_.find(id);
  if (iter == session_map_.end())
    return NULL;

  Session* session = iter->second;

  // match on "from"? or "to"?
  buzz::QName attr = buzz::QN_TO;
  if (incoming) {
    attr = buzz::QN_FROM;
  }
  buzz::Jid remote(session->remote_name());
  buzz::Jid match(stanza->Attr(attr));
  if (remote == match) {
    return session;
  }
  return NULL;
}

void SessionManager::OnIncomingMessage(const buzz::XmlElement* stanza) {
  ASSERT(stanza->Attr(buzz::QN_TYPE) == buzz::STR_SET);

  Session* session = FindSessionForStanza(stanza, true);
  if (session) {
    session->OnIncomingMessage(stanza);
    return;
  }

  const buzz::XmlElement* session_xml = stanza->FirstNamed(QN_SESSION);
  ASSERT(session_xml != NULL);
  if (session_xml->Attr(buzz::QN_TYPE) == "initiate") {
    std::string session_type = FindClient(session_xml);
    if (session_type.size() == 0) {
      SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify",
                       "unknown session description type", NULL);
    } else {
      SessionID id;
      id.set_id_str(session_xml->Attr(buzz::QN_ID));
      id.set_initiator(session_xml->Attr(QN_INITIATOR));

      session = CreateSession(stanza->Attr(buzz::QN_TO), 
                              id,
                              session_type,  true);
      session->OnIncomingMessage(stanza);

      // If we haven't rejected, and we haven't selected a transport yet,
      // let's do it now.
      if ((session->state() != Session::STATE_SENTREJECT) &&
          (session->state() != Session::STATE_SENTTERMINATE) &&
          (session->transport() == NULL)) {
        session->ChooseTransport(stanza);
      }
    }
    return;
  }

  SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify",
                  "unknown session", NULL);
}

void SessionManager::OnIncomingResponse(const buzz::XmlElement* orig_stanza,
    const buzz::XmlElement* response_stanza) {
  // We don't do anything with the response now.  If we need to we can forward
  // it to the session.
  return;
}

void SessionManager::OnFailedSend(const buzz::XmlElement* orig_stanza, 
                                  const buzz::XmlElement* error_stanza) {
  Session* session = FindSessionForStanza(orig_stanza, false);
  if (session) {
    scoped_ptr<buzz::XmlElement> synthetic_error;
    if (!error_stanza) {
      // A failed send is semantically equivalent to an error response, so we 
      // can just turn the former into the latter.
      synthetic_error.reset(
        CreateErrorMessage(orig_stanza, buzz::QN_STANZA_ITEM_NOT_FOUND, 
                           "cancel", "Recipient did not respond", NULL));
      error_stanza = synthetic_error.get();
    }

    session->OnFailedSend(orig_stanza, error_stanza);
  }
}

std::string SessionManager::FindClient(const buzz::XmlElement* session) {
  for (const buzz::XmlElement* elem = session->FirstElement();
       elem != NULL;
       elem = elem->NextElement()) {
    if (elem->Name().LocalPart() == "description") {
      ClientMap::iterator iter = client_map_.find(elem->Name().Namespace());
      if (iter != client_map_.end())
        return iter->first;
    }
  }
  return "";
}

void SessionManager::SendErrorMessage(const buzz::XmlElement* stanza,
                                      const buzz::QName& name,
                                      const std::string& type,
                                      const std::string& text,
                                      const buzz::XmlElement* extra_info) {
  scoped_ptr<buzz::XmlElement> msg(
      CreateErrorMessage(stanza, name, type, text, extra_info));
  SignalOutgoingMessage(msg.get());
}

buzz::XmlElement* SessionManager::CreateErrorMessage(
    const buzz::XmlElement* stanza,
    const buzz::QName& name,
    const std::string& type,
    const std::string& text,
    const buzz::XmlElement* extra_info) {
  buzz::XmlElement* iq = new buzz::XmlElement(buzz::QN_IQ);
  iq->SetAttr(buzz::QN_TO, stanza->Attr(buzz::QN_FROM));
  iq->SetAttr(buzz::QN_ID, stanza->Attr(buzz::QN_ID));
  iq->SetAttr(buzz::QN_TYPE, "error");

  for (const buzz::XmlElement* elem = stanza->FirstElement();
       elem != NULL;
       elem = elem->NextElement()) {
    iq->AddElement(new buzz::XmlElement(*elem));
  }

  buzz::XmlElement* error = new buzz::XmlElement(buzz::QN_ERROR);
  error->SetAttr(buzz::QN_TYPE, type);
  iq->AddElement(error);

  // If the error name is not in the standard namespace, we have to first add
  // some error from that namespace.
  if (name.Namespace() != buzz::NS_STANZA) {
     error->AddElement(
         new buzz::XmlElement(buzz::QN_STANZA_UNDEFINED_CONDITION));
  }
  error->AddElement(new buzz::XmlElement(name));

  if (extra_info)
    error->AddElement(new buzz::XmlElement(*extra_info));

  if (text.size() > 0) {
    // It's okay to always use English here.  This text is for debugging
    // purposes only.
    buzz::XmlElement* text_elem = new buzz::XmlElement(buzz::QN_STANZA_TEXT);
    text_elem->SetAttr(buzz::QN_XML_LANG, "en");
    text_elem->SetBodyText(text);
    error->AddElement(text_elem);
  }

  // TODO: Should we include error codes as well for SIP compatibility?

  return iq;
}

void SessionManager::OnOutgoingMessage(Session* session,
                                       const buzz::XmlElement* stanza) {
  SignalOutgoingMessage(stanza);
}

void SessionManager::OnErrorMessage(Session* session,
                                    const buzz::XmlElement* stanza,
                                    const buzz::QName& name,
                                    const std::string& type,
                                    const std::string& text,
                                    const buzz::XmlElement* extra_info) {
  SendErrorMessage(stanza, name, type, text, extra_info);
}

void SessionManager::OnSignalingReady() {
  for (SessionMap::iterator it = session_map_.begin();
      it != session_map_.end();
      ++it) {
    it->second->OnSignalingReady();
  }
}

void SessionManager::OnRequestSignaling(Session* session) {
  SignalRequestSignaling();
}

} // namespace cricket
