/*
 * Copyright 2013 Red Hat, Inc.
 *
 * Red Hat licenses this file to you 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.jbosson.plugins.fuse;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mc4j.ems.connection.bean.EmsBean;
import org.mc4j.ems.connection.bean.attribute.EmsAttribute;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.configuration.definition.PropertyDefinition;
import org.rhq.core.pluginapi.inventory.DiscoveredResourceDetails;
import org.rhq.core.pluginapi.inventory.ResourceContext;
import org.rhq.core.pluginapi.inventory.ResourceDiscoveryContext;
import org.rhq.plugins.jmx.JMXComponent;
import org.rhq.plugins.jmx.MBeanResourceDiscoveryComponent;

/**
 * Adds support to {@link org.rhq.plugins.jmx.MBeanResourceDiscoveryComponent} to allow mapping
 * bean attributes to resource properties.
 * <p/>
 * Also adds support for using parent resource properties in nameTemplate and descriptionTemplate.
 * <p/>
 * Also allows skipping unknown properties in objectName, specifically added for CXF.
 *
 * @author dbokde
 */
public class FuseMBeanDiscoveryComponent<T extends JMXComponent<?>> extends MBeanResourceDiscoveryComponent<T> {

    private final Log log = LogFactory.getLog(getClass());

    private static final String BEAN_PROPERTIES = "beanProperties";
    private static final String SKIP_UNKNOWN_PROPS_PROPERTY = "skipUnknownProps";
    public static final String STATS_OBJECT_NAME_PROPERTY = "statsObjectName";

    @Override
    public Set<DiscoveredResourceDetails> discoverResources(ResourceDiscoveryContext<T> context) {

        // call super to get resources, skip unknown props by default if not set in resource plugin config
        Set<DiscoveredResourceDetails> detailsSet = super.discoverResources(context,
            Boolean.parseBoolean(context.getDefaultPluginConfiguration().getSimpleValue(SKIP_UNKNOWN_PROPS_PROPERTY, "true")));

        // get the beanProperties property group
        final List<PropertyDefinition> beanProperties = context.getResourceType().getPluginConfigurationDefinition().
            getPropertiesInGroup(BEAN_PROPERTIES);

        // now substitute any parent resource plugin properties and MBean properties in name and description
        final Set<DiscoveredResourceDetails> resultSet = new HashSet<DiscoveredResourceDetails>();
        for (DiscoveredResourceDetails details : detailsSet) {

            // check if there are any bean beanProperties to set in the first place
            if (!beanProperties.isEmpty()) {
                if (!setBeanProperties(context, details, beanProperties)) {
                    // bean is ignored if required bean beanProperties are not found
                    // so this acts like a second filter, in addition to the objectName args
                    continue;
                }
            }

            final ResourceContext<?> parentContext = context.getParentResourceContext();
            // if it has a parent and we need to replace something in name or description
            if (parentContext != null &&
                (details.getResourceName().contains("{") || details.getResourceDescription().contains("}"))) {

                final Configuration parentConfiguration = parentContext.getPluginConfiguration();
                final Map<String, PropertySimple> simpleProperties = parentConfiguration.getSimpleProperties();

                setResourceNameAndDescription(details, simpleProperties.values());
            }

            processStatsObjectName(context, details);

            // pass along these details
            resultSet.add(details);
        }

        return resultSet;
    }

    // Substitutes parent and plugin properties in statsObjectName if present
    private void processStatsObjectName(ResourceDiscoveryContext discoveryContext, DiscoveredResourceDetails details) {
        final Configuration pluginConfiguration = details.getPluginConfiguration();
        final Configuration parentConfiguration = (discoveryContext.getParentResourceContext() != null) ?
            discoveryContext.getParentResourceContext().getPluginConfiguration() : null;
        String statsObjectName = pluginConfiguration.getSimpleValue(STATS_OBJECT_NAME_PROPERTY);
        if (statsObjectName != null) {
            // get stats object name and substitute resource and parent properties
            statsObjectName = substituteConfigProperties(statsObjectName, pluginConfiguration, false);
            if (parentConfiguration != null) {
                statsObjectName = substituteConfigProperties(statsObjectName, parentConfiguration, true);
            }
            pluginConfiguration.setSimpleValue(STATS_OBJECT_NAME_PROPERTY, statsObjectName);
        }
    }

    private String substituteConfigProperties(String objectName, Configuration configuration, boolean isParent) {
        Pattern p;
        String startDelimiter;
        String endDelimiter;
        if (isParent) {
            startDelimiter = "\\{";
            endDelimiter = "\\}";
            p = Pattern.compile("\\{([^\\{\\}]*)\\}");
        } else {
            startDelimiter = "\\%";
            endDelimiter = "\\%";
            p = Pattern.compile("\\%([^\\%]*)\\%");
        }
        Matcher m = p.matcher(objectName);
        String value;
        while (m.find()) {
            String name = m.group(1);
            value = configuration.getSimpleValue(name);
            objectName = objectName.replaceAll(startDelimiter + name + endDelimiter, value);
        }
        return objectName;
    }

    private boolean setBeanProperties(ResourceDiscoveryContext<T> context, DiscoveredResourceDetails details, List<PropertyDefinition> beanProperties) {
        // get all MBean properties from details, would this cause a reconnect???
        String beanName = details.getResourceKey();
        EmsBean bean = context.getParentResourceComponent().getEmsConnection().getBean(beanName);

        final List<PropertySimple> newProperties = new ArrayList<PropertySimple>(beanProperties.size());
        for (PropertyDefinition property : beanProperties) {
            String name = property.getName();
            EmsAttribute attribute = bean.getAttribute(name);

            if (attribute == null) {
                if (property.isRequired()) {
                    // missing property, ignore this bean resource
                    log.warn(String.format("Required Property %s is missing in Bean %s, ignoring Bean for resource type %s",
                        name, beanName, details.getResourceType().getName()));
                    return false;
                }
            } else {
                final String value = attribute.getValue().toString();
                // set the property as a simple String value in plugin configuration
                details.getPluginConfiguration().setSimpleValue(name, value);

                // collect properties for updating resource name and description
                newProperties.add(new PropertySimple(name, value));
            }
        }

        // update name and description using discovered properties
        setResourceNameAndDescription(details, newProperties);

        return true;
    }

    private void setResourceNameAndDescription(DiscoveredResourceDetails details, Collection<PropertySimple> simpleProperties) {
        String resourceName = details.getResourceName();
        String description = details.getResourceDescription();

        if (resourceName.contains("{") || description.contains("{")) {
            for (PropertySimple property : simpleProperties){
                final String name = property.getName();
                final String value = property.getStringValue();
                resourceName = resourceName.replaceAll("\\{" + name + "\\}", value);
                description = description.replaceAll("\\{" + name + "\\}", value);
            }
            // strip optional properties
            resourceName = resourceName.replaceAll("\\s*\\{[^\\{\\}]+\\}", "");
            description = description.replaceAll("\\s*\\{[^\\{\\}]+\\}", "");
            details.setResourceName(resourceName);
            details.setResourceDescription(description);
        }

    }

}
