package org.rhq.plugins.jbossas5;

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jetbrains.annotations.NotNull;
import org.mc4j.ems.connection.EmsConnection;

import org.jboss.deployers.spi.management.ManagementView;
import org.jboss.managed.api.ManagedComponent;
import org.jboss.managed.api.ManagedDeployment;
import org.jboss.managed.api.ManagedObject;
import org.jboss.managed.api.ManagedOperation;
import org.jboss.managed.api.ManagedProperty;
import org.jboss.metatype.api.types.MetaType;
import org.jboss.metatype.api.values.MetaValue;
import org.jboss.profileservice.spi.NoSuchDeploymentException;

import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.measurement.AvailabilityType;
import org.rhq.core.domain.measurement.DataType;
import org.rhq.core.domain.measurement.MeasurementDataNumeric;
import org.rhq.core.domain.measurement.MeasurementDataTrait;
import org.rhq.core.domain.measurement.MeasurementDefinition;
import org.rhq.core.domain.measurement.MeasurementReport;
import org.rhq.core.domain.measurement.MeasurementScheduleRequest;
import org.rhq.core.domain.operation.OperationDefinition;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.core.pluginapi.configuration.ConfigurationFacet;
import org.rhq.core.pluginapi.configuration.ConfigurationUpdateReport;
import org.rhq.core.pluginapi.inventory.CreateResourceReport;
import org.rhq.core.pluginapi.inventory.InvalidPluginConfigurationException;
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.plugins.jbossas5.adapter.api.MeasurementAdapter;
import org.rhq.plugins.jbossas5.adapter.impl.measurement.SimpleMetaValueMeasurementAdapter;
import org.rhq.plugins.jbossas5.connection.ProfileServiceConnection;
import org.rhq.plugins.jbossas5.util.ConversionUtils;
import org.rhq.plugins.jbossas5.util.ResourceTypeUtils;

public class PlatformComponent implements ProfileServiceComponent<ProfileServiceComponent<?>>, MeasurementFacet,
    OperationFacet, ConfigurationFacet {
    private final Log log = LogFactory.getLog(this.getClass());
    private ResourceContext<ProfileServiceComponent<?>> context;
    private String deployName;
    private String componentName;

    public PlatformComponent() {
    }

    public void start(ResourceContext<ProfileServiceComponent<?>> context) throws InvalidPluginConfigurationException,
        Exception {
        this.context = context;
    }

    public void stop() {
        return;
    }

    @NotNull
    public ResourceContext<ProfileServiceComponent<?>> getResourceContext() {
        return this.context;
    }

    public ProfileServiceConnection getConnection() {
        ApplicationServerComponent component = (ApplicationServerComponent) context.getParentResourceComponent();
        return component.getConnection();

    }

    public EmsConnection getEmsConnection() {
        return context.getParentResourceComponent().getEmsConnection();
    }

    public CreateResourceReport createResource(CreateResourceReport report) {
        // TODO Auto-generated method stub
        return null;
    }

    // ------------ MeasurementFacet Implementation ------------

    public void getValues(MeasurementReport report, Set<MeasurementScheduleRequest> requests) {
        try {
            ManagedComponent component = loadComponent();
            String[] propertyName;

            for (MeasurementScheduleRequest request : requests) {
                String metricName = request.getName();

                propertyName = parsePropertyName(metricName);
                if (propertyName.length == 0)
                    continue;

                ManagedProperty prop = component.getProperties().get(propertyName[0]);

                if (prop != null) {
                    MetaType type = prop.getMetaType();
                    MetaValue value = prop.getValue();

                    if (value != null) {
                        MeasurementDefinition measurementDefinition = ResourceTypeUtils.getMeasurementDefinition(
                            context.getResourceType(), metricName);

                        if (type.isSimple()) {
                            MeasurementAdapter measurementAdapter = new SimpleMetaValueMeasurementAdapter();
                            if (measurementDefinition != null & measurementAdapter != null)
                                measurementAdapter.setMeasurementData(report, value, request, measurementDefinition);
                        }
                        if (type.isComposite()) {

                            ManagedObject obj = prop.getManagedObject();
                            Object attachment = obj.getAttachment();

                            for (int i = 0; i < propertyName.length; i++) {
                                attachment = getObjectProperty(attachment, propertyName[i]);
                            }

                            setMeasurementData(report, attachment, request, measurementDefinition);

                        }

                    }
                }
            }

        } catch (NoSuchDeploymentException e) {
            log.error("Loaded component does not exist: " + e);
        } catch (SecurityException e) {
            log.error("Security Exception: " + e);

        }
    }

    public Configuration loadResourceConfiguration() throws Exception {
        // TODO Auto-generated method stub
        return null;
    }

    public void updateResourceConfiguration(ConfigurationUpdateReport report) {
        // TODO Auto-generated method stub

    }

    private ManagedComponent loadComponent() throws NoSuchDeploymentException {

        deployName = context.getPluginConfiguration().getSimple(PlatformDiscoveryComponent.PLATFORM_DEPLOYMENT_NAME)
            .getStringValue();

        componentName = context.getPluginConfiguration().getSimple(PlatformDiscoveryComponent.PLATFORM_COMPONENT_NAME)
            .getStringValue();

        ProfileServiceConnection connection = context.getParentResourceComponent().getConnection();

        ManagementView managementView = connection.getManagementView();

        ManagedDeployment deploy = managementView.getDeployment(deployName);
        if (deploy != null) {
            Map<String, ManagedComponent> components = deploy.getComponents();

            return components.get(componentName);
        }

        return null;
    }

    public AvailabilityType getAvailability() {
        // TODO Auto-generated method stub
        return null;
    }

    public String[] parsePropertyName(String name) {

        String[] ps;

        if (name.indexOf('{') == -1) {
            ps = new String[1];
            ps[0] = name;
            return ps;
        }
        name = name.substring(name.indexOf('{') + 1, name.indexOf('}'));
        ps = name.split("\\.");

        return ps;
    }

    protected Object getObjectProperty(Object value, String property) {

        try {
            PropertyDescriptor[] pds = Introspector.getBeanInfo(value.getClass()).getPropertyDescriptors();
            for (PropertyDescriptor pd : pds) {
                if (pd.getName().equals(property)) {
                    pd.getReadMethod().setAccessible(true);
                    value = pd.getReadMethod().invoke(value);
                }
            }
        } catch (Exception e) {
            log.error("Object " + value.toString() + " does not contain property " + property, e);
        }

        return value;
    }

    public void setMeasurementData(MeasurementReport report, Object value, MeasurementScheduleRequest request,
        MeasurementDefinition measurementDefinition) {

        DataType dataType = measurementDefinition.getDataType();
        switch (dataType) {
        case MEASUREMENT:
            try {
                MeasurementDataNumeric dataNumeric = new MeasurementDataNumeric(request, new Double(value.toString()));
                report.addData(dataNumeric);
            } catch (NumberFormatException e) {
                log.warn("Measurement request: " + request.getName()
                    + " did not return a numeric value from the Profile Service", e);
            }
            break;
        case TRAIT:
            MeasurementDataTrait dataTrait = new MeasurementDataTrait(request, String.valueOf(value));
            report.addData(dataTrait);
            break;
        default:
            throw new IllegalStateException("Unsupported measurement data type: " + dataType);
        }
    }

    public OperationResult invokeOperation(String name, Configuration parameters) throws InterruptedException,
        Exception {

        ResourceType resourceType = context.getResourceType();
        OperationDefinition operationDefinition = ResourceTypeUtils.getOperationDefinition(resourceType, name);

        ManagedComponent component = loadComponent();
        Set<ManagedOperation> operations = component.getOperations();

        for (ManagedOperation operation : operations) {
            if (operation.getName().equals(name)) {
                MetaValue[] params = ConversionUtils.convertOperationsParametersToMetaValues(operation, parameters,
                    operationDefinition);
                MetaValue operationResult = operation.invoke(params);

                OperationResult result = new OperationResult();
                ConversionUtils.convertManagedOperationResults(operation, operationResult, result.getComplexResults(),
                    operationDefinition);

                return result;
            }
        }
        return null;
    }
}
