/**
 * 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.cxf.management.jmx.export.runtime;

import java.lang.reflect.Method;

import javax.management.Descriptor;
import javax.management.modelmbean.ModelMBeanInfo;

import org.apache.cxf.management.annotation.ManagedAttribute;
import org.apache.cxf.management.annotation.ManagedNotification;
import org.apache.cxf.management.annotation.ManagedNotifications;
import org.apache.cxf.management.annotation.ManagedOperation;
import org.apache.cxf.management.annotation.ManagedOperationParameter;
import org.apache.cxf.management.annotation.ManagedOperationParameters;
import org.apache.cxf.management.annotation.ManagedResource;


public class ModelMBeanAssembler {
    private ModelMBeanInfoSupporter supporter = new ModelMBeanInfoSupporter();

    public ManagedResource getManagedResource(Class<?> clazz) {
        return clazz.getAnnotation(ManagedResource.class);
    }

    public ManagedAttribute getManagedAttribute(Method method) {
        return method.getAnnotation(ManagedAttribute.class);
    }

    public ManagedOperation getManagedOperation(Method method) {
        return method.getAnnotation(ManagedOperation.class);
    }

    public ManagedOperationParameter[] getManagedOperationParameters(Method method) {
        ManagedOperationParameters params = method.getAnnotation(ManagedOperationParameters.class);
        return params != null ? params.value() : new ManagedOperationParameter[0];
    }

    public ManagedNotification[] getManagedNotifications(Class<?> clazz) {
        ManagedNotifications notificationsAnn = clazz.getAnnotation(ManagedNotifications.class);
        return null != notificationsAnn ? notificationsAnn.value() : new ManagedNotification[0];
    }

    public String getAttributeName(String methodName) {
        if (methodName.indexOf("set") == 0) {
            return methodName.substring(3);
        }
        if (methodName.indexOf("get") == 0) {
            return methodName.substring(3);
        }
        if (methodName.indexOf("is") == 0) {
            return methodName.substring(2);
        }
        return null;
    }

    public static boolean checkMethod(Method[] methods, String methodName) {
        boolean result = false;
        for (int i = 0; i < methods.length; i++) {
            if (methods[i].getName().compareTo(methodName) == 0) {
                result = true;
                break;
            }
        }
        return result;
    }

    public static String getAttributeType(Method[] methods, String attributeName) {
        String result = null;
        String searchMethod = "get" + attributeName;
        for (int i = 0; i < methods.length; i++) {
            if (methods[i].getName().compareTo(searchMethod) == 0) {
                result = methods[i].getReturnType().getName();
                break;
            }
        }
        // check it is "is " attribute
        if (null == result) {
            searchMethod = "is" + attributeName;
            for (int i = 0; i < methods.length; i++) {
                if (methods[i].getName().compareTo(searchMethod) == 0) {
                    result = methods[i].getReturnType().getName();
                    break;
                }
            }
        }
        return result;
    }

    static class ManagedAttributeInfo {
        String fname;
        String ftype;
        String description;
        boolean read;
        boolean write;
        boolean is;
    };


    //get the attribut information for the method
    public ManagedAttributeInfo getAttributInfo(Method[] methods,
                                               String attributName,
                                               String attributType,
                                               ManagedAttribute managedAttribute) {
        ManagedAttributeInfo mai = new ManagedAttributeInfo();
        mai.fname = attributName;
        mai.ftype = attributType;
        mai.description = managedAttribute.description();
        mai.is = checkMethod(methods, "is" + attributName);
        mai.write = checkMethod(methods, "set" + attributName);

        if (mai.is) {
            mai.read = true;
        } else {
            mai.read = checkMethod(methods, "get" + attributName);
        }

        return mai;

    }

    Method findMethodByName(Method[] methods, String methodName) {
        for (int i = 0; i < methods.length; i++) {
            if (methods[i].getName().compareTo(methodName) == 0) {
                return methods[i];
            }
            continue;

        }
        return null;

    }

    void addAttributeOperation(Method method) {
        Descriptor operationDescriptor =
            supporter.buildAttributeOperationDescriptor(method.getName());

        Class<?>[] types = method.getParameterTypes();

        String[] paramTypes = new String[types.length];
        String[] paramNames = new String[types.length];
        String[] paramDescs = new String[types.length];

        for (int j = 0; j < types.length; j++) {
            paramTypes[j] = types[j].getName();
            paramDescs[j] = "";
            paramNames[j] = types[j].getName();
        }

        supporter.addModelMBeanMethod(method.getName(),
                                    paramTypes,
                                    paramNames,
                                    paramDescs,
                                    "",
                                    method.getReturnType().getName(),
                                    operationDescriptor);
    }

    public ModelMBeanInfo getModelMbeanInfo(Class<?> clazz) {
        supporter.clear();
        ManagedResource mr = getManagedResource(clazz);
        if (mr == null) {
            // the class is not need to expose to jmx
            return null;
        }
        // Clazz get all the method which should be managemed
        Descriptor mbeanDescriptor = supporter.buildMBeanDescriptor(mr);

        // add the notification
        ManagedNotification[] mns = getManagedNotifications(clazz);
        for (int k = 0; k < mns.length; k++) {
            supporter.addModelMBeanNotification(mns[k].notificationTypes(),
                                          mns[k].name(),
                                          mns[k].description(), null);
        }

        Method[] methods = clazz.getDeclaredMethods();

        for (int i = 0; i < methods.length; i++) {
            ManagedAttribute ma = getManagedAttribute(methods[i]);
            //add Attribute to the ModelMBean
            if (ma != null) {
                String attributeName = getAttributeName(methods[i].getName());
                if (!supporter.checkAttribute(attributeName)) {
                    String attributeType = getAttributeType(methods, attributeName);
                    ManagedAttributeInfo mai = getAttributInfo(methods,
                                                               attributeName,
                                                               attributeType,
                                                               ma);
                    Descriptor attributeDescriptor =
                        supporter.buildAttributeDescriptor(ma,
                                                         attributeName,
                                                         mai.is, mai.read, mai.write);

                    // should setup the description
                    supporter.addModelMBeanAttribute(mai.fname,
                                                   mai.ftype,
                                                   mai.read,
                                                   mai.write,
                                                   mai.is,
                                                   mai.description,
                                                   attributeDescriptor);

                    Method method;
                    // add the attribute methode to operation
                    if (mai.read) {
                        if (mai.is) {
                            method = findMethodByName(methods, "is" + attributeName);
                        } else {
                            method = findMethodByName(methods, "get" + attributeName);
                        }
                        addAttributeOperation(method);
                    }
                    if (mai.write) {
                        method = findMethodByName(methods, "set" + attributeName);
                        addAttributeOperation(method);
                    }
                }

            } else {
                // add Operation to the ModelMBean
                ManagedOperation mo = getManagedOperation(methods[i]);

                if (mo != null) {
                    Class<?>[] types = methods[i].getParameterTypes();
                    ManagedOperationParameter[] mop = getManagedOperationParameters(methods[i]);
                    String[] paramTypes = new String[types.length];
                    String[] paramNames = new String[types.length];
                    String[] paramDescs = new String[types.length];

                    for (int j = 0; j < types.length; j++) {
                        paramTypes[j] = types[j].getName();
                        if (j < mop.length) {
                            paramDescs[j] = mop[j].description();
                            paramNames[j] = mop[j].name();
                        } else {
                            paramDescs[j] = "";
                            paramNames[j] = types[j].getName();
                        }
                    }
                    Descriptor operationDescriptor =
                        supporter.buildOperationDescriptor(mo, methods[i].getName());
                    supporter.addModelMBeanMethod(methods[i].getName(),
                                                paramTypes,
                                                paramNames,
                                                paramDescs,
                                                mo.description(),
                                                methods[i].getReturnType().getName(),
                                                operationDescriptor);
                }
            }

        }
        return supporter.buildModelMBeanInfo(mbeanDescriptor);
    }
}
