/*
 * Copyright (c) 2004, 2022, 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 sun.tools.jconsole;

import java.awt.*;
import java.io.*;
import java.lang.management.*;
import java.lang.reflect.*;
import java.text.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.LongSupplier;

import javax.swing.*;


import static sun.tools.jconsole.Formatter.*;
import static sun.tools.jconsole.Utilities.*;

@SuppressWarnings("serial")
class SummaryTab extends Tab {
    private static final String cpuUsageKey = "cpu";

    private static final String newDivider =   "<tr><td colspan=4><font size =-1><hr>";
    private static final String newTable =     "<tr><td colspan=4 align=left><table cellpadding=1>";
    private static final String newLeftTable = "<tr><td colspan=2 align=left><table cellpadding=1>";
    private static final String newRightTable =  "<td colspan=2 align=left><table cellpadding=1>";
    private static final String endTable = "</table>";

    private static final int CPU_DECIMALS = 1;

    private CPUOverviewPanel overviewPanel;
    private DateFormat headerDateTimeFormat;
    private String pathSeparator = null;
    HTMLPane info;

    private static class Result {
        long upTime = -1L;
        long processCpuTime = -1L;
        long timeStamp;
        int nCPUs;
        String summary;
    }

    public static String getTabName() {
        return Messages.SUMMARY_TAB_TAB_NAME;
    }

    public SummaryTab(VMPanel vmPanel) {
        super(vmPanel, getTabName());

        setLayout(new BorderLayout());

        info = new HTMLPane();
        setAccessibleName(info, getTabName());
        add(new JScrollPane(info));

        headerDateTimeFormat =
            Formatter.getDateTimeFormat(Messages.SUMMARY_TAB_HEADER_DATE_TIME_FORMAT);
    }

    public SwingWorker<?, ?> newSwingWorker() {
        return new SwingWorker<Result, Object>() {
            public Result doInBackground() {
                return formatSummary();
            }


            protected void done() {
                try {
                    Result result = get();
                    if (result != null) {
                        info.setText(result.summary);
                        if (overviewPanel != null &&
                            result.upTime > 0L &&
                            result.processCpuTime >= 0L) {

                            overviewPanel.updateCPUInfo(result);
                        }
                    }
                } catch (InterruptedException ex) {
                } catch (ExecutionException ex) {
                    if (JConsole.isDebug()) {
                        ex.printStackTrace();
                    }
                }
            }
        };
    }

    StringBuilder buf;

    @SuppressWarnings("deprecation")
    synchronized Result formatSummary() {
        Result result = new Result();
        ProxyClient proxyClient = vmPanel.getProxyClient();
        if (proxyClient.isDead()) {
            return null;
        }

        buf = new StringBuilder();
        append("<table cellpadding=1>");

        try {
            RuntimeMXBean         rmBean     = proxyClient.getRuntimeMXBean();
            CompilationMXBean     cmpMBean   = proxyClient.getCompilationMXBean();
            ThreadMXBean          tmBean     = proxyClient.getThreadMXBean();
            MemoryMXBean          memoryBean = proxyClient.getMemoryMXBean();
            ClassLoadingMXBean    clMBean    = proxyClient.getClassLoadingMXBean();
            OperatingSystemMXBean osMBean    = proxyClient.getOperatingSystemMXBean();
            com.sun.management.OperatingSystemMXBean sunOSMBean  =
               proxyClient.getSunOperatingSystemMXBean();

            append("<tr><td colspan=4>");
            append("<center><b>" + Messages.SUMMARY_TAB_TAB_NAME + "</b></center>");
            String dateTime =
                headerDateTimeFormat.format(System.currentTimeMillis());
            append("<center>" + dateTime + "</center>");

            append(newDivider);

            {  // VM info
                append(newLeftTable);
                append(Messages.CONNECTION_NAME, vmPanel.getDisplayName());
                append(Messages.VIRTUAL_MACHINE,
                       Resources.format(Messages.SUMMARY_TAB_VM_VERSION,
                                        rmBean.getVmName(), rmBean.getVmVersion()));
                append(Messages.VENDOR, rmBean.getVmVendor());
                append(Messages.NAME, rmBean.getName());
                append(endTable);

                append(newRightTable);
                result.upTime = rmBean.getUptime();
                append(Messages.UPTIME, formatTime(result.upTime));
                if (sunOSMBean != null) {
                    result.processCpuTime = sunOSMBean.getProcessCpuTime();
                    append(Messages.PROCESS_CPU_TIME, formatNanoTime(result.processCpuTime));
                }

                if (cmpMBean != null) {
                    append(Messages.JIT_COMPILER, cmpMBean.getName());
                    append(Messages.TOTAL_COMPILE_TIME,
                           cmpMBean.isCompilationTimeMonitoringSupported()
                                    ? formatTime(cmpMBean.getTotalCompilationTime())
                                    : Messages.UNAVAILABLE);
                } else {
                    append(Messages.JIT_COMPILER, Messages.UNAVAILABLE);
                }
                append(endTable);
            }

            append(newDivider);

            {  // Threads and Classes
                append(newLeftTable);
                int tlCount = tmBean.getThreadCount();
                int tdCount = tmBean.getDaemonThreadCount();
                int tpCount = tmBean.getPeakThreadCount();
                long ttCount = tmBean.getTotalStartedThreadCount();
                String[] strings1 = formatLongs(tlCount, tpCount,
                                                tdCount, ttCount);
                append(Messages.LIVE_THREADS, strings1[0]);
                append(Messages.PEAK, strings1[1]);
                append(Messages.DAEMON_THREADS, strings1[2]);
                append(Messages.TOTAL_THREADS_STARTED, strings1[3]);
                append(endTable);

                append(newRightTable);
                long clCount = clMBean.getLoadedClassCount();
                long cuCount = clMBean.getUnloadedClassCount();
                long ctCount = clMBean.getTotalLoadedClassCount();
                String[] strings2 = formatLongs(clCount, cuCount, ctCount);
                append(Messages.CURRENT_CLASSES_LOADED, strings2[0]);
                append(Messages.TOTAL_CLASSES_LOADED, strings2[2]);
                append(Messages.TOTAL_CLASSES_UNLOADED, strings2[1]);
                append(null, "");
                append(endTable);
            }

            append(newDivider);

            {  // Memory
                MemoryUsage u = memoryBean.getHeapMemoryUsage();

                append(newLeftTable);
                String[] strings1 = formatKByteStrings(u.getUsed(), u.getMax());
                append(Messages.CURRENT_HEAP_SIZE, strings1[0]);
                append(Messages.MAXIMUM_HEAP_SIZE, strings1[1]);
                append(endTable);

                append(newRightTable);
                String[] strings2 = formatKByteStrings(u.getCommitted());
                append(Messages.COMMITTED_MEMORY,  strings2[0]);
                append(Messages.SUMMARY_TAB_PENDING_FINALIZATION_LABEL,
                       Resources.format(Messages.SUMMARY_TAB_PENDING_FINALIZATION_VALUE,
                                        memoryBean.getObjectPendingFinalizationCount()));
                append(endTable);

                append(newTable);
                Collection<GarbageCollectorMXBean> garbageCollectors =
                                            proxyClient.getGarbageCollectorMXBeans();
                for (GarbageCollectorMXBean garbageCollectorMBean : garbageCollectors) {
                    String gcName = garbageCollectorMBean.getName();
                    long gcCount = garbageCollectorMBean.getCollectionCount();
                    long gcTime = garbageCollectorMBean.getCollectionTime();

                    append(Messages.GARBAGE_COLLECTOR,
                           Resources.format(Messages.GC_INFO, gcName, gcCount,
                                            (gcTime >= 0) ? formatTime(gcTime)
                                                 : Messages.UNAVAILABLE),
                           4);
                }
                append(endTable);
            }

            append(newDivider);

            {  // Operating System info
                append(newLeftTable);
                String osName = osMBean.getName();
                String osVersion = osMBean.getVersion();
                String osArch = osMBean.getArch();
                result.nCPUs = osMBean.getAvailableProcessors();
                append(Messages.OPERATING_SYSTEM, osName + " " + osVersion);
                append(Messages.ARCHITECTURE, osArch);
                append(Messages.NUMBER_OF_PROCESSORS, result.nCPUs+"");

                if (pathSeparator == null) {
                    // Must use separator of remote OS, not File.pathSeparator
                    // from this local VM. In the future, consider using
                    // RuntimeMXBean to get the remote system property.
                    pathSeparator = osName.startsWith("Windows ") ? ";" : ":";
                }

                if (sunOSMBean != null) {
                    String[] kbStrings1 =
                        formatKByteStrings(sunOSMBean.getCommittedVirtualMemorySize());

                    // getTotalPhysicalMemorySize and getFreePhysicalMemorySize are deprecated,
                    // but we want be able to get the data for old target VMs (see JDK-8255934).
                    @SuppressWarnings("deprecation")
                    String[] kbStrings2 =
                        formatKByteStrings(tryToGet(sunOSMBean::getTotalMemorySize,
                                                    sunOSMBean::getTotalPhysicalMemorySize),
                                           tryToGet(sunOSMBean::getFreeMemorySize,
                                                    sunOSMBean::getFreePhysicalMemorySize),
                                           sunOSMBean.getTotalSwapSpaceSize(),
                                           sunOSMBean.getFreeSwapSpaceSize());

                    append(Messages.COMMITTED_VIRTUAL_MEMORY, kbStrings1[0]);
                    append(endTable);

                    append(newRightTable);
                    append(Messages.TOTAL_PHYSICAL_MEMORY, kbStrings2[0]);
                    append(Messages.FREE_PHYSICAL_MEMORY,  kbStrings2[1]);
                    append(Messages.TOTAL_SWAP_SPACE,      kbStrings2[2]);
                    append(Messages.FREE_SWAP_SPACE,       kbStrings2[3]);
                }

                append(endTable);
            }

            append(newDivider);

            {  // VM arguments and paths
                append(newTable);
                String args = "";
                java.util.List<String> inputArguments = rmBean.getInputArguments();
                for (String arg : inputArguments) {
                    args += arg + " ";
                }
                append(Messages.VM_ARGUMENTS, args, 4);
                append(Messages.CLASS_PATH,   rmBean.getClassPath(), 4);
                append(Messages.LIBRARY_PATH, rmBean.getLibraryPath(), 4);
                append(Messages.BOOT_CLASS_PATH,
                       rmBean.isBootClassPathSupported()
                                    ? rmBean.getBootClassPath()
                                    : Messages.UNAVAILABLE,
                       4);
                append(endTable);
            }
        } catch (IOException | UndeclaredThrowableException e) {
            if (JConsole.isDebug()) {
                e.printStackTrace();
            }
            proxyClient.markAsDead();
            return null;
        }

        append("</table>");

        result.timeStamp = System.currentTimeMillis();
        result.summary = buf.toString();

        return result;
    }

    /**
     * Tries to get the specified value from the list of suppliers.
     * Returns -1 if all suppliers fail.
     */
    private long tryToGet(LongSupplier ... getters) {
        for (LongSupplier getter : getters) {
            try {
                return getter.getAsLong();
            } catch (UndeclaredThrowableException e) {
            }
        }
        return -1;
    }

    private synchronized void append(String str) {
        buf.append(str);
    }

    void append(String label, String value) {
        append(newRow(label, value));
    }

    private void append(String label, String value, int columnPerRow) {
        if (columnPerRow == 4 && pathSeparator != null) {
            value = value.replace(pathSeparator,
                                  "<b></b>" + pathSeparator);
        }
        append(newRow(label, value, columnPerRow));
    }

    OverviewPanel[] getOverviewPanels() {
        if (overviewPanel == null) {
            overviewPanel = new CPUOverviewPanel();
        }
        return new OverviewPanel[] { overviewPanel };
    }

    private static class CPUOverviewPanel extends OverviewPanel {
        private long prevUpTime, prevProcessCpuTime;

        CPUOverviewPanel() {
            super(Messages.CPU_USAGE, cpuUsageKey, Messages.CPU_USAGE, Plotter.Unit.PERCENT);
            getPlotter().setDecimals(CPU_DECIMALS);
        }

        public void updateCPUInfo(Result result) {
            if (prevUpTime > 0L && result.upTime > prevUpTime) {
                // elapsedCpu is in ns and elapsedTime is in ms.
                long elapsedCpu = result.processCpuTime - prevProcessCpuTime;
                long elapsedTime = result.upTime - prevUpTime;
                // cpuUsage could go higher than 100% because elapsedTime
                // and elapsedCpu are not fetched simultaneously. Limit to
                // 99% to avoid Plotter showing a scale from 0% to 200%.
                float cpuUsage =
                    Math.min(99F,
                             elapsedCpu / (elapsedTime * 10000F * result.nCPUs));

                cpuUsage = Math.max(0F, cpuUsage);

                getPlotter().addValues(result.timeStamp,
                                Math.round(cpuUsage * Math.pow(10.0, CPU_DECIMALS)));
                getInfoLabel().setText(Resources.format(Messages.CPU_USAGE_FORMAT,
                                               String.format("%."+CPU_DECIMALS+"f", cpuUsage)));
            }
            this.prevUpTime = result.upTime;
            this.prevProcessCpuTime = result.processCpuTime;
        }
    }
}
