/*
 * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package org.graalvm.visualvm.modules.mbeans;

import javax.swing.*;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.FlowLayout;
import java.awt.Component;
import java.awt.event.*;
import java.rmi.UnmarshalException;
import java.util.*;

import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.*;

class XMBeanOperations extends JPanel implements ActionListener {
    private final static Logger LOGGER = Logger.getLogger(XMBeanOperations.class.getName());
    
    public final static String OPERATION_INVOCATION_EVENT =
            "jam.xoperations.invoke.result"; // NOI18N
    private java.util.List<NotificationListener> notificationListenersList;
    
    private Hashtable<JButton, OperationEntry> operationEntryTable;
    
    private XMBean mbean;
    private MBeanInfo mbeanInfo;
    private MBeansTab mbeansTab;
    
    public XMBeanOperations(MBeansTab mbeansTab) {
        super(new GridLayout(1,1));
        this.mbeansTab = mbeansTab;
        operationEntryTable = new Hashtable<JButton, OperationEntry>();
        ArrayList<NotificationListener> l =
                new ArrayList<NotificationListener>(1);
        notificationListenersList =
                Collections.synchronizedList(l);
    }
    
    // Call on EDT
    public void removeOperations() {
        removeAll();
    }
    
    // Call on EDT
    public void loadOperations(XMBean mbean,MBeanInfo mbeanInfo) {
        this.mbean = mbean;
        this.mbeanInfo = mbeanInfo;
        // add operations information
        MBeanOperationInfo operations[] = mbeanInfo.getOperations();
        invalidate();
        
        // remove listeners, if any
        Component listeners[] = getComponents();
        for (int i = 0; i < listeners.length; i++)
            if (listeners[i] instanceof JButton)
                ((JButton)listeners[i]).removeActionListener(this);
        
        removeAll();
        setLayout(new BorderLayout());
        
        JButton methodButton;
        JLabel methodLabel;
        JPanel innerPanelLeft,innerPanelRight;
        JPanel outerPanelLeft,outerPanelRight;
        outerPanelLeft  = new JPanel(new GridLayout(operations.length,1));
        outerPanelRight = new JPanel(new GridLayout(operations.length,1));
        
        for (int i=0;i<operations.length;i++) {
            innerPanelLeft  = new JPanel(new FlowLayout(FlowLayout.RIGHT));
            innerPanelRight = new JPanel(new FlowLayout(FlowLayout.LEFT));
            String returnType = operations[i].getReturnType();
            if (returnType == null) {
                methodLabel = new JLabel("null", JLabel.RIGHT); // NOI18N
                if (LOGGER.isLoggable(Level.WARNING)) {
                    LOGGER.warning(
                            "The operation's return type " + // NOI18N
                            "shouldn't be \"null\". Check how the " + // NOI18N
                            "MBeanOperationInfo for the \"" + // NOI18N
                            operations[i].getName() + "\" operation has " + // NOI18N
                            "been defined in the MBean's implementation code."); // NOI18N
                }
            } else {
                methodLabel = new JLabel(
                        Utils.getReadableClassName(returnType), JLabel.RIGHT);
            }
            innerPanelLeft.add(methodLabel);
            if (methodLabel.getText().length()>20) {
                methodLabel.setText(methodLabel.getText().
                        substring(methodLabel.getText().
                        lastIndexOf(".")+1, // NOI18N
                        methodLabel.getText().length()));
            }
            
            methodButton = new JButton(operations[i].getName());
            methodButton.setToolTipText(operations[i].getDescription());
            boolean callable = isCallable(operations[i].getSignature());
            if(callable)
                methodButton.addActionListener(this);
            else
                methodButton.setEnabled(false);
            
            MBeanParameterInfo[] signature = operations[i].getSignature();
            OperationEntry paramEntry = new OperationEntry(operations[i],
                    callable,
                    methodButton,
                    this);
            operationEntryTable.put(methodButton, paramEntry);
            innerPanelRight.add(methodButton);
            if(signature.length==0)
                innerPanelRight.add(new JLabel("( )",JLabel.CENTER)); // NOI18N
            else
                innerPanelRight.add(paramEntry);
            
            outerPanelLeft.add(innerPanelLeft,BorderLayout.WEST);
            outerPanelRight.add(innerPanelRight,BorderLayout.CENTER);
        }
        add(outerPanelLeft,BorderLayout.WEST);
        add(outerPanelRight,BorderLayout.CENTER);
        validate();
    }
    
    private boolean isCallable(MBeanParameterInfo[] signature) {
        for(int i = 0; i < signature.length; i++) {
            if(!Utils.isEditableType(signature[i].getType()))
                return false;
        }
        return true;
    }
    
    // Call on EDT
    public void actionPerformed(final ActionEvent e) {
        performInvokeRequest((JButton)e.getSource());
    }
    
    void performInvokeRequest(final JButton button) {
        final OperationEntry entryIf = operationEntryTable.get(button);
        SwingWorker<Object, Void> sw = new SwingWorker<Object, Void>() {
            @Override
            public Object doInBackground() throws Exception {
                return mbean.invoke(button.getText(),
                        entryIf.getParameters(), entryIf.getSignature());
            }
            @Override
            protected void done() {
                try {
                    Object result = get();
                    // sends result notification to upper level if
                    // there is a return value
                    if (entryIf.getReturnType() != null &&
                            !entryIf.getReturnType().equals(Void.TYPE.getName()) &&
                            !entryIf.getReturnType().equals(Void.class.getName()))
                        fireChangedNotification(OPERATION_INVOCATION_EVENT, button, result);
                    else
                        new ThreadDialog(
                                button,
                                Resources.getText("LBL_MethodSuccessfullyInvoked"), // NOI18N
                                Resources.getText("LBL_Info"), // NOI18N
                                JOptionPane.INFORMATION_MESSAGE).run();
                } catch (Throwable t) {
                    t = Utils.getActualException(t);
                    LOGGER.throwing(XMBeanOperations.class.getName(), "performInvokeRequest", t); // NOI18N
                    t = checkCNFE(t);
                    
                    new ThreadDialog(
                            button,
                            Resources.getText("LBL_ProblemInvoking") + " " + // NOI18N
                            button.getText() + " : " + t.toString(), // NOI18N
                            Resources.getText("LBL_Error"), // NOI18N
                            JOptionPane.ERROR_MESSAGE).run();
                }
            }

            private Throwable checkCNFE(Throwable t) {
                if (t instanceof UnmarshalException) {
                    Throwable nt = t.getCause();
                    if (nt instanceof ClassNotFoundException) {
                        return new RuntimeException("Cannot instantiate remote class "+nt.getMessage());  // NOI18N
                    }
                }
                return t;
            }
        };
        mbeansTab.getRequestProcessor().post(sw);
    }
    
    public void addOperationsListener(NotificationListener nl) {
        notificationListenersList.add(nl);
    }
    
    public void removeOperationsListener(NotificationListener nl) {
        notificationListenersList.remove(nl);
    }
    
    // Call on EDT
    private void fireChangedNotification(
            String type, Object source, Object handback) {
        Notification n = new Notification(type, source, 0);
        for(NotificationListener nl : notificationListenersList)
            nl.handleNotification(n, handback);
    }
    
}
