/*
 * 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.util.ArrayList;
import java.util.List;
import java.util.Objects;

import jdk.jfr.SettingDescriptor;

/**
 * Implementation of event type.
 *
 * To avoid memory leaks, this class must not hold strong reference to an event
 * class or a setting class
 */
public final class PlatformEventType extends Type {
    private final boolean isJVM;
    private final boolean  isJDK;
    private final boolean isMethodSampling;
    private final List<SettingDescriptor> settings = new ArrayList<>(5);
    private final boolean dynamicSettings;
    private final int stackTraceOffset;

    // default values
    private boolean enabled = false;
    private boolean stackTraceEnabled = true;
    private long thresholdTicks = 0;
    private long period = 0;
    private boolean hasHook;

    private boolean beginChunk;
    private boolean endChunk;
    private boolean hasStackTrace = true;
    private boolean hasDuration = true;
    private boolean hasPeriod = true;
    private boolean hasCutoff = false;
    private boolean isInstrumented;
    private boolean markForInstrumentation;
    private boolean registered = true;
    private boolean commitable = enabled && registered;


    // package private
    PlatformEventType(String name, long id, boolean isJDK, boolean dynamicSettings) {
        super(name, Type.SUPER_TYPE_EVENT, id);
        this.dynamicSettings = dynamicSettings;
        this.isJVM = Type.isDefinedByJVM(id);
        this.isMethodSampling = name.equals(Type.EVENT_NAME_PREFIX + "ExecutionSample") || name.equals(Type.EVENT_NAME_PREFIX + "NativeMethodSample");
        this.isJDK = isJDK;
        this.stackTraceOffset = stackTraceOffset(name, isJDK);
    }

    private static int stackTraceOffset(String name, boolean isJDK) {
        if (isJDK) {
            if (name.equals(Type.EVENT_NAME_PREFIX + "JavaExceptionThrow")) {
                return 5;
            }
            if (name.equals(Type.EVENT_NAME_PREFIX + "JavaErrorThrow")) {
                return 5;
            }
        }
        return 4;
    }

    public void add(SettingDescriptor settingDescriptor) {
        Objects.requireNonNull(settingDescriptor);
        settings.add(settingDescriptor);
    }

    public List<SettingDescriptor> getSettings() {
        if (dynamicSettings) {
            List<SettingDescriptor> list = new ArrayList<>(settings.size());
            for (SettingDescriptor s : settings) {
                if (Utils.isSettingVisible(s.getTypeId(), hasHook)) {
                    list.add(s);
                }
            }
            return list;
        }
        return settings;
    }

    public List<SettingDescriptor> getAllSettings() {
        return settings;
    }

    public void setHasStackTrace(boolean hasStackTrace) {
        this.hasStackTrace = hasStackTrace;
    }

    public void setHasDuration(boolean hasDuration) {
        this.hasDuration = hasDuration;
    }

    public void setHasCutoff(boolean hasCutoff) {
       this.hasCutoff = hasCutoff;
    }

    public void setCutoff(long cutoffNanos) {
        if (isJVM) {
            long cutoffTicks = Utils.nanosToTicks(cutoffNanos);
            JVM.getJVM().setCutoff(getId(), cutoffTicks);
        }
    }

    public void setHasPeriod(boolean hasPeriod) {
        this.hasPeriod = hasPeriod;
    }

    public boolean hasStackTrace() {
        return this.hasStackTrace;
    }

    public boolean hasDuration() {
        return this.hasDuration;
    }

    public boolean hasPeriod() {
        return this.hasPeriod;
    }

    public boolean hasCutoff() {
        return this.hasCutoff;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public boolean isJVM() {
        return isJVM;
    }

    public boolean isJDK() {
        return isJDK;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
        updateCommitable();
        if (isJVM) {
            if (isMethodSampling) {
                long p = enabled ? period : 0;
                JVM.getJVM().setMethodSamplingInterval(getId(), p);
            } else {
                JVM.getJVM().setEnabled(getId(), enabled);
            }
        }
    }

    public void setPeriod(long periodMillis, boolean beginChunk, boolean endChunk) {
        if (isMethodSampling) {
            long p = enabled ? periodMillis : 0;
            JVM.getJVM().setMethodSamplingInterval(getId(), p);
        }
        this.beginChunk = beginChunk;
        this.endChunk = endChunk;
        this.period = periodMillis;
    }

    public void setStackTraceEnabled(boolean stackTraceEnabled) {
        this.stackTraceEnabled = stackTraceEnabled;
        if (isJVM) {
            JVM.getJVM().setStackTraceEnabled(getId(), stackTraceEnabled);
        }
    }

    public void setThreshold(long thresholdNanos) {
        this.thresholdTicks = Utils.nanosToTicks(thresholdNanos);
        if (isJVM) {
            JVM.getJVM().setThreshold(getId(), thresholdTicks);
        }
    }

    public boolean isEveryChunk() {
        return period == 0;
    }

    public boolean getStackTraceEnabled() {
        return stackTraceEnabled;
    }

    public long getThresholdTicks() {
        return thresholdTicks;
    }

    public long getPeriod() {
        return period;
    }

    public boolean hasEventHook() {
        return hasHook;
    }

    public void setEventHook(boolean hasHook) {
        this.hasHook = hasHook;
    }

    public boolean isBeginChunk() {
        return beginChunk;
    }

    public boolean isEndChunk() {
        return endChunk;
    }

    public boolean isInstrumented() {
        return isInstrumented;
    }

    public void setInstrumented() {
        isInstrumented = true;
    }

    public void markForInstrumentation(boolean markForInstrumentation) {
        this.markForInstrumentation = markForInstrumentation;
    }

    public boolean isMarkedForInstrumentation() {
        return markForInstrumentation;
    }

    public boolean setRegistered(boolean registered) {
        if (this.registered != registered) {
            this.registered = registered;
            updateCommitable();
            LogTag logTag = isJVM() || isJDK() ? LogTag.JFR_SYSTEM_EVENT : LogTag.JFR_EVENT;
            if (registered) {
                Logger.log(logTag, LogLevel.INFO, "Registered " + getLogName());
            } else {
                Logger.log(logTag, LogLevel.INFO, "Unregistered " + getLogName());
            }
            if (!registered) {
                MetadataRepository.getInstance().setUnregistered();
            }
            return true;
        }
        return false;
    }

    private void updateCommitable() {
        this.commitable = enabled && registered;
    }

    public final boolean isRegistered() {
        return registered;
    }

    // Efficient check of enabled && registered
    public boolean isCommitable() {
        return commitable;
    }

    public int getStackTraceOffset() {
        return stackTraceOffset;
    }
}
