/*
 * JBoss, Home of Professional Open Source
 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * 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.jboss.managed.plugins.jmx;

import java.lang.management.ClassLoadingMXBean;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryManagerMXBean;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.RuntimeMXBean;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.lang.reflect.AnnotatedElement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.management.openmbean.ArrayType;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenDataException;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;

import org.jboss.managed.api.ManagedObject;
import org.jboss.managed.api.factory.ManagedObjectFactory;
import org.jboss.metadata.plugins.loader.reflection.AnnotatedElementMetaDataLoader;
import org.jboss.metadata.spi.MetaData;
import org.jboss.metadata.spi.retrieval.MetaDataRetrievalToMetaDataBridge;
import org.jboss.metatype.api.types.ArrayMetaType;
import org.jboss.metatype.api.types.CompositeMetaType;
import org.jboss.metatype.api.types.EnumMetaType;
import org.jboss.metatype.api.types.ImmutableCompositeMetaType;
import org.jboss.metatype.api.types.MetaType;
import org.jboss.metatype.api.types.SimpleMetaType;
import org.jboss.metatype.api.values.ArrayValue;
import org.jboss.metatype.api.values.CompositeValue;
import org.jboss.metatype.api.values.EnumValue;
import org.jboss.metatype.api.values.MetaValue;
import org.jboss.metatype.api.values.SimpleValue;

/**
 * A utility class that created ManagedObjects for the jmx platform mbeans
 * returned by {@link java.lang.management.ManagementFactory}
 * 
 * @author Scott.Stark@jboss.org
 * @version $Revision:$
 */
public class ManagementFactoryUtils
{
   /**
    * Return a Map of the platform mbean ManagedObjects. This does not include
    * the MemoryManagerMXBeans, GarbageCollectorMXBeans or MemoryPoolMXBeans
    * lists of ManagedObjects.
    * @param mof - the ManagedObjectFactory to use
    * @return Map of ManagedObjects for the platform mbeans keyed by the ManagedObject names
    */
   public static Map<String, ManagedObject> getPlatformMBeanMOs(ManagedObjectFactory mof)
   {
      HashMap<String, ManagedObject> mos = new HashMap<String, ManagedObject>();
      ManagedObject classLoadingMO = getClassLoadingMO(mof);
      mos.put(classLoadingMO.getName(), classLoadingMO);
      ManagedObject memoryMXBean = getMemoryMXBean(mof);
      mos.put(memoryMXBean.getName(), memoryMXBean);
      ManagedObject operatingSystemMXBean = getOperatingSystemMXBean(mof);
      mos.put(operatingSystemMXBean.getName(), operatingSystemMXBean);
      ManagedObject threadMXBean = getThreadMXBean(mof);
      mos.put(threadMXBean.getName(), threadMXBean);
      ManagedObject runtimeMXBean = getRuntimeMXBean(mof);
      mos.put(runtimeMXBean.getName(), runtimeMXBean);

      return mos;
   }

   /**
    * Build a ManagedObject for the ClassLoadingMXBean mean
    * @param mof - the ManagedObjectFactory to use
    * @return
    */
   public static ManagedObject getClassLoadingMO(ManagedObjectFactory mof)
   {
      ClassLoadingMXBean mbean = ManagementFactory.getClassLoadingMXBean();
      ManagedObject mo = getMO(mbean, ClassLoadingMXBeanMO.class, mof);
      return mo;
   }

   /**
    * Build a ManagedObject for the MemoryMXBean mean
    * @param mof - the ManagedObjectFactory to use
    * @return
    */
   public static ManagedObject getMemoryMXBean(ManagedObjectFactory mof)
   {
      MemoryMXBean mbean = ManagementFactory.getMemoryMXBean();
      ManagedObject mo = getMO(mbean, MemoryMXBeanMO.class, mof);
      return mo;
   }

   /**
    * Build a ManagedObject for the OperatingSystemMXBean mean
    * @param mof - the ManagedObjectFactory to use
    * @return
    */
   public static ManagedObject getOperatingSystemMXBean(ManagedObjectFactory mof)
   {
      OperatingSystemMXBean mbean = ManagementFactory.getOperatingSystemMXBean();
      ManagedObject mo = getMO(mbean, OperatingSystemMXBeanMO.class, mof);
      return mo;
   }

   /**
    * Build a ManagedObject for the ThreadMXBean mean
    * @param mof - the ManagedObjectFactory to use
    * @return
    */
   public static ManagedObject getThreadMXBean(ManagedObjectFactory mof)
   {
      ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
      ManagedObject mo = getMO(mbean, ThreadMXBeanMO.class, mof);
      return mo;
   }

   /**
    * Build a ManagedObject for the RuntimeMXBean mean
    * @param mof - the ManagedObjectFactory to use
    * @return
    */
   public static ManagedObject getRuntimeMXBean(ManagedObjectFactory mof)
   {
      RuntimeMXBean mbean = ManagementFactory.getRuntimeMXBean();
      ManagedObject mo = getMO(mbean, RuntimeMXBeanMO.class, mof);
      return mo;
   }

   /**
    * Build a list of ManagedObject for the MemoryManagerMXBean means
    * @param mof - the ManagedObjectFactory to use
    * @return
    */
   public static List<ManagedObject> getMemoryManagerMXBeans(ManagedObjectFactory mof)
   {
      List<MemoryManagerMXBean> mbeans = ManagementFactory.getMemoryManagerMXBeans();
      ArrayList<ManagedObject> mos = new ArrayList<ManagedObject>();
      for(MemoryManagerMXBean mbean : mbeans)
      {
         ManagedObject mo = getMO(mbean, MemoryManagerMXBeanMO.class, mof);
         mos.add(mo);
      }
      return mos;
   }

   /**
    * Build a list of ManagedObject for the GarbageCollectorMXBean means
    * @param mof - the ManagedObjectFactory to use
    * @return
    */
   public static List<ManagedObject> getGarbageCollectorMXBeans(ManagedObjectFactory mof)
   {
      List<GarbageCollectorMXBean> mbeans = ManagementFactory.getGarbageCollectorMXBeans();
      ArrayList<ManagedObject> mos = new ArrayList<ManagedObject>();
      for(GarbageCollectorMXBean mbean : mbeans)
      {
         ManagedObject mo = getMO(mbean, GarbageCollectorMXBeanMO.class, mof);
         mos.add(mo);
      }
      return mos;
   }

   /**
    * Build a list of ManagedObject for the MemoryPoolMXBean means
    * @param mof - the ManagedObjectFactory to use
    * @return
    */
   public static List<ManagedObject> getMemoryPoolMXBeans(ManagedObjectFactory mof)
   {
      List<MemoryPoolMXBean> mbeans = ManagementFactory.getMemoryPoolMXBeans();
      ArrayList<ManagedObject> mos = new ArrayList<ManagedObject>();
      for(MemoryPoolMXBean mbean : mbeans)
      {
         ManagedObject mo = getMO(mbean, MemoryPoolMXBeanMO.class, mof);
         mos.add(mo);
      }
      return mos;
   }

   /**
    * Unwrap a CompositeValue for a MemoryUsage instance into the MemoryUsage.
    * 
    * @param mv - the CompositeValue meta value
    * @return the corresponding MemoryUsage instance
    */
   public static MemoryUsage unwrapMemoryUsage(CompositeValue mv)
   {
      SimpleValue committedSV = SimpleValue.class.cast(mv.get("committed"));
      SimpleValue initSV = SimpleValue.class.cast(mv.get("init"));
      SimpleValue maxSV = SimpleValue.class.cast(mv.get("max"));
      SimpleValue usedSV = SimpleValue.class.cast(mv.get("used"));
      long committed = (Long) committedSV.getValue();
      long init = (Long) initSV.getValue();
      long max = (Long) maxSV.getValue();
      long used = (Long) usedSV.getValue();
      MemoryUsage mu = new MemoryUsage(init, used, committed, max);
      return mu;
   }

   public static <I> ManagedObject getMO(I mbean, final Class<? extends I> c, ManagedObjectFactory mof)
   {
      AnnotatedElement mbeanClass = c;
      AnnotatedElementMetaDataLoader retrieval = new AnnotatedElementMetaDataLoader(mbeanClass);
      MetaData metaData = new MetaDataRetrievalToMetaDataBridge(retrieval);
      ManagedObject mo = mof.initManagedObject(mbean, c, metaData, null, null);
      return mo;
   }

   public static ThreadInfo unwrapThreadInfo(CompositeValue mv)
      throws OpenDataException, ClassNotFoundException
   {
      CompositeData cd = (CompositeData) getOpenValue(mv);
      ThreadInfo ti = ThreadInfo.from(cd);
      return ti;
   }

   static OpenType getOpenType(MetaType type) throws OpenDataException
   {
      OpenType openType = null;
      if(type instanceof SimpleMetaType)
         openType = getOpenType(SimpleMetaType.class.cast(type));
      else if(type instanceof CompositeMetaType)
         openType = getOpenType(CompositeMetaType.class.cast(type));
      else if(type instanceof ArrayMetaType)
         openType = getOpenType(ArrayMetaType.class.cast(type));
      else if(type instanceof EnumMetaType)
         openType = SimpleType.STRING;
      else
         throw new OpenDataException("Unhandled MetaType: "+type);
      return openType;
   }

   static OpenType getOpenType(SimpleMetaType type)
   {
      OpenType openType = null;
      if(type.equalsIgnorePrimitive(SimpleMetaType.BOOLEAN))
         openType = SimpleType.BOOLEAN;
      else if(type.equalsIgnorePrimitive(SimpleMetaType.BYTE))
         openType = SimpleType.BYTE;
      else if(type.equalsIgnorePrimitive(SimpleMetaType.CHARACTER))
         openType = SimpleType.CHARACTER;
      else if(type.equalsIgnorePrimitive(SimpleMetaType.LONG))
         openType = SimpleType.LONG;
      else if(type.equalsIgnorePrimitive(SimpleMetaType.INTEGER))
         openType = SimpleType.INTEGER;
      else if(type.equalsIgnorePrimitive(SimpleMetaType.SHORT))
         openType = SimpleType.SHORT;
      else if(type.equalsIgnorePrimitive(SimpleMetaType.STRING))
         openType = SimpleType.STRING;
      return openType;
   }
   static ArrayType getOpenType(ArrayMetaType type) throws OpenDataException
   {
      int dimension = type.getDimension();
      OpenType elementType = getOpenType(type.getElementType());
      ArrayType openType = new ArrayType(dimension, elementType);
      return openType;
   }
   static CompositeType getOpenType(CompositeMetaType type)
      throws OpenDataException
   {
      String[] items = new String[type.itemSet().size()];
      type.itemSet().toArray(items);
      String[] descriptions = new String[type.itemSet().size()];
      OpenType[] itemTypes = new OpenType[items.length];
      for(int n = 0; n < items.length; n ++)
      {
         String item = items[n];
         descriptions[n] = type.getDescription(item);
         MetaType mt = type.getType(item);
         itemTypes[n] = getOpenType(mt);
      }

      CompositeType ct = new CompositeType(type.getTypeName(),
            type.getDescription(), items, descriptions, itemTypes);
      return ct;
   }

   static Object getOpenValue(MetaValue mv) throws OpenDataException
   {
      Object openValue = null;
      if(mv instanceof SimpleValue)
      {
         SimpleValue sv = SimpleValue.class.cast(mv);
         openValue = sv.getValue();
      }
      else if(mv instanceof CompositeValue)
      {
         CompositeValue cv = CompositeValue.class.cast(mv);
         HashMap<String, Object> itemsMap = new HashMap<String, Object>();
         CompositeMetaType cmt = cv.getMetaType();
         String[] items = new String[cmt.itemSet().size()];
         Object[] itemValues = new Object[items.length];
         cmt.itemSet().toArray(items);
         for(int n = 0; n < items.length; n ++)
         {
            String item = items[n];
            MetaValue itemMV = cv.get(item);
            Object itemValue = null;
            if(itemMV != null )
               itemValue = getOpenValue(itemMV);
            itemValues[n] = itemValue;
         }

         CompositeType ct = getOpenType(cmt);
         CompositeDataSupport cd = new CompositeDataSupport(ct, items, itemValues);
         openValue = cd;
      }
      else if(mv instanceof ArrayValue)
      {
         ArrayValue av = ArrayValue.class.cast(mv);
         if(av.getMetaType().isPrimitiveArray() || av.getMetaType().isSimple())
            openValue = av.getValue();
         else if(av.getMetaType().getElementType().isComposite())
         {
            CompositeData[] cd = new CompositeData[av.getLength()];
            for(int n = 0; n < av.getLength(); n ++)
            {
               cd[n] = (CompositeData) getOpenValue(CompositeValue.class.cast(av.getValue(n)));
            }
            openValue = cd;
         }
      }
      else if(mv instanceof EnumValue)
      {
         EnumValue ev = EnumValue.class.cast(mv);
         openValue = ev.getValue();
      }
      else
      {
         throw new OpenDataException("Unhandled MetaValue: "+mv);
      }
      return openValue;
   }

   static MetaType getMetaType(OpenType type)
      throws Exception
   {
      MetaType metaType = null;
      if(type instanceof SimpleType)
         metaType = getMetaType(SimpleType.class.cast(type));
      else if(type instanceof CompositeType)
         metaType = getMetaType(CompositeType.class.cast(type));
      else if(type instanceof ArrayType)
         metaType = getMetaType(ArrayType.class.cast(type));
      else
         throw new Exception("Unhandled OpenType: "+type);
      return metaType;
   }
   static SimpleMetaType getMetaType(SimpleType type)
   {
      SimpleMetaType metaType = null;
      if(type == SimpleType.BOOLEAN)
         metaType = SimpleMetaType.BOOLEAN;
      else if(type == SimpleType.BYTE)
         metaType = SimpleMetaType.BYTE;
      else if(type == SimpleType.CHARACTER)
         metaType = SimpleMetaType.CHARACTER;
      else if(type == SimpleType.LONG)
         metaType = SimpleMetaType.LONG;
      else if(type == SimpleType.INTEGER)
         metaType = SimpleMetaType.INTEGER;
      else if(type == SimpleType.SHORT)
         metaType = SimpleMetaType.SHORT;
      else if(type == SimpleType.STRING)
         metaType = SimpleMetaType.STRING;
      return metaType;
   }
   static ArrayMetaType getMetaType(ArrayType type)
      throws Exception
   {
      int dimension = type.getDimension();
      OpenType elementType = type.getElementOpenType();
      MetaType elementMetaType = getMetaType(elementType);
      ArrayMetaType metaType = new ArrayMetaType(dimension, elementMetaType);
      return metaType;
   }
   static CompositeMetaType getMetaType(CompositeType type)
      throws Exception
   {
      String[] itemNames = new String[type.keySet().size()];
      String[] itemDescriptions = new String[itemNames.length];
      MetaType[] itemTypes = new MetaType[itemNames.length];
      type.keySet().toArray(itemNames);
      for(int n = 0; n < itemNames.length; n ++)
      {
         String itemName = itemNames[n];
         itemDescriptions[n] = type.getDescription(itemName);
         OpenType ot = type.getType(itemName);
         itemTypes[n] = getMetaType(ot);
      }
      
      String typeName = type.getTypeName();
      String description = type.getDescription();
      CompositeMetaType cmt = new ImmutableCompositeMetaType(typeName, description, itemNames, itemDescriptions, itemTypes);
      return cmt;
   }
}
