#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "jvmti.h"

#define TRUE  1
#define FALSE 0

#define START_DEPTH 10

#define WAIT_MAX 1
#define TOTAL_WAIT_MAX 200

/*
 * Variables globales
 */
static jvmtiEnv *jvmti = NULL;
static jvmtiCapabilities capa;
static char *FILE_NAME = "agent.log";
static FILE *fp = NULL;

/*
 * Definition des structures
 */

// Global agent data structure
typedef struct {
	/* JVMTI Environment */
	jvmtiEnv *jvmti;
	jboolean vm_is_started;
	/* Data access Lock */
	jrawMonitorID lock;
} GlobalAgentData;

// Structure de sauvegarde du contexte par thread
typedef struct {
	time_t *last_contentation;
	int waitIsLog;
	int totalWaitIsLog;
	jlong totalTime;
	char* threadName;
} timerstack;

static GlobalAgentData *gdata;

/*
 * Signature de la methode de log
 */
static void agent_log(int lock, char *log_string, ...);

/*
 * Affichage du detail de l'erreur
 */
static void check_jvmti_error(jvmtiEnv *jvmti, jvmtiError errnum, const char *str) {
	if (errnum != JVMTI_ERROR_NONE) {
		char *errnum_str;
		errnum_str = NULL;
		(void) (*jvmti)->GetErrorName(jvmti, errnum, &errnum_str);
		agent_log(FALSE, "[Agent] ERROR: JVMTI: %d(%s): %s\n", errnum, (errnum_str == NULL ? "Unknown" : errnum_str),
				(str == NULL ? "" : str));
	}
}

/*
 * Pose d'un lock
 */
static void enter_critical_section(jvmtiEnv *jvmti) {
	jvmtiError error;
	error = (*jvmti)->RawMonitorEnter(jvmti, gdata->lock);
	check_jvmti_error(jvmti, error, "Cannot enter with raw monitor");
}

/*
 * Liberer le lock
 */
static void exit_critical_section(jvmtiEnv *jvmti) {
	jvmtiError error;
	error = (*jvmti)->RawMonitorExit(jvmti, gdata->lock);
	check_jvmti_error(jvmti, error, "Cannot exit with raw monitor");
}

/*
 * Methode de log
 */
static void agent_log(int lock_file, char *format_string, ...) {

	va_list args;
	va_start(args, format_string);

	if (lock_file)
		enter_critical_section(jvmti);

	vfprintf(fp, format_string, args);
	fflush(fp);

	if (lock_file) {
		exit_critical_section(jvmti);
	}

	vprintf(format_string, args);

	va_end(args);
}

void *perform_alloc(int size){
    void *p = malloc(size);

    if (p == NULL) {
        agent_log(FALSE, "[Agent] ERROR ***Out of Memory***, exiting [size=%d].\n", size);
    }
    memset(p, 0, size);
    return p;

}


/*
 * Description du message d'erreur
 */
void describe(jvmtiError err) {
	jvmtiError err0;
	char *descr;
	err0 = (*jvmti)->GetErrorName(jvmti, err, &descr);
	if (err0 == JVMTI_ERROR_NONE) {
		agent_log(FALSE, descr);
	} else {
		agent_log(FALSE, "[Agent] error [%d]", err);
	}
}

/*
 * Methode appelee au moment ou un Thread est mis en attente
 */
static void JNICALL callbackMonitorContendedEnter(jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread, jobject object) {
	enter_critical_section(jvmti); {
		jvmtiError err;
		jvmtiThreadInfo info;
		timerstack* s = NULL;
		timerstack* s_free = NULL;
		time_t *timer;

        (void)memset(&info,0, sizeof(info));
		err = (*jvmti)->GetThreadInfo(jvmti, thread, &info);
		if (err != JVMTI_ERROR_NONE) {
			agent_log(FALSE, "[Agent] (GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err);
			describe(err);
			jvmtiPhase phase;
			jvmtiError phaseStat;
			phaseStat = (*jvmti)->GetPhase(jvmti,&phase);
			agent_log(FALSE, "[Agent]     current phase is %d\n", phase);
		}


		err = (*jvmti)->GetThreadLocalStorage(jvmti, thread, &s);
		if (err != JVMTI_ERROR_NONE) {
			agent_log(FALSE, "[Agent] (GetThreadLocalStorage) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err);
		}

	    if(s != NULL) {
             free(s->last_contentation);

            timer = malloc ( sizeof(time_t));
            time(timer);
			s->last_contentation = timer;
		} else {

			agent_log(FALSE, "[Agent] Thread Storage init for : %s\n",info.name);
			s = perform_alloc(sizeof(timerstack ));

			s->threadName = strdup(info.name);
			s->totalTime = 0;

            timer = malloc ( sizeof(time_t));
            time(timer);

			s->last_contentation = timer;
			s->waitIsLog = 10;
			s->totalWaitIsLog = 0;

			err = (*jvmti)->SetThreadLocalStorage(jvmti, thread, s);
			if (err != JVMTI_ERROR_NONE) {
				agent_log(FALSE, "[Agent] (SetThreadLocalStorage) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err);
			}

		}
		(*jvmti)->Deallocate(jvmti, (unsigned)&info);

	}exit_critical_section(jvmti);
}

/*
 * Methode appelee au moment ou un Thread en attente est libere
 */
static void JNICALL callbackMonitorContendedEntered(jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread, jobject object) {
	enter_critical_section(jvmti); {
		jvmtiError err;
		jvmtiThreadInfo info;
		timerstack* s=NULL;
		time_t *timer;
		time_t *timer_start;
        int logInfo = FALSE;

            timer = malloc ( sizeof(time_t));
            time(timer);



		err = (*jvmti)->GetThreadLocalStorage(jvmti, thread, &s);

        timer_start = s->last_contentation;

        if(!s->totalWaitIsLog) {
  		     s->totalTime = s->totalTime + (*timer - *timer_start);
        }

		if((s->totalTime > TOTAL_WAIT_MAX) && (s->totalWaitIsLog == 0)) {
			agent_log(FALSE, "[Agent] Total time to long\n[Agent] %s - pose de %8ld : Total : %ld\n",s->threadName,(*timer-*timer_start),s->totalTime);
			s->totalWaitIsLog = 1;
			logInfo = TRUE;
		}

		if(((*timer - *timer_start) > WAIT_MAX) && (s->waitIsLog == 0)) {
			agent_log(FALSE, "[Agent] Thread wait time to long\n[Agent] %s - pose de %8ld : Total : %ld\n",s->threadName,(*timer - *timer_start),s->totalTime);
			s->waitIsLog = 1;
			logInfo = TRUE;
		}

		free(timer);

		if(logInfo) {
 		    jvmtiFrameInfo frames[START_DEPTH];
		    jint count;

            (void)memset(&info,0, sizeof(info));
            err = (*jvmti)->GetThreadInfo(jvmti, thread, &info);
			if (err != JVMTI_ERROR_NONE) {
				agent_log(FALSE, "[Agent] (GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err);
				describe(err);
				jvmtiPhase phase;
				jvmtiError phaseStat;
				phaseStat = (*jvmti)->GetPhase(jvmti,&phase);
				agent_log(FALSE, "[Agent]     current phase is %d\n", phase);
			}
			agent_log(FALSE, "[Agent] Running Thread : %s, Priority: %d\n", info.name, info.priority);


			err = (*jvmti)->GetStackTrace(jvmti, thread, 0, START_DEPTH, &frames, &count);
			if (err != JVMTI_ERROR_NONE) {
				agent_log(FALSE, "[Agent] (GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err);
				describe(err);
			}

			if (err == JVMTI_ERROR_NONE && count >=1) {
				int i=0;
				for(i=0; i<count; i++) {
					char *methodName;
					char *signature_ptr;
					char *signature;
					jclass* declaring_class_ptr;
					jint nbEntryTable;
					jvmtiLineNumberEntry *tableLineNumber;

					err = (*jvmti)->GetMethodName(jvmti, frames[i].method, &methodName, NULL, NULL);
					if (err != JVMTI_ERROR_NONE) {
						describe(err);
					}

					declaring_class_ptr = malloc ( sizeof(jclass));

					err = (*jvmti)->GetMethodDeclaringClass(jvmti, frames[i].method, declaring_class_ptr);
					if (err != JVMTI_ERROR_NONE) {
						describe(err);
					}

					err = (*jvmti)->GetClassSignature(jvmti, *declaring_class_ptr, &signature_ptr, NULL);
					if (err != JVMTI_ERROR_NONE) {
						describe(err);
					}

					err = (*jvmti)->GetLineNumberTable(jvmti, frames[i].method, &nbEntryTable, &tableLineNumber);
					if ((err != JVMTI_ERROR_NONE) && (err != JVMTI_ERROR_ABSENT_INFORMATION)) {
						describe(err);
					}


					if (err == JVMTI_ERROR_NONE) {
						int j;
						jint lineNumber = 0;
						signature = signature_ptr +1;

						lineNumber = tableLineNumber[0].line_number;

						for(j=0; (j<nbEntryTable) && (frames[i].location >= tableLineNumber[j].start_location); j++) {
							lineNumber = tableLineNumber[j].line_number;
						}

						for(j=0; j<strlen(signature); j++) {
							if(signature[j] == '/')
							signature[j] = '.';
							if(signature[j] == ';')
							signature[j] = '.';
						}

						agent_log(FALSE, "[Agent]   %s%s(%d)\n", signature, methodName,lineNumber);

					    (*jvmti)->Deallocate(jvmti, (unsigned)tableLineNumber);
					}

					(*jvmti)->Deallocate(jvmti, (unsigned)methodName);
					(*jvmti)->Deallocate(jvmti, (unsigned)signature_ptr);

			        free(declaring_class_ptr);
				}
			}

		(*jvmti)->Deallocate(jvmti, (unsigned)&info);
		}

	}exit_critical_section(jvmti);
}

/*
 * Methode appelee a l'arret de la VM
 */
static void JNICALL callbackVMDeath(jvmtiEnv *jvmti_env, JNIEnv* jni_env)
{
	enter_critical_section(jvmti); {
		agent_log(FALSE, "[Agent] Got VM Death event\n");
		fclose(fp);
	}exit_critical_section(jvmti);
}

/*
 * Methode appelee au demarrage de la VM
 */
static void JNICALL callbackVMInit(jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread)
{
	enter_critical_section(jvmti); {
		agent_log(FALSE, "[Agent] Got VM init event\n");
	}exit_critical_section(jvmti);
}

/*
 * Chargement
 */
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved)
{
	static GlobalAgentData data;
	jvmtiError error;
	jint res;
	jvmtiEventCallbacks callbacks;

	//*****************************
	//
	// Preparation de l'environemet
	//
	//*****************************

	// Preparation du fichier de log
	fp = fopen(FILE_NAME, "w");
	if (fp == 0) {
		fprintf(stderr, "[Agent] ERROR: can't write output file: %s\n", FILE_NAME);
	}

	agent_log(FALSE, "[Agent] START\n");

	// Allocation du GlobalAgentData
	(void)memset((void*)&data, 0, sizeof(data));
	gdata = &data;

	// Recuperation de l environement JVMTI
	res = (*jvm)->GetEnv(jvm, (void **) &jvmti, JVMTI_VERSION_1_0);
	if (res != JNI_OK || jvmti == NULL) {
		// Si on ne trouve pas la version de l'interface JVMTI
		agent_log(FALSE, "[Agent] ERROR: Unable to access JVMTI Version 1 (0x%x),"
				" is your J2SE a 1.5 or newer version?"
				" JNIEnv's GetEnv() returned %d\n",
				JVMTI_VERSION_1, res);

	}

	gdata->jvmti = jvmti;

	//*****************************
	//
	// Configuration des callback
	//
	//*****************************

	// Activation des capabilities
	(void)memset(&capa, 0, sizeof(jvmtiCapabilities));
	capa.can_get_current_contended_monitor = 1;
	capa.can_generate_monitor_events = 1;
	capa.can_get_owned_monitor_info = 1;
	capa.can_get_line_numbers = 1;
	error = (*jvmti)->AddCapabilities(jvmti, &capa);
	check_jvmti_error(jvmti, error, "[Agent] Unable to get necessary JVMTI capabilities.");

	// Declaration des methodes de callback
	(void)memset(&callbacks, 0, sizeof(callbacks));
	callbacks.VMInit = &callbackVMInit;
	callbacks.VMDeath = &callbackVMDeath;
	callbacks.MonitorContendedEnter = &callbackMonitorContendedEnter;
	callbacks.MonitorContendedEntered = &callbackMonitorContendedEntered;
	error = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, (jint)sizeof(callbacks));
	check_jvmti_error(jvmti, error, "[Agent] Cannot set jvmti callbacks");

	// Declaration des evenements a ecouter
	error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, (jthread)NULL);
	error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, (jthread)NULL);
	error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTER, (jthread)NULL);
	error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTERED, (jthread)NULL);
	check_jvmti_error(jvmti, error, "[Agent] Cannot set event notification");

	// Creation du rawMonitor
	error = (*jvmti)->CreateRawMonitor(jvmti, "agent data", &(gdata->lock));
	check_jvmti_error(jvmti, error, "[Agent] Cannot create raw monitor");



	// JNI_OK = succes
	return JNI_OK;
}

/*
 * Methode Unload pour liberer la memoire
 */
JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm)
{
	free(gdata->jvmti);
	free(gdata);
	free(&capa);
}
