/**
 * Copyright (C) 2004-2008 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.xmpp.workgroup.disco;

import org.jivesoftware.xmpp.workgroup.Workgroup;
import org.jivesoftware.xmpp.workgroup.WorkgroupManager;
import org.dom4j.Element;
import org.jivesoftware.openfire.commands.AdHocCommand;
import org.jivesoftware.openfire.commands.AdHocCommandManager;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.xmpp.packet.IQ;
import org.xmpp.packet.PacketError;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;

/**
 * This class is responsible for handling all the packets sent to the workgroup service with
 * namespace http://jabber.org/protocol/disco#info. If the disco packet was sent to the workgroup
 * itself then the identity of the service will be returned and its features. On the other hand, if
 * the packet was sent to a workgroup then the status of the workgroup will be returned in a data
 * form.
 *
 * @author Gaston Dombiak
 */
public class IQDiscoInfoHandler {

    private WorkgroupManager workgroupManager;
    private AdHocCommandManager commandManager;

    private Collection<DiscoFeaturesProvider> featuresProviders =
            new java.util.concurrent.CopyOnWriteArrayList<DiscoFeaturesProvider>();

    public IQDiscoInfoHandler(WorkgroupManager workgroupManager, AdHocCommandManager commandManager) {
        this.workgroupManager = workgroupManager;
        this.commandManager = commandManager;
    }

    /**
     * Adds a new features provider to the list of providers. The new features provider will be
     * used whenever a disco for information is made against the workgroup service.
     *
     * @param provider the DiscoFeaturesProvider that provides service features.
     */
    public void addServerFeaturesProvider(DiscoFeaturesProvider provider) {
        featuresProviders.add(provider);
    }

    /**
     * Removes a features provider from the list of providers.
     *
     * @param provider the DiscoFeaturesProvider to remove.
     */
    public void removeServerFeaturesProvider(DiscoFeaturesProvider provider) {
        featuresProviders.remove(provider);
    }

    public IQ handleIQ(IQ packet) {
        if (packet.getType() == IQ.Type.result) {
            List<Element> features = packet.getChildElement().elements("feature");
            // Detect if this item is the MUC service
            for (Element feature : features) {
                String variable = feature.attributeValue("var");

                if ("http://jabber.org/protocol/muc".equals(variable)) {
                    workgroupManager.setMUCServiceName(packet.getFrom().getDomain());
                }
            }
            return null;
        }

        // Create a copy of the sent pack that will be used as the reply
        // we only need to add the requested info to the reply if any, otherwise add
        // a not found error
        IQ reply = IQ.createResultIQ(packet);

        // Check if the disco#info was sent to the workgroup service itself
        if (workgroupManager.getAddress().equals(packet.getTo())) {
            Element iq = packet.getChildElement();
            String node = iq.attributeValue("node");
            reply.setChildElement(iq.createCopy());
            Element queryElement = reply.getChildElement();

            if (node == null) {
                // Create and add a the identity of the workgroup service
                Element identity = queryElement.addElement("identity");
                identity.addAttribute("category", "collaboration");
                // TODO Get the name from a property
                identity.addAttribute("name", "Fastpath");
                identity.addAttribute("type", "workgroup");

                // Create and add a the feature provided by the workgroup service
                Element feature = queryElement.addElement("feature");
                feature.addAttribute("var", "http://jabber.org/protocol/workgroup");
                // Create and add a the disco#info feature
                feature = queryElement.addElement("feature");
                feature.addAttribute("var", "http://jabber.org/protocol/disco#info");
                // Indicate that we can provide information about the software version being used
                feature = queryElement.addElement("feature");
                feature.addAttribute("var", "jabber:iq:version");
                // Indicate that we support ad-hoc commands
                feature = queryElement.addElement("feature");
                feature.addAttribute("var", "http://jabber.org/protocol/commands");

                // Add the features provided by the features providers
                for (DiscoFeaturesProvider provider : featuresProviders) {
                    for (String newFeature : provider.getFeatures()) {
                        feature = queryElement.addElement("feature");
                        feature.addAttribute("var", newFeature);
                    }
                }
            }
            else if ("http://jabber.org/protocol/commands".equals(node)) {
                // Create and add a the identity of the workgroup service
                Element identity = queryElement.addElement("identity");
                identity.addAttribute("category", "collaboration");
                // TODO Get the name from a property
                identity.addAttribute("name", "Fastpath");
                identity.addAttribute("type", "workgroup");

                // Create and add a the disco#info feature
                Element feature = queryElement.addElement("feature");
                feature.addAttribute("var", "http://jabber.org/protocol/disco#info");
                // Indicate that we support ad-hoc commands
                feature = queryElement.addElement("feature");
                feature.addAttribute("var", "http://jabber.org/protocol/commands");
            }
            else {
                // Check if the node matches a supported command
                boolean found = false;
                for (AdHocCommand command : commandManager.getCommands()) {
                    if (node.equals(command.getCode())) {
                        found = true;
                        // Only include commands that the sender can invoke (i.e. has enough permissions)
                        if (command.hasPermission(packet.getFrom())) {
                            // Create and add a the identity of the command
                            Element identity = queryElement.addElement("identity");
                            identity.addAttribute("category", "automation");
                            identity.addAttribute("name", command.getLabel());
                            identity.addAttribute("type", "command-node");

                            // Indicate that we support ad-hoc commands
                            Element feature = queryElement.addElement("feature");
                            feature.addAttribute("var", "http://jabber.org/protocol/commands");
                        }
                        else {
                            // Return Forbidden error
                            reply.setError(PacketError.Condition.forbidden);
                        }
                    }
                }
                if (!found) {
                    // Return item_not_found error
                    reply.setError(PacketError.Condition.item_not_found);
                }
            }

        }
        else {
            // Check if the disco#info was sent to a given workgroup
            try {
                Workgroup workgroup = workgroupManager.getWorkgroup(packet.getTo());
                Element iq = packet.getChildElement();
                reply.setChildElement(iq.createCopy());
                Element queryElement = reply.getChildElement();

                // Create and add a the identity of the workgroup service
                Element identity = queryElement.addElement("identity");
                identity.addAttribute("category", "collaboration");
                identity.addAttribute("name", workgroup.getJID().getNode());
                identity.addAttribute("type", "workgroup");

                // Create and add a the disco#info feature
                Element feature = queryElement.addElement("feature");
                feature.addAttribute("var", "http://jabber.org/protocol/disco#info");

                Element form = queryElement.addElement("x", "jabber:x:data");
                form.addAttribute("type", "result");
                // Add static field
                Element field = form.addElement("field");
                field.addAttribute("var", "FORM_TYPE");
                field.addAttribute("type", "hidden");
                field.addElement("value").setText("http://jabber.org/protocol/workgroup#workgroupinfo");
                // Add workgroup description
                field = form.addElement("field");
                field.addAttribute("var", "workgroup#description");
                field.addAttribute("label", "Description");
                field.addElement("value").setText(workgroup.getDescription() == null ?
                        "" : workgroup.getDescription());
                // Add workgroup online status
                field = form.addElement("field");
                field.addAttribute("var", "workgroup#online");
                field.addAttribute("label", "Status");
                field.addElement("value").setText(workgroup.getStatus().name());
            }
            catch (UserNotFoundException e) {
                // If we didn't find a workgroup then answer a not found error
                reply.setChildElement(packet.getChildElement().createCopy());
                reply.setError(PacketError.Condition.item_not_found);
            }
        }
        return reply;
    }
}
