/**
 * $RCSfile$
 * $Revision$
 * $Date$
 *
 * Copyright 2009 Jive Software.
 *
 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jivesoftware.smack;

import java.io.StringReader;

import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.sasl.SASLMechanism.Challenge;
import org.jivesoftware.smack.sasl.SASLMechanism.Failure;
import org.jivesoftware.smack.sasl.SASLMechanism.Success;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlPullParser;

import com.kenai.jbosh.AbstractBody;
import com.kenai.jbosh.BOSHClientResponseListener;
import com.kenai.jbosh.BOSHMessageEvent;
import com.kenai.jbosh.BodyQName;
import com.kenai.jbosh.ComposableBody;

/**
 * Listens for XML traffic from the BOSH connection manager and parses it into
 * packet objects.
 * 
 * @author Guenther Niess
 */
public class BOSHPacketReader implements BOSHClientResponseListener {

    private BOSHConnection connection;

    /**
     * Create a packet reader which listen on a BOSHConnection for received
     * HTTP responses, parse the packets and notifies the connection.
     * 
     * @param connection the corresponding connection for the received packets.
     */
    public BOSHPacketReader(BOSHConnection connection) {
        this.connection = connection;
    }

    /**
     * Parse the received packets and notify the corresponding connection.
     * 
     * @param event the BOSH client response which includes the received packet.
     */
    public void responseReceived(BOSHMessageEvent event) {
        AbstractBody body = event.getBody();
        if (body != null) {
            try {
                if (connection.sessionID == null) {
                    connection.sessionID = body.getAttribute(BodyQName.create(BOSHConnection.BOSH_URI, "sid"));
                }
                if (connection.authID == null) {
                    connection.authID = body.getAttribute(BodyQName.create(BOSHConnection.BOSH_URI, "authid"));
                }
                final XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
                parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES,
                        true);
                parser.setInput(new StringReader(body.toXML()));
                int eventType = parser.getEventType();
                do {
                    eventType = parser.next();
                    if (eventType == XmlPullParser.START_TAG) {
                        if (parser.getName().equals("body")) {
                            // ignore the container root element
                        } else if (parser.getName().equals("message")) {
                            connection.processPacket(PacketParserUtils.parseMessage(parser));
                        } else if (parser.getName().equals("iq")) {
                            connection.processPacket(PacketParserUtils.parseIQ(parser, connection));
                        } else if (parser.getName().equals("presence")) {
                            connection.processPacket(PacketParserUtils.parsePresence(parser));
                        } else if (parser.getName().equals("challenge")) {
                            // The server is challenging the SASL authentication
                            // made by the client
                            final String challengeData = parser.nextText();
                            connection.getSASLAuthentication()
                                    .challengeReceived(challengeData);
                            connection.processPacket(new Challenge(
                                    challengeData));
                        } else if (parser.getName().equals("success")) {
                            connection.send(ComposableBody.builder()
                                    .setNamespaceDefinition("xmpp", BOSHConnection.XMPP_BOSH_NS)
                                    .setAttribute(
                                            BodyQName.createWithPrefix(BOSHConnection.XMPP_BOSH_NS, "restart", "xmpp"),
                                            "true")
                                    .setAttribute(
                                            BodyQName.create(BOSHConnection.BOSH_URI, "to"),
                                            connection.getServiceName())
                                    .build());
                            connection.getSASLAuthentication().authenticated();
                            connection.processPacket(new Success(parser.nextText()));
                        } else if (parser.getName().equals("features")) {
                            parseFeatures(parser);
                        } else if (parser.getName().equals("failure")) {
                            if ("urn:ietf:params:xml:ns:xmpp-sasl".equals(parser.getNamespace(null))) {
                                final Failure failure = PacketParserUtils.parseSASLFailure(parser);
                                connection.getSASLAuthentication().authenticationFailed();
                                connection.processPacket(failure);
                            }
                        } else if (parser.getName().equals("error")) {
                            throw new XMPPException(PacketParserUtils.parseStreamError(parser));
                        }
                    }
                } while (eventType != XmlPullParser.END_DOCUMENT);
            }
            catch (Exception e) {
                if (connection.isConnected()) {
                    connection.notifyConnectionError(e);
                }
            }
        }
    }

    /**
     * Parse and setup the XML stream features.
     * 
     * @param parser the XML parser, positioned at the start of a message packet.
     * @throws Exception if an exception occurs while parsing the packet.
     */
    private void parseFeatures(XmlPullParser parser) throws Exception {
        boolean done = false;
        while (!done) {
            int eventType = parser.next();

            if (eventType == XmlPullParser.START_TAG) {
                if (parser.getName().equals("mechanisms")) {
                    // The server is reporting available SASL mechanisms. Store
                    // this information
                    // which will be used later while logging (i.e.
                    // authenticating) into
                    // the server
                    connection.getSASLAuthentication().setAvailableSASLMethods(
                            PacketParserUtils.parseMechanisms(parser));
                } else if (parser.getName().equals("bind")) {
                    // The server requires the client to bind a resource to the
                    // stream
                    connection.getSASLAuthentication().bindingRequired();
                } else if (parser.getName().equals("session")) {
                    // The server supports sessions
                    connection.getSASLAuthentication().sessionsSupported();
                } else if (parser.getName().equals("register")) {
                    connection.getAccountManager().setSupportsAccountCreation(
                            true);
                }
            } else if (eventType == XmlPullParser.END_TAG) {
                if (parser.getName().equals("features")) {
                    done = true;
                }
            }
        }
    }
}
