/*
 * Copyright (c) 2017, 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 jdk.jfr.internal;

import java.io.IOException;
import java.util.List;

import jdk.jfr.Event;

/**
 * Interface against the JVM.
 *
 */
public final class JVM {
    private static final JVM jvm = new JVM();

    // JVM signals file changes by doing Object#notifu on this object
    static final Object FILE_DELTA_CHANGE = new Object();

    static final long RESERVED_CLASS_ID_LIMIT = 400;

    private volatile boolean recording;
    private volatile boolean nativeOK;

    private static native void registerNatives();

    static {
        registerNatives();
        // XXX
        // for (LogTag tag : LogTag.values()) {
        //     subscribeLogLevel(tag, tag.id);
        // }
        Options.ensureInitialized();
        EventHandlerProxyCreator.ensureInitialized();
    }

    /**
     * Get the one and only JVM.
     *
     * @return the JVM
     */
    public static JVM getJVM() {
        return jvm;
    }

    private JVM() {
    }

    /**
     * Begin recording events
     *
     * Requires that JFR has been started with {@link #createNativeJFR()}
     */
    public native void beginRecording();

    /**
     * Return ticks
     *
     * @return the time, in ticks
     *
     */
//    @HotSpotIntrinsicCandidate
    public static native long counterTime();


    /**
     * Emits native periodic event.
     *
     * @param eventTypeId type id
     *
     * @param timestamp commit time for event
     * @param when when it is being done {@link Periodic.When}
     *
     * @return true if the event was committed
     */
    public native boolean emitEvent(long eventTypeId, long timestamp, long when);

    /**
     * End recording events, which includes flushing data in thread buffers
     *
     * Requires that JFR has been started with {@link #createNativeJFR()}
     *
     */
    public native void endRecording();

    /**
     * Return a list of all classes deriving from {@link Event}
     *
     * @return list of event classes.
     */
    public native List<Class<? extends Event>> getAllEventClasses();

    /**
     * Return a count of the number of unloaded classes deriving from {@link Event}
     *
     * @return number of unloaded event classes.
     */
    public native long getUnloadedEventClassCount();

    /**
     * Return a unique identifier for a class. The class is marked as being
     * "in use" in JFR.
     *
     * @param clazz clazz
     *
     * @return a unique class identifier
     */
//   @HotSpotIntrinsicCandidate
    public static native long getClassId(Class<?> clazz);

    // temporary workaround until we solve intrinsics supporting epoch shift tagging
    public static native long getClassIdNonIntrinsic(Class<?> clazz);

    /**
     * Return process identifier.
     *
     * @return process identifier
     */
    public native String getPid();

    /**
     * Return unique identifier for stack trace.
     *
     * Requires that JFR has been started with {@link #createNativeJFR()}
     *
     * @param skipCount number of frames to skip
     * @return a unique stack trace identifier
     */
    public native long getStackTraceId(int skipCount);

    /**
     * Return identifier for thread
     *
     * @param t thread
     * @return a unique thread identifier
     */
    public native long getThreadId(Thread t);

    /**
     * Frequency, ticks per second
     *
     * @return frequency
     */
    public native long getTicksFrequency();

    /**
     * Write message to log. Should swallow null or empty message, and be able
     * to handle any Java character and not crash with very large message
     *
     * @param tagSetId the tagset id
     * @param level on level
     * @param message log message
     *
     */
    public static native void log(int tagSetId, int level, String message);

    /**
     * Check whether the logger would output on the given level
     *
     * @param level on level
     * @return {@literal true} if the logger would output on the given level
     */
    public static native boolean shouldLog(int level);

     /**
      * Subscribe to LogLevel updates for LogTag
      *
      * @param lt the log tag to subscribe
      * @param tagSetId the tagset id
      */
     public static native void subscribeLogLevel(LogTag lt, int tagSetId);

    /**
     * Call to invoke event tagging and retransformation of the passed classes
     *
     * @param classes
     */
    public native synchronized void retransformClasses(Class<?>[] classes);

    /**
     * Enable event
     *
     * @param eventTypeId event type id
     *
     * @param enabled enable event
     */
    public native void setEnabled(long eventTypeId, boolean enabled);

    /**
     * Interval at which the JVM should notify on {@link #FILE_DELTA_CHANGE}
     *
     * @param delta number of bytes, reset after file rotation
     */
    public native void setFileNotification(long delta);

    /**
     * Set the number of global buffers to use
     *
     * @param count
     *
     * @throws IllegalArgumentException if count is not within a valid range
     * @throws IllegalStateException if value can't be changed
     */
    public native void setGlobalBufferCount(long count) throws IllegalArgumentException, IllegalStateException;

    /**
     * Set size of a global buffer
     *
     * @param size
     *
     * @throws IllegalArgumentException if buffer size is not within a valid
     *         range
     */
    public native void setGlobalBufferSize(long size) throws IllegalArgumentException;

    /**
     * Set overall memory size
     *
     * @param size
     *
     * @throws IllegalArgumentException if memory size is not within a valid
     *         range
     */
    public native void setMemorySize(long size) throws IllegalArgumentException;

    /**

    /**
     * Set interval for method samples, in milliseconds.
     *
     * Setting interval to 0 turns off the method sampler.
     *
     * @param intervalMillis the sampling interval
     */
    public native void setMethodSamplingInterval(long type, long intervalMillis);

      /**
     * Sets the file where data should be written.
     *
     * Requires that JFR has been started with {@link #createNativeJFR()}
     *
     * <pre>
     * Recording  Previous  Current  Action
     * ==============================================
     *    true     null      null     Ignore, keep recording in-memory
     *    true     null      file1    Start disk recording
     *    true     file      null     Copy out metadata to disk and continue in-memory recording
     *    true     file1     file2    Copy out metadata and start with new File (file2)
     *    false     *        null     Ignore, but start recording to memory with {@link #beginRecording()}
     *    false     *        file     Ignore, but start recording to disk with {@link #beginRecording()}
     *
     * </pre>
     *
     * recording can be set to true/false with {@link #beginRecording()}
     * {@link #endRecording()}
     *
     * @param file the file where data should be written, or null if it should
     *        not be copied out (in memory).
     *
     * @throws IOException
     */
    public native void setOutput(String file);

    /**
     * Controls if a class deriving from jdk.jfr.Event should
     * always be instrumented on class load.
     *
     * @param force, true to force initialization, false otherwise
     */
    public native void setForceInstrumentation(boolean force);

    /**
     * Turn on/off thread sampling.
     *
     * @param sampleThreads true if threads should be sampled, false otherwise.
     *
     * @throws IllegalStateException if state can't be changed.
     */
    public native void setSampleThreads(boolean sampleThreads) throws IllegalStateException;

    /**
     * Turn on/off compressed integers.
     *
     * @param compressed true if compressed integers should be used, false
     *        otherwise.
     *
     * @throws IllegalStateException if state can't be changed.
     */
    public native void setCompressedIntegers(boolean compressed) throws IllegalStateException;

    /**
     * Set stack depth.
     *
     * @param depth
     *
     * @throws IllegalArgumentException if not within a valid range
     * @throws IllegalStateException if depth can't be changed
     */
    public native void setStackDepth(int depth) throws IllegalArgumentException, IllegalStateException;

    /**
     * Turn on stack trace for an event
     *
     * @param eventTypeId the event id
     *
     * @param enabled if stack traces should be enabled
     */
    public native void setStackTraceEnabled(long eventTypeId, boolean enabled);

    /**
     * Set thread buffer size.
     *
     * @param size
     *
     * @throws IllegalArgumentException if size is not within a valid range
     * @throws IllegalStateException if size can't be changed
     */
    public native void setThreadBufferSize(long size) throws IllegalArgumentException, IllegalStateException;

    /**
     * Set threshold for event,
     *
     * Long.MAXIMUM_VALUE = no limit
     *
     * @param eventTypeId the id of the event type
     * @param ticks threshold in ticks,
     * @return true, if it could be set
     */
    public native boolean setThreshold(long eventTypeId, long ticks);

    /**
     * Store the metadata descriptor that is to be written at the end of a
     * chunk, data should be written after GMT offset and size of metadata event
     * should be adjusted
     *
     * Requires that JFR has been started with {@link #createNativeJFR()}
     *
     * @param bytes binary representation of metadata descriptor
     *
     * @param binary representation of descriptor
     */
    public native void storeMetadataDescriptor(byte[] bytes);

    public void endRecording_() {
        endRecording();
        recording = false;
    }

    public void beginRecording_() {
        beginRecording();
        recording = true;
    }

    public boolean isRecording() {
        return recording;
    }

    /**
     * If the JVM supports JVM TI and retransformation has not been disabled this
     * method will return true. This flag can not change during the lifetime of
     * the JVM.
     *
     * @return if transform is allowed
     */
    public native boolean getAllowedToDoEventRetransforms();

    /**
     * Set up native resources, data structures, threads etc. for JFR
     *
     * @param simulateFailure simulate a initialization failure and rollback in
     *        native, used for testing purposes
     *
     * @throws IllegalStateException if native part of JFR could not be created.
     *
     */
    private native boolean createJFR(boolean simulateFailure) throws IllegalStateException;

    /**
     * Destroys native part of JFR. If already destroy, call is ignored.
     *
     * Requires that JFR has been started with {@link #createNativeJFR()}
     *
     * @return if an instance was actually destroyed.
     *
     */
    private native boolean destroyJFR();

    public boolean createFailedNativeJFR() throws IllegalStateException {
        return createJFR(true);
    }

    public void createNativeJFR() {
        nativeOK = createJFR(false);
    }

    public boolean destroyNativeJFR() {
        boolean result = destroyJFR();
        nativeOK = !result;
        return result;
    }

    public boolean hasNativeJFR() {
        return nativeOK;
    }

    /**
     * Cheap test to check if JFR functionality is available.
     *
     * @return
     */
    public native boolean isAvailable();

    /**
     * To convert ticks to wall clock time.
     */
    public native double getTimeConversionFactor();

    /**
     * Return a unique identifier for a class. Compared to {@link #getClassId()}
     * , this method does not tag the class as being "in-use".
     *
     * @param clazz class
     *
     * @return a unique class identifier
     */
    public native long getTypeId(Class<?> clazz);

    /**
     * Fast path fetching the EventWriter using VM intrinsics
     *
     * @return thread local EventWriter
     */
//    @HotSpotIntrinsicCandidate
    public static native Object getEventWriter();

    /**
     * Create a new EventWriter
     *
     * @return thread local EventWriter
     */
    public static native EventWriter newEventWriter();

    /**
     * Flushes the EventWriter for this thread.
     */
    public static native boolean flush(EventWriter writer, int uncommittedSize, int requestedSize);

    /**
     * Sets the location of the disk repository, to be used at an emergency
     * dump.
     *
     * @param dirText
     */
    public native void setRepositoryLocation(String dirText);

    /**
    * Access to VM termination support.
    *
    *@param errorMsg descriptive message to be include in VM termination sequence
    */
    public native void abort(String errorMsg);

    /**
     * Adds a string to the string constant pool.
     *
     * If the same string is added twice, two entries will be created.
     *
     * @param id identifier associated with the string, not negative
     *
     * @param s string constant to be added, not null
     *
     * @return the current epoch of this insertion attempt
     */
    public static native boolean addStringConstant(boolean epoch, long id, String s);
    /**
     * Gets the address of the jboolean epoch.
     *
     * The epoch alternates every checkpoint.
     *
     * @return The address of the jboolean.
     */
    public native long getEpochAddress();

    public native void uncaughtException(Thread thread, Throwable t);
    /**
     * Sets cutoff for event.
     *
     * Determines how long the event should be allowed to run.
     *
     * Long.MAXIMUM_VALUE = no limit
     *
     * @param eventTypeId the id of the event type
     * @param cutoffTicks cutoff in ticks,
     * @return true, if it could be set
     */
    public native boolean setCutoff(long eventTypeId, long cutoffTicks);

    /**
     * Emit old object sample events.
     *
     * @param cutoff the cutoff in ticks
     * @param emitAll emit all samples in old object queue
     */
    public native void emitOldObjectSamples(long cutoff, boolean emitAll);

    /**
     * Test if a chunk rotation is warranted.
     *
     * @return if it is time to perform a chunk rotation
     */
    public native boolean shouldRotateDisk();
}