/*
 * Copyright (C) 2009 eXo Platform SAS.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.exoplatform.management.jmx.impl;

import org.exoplatform.container.management.MetaDataBuilder;
import org.exoplatform.management.annotations.ImpactType;
import org.exoplatform.management.spi.ManagedMethodMetaData;
import org.exoplatform.management.spi.ManagedMethodParameterMetaData;
import org.exoplatform.management.spi.ManagedPropertyMetaData;
import org.exoplatform.management.spi.ManagedTypeMetaData;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.management.Descriptor;
import javax.management.IntrospectionException;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.modelmbean.ModelMBeanAttributeInfo;
import javax.management.modelmbean.ModelMBeanConstructorInfo;
import javax.management.modelmbean.ModelMBeanInfo;
import javax.management.modelmbean.ModelMBeanInfoSupport;
import javax.management.modelmbean.ModelMBeanNotificationInfo;
import javax.management.modelmbean.ModelMBeanOperationInfo;

/**
 * <p>A class that build mbean meta data</p>
 *
 * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
 * @version $Revision$
 */
public class ExoMBeanInfoBuilder
{

   private static enum Role {
      SET("setter"), IS("getter"), GET("getter"), OP("operation");

      private final String name;

      private Role(String role)
      {
         this.name = role;
      }
   }

   private ManagedTypeMetaData typeMD;

   /**
    * Create a new builder.
    *
    * @param clazz the clazz
    * @throws IllegalArgumentException if the class is null or does not contain meta data
    */
   public ExoMBeanInfoBuilder(Class clazz) throws IllegalArgumentException
   {
      this.typeMD = new MetaDataBuilder(clazz).build();
   }

   public ExoMBeanInfoBuilder(ManagedTypeMetaData typeMD) throws IllegalArgumentException
   {
      this.typeMD = typeMD;
   }

   private ModelMBeanOperationInfo buildOperationInfo(Method method, String description, Role role,
      Collection<ManagedMethodParameterMetaData> parametersMD, ImpactType impactType)
   {
      ModelMBeanOperationInfo operationInfo = new ModelMBeanOperationInfo(description, method);

      //
      if (description == null)
      {
         description = "Management operation";
      }

      //
      MBeanParameterInfo[] parameterInfos = operationInfo.getSignature();
      for (ManagedMethodParameterMetaData parameterMD : parametersMD)
      {
         int i = parameterMD.getIndex();
         MBeanParameterInfo parameterInfo = parameterInfos[i];
         String parameterName = parameterInfo.getName();
         String parameterDescription = operationInfo.getSignature()[i].getDescription();
         if (parameterMD.getName() != null)
         {
            parameterName = parameterMD.getName();
         }
         else if (parameterMD.getDescription() != null)
         {
            parameterDescription = parameterMD.getDescription();
         }
         parameterInfos[i] = new MBeanParameterInfo(parameterName, parameterInfo.getType(), parameterDescription);
      }

      //
      int jmxImpact;
      switch (impactType)
      {
         case READ:
            jmxImpact = MBeanOperationInfo.INFO;
            break;
         case IDEMPOTENT_WRITE:
         case WRITE:
            jmxImpact = MBeanOperationInfo.ACTION;
            break;
         default:
            throw new AssertionError();
      }

      //
      Descriptor operationDescriptor = operationInfo.getDescriptor();
      operationDescriptor.setField("role", role.name);

      //
      return new ModelMBeanOperationInfo(operationInfo.getName(), description, parameterInfos, operationInfo
         .getReturnType(), jmxImpact, operationDescriptor);
   }

   /**
    * Build the info.
    *
    * @return returns the info
    * @throws IllegalStateException raised by any build time issue
    */
   public ModelMBeanInfo build() throws IllegalStateException
   {
      String mbeanDescription = "Exo model mbean";
      if (typeMD.getDescription() != null)
      {
         mbeanDescription = typeMD.getDescription();
      }

      //
      ArrayList<ModelMBeanOperationInfo> operations = new ArrayList<ModelMBeanOperationInfo>();
      for (ManagedMethodMetaData methodMD : typeMD.getMethods())
      {
         ModelMBeanOperationInfo operationInfo =
                  buildOperationInfo(methodMD.getMethod(), methodMD.getDescription(), Role.OP,
                           methodMD.getParameters(), methodMD.getImpact());
         operations.add(operationInfo);
      }

      //
      Map<String, ModelMBeanAttributeInfo> attributeInfos = new HashMap<String, ModelMBeanAttributeInfo>();
      for (ManagedPropertyMetaData propertyMD : typeMD.getProperties())
      {

         Method getter = propertyMD.getGetter();
         if (getter != null)
         {
            Role role;
            String getterName = getter.getName();
            if (getterName.startsWith("get") && getterName.length() > 3)
            {
               role = Role.GET;
            }
            else if (getterName.startsWith("is") && getterName.length() > 2)
            {
               role = Role.IS;
            }
            else
            {
               throw new AssertionError();
            }
            Collection<ManagedMethodParameterMetaData> blah = Collections.emptyList();
            ModelMBeanOperationInfo operationInfo =
               buildOperationInfo(getter, propertyMD.getGetterDescription(), role, blah, ImpactType.READ);
            operations.add(operationInfo);
         }

         //
         Method setter = propertyMD.getSetter();
         if (setter != null)
         {
            ManagedMethodParameterMetaData s = new ManagedMethodParameterMetaData(0);
            s.setDescription(propertyMD.getSetterParameter().getDescription());
            s.setName(propertyMD.getSetterParameter().getName());
            Collection<ManagedMethodParameterMetaData> blah = Collections.singletonList(s);
            ModelMBeanOperationInfo operationInfo =
               buildOperationInfo(setter, propertyMD.getSetterDescription(), Role.SET, blah, ImpactType.IDEMPOTENT_WRITE);
            operations.add(operationInfo);
         }

         //
         try
         {
            String attributeDescription =
               propertyMD.getDescription() != null ? propertyMD.getDescription() : ("Managed attribute " + propertyMD
                  .getName());

            //
            ModelMBeanAttributeInfo attributeInfo =
               new ModelMBeanAttributeInfo(propertyMD.getName(), attributeDescription, getter, setter);

            //
            Descriptor attributeDescriptor = attributeInfo.getDescriptor();
            if (getter != null)
            {
               attributeDescriptor.setField("getMethod", getter.getName());
            }
            if (setter != null)
            {
               attributeDescriptor.setField("setMethod", setter.getName());
            }
            attributeDescriptor.setField("currencyTimeLimit", "-1");
            attributeDescriptor.setField("persistPolicy", "Never");
            attributeInfo.setDescriptor(attributeDescriptor);

            //
            ModelMBeanAttributeInfo previous = attributeInfos.put(propertyMD.getName(), attributeInfo);
            if (previous != null)
            {
               throw new IllegalArgumentException();
            }
         }
         catch (IntrospectionException e)
         {
            throw new AssertionError(e);
         }
      }

      //
      return new ModelMBeanInfoSupport(typeMD.getType().getName(), mbeanDescription, attributeInfos.values().toArray(
         new ModelMBeanAttributeInfo[attributeInfos.size()]), new ModelMBeanConstructorInfo[0], operations
         .toArray(new ModelMBeanOperationInfo[operations.size()]), new ModelMBeanNotificationInfo[0]);
   }
}
