/*
 * Copyright (c) 2011 Kevin Smith
 * Licensed under the GNU General Public License v3.
 * See Documentation/Licenses/GPLv3.txt for more information.
 */


#include <Swiften/Session/BOSHSessionStream.h>

#include <boost/bind.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/variate_generator.hpp>

#include <Swiften/Base/foreach.h>
#include <Swiften/Elements/StreamType.h>
#include <Swiften/StreamStack/XMPPLayer.h>
#include <Swiften/StreamStack/StreamStack.h>
#include <Swiften/StreamStack/ConnectionLayer.h>
#include <Swiften/StreamStack/WhitespacePingLayer.h>
#include <Swiften/StreamStack/CompressionLayer.h>
#include <Swiften/StreamStack/TLSLayer.h>
#include <Swiften/TLS/TLSContextFactory.h>
#include <Swiften/TLS/TLSContext.h>
#include <Swiften/EventLoop/EventLoop.h>

namespace Swift {

BOSHSessionStream::BOSHSessionStream(
		const URL& boshURL,
		PayloadParserFactoryCollection* payloadParserFactories, 
		PayloadSerializerCollection* payloadSerializers, 
		ConnectionFactory* connectionFactory,
		TLSContextFactory* tlsContextFactory, 
		TimerFactory* timerFactory,
		XMLParserFactory* xmlParserFactory,
		EventLoop* eventLoop,
		DomainNameResolver* resolver,
		const std::string& to,
		const URL& boshHTTPConnectProxyURL,
		const SafeString& boshHTTPConnectProxyAuthID,
		const SafeString& boshHTTPConnectProxyAuthPassword) :
			available(false), 
			eventLoop(eventLoop),
			firstHeader(true) {

	boost::mt19937 random;
	boost::uniform_int<unsigned long long> dist(0, (1LL<<53) - 1);
	random.seed(time(NULL));
	unsigned long long initialRID = boost::variate_generator<boost::mt19937&, boost::uniform_int<unsigned long long> >(random, dist)();

	connectionPool = new BOSHConnectionPool(boshURL, resolver, connectionFactory, xmlParserFactory, tlsContextFactory, timerFactory, eventLoop, to, initialRID, boshHTTPConnectProxyURL, boshHTTPConnectProxyAuthID, boshHTTPConnectProxyAuthPassword);
	connectionPool->onSessionTerminated.connect(boost::bind(&BOSHSessionStream::handlePoolSessionTerminated, this, _1));
	connectionPool->onSessionStarted.connect(boost::bind(&BOSHSessionStream::handlePoolSessionStarted, this));
	connectionPool->onXMPPDataRead.connect(boost::bind(&BOSHSessionStream::handlePoolXMPPDataRead, this, _1));
	connectionPool->onBOSHDataRead.connect(boost::bind(&BOSHSessionStream::handlePoolBOSHDataRead, this, _1));
	connectionPool->onBOSHDataWritten.connect(boost::bind(&BOSHSessionStream::handlePoolBOSHDataWritten, this, _1));

	xmppLayer = new XMPPLayer(payloadParserFactories, payloadSerializers, xmlParserFactory, ClientStreamType);
	xmppLayer->onStreamStart.connect(boost::bind(&BOSHSessionStream::handleStreamStartReceived, this, _1));
	xmppLayer->onElement.connect(boost::bind(&BOSHSessionStream::handleElementReceived, this, _1));
	xmppLayer->onError.connect(boost::bind(&BOSHSessionStream::handleXMPPError, this));
	xmppLayer->onWriteData.connect(boost::bind(&BOSHSessionStream::handleXMPPLayerDataWritten, this, _1));

	available = true;
}

BOSHSessionStream::~BOSHSessionStream() {
	close();
	connectionPool->onSessionTerminated.disconnect(boost::bind(&BOSHSessionStream::handlePoolSessionTerminated, this, _1));
	connectionPool->onSessionStarted.disconnect(boost::bind(&BOSHSessionStream::handlePoolSessionStarted, this));
	connectionPool->onXMPPDataRead.disconnect(boost::bind(&BOSHSessionStream::handlePoolXMPPDataRead, this, _1));
	connectionPool->onBOSHDataRead.disconnect(boost::bind(&BOSHSessionStream::handlePoolBOSHDataRead, this, _1));
	connectionPool->onBOSHDataWritten.disconnect(boost::bind(&BOSHSessionStream::handlePoolBOSHDataWritten, this, _1));
	delete connectionPool;
	connectionPool = NULL;
	xmppLayer->onStreamStart.disconnect(boost::bind(&BOSHSessionStream::handleStreamStartReceived, this, _1));
	xmppLayer->onElement.disconnect(boost::bind(&BOSHSessionStream::handleElementReceived, this, _1));
	xmppLayer->onError.disconnect(boost::bind(&BOSHSessionStream::handleXMPPError, this));
	xmppLayer->onWriteData.disconnect(boost::bind(&BOSHSessionStream::handleXMPPLayerDataWritten, this, _1));
	delete xmppLayer;
	xmppLayer = NULL;
}

void BOSHSessionStream::handlePoolXMPPDataRead(const SafeByteArray& data) {
	xmppLayer->handleDataRead(data);
}

void BOSHSessionStream::writeElement(boost::shared_ptr<Element> element) {
	assert(available);
	xmppLayer->writeElement(element);
}

void BOSHSessionStream::writeFooter() {
	connectionPool->writeFooter();
}

void BOSHSessionStream::writeData(const std::string& data) {
	assert(available);
	xmppLayer->writeData(data);
}

void BOSHSessionStream::close() {
	connectionPool->close();
}

bool BOSHSessionStream::isOpen() {
	return available;
}

bool BOSHSessionStream::supportsTLSEncryption() {
	return false;
}

void BOSHSessionStream::addTLSEncryption() {
	assert(available);
}

bool BOSHSessionStream::isTLSEncrypted() {
	return false;
}

Certificate::ref BOSHSessionStream::getPeerCertificate() const {
	return Certificate::ref();
}

std::vector<Certificate::ref> BOSHSessionStream::getPeerCertificateChain() const {
	return std::vector<Certificate::ref>();
}

boost::shared_ptr<CertificateVerificationError> BOSHSessionStream::getPeerCertificateVerificationError() const {
	return boost::shared_ptr<CertificateVerificationError>();
}

ByteArray BOSHSessionStream::getTLSFinishMessage() const {
	return ByteArray();
}

bool BOSHSessionStream::supportsZLibCompression() {
	return false;
}

void BOSHSessionStream::addZLibCompression() {

}

void BOSHSessionStream::setWhitespacePingEnabled(bool /*enabled*/) {
	return;
}

void BOSHSessionStream::resetXMPPParser() {
	xmppLayer->resetParser();
}

void BOSHSessionStream::handleStreamStartReceived(const ProtocolHeader& header) {
	onStreamStartReceived(header);
}

void BOSHSessionStream::handleElementReceived(boost::shared_ptr<Element> element) {
	onElementReceived(element);
}

void BOSHSessionStream::handleXMPPError() {
	available = false;
	onClosed(boost::make_shared<SessionStreamError>(SessionStreamError::ParseError));
}

void BOSHSessionStream::handlePoolSessionStarted() {
	fakeStreamHeaderReceipt();
}

void BOSHSessionStream::handlePoolSessionTerminated(BOSHError::ref error) {
	eventLoop->postEvent(boost::bind(&BOSHSessionStream::fakeStreamFooterReceipt, this, error), shared_from_this());
}

void BOSHSessionStream::writeHeader(const ProtocolHeader& header) {
	streamHeader = header;
	/*First time we're told to do this, don't (the sending of the initial header is handled on connect)
	  On subsequent requests we should restart the stream the BOSH way.
	*/
	if (!firstHeader) {
		eventLoop->postEvent(boost::bind(&BOSHSessionStream::fakeStreamHeaderReceipt, this), shared_from_this());
		eventLoop->postEvent(boost::bind(&BOSHConnectionPool::restartStream, connectionPool), shared_from_this());
	}
	firstHeader = false;
}


void BOSHSessionStream::fakeStreamHeaderReceipt() {
	std::stringstream header;
	header << "<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' from='";
	header << streamHeader.getTo() << "' id='dummy' version='1.0'>";

	xmppLayer->handleDataRead(createSafeByteArray(header.str()));
}

void BOSHSessionStream::fakeStreamFooterReceipt(BOSHError::ref error) {
	std::string footer("</stream:stream>");
	xmppLayer->handleDataRead(createSafeByteArray(footer));
	onClosed(error);
}

void BOSHSessionStream::handleXMPPLayerDataWritten(const SafeByteArray& data) {
	eventLoop->postEvent(boost::bind(&BOSHConnectionPool::write, connectionPool, data), shared_from_this());
}

void BOSHSessionStream::handlePoolBOSHDataRead(const SafeByteArray& data) {
	onDataRead(data);
}

void BOSHSessionStream::handlePoolBOSHDataWritten(const SafeByteArray& data) {
	onDataWritten(data);
}

};