/*
 * 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 _xmppengine_h_
#define _xmppengine_h_

// also part of the API
#include "talk/xmpp/jid.h"
#include "talk/xmllite/qname.h"
#include "talk/xmllite/xmlelement.h"


namespace buzz {

class XmppEngine;
class SaslHandler;
typedef void * XmppIqCookie;

//! XMPP stanza error codes.
//! Used in XmppEngine.SendStanzaError().
enum XmppStanzaError {
  XSE_BAD_REQUEST,
  XSE_CONFLICT,
  XSE_FEATURE_NOT_IMPLEMENTED,
  XSE_FORBIDDEN,
  XSE_GONE,
  XSE_INTERNAL_SERVER_ERROR,
  XSE_ITEM_NOT_FOUND,
  XSE_JID_MALFORMED,
  XSE_NOT_ACCEPTABLE,
  XSE_NOT_ALLOWED,
  XSE_PAYMENT_REQUIRED,
  XSE_RECIPIENT_UNAVAILABLE,
  XSE_REDIRECT,
  XSE_REGISTRATION_REQUIRED,
  XSE_SERVER_NOT_FOUND,
  XSE_SERVER_TIMEOUT,
  XSE_RESOURCE_CONSTRAINT,
  XSE_SERVICE_UNAVAILABLE,
  XSE_SUBSCRIPTION_REQUIRED,
  XSE_UNDEFINED_CONDITION,
  XSE_UNEXPECTED_REQUEST,
};

// XmppReturnStatus
//    This is used by API functions to synchronously return status.
enum XmppReturnStatus {
  XMPP_RETURN_OK,
  XMPP_RETURN_BADARGUMENT,
  XMPP_RETURN_BADSTATE,
  XMPP_RETURN_PENDING,
  XMPP_RETURN_UNEXPECTED,
  XMPP_RETURN_NOTYETIMPLEMENTED,
};

//! Callback for socket output for an XmppEngine connection.
//! Register via XmppEngine.SetOutputHandler.  An XmppEngine
//! can call back to this handler while it is processing
//! Connect, SendStanza, SendIq, Disconnect, or HandleInput.
class XmppOutputHandler {
public:
  virtual ~XmppOutputHandler() {}

  //! Deliver the specified bytes to the XMPP socket.
  virtual void WriteOutput(const char * bytes, size_t len) = 0;

  //! Initiate TLS encryption on the socket.
  //! The implementation must verify that the SSL
  //! certificate matches the given domainname.
  virtual void StartTls(const std::string & domainname) = 0;

  //! Called when engine wants the connecton closed.
  virtual void CloseConnection() = 0;
};

//! Callback to deliver engine state change notifications
//! to the object managing the engine.
class XmppSessionHandler {
public:
  virtual ~XmppSessionHandler() {}
  //! Called when engine changes state. Argument is new state.
  virtual void OnStateChange(int state) = 0;
};

//! Callback to deliver stanzas to an Xmpp application module.
//! Register via XmppEngine.SetDefaultSessionHandler or via
//! XmppEngine.AddSessionHAndler.  
class XmppStanzaHandler {
public:
  virtual ~XmppStanzaHandler() {}
  //! Process the given stanza.
  //! The handler must return true if it has handled the stanza.
  //! A false return value causes the stanza to be passed on to
  //! the next registered handler.
  virtual bool HandleStanza(const XmlElement * stanza) = 0;
};

//! Callback to deliver iq responses (results and errors).
//! Register while sending an iq via XmppEngine.SendIq.
//! Iq responses are routed to matching XmppIqHandlers in preference
//! to sending to any registered SessionHandlers.
class XmppIqHandler {
public:
  virtual ~XmppIqHandler() {}
  //! Called to handle the iq response.
  //! The response may be either a result or an error, and will have
  //! an 'id' that matches the request and a 'from' that matches the
  //! 'to' of the request.  Called no more than once; once this is
  //! called, the handler is automatically unregistered.
  virtual void IqResponse(XmppIqCookie cookie, const XmlElement * pelStanza) = 0;
};

//! 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 XmppEngine {
public:
  static XmppEngine * Create();
  virtual ~XmppEngine() {}

  //! Error codes. See GetError().
  enum Error {
    ERROR_NONE = 0,         //!< No error
    ERROR_XML,              //!< Malformed XML or encoding error
    ERROR_STREAM,           //!< XMPP stream error - see GetStreamError()
    ERROR_VERSION,          //!< XMPP version error
    ERROR_UNAUTHORIZED,     //!< User is not authorized (rejected credentials)
    ERROR_TLS,              //!< TLS could not be negotiated
    ERROR_AUTH,             //!< Authentication could not be negotiated
    ERROR_BIND,             //!< Resource or session binding could not be negotiated
    ERROR_CONNECTION_CLOSED,//!< Connection closed by output handler.
    ERROR_DOCUMENT_CLOSED,  //!< Closed by </stream:stream>
    ERROR_SOCKET,           //!< Socket error
    ERROR_NETWORK_TIMEOUT,  //!< Some sort of timeout (eg., we never got the roster)
    ERROR_MISSING_USERNAME  //!< User has a Google Account but no nickname
  };

  //! States.  See GetState().
  enum State {
    STATE_NONE = 0,        //!< Nonexistent state
    STATE_START,           //!< Initial state.
    STATE_OPENING,         //!< Exchanging stream headers, authenticating and so on.
    STATE_OPEN,            //!< Authenticated and bound.
    STATE_CLOSED,          //!< Session closed, possibly due to error.
  };

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

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

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

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

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

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

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

  //! Provides different methods for credentials for login.
  //! Takes ownership of this object; deletes when login is done
  virtual XmppReturnStatus SetSaslHandler(SaslHandler * h) = 0;

  //! Sets whether TLS will be used within the connection (default true).
  virtual XmppReturnStatus SetUseTls(bool useTls) = 0;

  //! 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) = 0;

  //! Gets whether TLS will be used within the connection.
  virtual bool GetUseTls() = 0;

  //! 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) = 0;

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

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

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

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

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

  //! The current engine state.
  virtual State GetState() = 0;

  //! Returns true if the connection is encrypted (under TLS)
  virtual bool IsEncrypted() = 0;

  //! The error code.
  //! Consult this after XmppOutputHandler.OnClose().
  virtual Error GetError(int *subcode) = 0;

  //! 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() = 0;

  //! 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() = 0;

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

  enum HandlerLevel {
    HL_NONE = 0,
    HL_PEEK,   //!< Sees messages before all other processing; cannot abort
    HL_SINGLE, //!< Watches for a single message, e.g., by id and sender
    HL_SENDER, //!< Watches for a type of message from a specific sender
    HL_TYPE,   //!< Watches a type of message, e.g., all groupchat msgs
    HL_ALL,    //!< Watches all messages - gets last shot
    HL_COUNT,  //!< Count of handler levels
  };

  //! 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, HandlerLevel level = HL_PEEK) = 0;

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

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

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

  //! 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* pelStanza,
                                  XmppIqHandler* iq_handler,
                                  XmppIqCookie* cookie) = 0;

  //! 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) = 0;


  //! 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) = 0;

  //! 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() = 0;

  //! 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() = 0;

};

}


// Move these to a better location

#define XMPP_FAILED(x)                      \
  ( (x) == buzz::XMPP_RETURN_OK ? false : true)   \


#define XMPP_SUCCEEDED(x)                   \
  ( (x) == buzz::XMPP_RETURN_OK ? true : false)   \

#define IFR(x)                        \
  do {                                \
    xmpp_status = (x);                \
    if (XMPP_FAILED(xmpp_status)) {   \
      return xmpp_status;             \
    }                                 \
  } while (false)                     \


#define IFC(x)                        \
  do {                                \
    xmpp_status = (x);                \
    if (XMPP_FAILED(xmpp_status)) {   \
      goto Cleanup;                   \
    }                                 \
  } while (false)                     \


#endif