/*
 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
 */
#include <stdio.h>
#include "mat_common.h"

#include <string.h>
#include <stdint.h>
#include <sys/time.h>
#include <pthread.h>
#include <unistd.h>

#include "com_sun_glass_ui_Application.h"
#include "com_sun_glass_ui_Timer.h"
#include "com_sun_glass_ui_x11_X11Application.h"

#define ONE_SEC_MICRO			1000000
#define WAIT_TIME_MICRO			10
#define HIGH_PRIORITY_TIMER 	1

typedef struct _GlassTimer {
	pthread_t	thread;
    jboolean	running;
    jboolean	quit;
    uint64_t	jPeriod;
    jobject		jRunnable;
    JNIEnv      *env;
} GlassTimer;

static inline uint64_t _getTimeMicroseconds() {
	struct timeval now;
	gettimeofday(&now, NULL);
	return (now.tv_usec + (ONE_SEC_MICRO * now.tv_sec));
}

static void *_GlassTimerTask(void *data) {
    // pthread_setname_np("Glass Timer Thread");

	GlassTimer *timer = (GlassTimer *)data;

 	JNIEnv *env = NULL;
    jint error = (*jVM)->AttachCurrentThreadAsDaemon(jVM, (void **)&env, NULL);
    if (error == 0) {
	    timer->quit = JNI_FALSE;
	    timer->running = JNI_TRUE;
        while (timer->running == JNI_TRUE) {
            uint64_t sleep = timer->jPeriod;
            uint64_t now = _getTimeMicroseconds();
            (*env)->CallVoidMethod(env, timer->jRunnable, mat_jRunnableRun);
            uint64_t duration = (_getTimeMicroseconds() - now);
            sleep -= duration;

            if ((sleep <= 0) || (sleep > timer->jPeriod)) {
#if 1
                sched_yield();
#else
                pthread_yield_np();
#endif
            } else {
                usleep(sleep);
            }
        }
        (*jVM)->DetachCurrentThread(jVM);
        timer->quit = JNI_TRUE;
    } else {
        printf("ERROR: Glass could not attach Timer thread to VM, result:%d\n", error);
    	timer->quit = JNI_TRUE; // we already quit
	    timer->running = JNI_FALSE; // but we pretend we're running
    }

	return NULL;
}

/*
 * Class:     com_sun_glass_ui_x11_X11Timer
 * Method:    _getMinPeriod
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_com_sun_glass_ui_x11_X11Timer__1getMinPeriod
  (JNIEnv *env, jclass cl) {
    return 0;
}

/*
 * Class:     com_sun_glass_ui_x11_X11Timer
 * Method:    _getMaxPeriod
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_com_sun_glass_ui_x11_X11Timer__1getMaxPeriod
  (JNIEnv *env, jclass cl) {

    return 1000000;
}

/*
 * Class:     com_sun_glass_ui_x11_X11Timer
 * Method:    _start
 * Signature: (I)J
 */
JNIEXPORT jlong JNICALL Java_com_sun_glass_ui_x11_X11Timer__1start
  (JNIEnv *env, jobject timer, jobject runnable, jint period) {

    GlassTimer *gt;

    gt = (GlassTimer *) malloc (sizeof (GlassTimer));
    gt->running = JNI_FALSE;
    gt->quit = JNI_FALSE;
    gt->env = env;

    gt->jRunnable = (*env)->NewGlobalRef(env, runnable);
	gt->jPeriod = (1000 * period); // ms --> us

    struct sched_param param;
    memset(&param, 0x00, sizeof(param));
#if HIGH_PRIORITY_TIMER
    param.sched_priority = sched_get_priority_max(SCHED_FIFO)-1; // notice: 2nd highest priority (gznote: do we really want that?)
#endif

    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
    // pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
    // pthread_attr_setschedparam(&attr, &param);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    int err = pthread_create(&gt->thread, &attr, _GlassTimerTask, gt);

    while (gt->running == JNI_FALSE) {
    	usleep(WAIT_TIME_MICRO);
    }

    if (gt->quit == JNI_FALSE) {
	    return (long)gt;
    } else {
    	free (gt);
        return 0L;
    }
}

/*
 * Class:     com_sun_glass_ui_x11_X11Timer
 * Method:    _stop
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11Timer__1stop
                                (JNIEnv *env, jobject timer, jlong ptr) {
	GlassTimer *gt = (GlassTimer*)jlong_to_ptr(ptr);
    gt->running = JNI_FALSE;
    while (gt->quit == JNI_FALSE) {
    	usleep(WAIT_TIME_MICRO);
    }
    free (gt);
}
