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

#include "debug_util.h"

#if defined(DEBUG)
static void JNICALL DTrace_PrintStdErr(const char *msg);

enum {
    MAX_TRACES = 200,           /* max number of defined trace points allowed */
    MAX_TRACE_BUFFER = 512,     /* maximum size of a given trace output */
    MAX_LINE = 100000,          /* reasonable upper limit on line number in source file */
    MAX_ARGC = 8                /* maximum number of arguments to print functions */
};

typedef enum dtrace_scope {
    DTRACE_FILE,
    DTRACE_LINE
} dtrace_scope;

typedef struct dtrace_info {
    char                file[FILENAME_MAX+1];
    int                 line;
    int                 enabled;
    dtrace_scope        scope;
} dtrace_info, * p_dtrace_info;

static dtrace_info      DTraceInfo[MAX_TRACES];
static char             DTraceBuffer[MAX_TRACE_BUFFER*2+1]; /* double the buffer size to catch overruns */
static dmutex_t         DTraceMutex = NULL;
static dbool_t          GlobalTracingEnabled = FALSE;
static int              NumTraces = 0;

static DTRACE_OUTPUT_CALLBACK   PfnTraceCallback = DTrace_PrintStdErr;

static p_dtrace_info DTrace_GetInfo(dtrace_id tid) {
    DASSERT(tid < MAX_TRACES);
    return &DTraceInfo[tid];
}

static dtrace_id DTrace_CreateTraceId(const char * file, int line, dtrace_scope scope) {
    dtrace_id           tid = NumTraces++;
    p_dtrace_info       info = &DTraceInfo[tid];
    DASSERT(NumTraces < MAX_TRACES);

    strcpy(info->file, file);
    info->line = line;
    info->enabled = FALSE;
    info->scope = scope;
    return tid;
}

/*
 * Compares the trailing characters in a filename to see if they match
 * e.g. "src\win32\foobar.c" and "foobar.c" would be considered equal
 * but "src\win32\foo.c" and "src\win32\bar.c" would not.
 */
static dbool_t FileNamesSame(const char * fileOne, const char * fileTwo) {
    size_t      lengthOne = strlen(fileOne);
    size_t      lengthTwo = strlen(fileTwo);
    size_t      numCompareChars;
    dbool_t     tailsEqual;

    if (fileOne == fileTwo) {
        return TRUE;
    } else if (fileOne == NULL || fileTwo == NULL) {
        return FALSE;
    }
    /* compare the tail ends of the strings for equality */
    numCompareChars = lengthOne < lengthTwo ? lengthOne : lengthTwo;
    tailsEqual = strcmp(fileOne + lengthOne - numCompareChars,
                        fileTwo + lengthTwo - numCompareChars) == 0;
    return tailsEqual;
}

/*
 * Finds the trace id for a given file/line location or creates one
 * if it doesn't exist
 */
static dtrace_id DTrace_GetTraceId(const char * file, int line, dtrace_scope scope) {
    dtrace_id           tid;
    p_dtrace_info       info;

    /* check to see if the trace point has already been created */
    for ( tid = 0; tid < NumTraces; tid++ ) {
        info = DTrace_GetInfo(tid);
        if ( info->scope == scope ) {
            dbool_t     sameFile = FileNamesSame(file, info->file);
            dbool_t     sameLine = info->line == line;

            if ( (info->scope == DTRACE_FILE && sameFile) ||
                 (info->scope == DTRACE_LINE && sameFile && sameLine) ) {
                goto Exit;
            }
        }
    }

    /* trace point wasn't created, so force it's creation */
    tid = DTrace_CreateTraceId(file, line, scope);
Exit:
    return tid;
}


static dbool_t DTrace_IsEnabledAt(dtrace_id * pfileid, dtrace_id * plineid, const char * file, int line) {
    DASSERT(pfileid != NULL && plineid != NULL);

    if ( *pfileid == UNDEFINED_TRACE_ID ) {
    /* first time calling the trace for this file, so obtain a trace id */
         *pfileid = DTrace_GetTraceId(file, -1, DTRACE_FILE);
    }
    if ( *plineid == UNDEFINED_TRACE_ID ) {
    /* first time calling the trace for this line, so obtain a trace id */
         *plineid = DTrace_GetTraceId(file, line, DTRACE_LINE);
    }

    return GlobalTracingEnabled || DTraceInfo[*pfileid].enabled || DTraceInfo[*plineid].enabled;
}

/*
 * Initialize trace functionality. This MUST BE CALLED before any
 * tracing function is called.
 */
void DTrace_Initialize() {
    DTraceMutex = DMutex_Create();
}

/*
 * Cleans up tracing system. Should be called when tracing functionality
 * is no longer needed.
 */
void DTrace_Shutdown() {
    DMutex_Destroy(DTraceMutex);
}

void DTrace_DisableMutex() {
    DTraceMutex = NULL;
}

/*
 * Enable tracing for all modules.
 */
void DTrace_EnableAll(dbool_t enabled) {
    DMutex_Enter(DTraceMutex);
    GlobalTracingEnabled = enabled;
    DMutex_Exit(DTraceMutex);
}

/*
 * Enable tracing for a specific module. Filename may
 * be fully or partially qualified.
 * e.g. awt_Component.cpp
 *              or
 *      src\win32\native\sun\windows\awt_Component.cpp
 */
void DTrace_EnableFile(const char * file, dbool_t enabled) {
    dtrace_id tid;
    p_dtrace_info info;

    DASSERT(file != NULL);
    DMutex_Enter(DTraceMutex);
    tid = DTrace_GetTraceId(file, -1, DTRACE_FILE);
    info = DTrace_GetInfo(tid);
    info->enabled = enabled;
    DMutex_Exit(DTraceMutex);
}

/*
 * Enable tracing for a specific line in a specific module.
 * See comments above regarding filename argument.
 */
void DTrace_EnableLine(const char * file, int line, dbool_t enabled) {
    dtrace_id tid;
    p_dtrace_info info;

    DASSERT(file != NULL && (line > 0 && line < MAX_LINE));
    DMutex_Enter(DTraceMutex);
    tid = DTrace_GetTraceId(file, line, DTRACE_LINE);
    info = DTrace_GetInfo(tid);
    info->enabled = enabled;
    DMutex_Exit(DTraceMutex);
}

static void DTrace_ClientPrint(const char * msg) {
    DASSERT(msg != NULL && PfnTraceCallback != NULL);
    (*PfnTraceCallback)(msg);
}

/*
 * Print implementation for the use of client defined trace macros. Unsynchronized so it must
 * be used from within a DTRACE_PRINT_CALLBACK function.
 */
void DTrace_VPrintImpl(const char * fmt, va_list arglist) {
    DASSERT(fmt != NULL);

    /* format the trace message */
    vsnprintf(DTraceBuffer, sizeof(DTraceBuffer), fmt, arglist);
    /* not a real great overflow check (memory would already be hammered) but better than nothing */
    DASSERT(strlen(DTraceBuffer) < MAX_TRACE_BUFFER);
    /* output the trace message */
    DTrace_ClientPrint(DTraceBuffer);
}

/*
 * Print implementation for the use of client defined trace macros. Unsynchronized so it must
 * be used from within a DTRACE_PRINT_CALLBACK function.
 */
void DTrace_PrintImpl(const char * fmt, ...) {
    va_list     arglist;

    va_start(arglist, fmt);
    DTrace_VPrintImpl(fmt, arglist);
    va_end(arglist);
}

/*
 * Called via DTRACE_PRINT macro. Outputs printf style formatted text.
 * JNIEXPORT because these functions are also called from libawt_xawt.
 */
JNIEXPORT void JNICALL
DTrace_VPrint( const char * file, int line, int argc, const char * fmt, va_list arglist ) {
    DASSERT(fmt != NULL);
    DTrace_VPrintImpl(fmt, arglist);
}

/*
 * Called via DTRACE_PRINTLN macro. Outputs printf style formatted text with an automatic newline.
 * JNIEXPORT because these functions are also called from libawt_xawt.
 */
JNIEXPORT void JNICALL
DTrace_VPrintln( const char * file, int line, int argc, const char * fmt, va_list arglist ) {
    DTrace_VPrintImpl(fmt, arglist);
    DTrace_PrintImpl("\n");
}

/*
 * Called via DTRACE_ macros. If tracing is enabled at the given location, it enters
 * the trace mutex and invokes the callback function to output the trace.
 * JNIEXPORT because these functions are also called from libawt_xawt.
 */
JNIEXPORT void JNICALL
DTrace_PrintFunction( DTRACE_PRINT_CALLBACK pfn, dtrace_id * pFileTraceId, dtrace_id * pLineTraceId,
                      const char * file, int line,
                      int argc, const char * fmt, ... ) {
    va_list     arglist;

    DASSERT(file != NULL);
    DASSERT(line > 0 && line < MAX_LINE);
    DASSERT(argc <= MAX_ARGC);
    DASSERT(fmt != NULL);

    DMutex_Enter(DTraceMutex);
    if ( DTrace_IsEnabledAt(pFileTraceId, pLineTraceId, file, line) ) {
        va_start(arglist, fmt);
        (*pfn)(file, line, argc, fmt, arglist);
        va_end(arglist);
    }
    DMutex_Exit(DTraceMutex);
}

/*
 * Sets a callback function to be used to output
 * trace statements.
 */
void DTrace_SetOutputCallback(DTRACE_OUTPUT_CALLBACK pfn) {
    DASSERT(pfn != NULL);

    DMutex_Enter(DTraceMutex);
    PfnTraceCallback = pfn;
    DMutex_Exit(DTraceMutex);
}

/**********************************************************************************
 * Support for Java tracing in release or debug mode builds
 */

static void JNICALL DTrace_PrintStdErr(const char *msg) {
    fprintf(stderr, "%s", msg);
    fflush(stderr);
}
#endif /* DEBUG */

/*********************************************************************************
 * Native method implementations. Java print trace calls are functional in
 * release builds, but functions to enable/disable native tracing are not.
 */

/* Implementation of DebugSettings.setCTracingOn*/
JNIEXPORT void JNICALL
Java_sun_awt_DebugSettings_setCTracingOn__Z(JNIEnv *env, jobject self, jboolean enabled) {
#if defined(DEBUG)
    DTrace_EnableAll(enabled == JNI_TRUE);
#endif
}

/* Implementation of DebugSettings.setCTracingOn*/
JNIEXPORT void JNICALL
Java_sun_awt_DebugSettings_setCTracingOn__ZLjava_lang_String_2(
    JNIEnv *env,
    jobject self,
    jboolean enabled,
    jstring file ) {
#if defined(DEBUG)
    const char *        cfile;
    cfile = JNU_GetStringPlatformChars(env, file, NULL);
    if ( cfile == NULL ) {
        return;
    }
    DTrace_EnableFile(cfile, enabled == JNI_TRUE);
    JNU_ReleaseStringPlatformChars(env, file, cfile);
#endif
}

/* Implementation of DebugSettings.setCTracingOn*/
JNIEXPORT void JNICALL
Java_sun_awt_DebugSettings_setCTracingOn__ZLjava_lang_String_2I(
    JNIEnv *env,
    jobject self,
    jboolean enabled,
    jstring file,
    jint line ) {
#if defined(DEBUG)
    const char *        cfile;
    cfile = JNU_GetStringPlatformChars(env, file, NULL);
    if ( cfile == NULL ) {
        return;
    }
    DTrace_EnableLine(cfile, line, enabled == JNI_TRUE);
    JNU_ReleaseStringPlatformChars(env, file, cfile);
#endif
}
