/*
 * RHQ Management Platform
 * Copyright (C) 2005-2011 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package org.rhq.enterprise.server.sync.validators;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.plugin.Plugin;
import org.rhq.core.domain.plugin.PluginDeploymentType;
import org.rhq.enterprise.server.resource.metadata.PluginManagerLocal;
import org.rhq.enterprise.server.sync.ExportReader;
import org.rhq.enterprise.server.sync.ExportWriter;
import org.rhq.enterprise.server.util.LookupUtil;

/**
 * This implementation checks that the two installations
 * have the exact same plugins deployed.
 *
 * Note that this class is currently not used by any synchronizer but
 * exists for "demo" purposes.
 * 
 * @author Lukas Krejci
 */
public class DeployedAgentPluginsValidator implements ConsistencyValidator {

    public static class ConsistentPlugin extends Plugin {
        
        private static final long serialVersionUID = 1L;

        public ConsistentPlugin() {
            
        }
        
        public ConsistentPlugin(Plugin p) {
            setName(p.getName());
            setVersion(p.getVersion());
            setMd5(p.getMd5());
        }
        
        @Override
        public int hashCode() {
            return getName().hashCode() * getVersion().hashCode() * getMd5().hashCode();
        }
        
        @Override
        public boolean equals(Object other) {
            if (this == other) {
                return true;
            }
            
            if (!(other instanceof ConsistentPlugin)) {
                return false;
            }
            
            ConsistentPlugin p = (ConsistentPlugin) other;
            
            return getName().equals(p.getName()) && getVersion().equals(p.getVersion())
                && getMd5().equals(p.getMd5());
        }
    }
    
    private PluginManagerLocal pluginManager;

    private Set<ConsistentPlugin> pluginsToValidate;

    public DeployedAgentPluginsValidator() {
        this(LookupUtil.getPluginManager());        
    }
    
    public DeployedAgentPluginsValidator(PluginManagerLocal pluginManager) {
        this.pluginManager = pluginManager;
    }
    
    /**
     * Provided for testability
     * @return the pluginsToValidate
     */
    public Set<ConsistentPlugin> getPluginsToValidate() {
        return pluginsToValidate;
    }
    
    /**
     * Provided for testability
     * @param pluginsToValidate the pluginsToValidate to set
     */
    public void setPluginsToValidate(Set<ConsistentPlugin> pluginsToValidate) {
        this.pluginsToValidate = pluginsToValidate;
    }
    
    @Override
    public void initialize(Subject subject, EntityManager entityManager) {
    }
    
    @Override
    public void exportState(ExportWriter writer) throws XMLStreamException {
        List<Plugin> plugins = pluginManager.getInstalledPlugins();

        for (Plugin plugin : plugins) {
            if (plugin.getDeployment() == PluginDeploymentType.AGENT) {
                writer.writeStartElement("plugin");
                writer.writeAttribute("name", plugin.getName());
                writer.writeAttribute("hash", plugin.getMd5());
                writer.writeAttribute("version", plugin.getVersion());
                writer.writeEndElement();
            }
        }
    }

    @Override
    public void initializeExportedStateValidation(ExportReader reader) throws XMLStreamException {
        pluginsToValidate = new HashSet<ConsistentPlugin>();

        while (reader.hasNext()) {
            switch (reader.next()) {
            case XMLStreamReader.START_ELEMENT:
                if ("plugin".equals(reader.getName().getLocalPart())) {
                    ConsistentPlugin p = new ConsistentPlugin();
                    p.setName(reader.getAttributeValue(null, "name"));
                    p.setMd5(reader.getAttributeValue(null, "hash"));
                    p.setVersion(reader.getAttributeValue(null, "version"));
                    pluginsToValidate.add(p);
                } else {
                    throw new XMLStreamException(
                        "Illegal tag encountered in the DeployedAgentPluginsValidator section: " + reader.getName()
                            + " on location: " + reader.getLocation());
                }
            }
        }
    }

    @Override
    public void validateExportedState() throws InconsistentStateException {
        List<Plugin> localPlugins = pluginManager.getInstalledPlugins();
        Set<ConsistentPlugin> localAgentPlugins = new HashSet<ConsistentPlugin>();
        for(Plugin p : localPlugins) {
            if (p.getDeployment() == PluginDeploymentType.AGENT) {
                localAgentPlugins.add(new ConsistentPlugin(p));
            }            
        }
        
        if (localAgentPlugins.size() != pluginsToValidate.size()) {
            throwIncosistentException(localAgentPlugins, pluginsToValidate);
        }

        for (ConsistentPlugin localPlugin : localAgentPlugins) {
            if (!pluginsToValidate.contains(localPlugin)) {
                throwIncosistentException(localAgentPlugins, pluginsToValidate);
            }
        }
    }

    @Override
    public int hashCode() {
        //all the deployed plugins validators are equal
        return 0;
    }

    @Override
    public boolean equals(Object obj) {
        //The deployed plugins validators are all equal to each other
        //because there is no difference in the "output" given by any
        //instance.

        if (this == obj) {
            return true;
        }

        if (obj instanceof DeployedAgentPluginsValidator) {
            return true;
        }

        return false;
    }

    private static void throwIncosistentException(Collection<ConsistentPlugin> localPlugins, Collection<ConsistentPlugin> expectedPlugins)
        throws InconsistentStateException {
        StringBuilder bld = new StringBuilder(
            "Installed plugins are not consistent with the plugins required by the export.");

        bld.append(" The export expects these plugins to be deployed: ");
        appendPlugins(bld, expectedPlugins);
        bld.append(". But the following plugins were found in the local installation: ");
        appendPlugins(bld, localPlugins);

        throw new InconsistentStateException(bld.toString());
    }

    private static void appendPlugins(StringBuilder bld, Collection<ConsistentPlugin> plugins) {
        bld.append("[");
        int size = plugins.size();
        int i = 0;
        for (Plugin p : plugins) {
            ++i;
            appendPlugin(bld, p);
            if (i < size) {
                bld.append(", ");
            }
        }
        bld.append("]");
    }

    private static void appendPlugin(StringBuilder bld, Plugin p) {
        bld.append("Plugin[name='").append(p.getName()).append("'");
        bld.append(", version='").append(p.getVersion()).append("'");
        bld.append(", md5='").append(p.getMd5()).append("']");
    }
}
