/*
 * 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.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.management.ManagementFactory;
import java.util.List;
import java.util.Set;

import javax.management.MBeanServer;
import javax.management.ObjectName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.sigar.CpuPerc;
import org.hyperic.sigar.Mem;
import org.hyperic.sigar.Swap;

import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.ConfigurationUpdateStatus;
import org.rhq.core.domain.configuration.PropertyList;
import org.rhq.core.domain.configuration.PropertyMap;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.measurement.AvailabilityType;
import org.rhq.core.domain.measurement.MeasurementDataNumeric;
import org.rhq.core.domain.measurement.MeasurementDataTrait;
import org.rhq.core.domain.measurement.MeasurementReport;
import org.rhq.core.domain.measurement.MeasurementScheduleRequest;
import org.rhq.core.pluginapi.configuration.ConfigurationFacet;
import org.rhq.core.pluginapi.configuration.ConfigurationUpdateReport;
import org.rhq.core.pluginapi.inventory.ResourceComponent;
import org.rhq.core.pluginapi.inventory.ResourceContext;
import org.rhq.core.pluginapi.measurement.MeasurementFacet;
import org.rhq.core.pluginapi.operation.OperationFacet;
import org.rhq.core.pluginapi.operation.OperationResult;
import org.rhq.core.pluginapi.util.ObjectUtil;
import org.rhq.core.system.ProcessInfo;
import org.rhq.core.system.SigarAccess;
import org.rhq.core.system.SystemInfo;

/**
 * Represents the platform resource which is the root resource for all other resources managed.
 *
 * @author John Mazzitelli
 */
public class PlatformComponent implements ResourceComponent, ConfigurationFacet, MeasurementFacet, OperationFacet {

    private final Log log = LogFactory.getLog(PlatformComponent.class);

    /**
     * This is a substring that starts all native-only measurement property names. If a measurement property name starts
     * with this, it can only be collected if we have native support for our platform.
     */
    private static final String NATIVE_INDICATOR = "Native.";

    /**
     * This is a substring that starts all trait measurement property names. If its a native trait, it will appear after
     * the {@link #NATIVE_INDICATOR}.
     */
    private static final String TRAIT_INDICATOR = "Trait.";

    // these are the "property" names for all trait metrics
    private static final String TRAIT_HOSTNAME = TRAIT_INDICATOR + "hostname";
    private static final String TRAIT_OSNAME = TRAIT_INDICATOR + "osname";
    private static final String TRAIT_OSVERSION = TRAIT_INDICATOR + "osversion";
    private static final String TRAIT_ARCH = TRAIT_INDICATOR + "sysarch";

    protected ResourceContext resourceContext;
    private SystemInfo sysinfo;

    public void start(ResourceContext context) {
        this.resourceContext = context;
        this.sysinfo = context.getSystemInformation();
    }

    public void stop() {
    }

    public AvailabilityType getAvailability() {
        // if we are running then obviously, by definition, the platform is up
        return AvailabilityType.UP;
    }

    public Configuration loadResourceConfiguration() {
        // platform configuration is "read-only" and is exposed as traits
        Configuration config = new Configuration();
        return config;
    }

    public void updateResourceConfiguration(ConfigurationUpdateReport report) {
        // most of the platform configuration are "read-only" and
        // are implemented as traits - nothing to update
        report.setErrorMessage("Cannot update platform resource configuration - it is read only");
        report.setStatus(ConfigurationUpdateStatus.FAILURE);
        return;
    }

    public void getValues(MeasurementReport report, Set<MeasurementScheduleRequest> metrics) {
        SystemInfo info = this.resourceContext.getSystemInformation();
        boolean isNative = info.isNative();
        Mem platformMemoryInfo = null;
        Swap platformSwapInfo = null;
        CpuPerc cpuPerc = null;
        try {
            cpuPerc = SigarAccess.getSigar().getCpuPerc();
        } catch (Exception e) {
            // probably native api is unavailable, but getCpuPerc might also have a problem; in either case, nothing we can do
        }

        for (MeasurementScheduleRequest request : metrics) {
            String property = request.getName();

            if (property.startsWith(NATIVE_INDICATOR)) {
                // we cannot collect a native measurement without native support - go on to the next
                if (!isNative) {
                    continue;
                }
                property = property.substring(NATIVE_INDICATOR.length());
            }

            if (property.startsWith(TRAIT_INDICATOR)) {
                report.addData(getMeasurementDataTrait(request));
            } else if (property.startsWith("MemoryInfo.")) {
                if (platformMemoryInfo == null) {
                    platformMemoryInfo = info.getMemoryInfo();
                }

                property = property.substring(property.indexOf(".") + 1);
                double memoryValue = ((Number) getObjectProperty(platformMemoryInfo, property)).doubleValue();
                report.addData(new MeasurementDataNumeric(request, memoryValue));
            } else if (property.startsWith("SwapInfo.")) {
                if (platformSwapInfo == null) {
                    platformSwapInfo = info.getSwapInfo();
                }
                if (platformSwapInfo != null) {
                    property = property.substring(property.indexOf(".") + 1);
                    double swapValue = ((Number) getObjectProperty(platformSwapInfo, property)).doubleValue();
                    report.addData(new MeasurementDataNumeric(request, swapValue));
                }
            } else if (property.startsWith("CpuPerc.")) {

                if (cpuPerc != null) {
                    property = property.substring(property.indexOf(".") + 1);
                    Number num = ((Number) ObjectUtil.lookupAttributeProperty(cpuPerc, property));
                    if (num != null) {
                        report.addData(new MeasurementDataNumeric(request, num.doubleValue()));
                    }
                }
            }
        }
    }

    private Object getObjectProperty(Object object, String name) {
        try {
            BeanInfo info = Introspector.getBeanInfo(object.getClass());
            for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
                if (pd.getName().equals(name)) {
                    return pd.getReadMethod().invoke(object);
                }
            }
        } catch (Exception skip) {
            if (log.isDebugEnabled())
                log.debug(skip);
        }

        return Double.NaN;
    }

    private MeasurementDataTrait getMeasurementDataTrait(MeasurementScheduleRequest request) {
        String name = request.getName();

        MeasurementDataTrait trait = new MeasurementDataTrait(request, "?");

        try {
            if (TRAIT_HOSTNAME.indexOf(name) > -1) {
                trait.setValue(sysinfo.getHostname());
            } else if (TRAIT_OSNAME.indexOf(name) > -1) {
                trait.setValue(sysinfo.getOperatingSystemName());
            } else if (TRAIT_OSVERSION.indexOf(name) > -1) {
                trait.setValue(sysinfo.getOperatingSystemVersion());
            } else if (TRAIT_ARCH.indexOf(name) > -1) {
                trait.setValue(sysinfo.getSystemArchitecture());
            } else {
                log.error("Being asked to collect an unknown trait measurement: " + name);
            }
        } catch (Exception skip) {
            log.debug(skip);
        }

        return trait;
    }

    public OperationResult invokeOperation(String name, Configuration parameters) throws Exception {
        if ("discovery".equals(name)) {
            Boolean detailed = parameters.getSimple("detailedDiscovery").getBooleanValue();
            MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
            String results = (String) mbs.invoke(new ObjectName("rhq.pc:type=PluginContainer"), "executeDiscovery",
                new Object[] { detailed }, new String[] { Boolean.class.getName() });
            return new OperationResult(results);
        } else if ("viewProcessList".equals(name)) {
            OperationResult result = new OperationResult();
            List<ProcessInfo> processes = this.resourceContext.getSystemInformation().getAllProcesses();
            PropertyList processList = new PropertyList("processList");
            for (ProcessInfo process : processes) {
                PropertyMap pm = new PropertyMap("process");
                pm.put(new PropertySimple("pid", process.getPid()));
                pm.put(new PropertySimple("name", process.getBaseName()));
                pm.put(new PropertySimple("size", (process.getMemory() != null) ? process.getMemory().getSize() : "0"));
                pm.put(new PropertySimple("userTime", (process.getTime() != null) ? process.getTime().getUser() : "0"));
                pm
                    .put(new PropertySimple("kernelTime", (process.getTime() != null) ? process.getTime().getSys()
                        : "0"));
                processList.add(pm);
            }

            result.getComplexResults().put(processList);
            return result;
        }

        throw new UnsupportedOperationException("Operation [" + name + "] not supported on "
            + resourceContext.getResourceType() + ".");
    }
}