/**
 * $RCSfile$
 * $Revision$
 * $Date$
 *
 * Copyright (C) 2004 Jive Software. All rights reserved.
 *
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution.
 */

package org.jivesoftware.messenger.spi;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import javax.xml.stream.XMLStreamException;
import org.jivesoftware.messenger.*;
import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.messenger.chatbot.ChatbotManager;
import org.jivesoftware.messenger.container.BasicModule;
import org.jivesoftware.messenger.container.ServiceLookupFactory;
import org.jivesoftware.messenger.container.TrackInfo;
import org.jivesoftware.messenger.handler.IQHandler;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log;

/**
 * Generic presence routing base class.
 *
 * @author Iain Shigeoka
 */
public class IQRouterImpl extends BasicModule implements IQRouter {

    public XMPPServer localServer;
    public OfflineMessageStore messageStore;
    public RoutingTable routingTable;
    public LinkedList iqHandlers = new LinkedList();
    private HashMap namespace2Handlers = new HashMap();
    private ChatbotManager chatBotManager;

    /**
     * Creates a packet router.
     */
    public IQRouterImpl() {
        super("XMPP IQ Router");
    }

    public void route(IQ packet) {
        if (packet == null) {
            throw new NullPointerException();
        }
        if (packet.getOriginatingSession() == null
                || packet.getOriginatingSession().getStatus() == Session.STATUS_AUTHENTICATED
                || (isLocalServer(packet.getRecipient())
                && ("jabber:iq:auth".equals(packet.getChildNamespace())
                || "jabber:iq:register".equals(packet.getChildNamespace())))
        ) {
            handle(packet);
        }
        else {
            packet.setRecipient(packet.getOriginatingSession().getAddress());
            packet.setSender(null);
            packet.setError(XMPPError.Code.UNAUTHORIZED);
            try {
                packet.getOriginatingSession().process(packet);
            }
            catch (UnauthorizedException ue) {
                Log.error(ue);
            }
        }
    }

    private boolean isLocalServer(XMPPAddress recipientJID) {
        boolean isLocalServer = recipientJID == null || recipientJID.getHost() == null
                || "".equals(recipientJID.getHost()) || recipientJID.getResource() == null
                || "".equals(recipientJID.getResource());



        if (recipientJID != null && isLocalServer) {
            if (chatBotManager == null) {
                try {
                    chatBotManager = (ChatbotManager)ServiceLookupFactory.getLookup().lookup(ChatbotManager.class);
                }
                catch (UnauthorizedException e) {
                    Log.error(e);
                }
            }
            isLocalServer = !chatBotManager.isChatbot(recipientJID);
        }
        return isLocalServer;
    }

    private void handle(IQ packet) {

        XMPPAddress recipientJID = packet.getRecipient();

        try {
            if (isLocalServer(recipientJID)) {

                String namespace = packet.getChildNamespace();
                if (namespace == null) {
                    // Do nothing. We can't handle queries outside of a valid namespace
                    Log.warn("Unknown packet " + packet);
                }
                else {
                    IQHandler handler = getHandler(namespace);
                    if (handler == null) {
                        // Answer an error if JID is of the form <domain>
                        if (recipientJID.getName() == null || "".equals(recipientJID.getName())) {
                            packet.setError(XMPPError.Code.NOT_IMPLEMENTED);
                        }
                        else {
                            // JID is of the form <node@domain>
                            try {
                                // Let a "service" handle this packet otherwise return an error
                                // Useful for MUC where node refers to a room and domain is the
                                // MUC service.
                                ChannelHandler route = routingTable.getRoute(recipientJID);
                                if (route instanceof BasicModule) {
                                    route.process(packet);
                                    return;
                                }
                            }
                            catch (NoSuchRouteException e) {
                                // do nothing
                            }
                            // Answer an error since the server can't handle packets sent to a node
                            packet.setError(XMPPError.Code.SERVICE_UNAVAILABLE);
                        }
                        Session session = packet.getOriginatingSession();
                        if (session != null) {
                            session.getConnection().deliver(packet);
                        }
                        else {
                            Log.warn("Packet could not be delivered " + packet);
                        }
                    }
                    else {
                        handler.process(packet);
                    }
                }

            }
            else {
                // JID is of the form <node@domain/resource>
                ChannelHandler route = routingTable.getRoute(recipientJID);
                route.process(packet);
            }
        }
        catch (NoSuchRouteException e) {
            Log.info("Packet sent to unreachable address " + packet);
            Session session = packet.getOriginatingSession();
            if (session != null) {
                try {
                    packet.setError(XMPPError.Code.SERVICE_UNAVAILABLE);
                    session.getConnection().deliver(packet);
                }
                catch (UnauthorizedException ex) {
                    // do nothing
                }
                catch (XMLStreamException ex) {
                    Log.error(LocaleUtils.getLocalizedString("admin.error.routing"), e);
                }
            }
        }
        catch (Exception e) {
            Log.error(LocaleUtils.getLocalizedString("admin.error.routing"), e);
            try {
                Session session = packet.getOriginatingSession();
                if (session != null) {
                    Connection conn = session.getConnection();
                    if (conn != null) {
                        conn.close();
                    }
                }
            }
            catch (UnauthorizedException e1) {
                // do nothing
            }
        }
    }

    private IQHandler getHandler(String namespace) {
        IQHandler handler = null;

        handler = (IQHandler)namespace2Handlers.get(namespace);
        if (handler == null) {
            Iterator handlerIter = iqHandlers.iterator();
            while (handlerIter.hasNext() && handler == null) {
                IQHandler handlerCandidate = (IQHandler)handlerIter.next();
                IQHandlerInfo handlerInfo = handlerCandidate.getInfo();
                if (handlerInfo != null && namespace.equalsIgnoreCase(handlerInfo.getNamespace())) {
                    handler = handlerCandidate;
                }
            }
            if (handler != null) {
                namespace2Handlers.put(namespace, handler);
            }
        }
        return handler;
    }

    protected TrackInfo getTrackInfo() {
        TrackInfo trackInfo = new TrackInfo();
        trackInfo.getTrackerClasses().put(XMPPServer.class, "localServer");
        trackInfo.getTrackerClasses().put(OfflineMessageStore.class, "messageStore");
        trackInfo.getTrackerClasses().put(RoutingTable.class, "routingTable");
        trackInfo.getTrackerClasses().put(IQHandler.class, "iqHandlers");
        return trackInfo;
    }

}
