/*
 * Copyright (c) 1999, 2017, 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.
 */

#include <stdatomic.h>

#include "vmDebug.h"

#include "JDWP.h"
#include "debugLoop.h"
#include "transport.h"
#include "util.h"

static _Atomic(jlong) lastDebuggerActivity = ATOMIC_VAR_INIT(0LL);
static _Atomic(jboolean) hasSeenDebuggerActivity = ATOMIC_VAR_INIT(JNI_FALSE);

// Reset the tracking variables.
void vmDebug_onDisconnect()
{
    atomic_store(&lastDebuggerActivity, 0LL);
    atomic_store(&hasSeenDebuggerActivity, JNI_FALSE);
}

// Mark us as having seen actual debugger activity (so isDebuggerConnected can return true) and that
// we are currently doing something as the debugger.
void vmDebug_notifyDebuggerActivityStart()
{
    atomic_store(&lastDebuggerActivity, 0LL);
    atomic_store(&hasSeenDebuggerActivity, JNI_TRUE);
}

// Update the timestamp for the last debugger activity.
void vmDebug_notifyDebuggerActivityEnd()
{
    atomic_store(&lastDebuggerActivity, milliTime());
}

// For backwards compatibility we are only considered 'connected' as far as VMDebug is concerned if
// we have gotten at least one non-ddms JDWP packet.
static jboolean
isDebuggerConnected()
{
    return transport_is_open() && atomic_load(&hasSeenDebuggerActivity);
}

static jboolean JNICALL
VMDebug_isDebuggerConnected(JNIEnv* env, jclass klass)
{
    return isDebuggerConnected();
}

static jboolean JNICALL
VMDebug_isDebuggingEnabled(JNIEnv* env, jclass klass)
{
    // We are running the debugger so debugging is definitely enabled.
    return JNI_TRUE;
}

static jlong JNICALL
VMDebug_lastDebuggerActivity(JNIEnv* env, jclass klass)
{
    if (!isDebuggerConnected()) {
        LOG_ERROR(("VMDebug.lastDebuggerActivity called without active debugger"));
        return -1;
    }
    jlong last_time = atomic_load(&lastDebuggerActivity);
    if (last_time == 0) {
        LOG_MISC(("debugger is performing an action"));
        return 0;
    }

    jlong cur_time = milliTime();

    if (cur_time < last_time) {
        LOG_ERROR(("Time seemed to go backwards: last was %lld, current is %lld",
                   last_time, cur_time));
        return 0;
    }
    jlong res = cur_time - last_time;
    LOG_MISC(("Debugger interval is %lld", res));
    return res;
}

void
vmDebug_initalize(JNIEnv* env)
{
    WITH_LOCAL_REFS(env, 1) {
        jclass vmdebug_class = JNI_FUNC_PTR(env,FindClass)(env, "dalvik/system/VMDebug");
        if (vmdebug_class == NULL) {
            // The VMDebug class isn't available. We don't need to do anything.
            LOG_MISC(("dalvik.system.VMDebug does not seem to be available on this runtime."));
            // Get rid of the ClassNotFoundException.
            JNI_FUNC_PTR(env,ExceptionClear)(env);
            goto finish;
        }

        JNINativeMethod methods[3];

        // Take over the implementation of these three functions.
        methods[0].name = "lastDebuggerActivity";
        methods[0].signature = "()J";
        methods[0].fnPtr = (void*)VMDebug_lastDebuggerActivity;

        methods[1].name = "isDebuggingEnabled";
        methods[1].signature = "()Z";
        methods[1].fnPtr = (void*)VMDebug_isDebuggingEnabled;

        methods[2].name = "isDebuggerConnected";
        methods[2].signature = "()Z";
        methods[2].fnPtr = (void*)VMDebug_isDebuggerConnected;

        jint res = JNI_FUNC_PTR(env,RegisterNatives)(env,
                                                     vmdebug_class,
                                                     methods,
                                                     sizeof(methods) / sizeof(JNINativeMethod));
        if (res != JNI_OK) {
            EXIT_ERROR(JVMTI_ERROR_INTERNAL,
                       "RegisterNatives returned failure for VMDebug class");
        }

        finish: ;
    } END_WITH_LOCAL_REFS(env);
}