package org.cyclopsgroup.jmxterm.cmd;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Pattern;

import javax.management.JMException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanFeatureInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang.builder.CompareToBuilder;
import org.cyclopsgroup.jcli.annotation.Cli;
import org.cyclopsgroup.jcli.annotation.Option;
import org.cyclopsgroup.jmxterm.Command;
import org.cyclopsgroup.jmxterm.Session;

/**
 * Command that displays attributes and operations of an MBean
 * 
 * @author <a href="mailto:jiaqi.guo@gmail.com">Jiaqi Guo</a>
 */
@Cli( name = "info", description = "Display detail information about an MBean", note = "If -b option is not specified, current selected MBean is applied" )
public class InfoCommand
    extends Command
{
    private static final Comparator<MBeanFeatureInfo> INFO_COMPARATOR = new Comparator<MBeanFeatureInfo>()
    {
        public int compare( MBeanFeatureInfo o1, MBeanFeatureInfo o2 )
        {
            return new CompareToBuilder().append( o1.getName(), o2.getName() ).append( o1.hashCode(), o2.hashCode() ).toComparison();
        }
    };

    private static final String TEXT_ATTRIBUTES = "# attributes";

    private static final String TEXT_NOTIFICATIONS = "# notifications";

    private static final String TEXT_OPERATIONS = "# operations";

    private String bean;

    private String domain;

    private boolean showDescription;

    private String type = "aon";

    private void displayAttributes( MBeanInfo info )
    {
        Session session = getSession();
        MBeanAttributeInfo[] attrInfos = info.getAttributes();
        if ( attrInfos.length == 0 )
        {
            session.output.printMessage( "there is no attribute" );
            return;
        }
        int index = 0;
        session.output.println( TEXT_ATTRIBUTES );
        List<MBeanAttributeInfo> infos = new ArrayList<MBeanAttributeInfo>( Arrays.asList( attrInfos ) );
        Collections.sort( infos, INFO_COMPARATOR );
        for ( MBeanAttributeInfo attr : infos )
        {
            String rw = "" + ( attr.isReadable() ? "r" : "" ) + ( attr.isWritable() ? "w" : "" );
            session.output.println( String.format( "  %%%-3d - %s (%s, %s)" + ( showDescription ? ", %s" : "" ),
                                                   index++, attr.getName(), attr.getType(), rw, attr.getDescription() ) );
        }
    }

    private void displayNotifications( MBeanInfo info )
    {
        Session session = getSession();
        MBeanNotificationInfo[] notificationInfos = info.getNotifications();
        if ( notificationInfos.length == 0 )
        {
            session.output.printMessage( "there's no notifications" );
            return;
        }
        int index = 0;
        session.output.println( TEXT_NOTIFICATIONS );
        for ( MBeanNotificationInfo notification : notificationInfos )
        {
            session.output.println( String.format( "  %%%-3d - %s(%s)" + ( showDescription ? ", %s" : "" ), index++,
                                                   notification.getName(),
                                                   StringUtils.join( notification.getNotifTypes(), "," ),
                                                   notification.getDescription() ) );
        }

    }

    private void displayOperations( MBeanInfo info )
    {
        Session session = getSession();
        MBeanOperationInfo[] operationInfos = info.getOperations();
        if ( operationInfos.length == 0 )
        {
            session.output.printMessage( "there's no operations" );
            return;
        }
        List<MBeanOperationInfo> operations = new ArrayList<MBeanOperationInfo>( Arrays.asList( operationInfos ) );
        Collections.sort( operations, INFO_COMPARATOR );
        session.output.println( TEXT_OPERATIONS );
        int index = 0;
        for ( MBeanOperationInfo op : operations )
        {
            MBeanParameterInfo[] paramInfos = op.getSignature();
            List<String> paramTypes = new ArrayList<String>( paramInfos.length );
            for ( MBeanParameterInfo paramInfo : paramInfos )
            {
                paramTypes.add( paramInfo.getType() + " " + paramInfo.getName() );
            }
            session.output.println( String.format( "  %%%-3d - %s %s(%s)" + ( showDescription ? ", %s" : "" ), index++,
                                                   op.getReturnType(), op.getName(),
                                                   StringUtils.join( paramTypes, ',' ), op.getDescription() ) );
        }
    }

    /**
     * @inheritDoc
     */
    @Override
    public void execute()
        throws IOException, JMException
    {
        Session session = getSession();
        String beanName = BeanCommand.getBeanName( bean, domain, session );
        if ( beanName == null )
        {
            throw new IllegalArgumentException( "Please specify a bean using either -b option or bean command" );
        }
        ObjectName name = new ObjectName( beanName );
        MBeanServerConnection con = session.getConnection().getServerConnection();
        MBeanInfo info = con.getMBeanInfo( name );
        session.output.printMessage( "mbean = " + beanName );
        session.output.printMessage( "class name = " + info.getClassName() );
        for ( char t : type.toCharArray() )
        {
            switch ( t )
            {
                case 'a':
                    displayAttributes( info );
                    break;
                case 'o':
                    displayOperations( info );
                    break;
                case 'n':
                    displayNotifications( info );
                    break;
                default:
                    throw new IllegalArgumentException( "Unrecognizable character " + t + " in type option " + type );
            }
        }
    }

    /**
     * @param bean Bean for which information is displayed
     */
    @Option( name = "b", longName = "bean", description = "Name of MBean" )
    public final void setBean( String bean )
    {
        this.bean = bean;
    }

    /**
     * Given domain
     * 
     * @param domain Domain name
     */
    @Option( name = "d", longName = "domain", description = "Domain for bean" )
    public final void setDomain( String domain )
    {
        this.domain = domain;
    }

    /**
     * @param showDescription True to show detail description
     */
    @Option( name = "e", longName = "detail", description = "Show description" )
    public final void setShowDescription( boolean showDescription )
    {
        this.showDescription = showDescription;
    }

    /**
     * @param type Type of detail to display
     */
    @Option( name = "t", longName = "type", description = "Types(a|o|u) to display, for example aon for all attributes, operations and notifications" )
    public void setType( String type )
    {
        Validate.isTrue( StringUtils.isNotEmpty( type ), "Type can't be NULL" );
        Validate.isTrue( Pattern.matches( "^a?o?n?$", type ), "Type must be a?|o?|n?" );
        this.type = type;
    }
}
