/*
 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
 */

#import "common.h"

#import "ProcessInfo.h"
#import "GlassMacros.h"
#import "GlassStatics.h"
#import "GlassHelper.h"
#import "GlassTimer.h"

#include <sys/time.h>

//#define VERBOSE
#ifndef VERBOSE
    #define LOG(MSG, ...)
#else
    #define LOG(MSG, ...) GLASS_LOG(MSG, ## __VA_ARGS__);
#endif

#define ONE_SEC_MICRO			1000000
#define HIGH_PRIORITY_TIMER 	1

static void *_GlassTimerTask(void *data)
{
    pthread_setname_np("Glass Timer Thread");
    
    GlassTimer *timer = (GlassTimer*)data;
    if (timer != NULL)
    {
        jint error = (*MAIN_JVM)->AttachCurrentThreadAsDaemon(MAIN_JVM, (void **)&timer->_env, NULL);
        if (error == 0)
        {
            timer->_running = YES;
            while (timer->_running == YES)
            {
                uint64_t now = getTimeMicroseconds();
                {
                    if (timer->_runnable != NULL)
                    {
                        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
                        {
                            (*timer->_env)->CallVoidMethod(timer->_env, timer->_runnable, jRunnableRun);
                        }
                        [pool drain];
                        GLASS_CHECK_EXCEPTION(timer->_env);
                    }
                }
                uint64_t duration = (getTimeMicroseconds() - now);
                int64_t sleep = (timer->_period - duration);
                if ((sleep < 0) || (sleep > timer->_period))
                {
#if 1
                    sched_yield();
#else
                    pthread_yield_np();
#endif
                }
                else
                {
                    usleep(sleep);
                }
            }
            
            if (timer->_runnable != NULL)
            {
                (*timer->_env)->DeleteGlobalRef(timer->_env, timer->_runnable);
            }
            timer->_runnable = NULL;
            
            NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
            {
                [timer release];
            }
            [pool drain];
            
            (*MAIN_JVM)->DetachCurrentThread(MAIN_JVM);
        }
        else
        {
            NSLog(@"ERROR: Glass could not attach Timer _thread to VM, result:%d\n", (int)error);
        }
    }
    
    return NULL;
}

@implementation GlassTimer

- (id)initWithRunnable:(jobject)runnable withEnv:(JNIEnv*)env withPeriod:(jint)period
{
    self = [super init];
    if (self != nil)
    {
        self->_thread = NULL;
        self->_running = NO;
        self->_runnable = NULL;
        self->_period = (1000 * period); // ms --> us
        
        pthread_attr_t attr;
        pthread_attr_init(&attr);
        pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
        pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
        
#if HIGH_PRIORITY_TIMER
        struct sched_param param;
        memset(&param, 0x00, sizeof(param));
        param.sched_priority = sched_get_priority_max(SCHED_FIFO)-1; // notice: 2nd highest priority (gznote: do we really want that?)
        pthread_attr_setschedparam(&attr, &param);
#endif
        
        int err = pthread_create(&self->_thread, &attr, _GlassTimerTask, self);
        if (err == 0)
        {
            // detach thread, so its resources can be reclaimed as soon as it's finished
            pthread_detach(self->_thread);
            
            self->_runnable = (*env)->NewGlobalRef(env, runnable);
        }
        else
        {
            NSLog(@"GlassTimer error: pthread_create returned %d", err);
            
            self = nil;
        }
    }
    return self;
}

@end

/*
 * Class:     com_sun_glass_ui_mac_MacTimer
 * Method:    _start
 * Signature: (I)J
 */
JNIEXPORT jlong JNICALL Java_com_sun_glass_ui_mac_MacTimer__1start
(JNIEnv *env, jobject jThis, jobject jRunnable, jint jPeriod)
{
    LOG("Java_com_sun_glass_ui_mac_MacTimer__1start");
    
    jlong jTimerPtr = 0L;
    
    GLASS_ASSERT_MAIN_JAVA_THREAD(env);
    GLASS_POOL_ENTER;
    {
        jTimerPtr = ptr_to_jlong([[GlassTimer alloc] initWithRunnable:jRunnable withEnv:env withPeriod:jPeriod]);
    }
    GLASS_POOL_EXIT;
    GLASS_CHECK_EXCEPTION(env);
    
    return jTimerPtr;
}

/*
 * Class:     com_sun_glass_ui_mac_MacTimer
 * Method:    _stop
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_mac_MacTimer__1stop
(JNIEnv * env, jobject jRunnable, jlong jTimerPtr)
{
    LOG("Java_com_sun_glass_ui_mac_MacTimer__1stop");
    
    GLASS_ASSERT_MAIN_JAVA_THREAD(env);
    GLASS_POOL_ENTER;
    {
        GlassTimer *timer = (GlassTimer*)jlong_to_ptr(jTimerPtr);
        timer->_running = NO;
    }
    GLASS_POOL_EXIT;
    GLASS_CHECK_EXCEPTION(env);
}

/*
 * Class:     com_sun_glass_ui_mac_MacTimer
 * Method:    _getMinPeriod
 * Signature: ()I
 */
JNIEXPORT int JNICALL Java_com_sun_glass_ui_mac_MacTimer__1getMinPeriod
(JNIEnv * env, jclass cls)
{
    LOG("Java_com_sun_glass_ui_mac_MacTimer__1getMinPeriod");
    
    int period = 0;
    
    GLASS_ASSERT_MAIN_JAVA_THREAD(env);
    GLASS_POOL_ENTER;
    {
        period = 0; // in ms
    }
    GLASS_POOL_EXIT;
    GLASS_CHECK_EXCEPTION(env);
    
    return period;
}

/*
 * Class:     com_sun_glass_ui_mac_MacTimer
 * Method:    _getMaxPeriod
 * Signature: ()I
 */
JNIEXPORT int JNICALL Java_com_sun_glass_ui_mac_MacTimer__1getMaxPeriod
(JNIEnv * env, jclass cls)
{
    LOG("Java_com_sun_glass_ui_mac_MacTimer__1getMaxPeriod");
    
    int period = 0;
    
    GLASS_ASSERT_MAIN_JAVA_THREAD(env);
    GLASS_POOL_ENTER;
    {
        period = 1000000; // in ms (100 sec)
    }
    GLASS_POOL_EXIT;
    GLASS_CHECK_EXCEPTION(env);
    
    return period;
}
