/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2011 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */


package org.ow2.jonas.configadmin.internal;

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

import org.osgi.service.cm.Configuration;
import org.ow2.jonas.configadmin.AdapterException;
import org.ow2.jonas.configadmin.ConfigurationInfo;
import org.ow2.jonas.configadmin.XmlConfigurationAdapter;
import org.ow2.jonas.configadmin.XmlConfigurationAdapterRegistry;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;
import org.w3c.dom.Element;

/**
 * A {@code ConfigurationItem} contains the XML DOM Element representing
 * the not adapted configurations and the actual CA Configuration instance (if associated).
 *
 * @author Guillaume Sauthier
 */
public class ConfigurationItem implements AdapterRegistryListener {

    /**
     * Logger.
     */
    private static final Log logger = LogFactory.getLog(ConfigurationItem.class);

    /**
     * DOM Element read from the input deployed XML file.
     * This field is never null.
     */
    private Element element;

    /**
     * CA Configuration. May be null if not associated.
     */
    private Set<Configuration> configurations;

    /**
     * Used registry (when associated), null otherwise.
     */
    private XmlConfigurationAdapterRegistry registry;

    /**
     * Listeners interested in association/dissociation events.
     */
    private List<ConfigurationAssociationListener> listeners;

    /**
     * State of this item.
     */
    private boolean failed = false;

    /**
     * Construct an item around the given DOM Element.
      * @param element DOM Element
     */
    public ConfigurationItem(Element element) {
        this.listeners = new ArrayList<ConfigurationAssociationListener>();
        this.configurations = new HashSet<Configuration>();
        this.element = element;
    }

    public Set<Configuration> getConfigurations() {
        return configurations;
    }

    public boolean isFailed() {
        return failed;
    }

    public void addAssociationListener(ConfigurationAssociationListener listener) {
        listeners.add(listener);
    }

    public void removeAssociationListener(ConfigurationAssociationListener listener) {
        listeners.remove(listener);
    }

    public void onAdapterRegistryArrival(AdapterRegistryEvent event) {
        if (registry == null) {
            // Still not associated
            // Consider the current event and use it if it's OK

            // Filter on the namespace required by this element
            if (event.getNamespace().equals(element.getNamespaceURI())) {

                // Try to find an appropriate adapter
                XmlConfigurationAdapterRegistry registry = event.getSource();
                XmlConfigurationAdapter adapter = registry.getAdapter(element.getLocalName());
                if (adapter != null) {
                    try {
                        firesAssociationEvent(adapter.convert(element));
                        this.registry = registry;
                        this.failed = false;
                    } catch (AdapterException e) {
                        // Ignored
                        logger.error("Cannot convert <{0}:{1} ...> using {2}",
                                element.getPrefix(),
                                element.getLocalName(),
                                adapter.getClass(),
                                e);
                        failed = true;
                    } catch (RuntimeException re) {
                        // Any other error that may be thrown during eventing
                        logger.error("Error when handling <{0}:{1} ...>",
                                element.getPrefix(),
                                element.getLocalName(),
                                re);
                        failed = true;
                    }
                }
            }
        }
    }

    private void firesAssociationEvent(Set<ConfigurationInfo> info) {
        ConfigurationAssociationEvent event = new ConfigurationAssociationEvent(this, info);
        for (ConfigurationAssociationListener listener : listeners) {
            listener.onAssociation(event);
        }
    }

    public void onAdapterRegistryRemoval(AdapterRegistryEvent event) {
        if (registry != null) {
            // We've been associated with something

            // If the registry source of the event is the one we've used
            // Intentionally used the == to test if it is the same object
            if (registry == event.getSource()) {
                registry = null;
                firesDissociationEvent();
            }

        }
    }

    private void firesDissociationEvent() {
        ConfigurationAssociationEvent event = new ConfigurationAssociationEvent(this, null);
        for (ConfigurationAssociationListener listener : listeners) {
            listener.onDissociation(event);
        }
    }

    public void close() {
        // Only do something when associated
        if (registry != null) {
            firesDissociationEvent();
        }
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        sb.append("ConfigurationItem");
        sb.append("{element=").append(element);
        sb.append(", configurations=").append(configurations);
        sb.append('}');
        return sb.toString();
    }
}
