/*
 * 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.
 */

#ifndef TALK_XMPP_XMPPENGINEIMPL_H_
#define TALK_XMPP_XMPPENGINEIMPL_H_

#include <sstream>
#include <vector>
#include "talk/xmpp/xmppengine.h"
#include "talk/xmpp/xmppstanzaparser.h"

namespace buzz {

class XmppLoginTask;
class XmppEngine;
class XmppIqEntry;
class SaslHandler;
class SaslMechanism;

//! The XMPP connection engine.
//! This engine implements the client side of the 'core' XMPP protocol.
//! To use it, register an XmppOutputHandler to handle socket output
//! and pass socket input to HandleInput.  Then application code can
//! set up the connection with a user, password, and other settings,
//! and then call Connect() to initiate the connection.
//! An application can listen for events and receive stanzas by
//! registering an XmppStanzaHandler via AddStanzaHandler().
class XmppEngineImpl : public XmppEngine {
 public:
  XmppEngineImpl();
  virtual ~XmppEngineImpl();

  // SOCKET INPUT AND OUTPUT ------------------------------------------------

  //! Registers the handler for socket output
  virtual XmppReturnStatus SetOutputHandler(XmppOutputHandler *pxoh);

  //! Provides socket input to the engine
  virtual XmppReturnStatus HandleInput(const char* bytes, size_t len);

  //! Advises the engine that the socket has closed
  virtual XmppReturnStatus ConnectionClosed(int subcode);

  // SESSION SETUP ---------------------------------------------------------

  //! Indicates the (bare) JID for the user to use.
  virtual XmppReturnStatus SetUser(const Jid& jid);

  //! Get the login (bare) JID.
  virtual const Jid& GetUser();

  //! Indicates the autentication to use.  Takes ownership of the object.
  virtual XmppReturnStatus SetSaslHandler(SaslHandler* sasl_handler);

  //! Sets whether TLS will be used within the connection (default true).
  virtual XmppReturnStatus SetTls(TlsOptions use_tls);

  //! Sets an alternate domain from which we allows TLS certificates.
  //! This is for use in the case where a we want to allow a proxy to
  //! serve up its own certificate rather than one owned by the underlying
  //! domain.
  virtual XmppReturnStatus SetTlsServer(const std::string& proxy_hostname,
                                        const std::string& proxy_domain);

  //! Gets whether TLS will be used within the connection.
  virtual TlsOptions GetTls();

  //! Sets the request resource name, if any (optional).
  //! Note that the resource name may be overridden by the server; after
  //! binding, the actual resource name is available as part of FullJid().
  virtual XmppReturnStatus SetRequestedResource(const std::string& resource);

  //! Gets the request resource name.
  virtual const std::string& GetRequestedResource();

  //! Sets language
  virtual void SetLanguage(const std::string& lang) {
    lang_ = lang;
  }

  // SESSION MANAGEMENT ---------------------------------------------------

  //! Set callback for state changes.
  virtual XmppReturnStatus SetSessionHandler(XmppSessionHandler* handler);

  //! Initiates the XMPP connection.
  //! After supplying connection settings, call this once to initiate,
  //! (optionally) encrypt, authenticate, and bind the connection.
  virtual XmppReturnStatus Connect();

  //! The current engine state.
  virtual State GetState() { return state_; }

  //! Returns true if the connection is encrypted (under TLS)
  virtual bool IsEncrypted() { return encrypted_; }

  //! The error code.
  //! Consult this after XmppOutputHandler.OnClose().
  virtual Error GetError(int *subcode) {
     if (subcode) {
       *subcode = subcode_;
     }
     return error_code_;
  }

  //! The stream:error stanza, when the error is XmppEngine::ERROR_STREAM.
  //! Notice the stanza returned is owned by the XmppEngine and
  //! is deleted when the engine is destroyed.
  virtual const XmlElement* GetStreamError() { return stream_error_.get(); }

  //! Closes down the connection.
  //! Sends CloseConnection to output, and disconnects and registered
  //! session handlers.  After Disconnect completes, it is guaranteed
  //! that no further callbacks will be made.
  virtual XmppReturnStatus Disconnect();

  // APPLICATION USE -------------------------------------------------------

  //! Adds a listener for session events.
  //! Stanza delivery is chained to session handlers; the first to
  //! return 'true' is the last to get each stanza.
  virtual XmppReturnStatus AddStanzaHandler(XmppStanzaHandler* handler,
                                            XmppEngine::HandlerLevel level);

  //! Removes a listener for session events.
  virtual XmppReturnStatus RemoveStanzaHandler(XmppStanzaHandler* handler);

  //! Sends a stanza to the server.
  virtual XmppReturnStatus SendStanza(const XmlElement* stanza);

  //! Sends raw text to the server
  virtual XmppReturnStatus SendRaw(const std::string& text);

  //! Sends an iq to the server, and registers a callback for the result.
  //! Returns the cookie passed to the result handler.
  virtual XmppReturnStatus SendIq(const XmlElement* stanza,
                                  XmppIqHandler* iq_handler,
                                  XmppIqCookie* cookie);

  //! Unregisters an iq callback handler given its cookie.
  //! No callback will come to this handler after it's unregistered.
  virtual XmppReturnStatus RemoveIqHandler(XmppIqCookie cookie,
                                      XmppIqHandler** iq_handler);

  //! Forms and sends an error in response to the given stanza.
  //! Swaps to and from, sets type to "error", and adds error information
  //! based on the passed code.  Text is optional and may be STR_EMPTY.
  virtual XmppReturnStatus SendStanzaError(const XmlElement* pelOriginal,
                                           XmppStanzaError code,
                                           const std::string& text);

  //! The fullly bound JID.
  //! This JID is only valid after binding has succeeded.  If the value
  //! is JID_NULL, the binding has not succeeded.
  virtual const Jid& FullJid() { return bound_jid_; }

  //! The next unused iq id for this connection.
  //! Call this when building iq stanzas, to ensure that each iq
  //! gets its own unique id.
  virtual std::string NextId();

 private:
  friend class XmppLoginTask;
  friend class XmppIqEntry;

  void IncomingStanza(const XmlElement *stanza);
  void IncomingStart(const XmlElement *stanza);
  void IncomingEnd(bool isError);

  void InternalSendStart(const std::string& domainName);
  void InternalSendStanza(const XmlElement* stanza);
  std::string ChooseBestSaslMechanism(
      const std::vector<std::string>& mechanisms, bool encrypted);
  SaslMechanism* GetSaslMechanism(const std::string& name);
  void SignalBound(const Jid& fullJid);
  void SignalStreamError(const XmlElement* streamError);
  void SignalError(Error errorCode, int subCode);
  bool HasError();
  void DeleteIqCookies();
  bool HandleIqResponse(const XmlElement* element);
  void StartTls(const std::string& domain);
  void RaiseReset() { raised_reset_ = true; }

  class StanzaParseHandler : public XmppStanzaParseHandler {
   public:
    StanzaParseHandler(XmppEngineImpl* outer) : outer_(outer) {}
    virtual ~StanzaParseHandler() {}

    virtual void StartStream(const XmlElement* stream) {
      outer_->IncomingStart(stream);
    }
    virtual void Stanza(const XmlElement* stanza) {
      outer_->IncomingStanza(stanza);
    }
    virtual void EndStream() {
      outer_->IncomingEnd(false);
    }
    virtual void XmlError() {
      outer_->IncomingEnd(true);
    }

   private:
    XmppEngineImpl* const outer_;
  };

  class EnterExit {
   public:
    EnterExit(XmppEngineImpl* engine);
    ~EnterExit();
   private:
    XmppEngineImpl* engine_;
    State state_;
    Error error_;

  };

  friend class StanzaParseHandler;
  friend class EnterExit;

  StanzaParseHandler stanza_parse_handler_;
  XmppStanzaParser stanza_parser_;

  // state
  int engine_entered_;
  Jid user_jid_;
  std::string password_;
  std::string requested_resource_;
  TlsOptions tls_option_;
  std::string tls_server_hostname_;
  std::string tls_server_domain_;
  talk_base::scoped_ptr<XmppLoginTask> login_task_;
  std::string lang_;

  int next_id_;
  Jid bound_jid_;
  State state_;
  bool encrypted_;
  Error error_code_;
  int subcode_;
  talk_base::scoped_ptr<XmlElement> stream_error_;
  bool raised_reset_;
  XmppOutputHandler* output_handler_;
  XmppSessionHandler* session_handler_;

  XmlnsStack xmlns_stack_;

  typedef std::vector<XmppStanzaHandler*> StanzaHandlerVector;
  talk_base::scoped_ptr<StanzaHandlerVector> stanza_handlers_[HL_COUNT];

  typedef std::vector<XmppIqEntry*> IqEntryVector;
  talk_base::scoped_ptr<IqEntryVector> iq_entries_;

  talk_base::scoped_ptr<SaslHandler> sasl_handler_;

  talk_base::scoped_ptr<std::stringstream> output_;
};

}  // namespace buzz

#endif  // TALK_XMPP_XMPPENGINEIMPL_H_
