/*
 * Copyright (c) 2010-2012 Remko Tronçon
 * Licensed under the GNU General Public License v3.
 * See Documentation/Licenses/GPLv3.txt for more information.
 */

#pragma once

#include <string>
#include <boost/shared_ptr.hpp>

#include <Swiften/Base/API.h>
#include <Swiften/Base/boost_bsignals.h>
#include <Swiften/Entity/Entity.h>
#include <Swiften/JID/JID.h>
#include <Swiften/Client/ClientError.h>
#include <Swiften/Client/ClientOptions.h>
#include <Swiften/Base/SafeByteArray.h>
#include <Swiften/TLS/CertificateWithKey.h>

namespace Swift {
	class ChainedConnector;
	class Message;
	class Presence;
	class Error;
	class IQRouter;
	class TLSContextFactory;
	class ConnectionFactory;
	class Connection;
	class TimerFactory;
	class ClientSession;
	class StanzaChannel;
	class Stanza;
	class SessionStream;
	class CertificateTrustChecker;
	class NetworkFactories;
	class ClientSessionStanzaChannel;

	/** 
	 * The central class for communicating with an XMPP server.
	 *
	 * This class is responsible for setting up the connection with the XMPP
	 * server, authenticating, and initializing the session.
	 *
	 * This class can be used directly in your application, although the Client
	 * subclass provides more functionality and interfaces, and is better suited
	 * for most needs.
	 */
	class SWIFTEN_API CoreClient : public Entity {
		public: 
			/**
			 * Constructs a client for the given JID with the given password.
			 * The given eventLoop will be used to post events to.
			 */
			CoreClient(const JID& jid, const SafeByteArray& password, NetworkFactories* networkFactories);
			~CoreClient();

			/**
			 * Set a client certificate to use for strong authentication with the server.
			 * Ensure that it is of the correct type for the TLS engine in use.
			 * This means, largely, PKCS12Certificate for OpenSSL and CAPICertificate for CAPI.
			 */
			void setCertificate(CertificateWithKey::ref certificate);

			/**
			 * Connects the client to the server.
			 *
			 * After the connection is established, the client will set 
			 * initialize the stream and authenticate.
			 */
			void connect(const ClientOptions& = ClientOptions());

			/**
			 * Disconnects the client from the server.
			 */
			void disconnect();

			/**
			 * Sends a message.
			 */
			void sendMessage(boost::shared_ptr<Message>);

			/**
			 * Sends a presence stanza.
			 */
			void sendPresence(boost::shared_ptr<Presence>);

			/**
			 * Sends raw, unchecked data.
			 */
			void sendData(const std::string& data);

			/**
			 * Returns the IQ router for this client.
			 */
			IQRouter* getIQRouter() const {
				return iqRouter_;
			}

			/**
			 * Checks whether the client is connected to the server,
			 * and stanzas can be sent.
			 */
			bool isAvailable() const;

			/**
			 * Checks whether the client is active.
			 *
			 * A client is active when it is connected or connecting to the server.
			 */
			bool isActive() const;

			/**
			 * Returns the JID of the client. 
			 * After the session was initialized, this returns the bound JID.
			 */
			const JID& getJID() const;

			/**
			 * Checks whether stream management is enabled.
			 *
			 * If stream management is enabled, onStanzaAcked will be
			 * emitted when a stanza is received by the server.
			 *
			 * \see onStanzaAcked
			 */
			bool getStreamManagementEnabled() const;

			/**
			 * Checks whether stream encryption (TLS) is currently active.
			 */
			bool isStreamEncrypted() const;

			StanzaChannel* getStanzaChannel() const;

			/**
			 * Sets the certificate trust checker.
			 *
			 * This checker will be called when the server sends a
			 * TLS certificate that does not validate. If the trust checker
			 * says the certificate is trusted, then connecting will proceed;
			 * if not, the connection will end with an error.
			 */
			void setCertificateTrustChecker(CertificateTrustChecker*);

		public:
			/**
			 * Emitted when the client was disconnected from the network.
			 *
			 * If the connection was due to a non-recoverable error, the type
			 * of error will be passed as a parameter.
			 */
			boost::signal<void (const boost::optional<ClientError>&)> onDisconnected;

			/**
			 * Emitted when the client is connected and authenticated,
			 * and stanzas can be sent.
			 */
			boost::signal<void ()> onConnected;

			/**
			 * Emitted when the client receives data.
			 *
			 * This signal is emitted before the XML data is parsed,
			 * so this data is unformatted.
			 */
			boost::signal<void (const SafeByteArray&)> onDataRead;

			/**
			 * Emitted when the client sends data.
			 *
			 * This signal is emitted after the XML was serialized, and 
			 * is unformatted.
			 */
			boost::signal<void (const SafeByteArray&)> onDataWritten;

			/**
			 * Emitted when a message is received.
			 */
			boost::signal<void (boost::shared_ptr<Message>)> onMessageReceived;

			/**
			 * Emitted when a presence stanza is received.
			 */
			boost::signal<void (boost::shared_ptr<Presence>) > onPresenceReceived;

			/**
			 * Emitted when the server acknowledges receipt of a
			 * stanza (if acknowledgements are available).
			 *
			 * \see getStreamManagementEnabled()
			 */
			boost::signal<void (boost::shared_ptr<Stanza>)> onStanzaAcked;

		protected:
			boost::shared_ptr<ClientSession> getSession() const {
				return session_;
			}

			NetworkFactories* getNetworkFactories() const {
				return networkFactories;
			}

			/**
			 * Called before onConnected signal is emmitted.
			 */
			virtual void handleConnected() {};

		private:
			void handleConnectorFinished(boost::shared_ptr<Connection>, boost::shared_ptr<Error> error);
			void handleStanzaChannelAvailableChanged(bool available);
			void handleSessionFinished(boost::shared_ptr<Error>);
			void handleNeedCredentials();
			void handleDataRead(const SafeByteArray&);
			void handleDataWritten(const SafeByteArray&);
			void handlePresenceReceived(boost::shared_ptr<Presence>);
			void handleMessageReceived(boost::shared_ptr<Message>);
			void handleStanzaAcked(boost::shared_ptr<Stanza>);
			void purgePassword();
			void bindSessionToStream();

			void resetConnector();
			void resetSession();
			void forceReset();

		private:
			JID jid_;
			SafeByteArray password_;
			NetworkFactories* networkFactories;
			ClientSessionStanzaChannel* stanzaChannel_;
			IQRouter* iqRouter_;
			ClientOptions options;
			boost::shared_ptr<ChainedConnector> connector_;
			std::vector<ConnectionFactory*> proxyConnectionFactories;
			boost::shared_ptr<Connection> connection_;
			boost::shared_ptr<SessionStream> sessionStream_;
			boost::shared_ptr<ClientSession> session_;
			CertificateWithKey::ref certificate_;
			bool disconnectRequested_;
			CertificateTrustChecker* certificateTrustChecker;
	};
}
