/*
 * 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 java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashMap;
import java.util.Iterator;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.Timer;

@SuppressWarnings("serial")
class XPlottingViewer extends PlotterPanel implements ActionListener {

    private final static Logger LOGGER =
            Logger.getLogger(XPlottingViewer.class.getName());

    // TODO: Make number of decimal places customizable
    private static final int PLOTTER_DECIMALS = 4;

    private JButton plotButton;
    // The plotter cache holds Plotter instances for the various attributes
    private static HashMap<String, XPlottingViewer> plotterCache =
            new HashMap<String, XPlottingViewer>();
    private static HashMap<String, Timer> timerCache =
            new HashMap<String, Timer>();
    private MBeansTab tab;
    private XMBean mbean;
    private String attributeName;
    private String key;
    private JTable table;

    private XPlottingViewer(String key,
                            XMBean mbean,
                            String attributeName,
                            Object value,
                            JTable table,
                            MBeansTab tab) {
        super(null);
        this.tab = tab;
        this.key = key;
        this.mbean = mbean;
        this.table = table;
        this.attributeName = attributeName;
        setupDisplay(createPlotter(mbean, attributeName, key, table));
    }

    static void dispose(MBeansTab tab) {
        Iterator it = plotterCache.keySet().iterator();
        while(it.hasNext()) {
            String key = (String) it.next();
            if(key.startsWith(String.valueOf(tab.hashCode()))) {
                it.remove();
            }
        }
        //plotterCache.clear();
        it = timerCache.keySet().iterator();
        while(it.hasNext()) {
            String key = (String) it.next();
            if(key.startsWith(String.valueOf(tab.hashCode()))) {
                Timer t = timerCache.get(key);
                t.stop();
                it.remove();
            }
        }
    }

    public static boolean isViewableValue(Object value) {
        return (value instanceof Number);
    }

    // Fired by dbl click
    public static Component loadPlotting(
            XMBean mbean, String attributeName, Object value, JTable table,
            MBeansTab tab) {
        Component comp = null;
        if (isViewableValue(value)) {
            String key = String.valueOf(tab.hashCode()) + " " + // NOI18N
                    String.valueOf(mbean.hashCode()) + " " + // NOI18N
                    mbean.getObjectName().getCanonicalName() + attributeName;
            XPlottingViewer p = plotterCache.get(key);
            if (p == null) {
                p = new XPlottingViewer(key, mbean, attributeName, value,
                        table, tab);
                plotterCache.put(key, p);
            }
            comp = p;
        }
        return comp;
    }

    @Override
    public void actionPerformed(ActionEvent evt) {
        plotterCache.remove(key);
        Timer t = timerCache.remove(key);
        t.stop();
        ((XMBeanAttributes) table).collapse(attributeName, this);
    }

    // Create plotter instance
    public Plotter createPlotter(final XMBean xmbean,
                                 final String attributeName,
                                 String key,
                                 JTable table) {
        final Plotter p = new XPlotter(table, Plotter.Unit.NONE) {
            Dimension prefSize = new Dimension(400, 170);
            @Override
            public Dimension getPreferredSize() {
                return prefSize;
            }
            @Override
            public Dimension getMinimumSize() {
                return prefSize;
            }
        };

        p.createSequence(attributeName, attributeName, null, true);

        Timer timer = new Timer(tab.getUpdateInterval(), new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                intervalElapsed(p);
            }
        });
        timer.setCoalesce(true);
        timer.setInitialDelay(0);
        timer.start();
        timerCache.put(key, timer);
        return p;
    }

    void intervalElapsed(final Plotter p) {
        tab.getRequestProcessor().post(new Runnable() {
            public void run() {
                try {
                    Number n = (Number) mbean.getCachedMBeanServerConnection().
                            getAttribute(
                            mbean.getObjectName(), attributeName);
                    long v;
                    if (n instanceof Float || n instanceof Double) {
                        p.setDecimals(PLOTTER_DECIMALS);
                        double d = (n instanceof Float) ? (Float) n : (Double) n;
                        v = Math.round(d * Math.pow(10.0, PLOTTER_DECIMALS));
                    } else {
                        v = n.longValue();
                    }
                    p.addValues(System.currentTimeMillis(), v);
                } catch (Exception e) {
                    LOGGER.throwing(XPlottingViewer.class.getName(),
                            "intervalElapsed", e); // NOI18N
                }
            }
        });
    }

    // Create Plotter display
    private void setupDisplay(Plotter p) {
        final JPanel buttonPanel = new JPanel();
        final GridBagLayout gbl = new GridBagLayout();
        buttonPanel.setLayout(gbl);
        setLayout(new BorderLayout());
        plotButton = new JButton(Resources.getText("LBL_DiscardChart")); // NOI18N
        plotButton.addActionListener(this);
        plotButton.setEnabled(true);

        GridBagConstraints buttonConstraints = new GridBagConstraints();
        buttonConstraints.gridx = 0;
        buttonConstraints.gridy = 0;
        buttonConstraints.fill = GridBagConstraints.VERTICAL;
        buttonConstraints.anchor = GridBagConstraints.CENTER;
        gbl.setConstraints(plotButton, buttonConstraints);
        buttonPanel.add(plotButton);

        if (attributeName != null && attributeName.length()!=0) {
            final JPanel plotterLabelPanel = new JPanel();
            final JLabel atlabel = new JLabel(attributeName);
            final GridBagLayout gbl2 = new GridBagLayout();
            plotterLabelPanel.setLayout(gbl2);
            final GridBagConstraints labelConstraints = new GridBagConstraints();
            labelConstraints.gridx = 0;
            labelConstraints.gridy = 0;
            labelConstraints.fill = GridBagConstraints.VERTICAL;
            labelConstraints.anchor = GridBagConstraints.CENTER;
            labelConstraints.ipady = 10;
            gbl2.setConstraints(atlabel, labelConstraints);
            plotterLabelPanel.add(atlabel);
            add(plotterLabelPanel, BorderLayout.NORTH);
        }

        setPlotter(p);
        add(buttonPanel, BorderLayout.SOUTH);
        repaint();
    }
}
