/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF 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.apache.brooklyn.entity.software.base.test.jmx;

import static com.google.common.base.Preconditions.checkArgument;

import java.util.Map;
import java.util.Map.Entry;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.DynamicMBean;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanConstructorInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;

import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;

/**
 * A quick-and-simple general-purpose implementation of DynamicMBean.
 *
 * This class provides an implementation of {@link DynamicMBean}. Its initial set of attribute names and values are
 * provided to the constructor; from this it figures an {@link MBeanInfo}.
 * <p>
 * It presently assumes that all attributes are read-only; operations and notifications are not currently supported.
 * Choosing the descriptions is not supported - they are set to be the same as the name.
 * <p>
 * Getting a valid dynamic MBean (in Groovy) is as simple as:
 * <pre>
 * new GeneralisedDynamicMBean(meaning: 42, advice: "Don't panic")
 * </pre>
 */
public class GeneralisedDynamicMBean implements DynamicMBean {
    private final MBeanInfo mBeanInfo;
    private final Map<String,Object> attributes = Maps.newLinkedHashMap();
    private final Map<String,Function> operations = Maps.newLinkedHashMap();
    
    public GeneralisedDynamicMBean(Map<String,?> initialAttributes, Map<?,?> initialOperations) {
        attributes.putAll(initialAttributes);

        for (Entry<?,?> entry : initialOperations.entrySet()) {
            checkArgument(entry.getKey() instanceof String || entry.getKey() instanceof MBeanOperationInfo, "entry.key=%s", entry.getKey());
            String opName = (entry.getKey() instanceof String) ? (String)entry.getKey() : ((MBeanOperationInfo)entry.getKey()).getName();
            operations.put(opName, (Function) entry.getValue());
        }
        
        Iterable<MBeanAttributeInfo> attrInfo = Iterables.transform(initialAttributes.entrySet(), new Function<Map.Entry<String,?>, MBeanAttributeInfo>() {
            @Override public MBeanAttributeInfo apply(Map.Entry<String,?> entry) {
                return new MBeanAttributeInfo(entry.getKey(), entry.getValue().getClass().getName(), entry.getKey(), true, false, false);
            }
        });
        
        Iterable<MBeanOperationInfo> opInfo = Iterables.transform(initialOperations.keySet(), new Function<Object, MBeanOperationInfo>() {
            @Override
            public MBeanOperationInfo apply(Object it) {
                if (it instanceof MBeanOperationInfo) {
                    return (MBeanOperationInfo) it;
                } else if (it instanceof CharSequence) {
                    return new MBeanOperationInfo(
                            it.toString(),
                            "my descr", 
                            new MBeanParameterInfo[0], 
                            "void", 
                            MBeanOperationInfo.ACTION_INFO);
                } else {
                    throw new IllegalArgumentException("Cannot convert "+it+" to MBeanOperationInfo");
                }
            }});
        
        mBeanInfo = new MBeanInfo(
                GeneralisedDynamicMBean.class.getName(), 
                GeneralisedDynamicMBean.class.getName(), 
                Iterables.toArray(attrInfo, MBeanAttributeInfo.class),
                new MBeanConstructorInfo[0], 
                Iterables.toArray(opInfo, MBeanOperationInfo.class),
                new MBeanNotificationInfo[0]);
    }

    public void updateAttributeValue(String name, Object value) {
        attributes.put(name, value);
    }

    @Override
    public Object getAttribute(String s) {
        return attributes.get(s);
    }

    @Override
    public void setAttribute(Attribute attribute) {
        attributes.put(attribute.getName(), attribute.getValue());
    }

    @Override
    public AttributeList getAttributes(String[] strings) {
        AttributeList result = new AttributeList();
        for (Object obj : mBeanInfo.getAttributes()) {
            Attribute attrib = (Attribute) obj;
            result.add(new Attribute(attrib.getName(), attributes.get(attrib.getName())));
        }
        return result;
    }

    @Override
    public AttributeList setAttributes(AttributeList attributeList) {
        for (Object element : attributeList) {
            Attribute attrib = (Attribute) element;
            attributes.put(attrib.getName(), attrib.getValue());
        }
        return attributeList;
    }

    @Override
    public Object invoke(String s, Object[] objects, String[] strings) {
        Function op = operations.get(s);
        if (op != null) {
            return op.apply(objects);
        } else {
            throw new RuntimeException("Unknown operation "+s);
        }
    }

    @Override
    public MBeanInfo getMBeanInfo() {
        return mBeanInfo;
    }
}
