/*
 * Copyright (c) 2007, 2011, 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 com.sun.tools.visualvm.sampler.cpu;

import com.sun.tools.visualvm.core.datasupport.Utils;
import com.sun.tools.visualvm.core.options.GlobalPreferences;
import com.sun.tools.visualvm.core.ui.components.DataViewComponent;
import com.sun.tools.visualvm.sampler.AbstractSamplerSupport;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.management.ThreadInfo;
import java.util.HashSet;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.SwingUtilities;
import org.netbeans.lib.profiler.common.ProfilingSettings;
import org.netbeans.lib.profiler.common.ProfilingSettingsPresets;
import org.netbeans.lib.profiler.common.filters.SimpleFilter;
import org.netbeans.lib.profiler.global.InstrumentationFilter;
import org.netbeans.lib.profiler.results.cpu.CPUResultsSnapshot;
import org.netbeans.lib.profiler.results.cpu.CPUResultsSnapshot.NoDataAvailableException;
import org.netbeans.lib.profiler.results.cpu.StackTraceSnapshotBuilder;
import org.netbeans.modules.profiler.LoadedSnapshot;
import org.netbeans.modules.profiler.ResultsManager;
import org.openide.util.NbBundle;

/**
 *
 * @author Jiri Sedlacek
 * @author Tomas Hurka
 */
public abstract class CPUSamplerSupport extends AbstractSamplerSupport {

    private final ThreadInfoProvider threadInfoProvider;
    private final SnapshotDumper snapshotDumper;
    private final ThreadDumper threadDumper;

    private Timer timer;
    private TimerTask samplerTask;
    private final Refresher refresher;
    private int refreshRate;

    private StackTraceSnapshotBuilder builder;

    private volatile boolean sampleRunning;
    private final Object updateLock = new Object();
    private long currentLiveUpdate;
    private long lastLiveUpdate;

    private CPUView cpuView;
    private DataViewComponent.DetailsView[] detailsViews;

    private javax.swing.Timer threadCPUTimer;
    private Refresher threadCPURefresher;
    private ThreadsCPUView threadCPUView;
    private ThreadsCPU threadsCPU;

    public CPUSamplerSupport(ThreadInfoProvider tip, ThreadsCPU tcpu, SnapshotDumper snapshotDumper, ThreadDumper threadDumper) {
        threadInfoProvider = tip;
        threadsCPU = tcpu;
        this.snapshotDumper = snapshotDumper;
        this.threadDumper = threadDumper;

        refreshRate = GlobalPreferences.sharedInstance().getMonitoredDataPoll() * 1000;

        refresher = new Refresher() {
            public void setRefreshRate(int rr) {
                CPUSamplerSupport.this.refreshRate = rr;
            }
            public int getRefreshRate() {
                return CPUSamplerSupport.this.refreshRate;
            }
            protected boolean checkRefresh() {
                return samplerTask != null && cpuView.isShowing();
            }
            protected void doRefresh() {
                doRefreshImpl();
            }
        };
        
        if (threadsCPU != null) {
            threadCPURefresher = new Refresher() {
                public final boolean checkRefresh() {
                    if (threadCPUTimer == null) return false;
                    if (!threadCPUTimer.isRunning()) return false;
                    return threadCPUView.isShowing();
                }
                public final void doRefresh() {
                    doRefreshImpl(threadCPUTimer, threadCPUView);
                }
                public final void setRefreshRate(int refreshRate) {
                    threadCPUTimer.setDelay(refreshRate);
                    threadCPUTimer.setInitialDelay(refreshRate);
                    threadCPUTimer.restart();
                }
                public final int getRefreshRate() {
                    return threadCPUTimer.getDelay();
                }
            };
        }
    }


    public DataViewComponent.DetailsView[] getDetailsView() {
        if (detailsViews == null) {
            cpuView = new CPUView(refresher, snapshotDumper, threadDumper);
            detailsViews = new DataViewComponent.DetailsView[threadsCPU != null ? 2:1];
            detailsViews[0] = new DataViewComponent.DetailsView(NbBundle.getMessage(
                CPUSamplerSupport.class, "LBL_Cpu_samples"), null, 10, cpuView, null); // NOI18N
            if (threadsCPU != null) {
                threadCPUView = new ThreadsCPUView(threadCPURefresher);
                detailsViews[1] = new DataViewComponent.DetailsView(NbBundle.getMessage(
                CPUSamplerSupport.class, "LBL_ThreadAlloc"), null, 20, threadCPUView, null); // NOI18N
                
            }
        }
        cpuView.initSession();
        if (threadsCPU != null) {
            threadCPUView.initSession();
        }
        return detailsViews.clone();
    }

    public boolean startSampling(ProfilingSettings settings, int samplingRate, int refreshRate) {
        InstrumentationFilter filter = new InstrumentationFilter();
        SimpleFilter sf = (SimpleFilter)settings.getSelectedInstrumentationFilter();
        filter.setFilterStrings(sf.getFilterValue());
        filter.setFilterType(convertFilterType(sf.getFilterType()));
        builder = snapshotDumper.getNewBuilder(filter);
                
        refresher.setRefreshRate(refreshRate);

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                cpuView.setResultsPanel(new SampledLivePanel(builder));
            }
        });

        if (timer == null) timer = getTimer();
        samplerTask = new SamplerTask(builder);

        timer.scheduleAtFixedRate(samplerTask, 0, samplingRate);
        
        if (threadsCPU != null) {
            threadCPUTimer = new javax.swing.Timer(refreshRate, new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    threadCPURefresher.refresh();
                }
            });
            threadCPURefresher.setRefreshRate(refreshRate);
        }
        return true;
    }

    public synchronized void stopSampling() {
        if (samplerTask != null) {
            samplerTask.cancel();
            samplerTask = null;
        }
        if (threadCPUTimer != null) {
            threadCPUTimer.stop();
            threadCPUTimer = null;
        }
    }

    public synchronized void terminate() {
        if (timer != null) {
            timer.cancel();
            timer = null;
        }
        if (cpuView != null) cpuView.terminate();
        if (threadCPUView != null) threadCPUView.terminate();
        builder = null;  // release data
    }


    private void doRefreshImpl() {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                if (samplerTask == null) return;
                if (!sampleRunning) {
                    synchronized (updateLock) {
                        lastLiveUpdate = currentLiveUpdate;
                        cpuView.refresh();
                    }
                } else {
                    SwingUtilities.invokeLater(this);
                }
            }
        });
    }
    private void doRefreshImpl(final javax.swing.Timer stimer, final ThreadsCPUView view) {
        if (!stimer.isRunning() || view.isPaused()) return;
        
        try {
            timer.schedule(new TimerTask() {
                public void run() {
                    try {
                        if (!stimer.isRunning()) return;
                        doRefreshImplImpl(threadsCPU.getThreadsCPUInfo(), view);
                    } catch (Exception e) {
                        terminate();
                    }
                }
            }, 0);
        } catch (Exception e) {
            terminate();
        }
    }
    
    private void doRefreshImplImpl(final ThreadsCPUInfo info, final ThreadsCPUView view) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    view.refresh(info);
                }
            });
    }

    private int convertFilterType(int simpleFilterrType) {
        if (simpleFilterrType == SimpleFilter.SIMPLE_FILTER_NONE) {
            return InstrumentationFilter.INSTR_FILTER_NONE;
        }
        if (simpleFilterrType == SimpleFilter.SIMPLE_FILTER_EXCLUSIVE) {
            return InstrumentationFilter.INSTR_FILTER_EXCLUSIVE;
        }
        if (simpleFilterrType == SimpleFilter.SIMPLE_FILTER_INCLUSIVE) {
            return InstrumentationFilter.INSTR_FILTER_INCLUSIVE;
        }
        throw new IllegalArgumentException("type "+simpleFilterrType); // NOI18N
    }


    private class SamplerTask extends TimerTask {

        private final StackTraceSnapshotBuilder builder;
        private final Set samplingThreads = new HashSet();

        public SamplerTask(StackTraceSnapshotBuilder builder) {
            this.builder = builder;
        }

        public void run() {
            if (sampleRunning) return;
            sampleRunning = true;
            synchronized (updateLock) {
                try {
                    ThreadInfo[] infos = threadInfoProvider.dumpAllThreads();
                    long timestamp = System.nanoTime();
                    String samplingThreadName = findSamplingThread(infos);
                    if (samplingThreadName != null) {
                        if (samplingThreads.add(samplingThreadName)) {
//                                System.out.println("New ignored thread: "+samplingThreadName);
                            builder.setIgnoredThreads(samplingThreads);
                        }
                    }
                    builder.addStacktrace(infos, timestamp);

                    currentLiveUpdate = timestamp / 1000000;
                    if (currentLiveUpdate - lastLiveUpdate >= refreshRate)
                        refresher.refresh();

                } catch (Throwable ex) {
                    terminate();
                } finally {
                    sampleRunning = false;
                }
            }
        }

        private String findSamplingThread(ThreadInfo[] infos) {
//                for (ThreadInfo info : infos) {
//                    if (info.getThreadState() == Thread.State.RUNNABLE) {
//                        StackTraceElement[] stack = info.getStackTrace();
//
//                        if (stack.length > 0) {
//                            StackTraceElement topStack = stack[0];
//
//                            if (!topStack.isNativeMethod()) {
//                                continue;
//                            }
//                            if (!"sun.management.ThreadImpl".equals(topStack.getClassName())) {  // NOI18N
//                                continue;
//                            }
//                            if ("getThreadInfo0".equals(topStack.getMethodName())) {
//                                return info.getThreadName();
//                            }
//                        }
//                    }
//                }
            return null;
        }
    }
    
    public static abstract class ThreadDumper {
        public abstract void takeThreadDump(boolean openView);
    }

    public static abstract class SnapshotDumper {
        private StackTraceSnapshotBuilder builder;
                
        StackTraceSnapshotBuilder getNewBuilder(InstrumentationFilter filter) {
            builder = new StackTraceSnapshotBuilder(1,filter);
            return builder;
        }
        
        public final LoadedSnapshot takeNPSSnapshot(File directory) throws IOException, NoDataAvailableException {
            if (builder == null) throw new IllegalStateException("Builder is null"); // NOI18N
            long time = System.currentTimeMillis();
            CPUResultsSnapshot snapshot = builder.createSnapshot(time);
            LoadedSnapshot ls = new LoadedSnapshot(snapshot, ProfilingSettingsPresets.createCPUPreset(), null, null);
            File file = Utils.getUniqueFile(directory,
                    ResultsManager.getDefault().getDefaultSnapshotFileName(ls),
                    "." + ResultsManager.SNAPSHOT_EXTENSION); // NOI18N
            DataOutputStream dos = new DataOutputStream(new FileOutputStream(file));
            try {
                ls.save(dos);
                ls.setFile(file);
                ls.setSaved(true);
            } finally {
                dos.close();
            }
            return ls;
        }

        public abstract void takeSnapshot(boolean openView);
    }

}
