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

#include "Swiften/Parser/XMPPParser.h"

#include <iostream>
#include <cassert>

#include "Swiften/Elements/ProtocolHeader.h"
#include <string>
#include "Swiften/Parser/XMLParser.h"
#include "Swiften/Parser/PlatformXMLParserFactory.h"
#include "Swiften/Parser/XMPPParserClient.h"
#include "Swiften/Parser/XMPPParser.h"
#include "Swiften/Parser/ElementParser.h"
#include "Swiften/Parser/PresenceParser.h"
#include "Swiften/Parser/IQParser.h"
#include "Swiften/Parser/MessageParser.h"
#include "Swiften/Parser/StreamFeaturesParser.h"
#include "Swiften/Parser/StreamErrorParser.h"
#include "Swiften/Parser/AuthRequestParser.h"
#include "Swiften/Parser/AuthSuccessParser.h"
#include "Swiften/Parser/AuthFailureParser.h"
#include "Swiften/Parser/AuthChallengeParser.h"
#include "Swiften/Parser/AuthResponseParser.h"
#include "Swiften/Parser/EnableStreamManagementParser.h"
#include "Swiften/Parser/StreamManagementEnabledParser.h"
#include "Swiften/Parser/StreamManagementFailedParser.h"
#include "Swiften/Parser/StanzaAckParser.h"
#include "Swiften/Parser/StanzaAckRequestParser.h"
#include "Swiften/Parser/StartTLSParser.h"
#include "Swiften/Parser/StartTLSFailureParser.h"
#include "Swiften/Parser/CompressParser.h"
#include "Swiften/Parser/CompressFailureParser.h"
#include "Swiften/Parser/CompressedParser.h"
#include "Swiften/Parser/UnknownElementParser.h"
#include "Swiften/Parser/TLSProceedParser.h"
#include "Swiften/Parser/ComponentHandshakeParser.h"

// TODO: Whenever an error occurs in the handlers, stop the parser by returing
// a bool value, and stopping the XML parser

namespace Swift {

XMPPParser::XMPPParser(
		XMPPParserClient* client, 
		PayloadParserFactoryCollection* payloadParserFactories) : 
				xmlParser_(0),
				client_(client), 
				payloadParserFactories_(payloadParserFactories), 
				level_(0),
				currentElementParser_(0),
				parseErrorOccurred_(false) {
	xmlParser_ = PlatformXMLParserFactory().createXMLParser(this);
}

XMPPParser::~XMPPParser() {
	delete currentElementParser_;
	delete xmlParser_;
}

bool XMPPParser::parse(const std::string& data) {
	bool xmlParseResult = xmlParser_->parse(data);
	return xmlParseResult && !parseErrorOccurred_;
}

void XMPPParser::handleStartElement(const std::string& element, const std::string& ns, const AttributeMap& attributes) {
	if (!parseErrorOccurred_) {
		if (level_ == TopLevel) {
			if (element == "stream" && ns == "http://etherx.jabber.org/streams") {
				ProtocolHeader header;
				header.setFrom(attributes.getAttribute("from"));
				header.setTo(attributes.getAttribute("to"));
				header.setID(attributes.getAttribute("id"));
				header.setVersion(attributes.getAttribute("version"));
				client_->handleStreamStart(header);
			}
			else {
				parseErrorOccurred_ = true;
			}
		}
		else {
			if (level_ == StreamLevel) {
				assert(!currentElementParser_);
				currentElementParser_ = createElementParser(element, ns);
			}
			currentElementParser_->handleStartElement(element, ns, attributes);
		}
	}
	++level_;
}

void XMPPParser::handleEndElement(const std::string& element, const std::string& ns) {
	assert(level_ > TopLevel);
	--level_;
	if (!parseErrorOccurred_) {
		if (level_ == TopLevel) {
			assert(element == "stream");
			client_->handleStreamEnd();
		}
		else {
			assert(currentElementParser_);
			currentElementParser_->handleEndElement(element, ns);
			if (level_ == StreamLevel) {
				client_->handleElement(currentElementParser_->getElement());
				delete currentElementParser_;
				currentElementParser_ = NULL;
			}
		}
	}
}

void XMPPParser::handleCharacterData(const std::string& data) {
	if (!parseErrorOccurred_) {
		if (currentElementParser_) {
			currentElementParser_->handleCharacterData(data);
		}
	//else {
	//	std::cerr << "XMPPParser: Ignoring stray character data: " << data << std::endl;
	//}
	}
}

ElementParser* XMPPParser::createElementParser(const std::string& element, const std::string& ns) {
	if (element == "presence") {
		return new PresenceParser(payloadParserFactories_);
	}
	else if (element == "iq") {
		return new IQParser(payloadParserFactories_);
	}
	else if (element == "message") {
		return new MessageParser(payloadParserFactories_);
	}
	else if (element == "features"  && ns == "http://etherx.jabber.org/streams") {
		return new StreamFeaturesParser();
	}
	else if (element == "error" && ns == "http://etherx.jabber.org/streams") {
		return new StreamErrorParser();
	}
	else if (element == "auth") {
		return new AuthRequestParser();
	}
	else if (element == "success") {
		return new AuthSuccessParser();
	}
	else if (element == "failure" && ns == "urn:ietf:params:xml:ns:xmpp-sasl") {
		return new AuthFailureParser();
	}
	else if (element == "challenge" && ns == "urn:ietf:params:xml:ns:xmpp-sasl") {
		return new AuthChallengeParser();
	}
	else if (element == "response" && ns == "urn:ietf:params:xml:ns:xmpp-sasl") {
		return new AuthResponseParser();
	}
	else if (element == "starttls") {
		return new StartTLSParser();
	}
	else if (element == "failure" && ns == "urn:ietf:params:xml:ns:xmpp-tls") {
		return new StartTLSFailureParser();
	}
	else if (element == "compress") {
		return new CompressParser();
	}
	else if (element == "compressed") {
		return new CompressedParser();
	}
	else if (element == "failure" && ns == "http://jabber.org/protocol/compress") {
		return new CompressFailureParser();
	}
	else if (element == "proceed") {
		return new TLSProceedParser();
	}
	else if (element == "enable" && ns == "urn:xmpp:sm:2") {
		return new EnableStreamManagementParser();
	}
	else if (element == "enabled" && ns == "urn:xmpp:sm:2") {
		return new StreamManagementEnabledParser();
	}
	else if (element == "failed" && ns == "urn:xmpp:sm:2") {
		return new StreamManagementFailedParser();
	}
	else if (element == "a" && ns == "urn:xmpp:sm:2") {
		return new StanzaAckParser();
	}
	else if (element == "r" && ns == "urn:xmpp:sm:2") {
		return new StanzaAckRequestParser();
	}
	else if (element == "handshake") {
		return new ComponentHandshakeParser();
	}
	return new UnknownElementParser();
}

}