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

#pragma once

#ifndef	GLASS_MACROS_H
#define	GLASS_MACROS_H

#import <jni.h>
#import <Cocoa/Cocoa.h>
#import <assert.h>

#import "GlassStatics.h"
#import "common.h"

// thread specific data to provide multiple call stack mechanism for pools
struct
{
	NSAutoreleasePool   *pool;
    int                 counter;
}
typedef GlassThreadData;

#define MAIN_JVM jVM

// debugging utility
static __inline__ void GlassLog(char *message, ...)
{
    va_list argList;
    static char buffer[4096];
    va_start(argList, message);
    vsnprintf(buffer, sizeof(buffer), message, argList);
    va_end(argList);
    fprintf(stderr, "%s [on thread \"0x%p\", is main:%d]\n", buffer, (void*)pthread_self(), pthread_main_np());fflush(stderr);
}

#define GLASS_LOG(MSG, ...) GlassLog(MSG, ## __VA_ARGS__ )

// assert there is no outstanding java exception pending
#define GLASS_CHECK_EXCEPTION(ENV) \
    if ((*ENV)->ExceptionCheck(ENV) == JNI_TRUE) {                                 \
        fprintf(stderr, "Glass detected outstanding Java exception at %s:%s:%d\n", \
                __FUNCTION__, __FILE__, __LINE__);                                 \
        (*ENV)->ExceptionDescribe(ENV);                                            \
        (*ENV)->ExceptionClear(ENV);                                               \
    };

// assert main Java thread is still attached
#define GLASS_ASSERT_MAIN_JAVA_THREAD(env) \
	if ((pthread_main_np() == 0) && (jEnv == NULL)) { \
		GLASS_CHECK_EXCEPTION(env); \
		jclass newExcCls = (*env)->FindClass(env, "java/lang/RuntimeException"); \
		if (newExcCls != 0) { \
			(*env)->ThrowNew(env, newExcCls, "Main Java thread is detached."); \
		} \
	}

// setup/release autorelase pools
#define GLASS_POOL_ENTER \
{ \
    GlassThreadData *_GlassThreadData = (GlassThreadData*)pthread_getspecific(GlassThreadDataKey); \
    if (_GlassThreadData == NULL) \
    { \
        _GlassThreadData = malloc(sizeof(GlassThreadData)); \
        memset(_GlassThreadData, 0x00, sizeof(GlassThreadData)); \
        pthread_setspecific(GlassThreadDataKey, _GlassThreadData); \
    } \
    assert(_GlassThreadData->counter >= 0); \
    if (_GlassThreadData->counter++ == 0) \
    { \
        _GlassThreadData->pool = [[NSAutoreleasePool alloc] init]; \
    }
#define GLASS_POOL_EXIT \
    if (--_GlassThreadData->counter == 0) \
    { \
        [_GlassThreadData->pool drain]; \
        _GlassThreadData->pool = nil; \
    } \
    assert(_GlassThreadData->counter >= 0); \
}

// variations of GLASS_POOL_ENTER/GLASS_POOL_EXIT allowing them to be used accross different calls
#define GLASS_POOL_PUSH \
    GLASS_POOL_ENTER \
}
#define GLASS_POOL_POP \
{ \
    GlassThreadData *_GlassThreadData = (GlassThreadData*)pthread_getspecific(GlassThreadDataKey); \
    GLASS_POOL_EXIT

// retrieve main thread Java env asserting the call originated on main thread
#define GET_MAIN_JENV \
	assert(pthread_main_np() == 1); \
	if (jEnv == NULL) fprintf(stderr, "Java has been detached already, but someone is still trying to use it at %s:%s:%d\n", __FUNCTION__, __FILE__, __LINE__); \
    JNIEnv *env = jEnv;
	

#define GLASS_PERFORM_WITH_ARG(obj, sel, arg, wait) \
    if ((wait) == YES && [NSThread isMainThread] == YES) { \
        [(obj) sel: (arg)]; \
    } else { \
        [(obj) performSelectorOnMainThread: @selector(sel:) withObject:(arg) waitUntilDone:(wait)]; \
    }

#endif
