/*
  * RHQ Management Platform
  * Copyright (C) 2005-2008 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, version 2, as
  * published by the Free Software Foundation, and/or the GNU Lesser
  * General Public License, version 2.1, also as published by the Free
  * Software Foundation.
  *
  * 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 and the GNU Lesser General Public License
  * for more details.
  *
  * You should have received a copy of the GNU General Public License
  * and the GNU Lesser General Public License along with this program;
  * if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  */
package org.rhq.plugins.platform;

import java.net.InetAddress;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.pluginapi.inventory.DiscoveredResourceDetails;
import org.rhq.core.pluginapi.inventory.ResourceDiscoveryComponent;
import org.rhq.core.pluginapi.inventory.ResourceDiscoveryContext;
import org.rhq.core.system.SystemInfo;

/**
 * Discovers the platform resource. This will discover one and only one resource. This is an abstract superclass to all
 * the natively supported platform discovery components and the fallback Java-only discovery component.
 *
 * @author John Mazzitelli
 */
public abstract class PlatformDiscoveryComponent implements ResourceDiscoveryComponent {

    /**
     * This will build the platform's resource details, where its name and key are by default
     * the plugin container's name.  Subclasses of this platform discovery component
     * are free to override this behavior if they wish to make the platform name different,
     * but under normal circumstances, subclasses will not want to alter the key value.
     * 
     * @see ResourceDiscoveryComponent#discoverResources(ResourceDiscoveryContext)
     */
    public Set<DiscoveredResourceDetails> discoverResources(ResourceDiscoveryContext context) {
        if (!isPlatformSupported(context)) {
            return Collections.emptySet();
        }

        SystemInfo systemInfo = context.getSystemInformation();

        // platform resources will use the plugin container name as its key.
        // we are guaranteed this string is unique across all agents/plugin containers.
        // (it is usually the hostname, but is not guaranteed to be)
        String key = determineResourceKey(context);

        // build the resource name
        String name = determineResourceName(context);

        // use the platform type description as the description for this resource
        String description = context.getResourceType().getDescription();

        String version;
        try {
            version = systemInfo.getOperatingSystemName() + " " + systemInfo.getOperatingSystemVersion();
        } catch (Exception e) {
            version = "?";
        }

        Configuration config = context.getDefaultPluginConfiguration();
        discoverConfiguration(config);

        DiscoveredResourceDetails discoveredResource = new DiscoveredResourceDetails(context.getResourceType(), key,
            name, version, description, null, null);

        HashSet<DiscoveredResourceDetails> results = new HashSet<DiscoveredResourceDetails>();
        results.add(discoveredResource);
        return results;
    }

    /**
     * Override this method to add additional configurations to discovered platforms
     * @param configuration
     */
    protected void discoverConfiguration(Configuration configuration) {

    }

    /**
     * This will determine what the new platform's resource key should be. This default
     * implementation first tries to use the plugin container's name which is guaranteed
     * to be unique across all agents/plugin containers.  If, for some odd reason, it is
     * <code>null</code>, the platform's hostname is used.  This is less than optimal
     * but the plugin container's name will never be <code>null</code> under virtually
     * all use-cases (except perhaps in test scenarios).
     * 
     * @param context the discovery context used to get the plugin container name or host name if its needed
     * 
     * @return the new platform's resource key
     */
    protected String determineResourceKey(ResourceDiscoveryContext context) {
        String pcName = context.getPluginContainerName();
        String resourceKey = pcName;

        if (pcName == null) {
            resourceKey = getHostname(context.getSystemInformation());
        }

        return resourceKey;
    }

    /**
     * This will determine what the new platform's resource name should be. This default
     * implementation first tries to use the plugin container's name which is guaranteed
     * to be unique across all agents/plugin containers.  If, for some odd reason, it is
     * <code>null</code>, the platform's hostname is used.
     * 
     * @param context the discovery context used to get the plugin container name or host name if its needed
     * 
     * @return the new platform's resource key
     */
    protected String determineResourceName(ResourceDiscoveryContext context) {
        String pcName = context.getPluginContainerName();
        String resourceName = pcName;

        if (pcName == null) {
            resourceName = getHostname(context.getSystemInformation());
        }

        return resourceName;
    }

    /**
     * Tries to determine this platform's hostname, using the (possibly native)
     * system info API.  If, for whatever reason, this method cannot determine the
     * hostname, a generic "Unnamed Platform" String will be returned (this will
     * rarely, if ever, happen under normal situations).
     * 
     * @param systemInformation
     * 
     * @return the platform's hostname (will never be <code>null</code>)
     */
    protected String getHostname(SystemInfo systemInformation) {
        String name;

        // ask the system info class to get us the hostname - possibly via the native library
        try {
            name = systemInformation.getHostname();
        } catch (Exception e) {
            name = null;
        }

        // under certain conditions, we might not be able to natively get the hostname so try to get it via Java
        if (name == null) {
            try {
                name = InetAddress.getLocalHost().getHostAddress();
            } catch (Exception e) {
                name = null;
            }

            // we fought the good fight but we just can't get this machine's hostname, give a generic platform name
            if (name == null) {
                name = "Unnamed Platform";
            }
        }
        return name;
    }

    /**
     * Subclasses will implement this method to tell this class whether or not it can discover the platform of the
     * resource type found in the given context. If this returns <code>false</code>, this subclass discovery component
     * instance does not recognize the platform.
     *
     * <p>It is very important that all subclasses must return <code>false</code> <i>except one</i> for any given
     * platform. That is to say, if running on "RHEL 5", only the subclass that supports RHEL 5 must return <code>
     * true</code>, all others must return <code>false</code>. If running on "Windows XP", only the suclass that
     * supports Windows XP must return <code>true</code>. The only exception to this rule is the
     * {@link JavaPlatformDiscoveryComponent fallback Java platform discovery component}, which will always return
     * <code>true</code> because the fallback Java platform is always supported.</p>
     *
     * @param  context
     *
     * @return <code>true</code> if this instance can support the platform this plugin is running on; <code>false</code>
     *         otherwise
     */
    protected abstract boolean isPlatformSupported(ResourceDiscoveryContext context);
}