/*
 * Copyright (c) 2009 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 * - Redistribution of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * 
 * - Redistribution in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * 
 * Neither the name of Sun Microsystems, Inc. or the names of
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
 * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
 * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
 * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
 * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
 * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
 * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 * 
 */

#import <inttypes.h>

#import "com_jogamp_newt_impl_macosx_MacWindow.h"
#import "NewtMacWindow.h"

#import "EventListener.h"
#import "MouseEvent.h"
#import "KeyEvent.h"

#import <ApplicationServices/ApplicationServices.h>

#import <stdio.h>

NSString* jstringToNSString(JNIEnv* env, jstring jstr)
{
    const jchar* jstrChars = (*env)->GetStringChars(env, jstr, NULL);
    NSString* str = [[NSString alloc] initWithCharacters: jstrChars length: (*env)->GetStringLength(env, jstr)];
    (*env)->ReleaseStringChars(env, jstr, jstrChars);
    return str;
}

void setFrameTopLeftPoint(NSWindow* pwin, NSWindow* win, jint x, jint y)
{
    NSScreen* screen = [NSScreen mainScreen];

    // this allows for better compatibility with awt behavior
    NSRect visibleRect; // either screen or parent-window 
    NSPoint pt;
    int d_pty=0; // parent titlebar height
    int d_ptx=0; 
    
    if(NULL==pwin) {
        visibleRect = [screen frame];
    } else {
        visibleRect = [pwin frame];
        NSView* pview = [pwin contentView];
        NSRect viewRect = [pview frame];
        d_pty = visibleRect.size.height - viewRect.size.height;
        //d_pty = visibleRect.origin.y - viewRect.size.height;
        //d_ptx = visibleRect.size.height - viewRect.size.height;
        fprintf(stderr, "pwin %lf/%lf %lfx%lf, pview %lf/%lf %lfx%lf -> %d/%d\n", 
            visibleRect.origin.x,
            visibleRect.origin.y,
            visibleRect.size.width,
            visibleRect.size.height, 
            viewRect.origin.x,
            viewRect.origin.y,
            viewRect.size.width,
            viewRect.size.height, 
            x, y);

    }

    pt = NSMakePoint(visibleRect.origin.x + x, visibleRect.origin.y + visibleRect.size.height - y - d_pty);

    [win setFrameTopLeftPoint: pt];
}

static NewtView * changeContentView(JNIEnv *env, jobject javaWindowObject, NSWindow *pwin, NSWindow *win, NewtView *newView) {
    NSView* oldNSView = [win contentView];
    NewtView* oldView = NULL;

    if(NULL!=oldNSView) {
NS_DURING
        // Available >= 10.5 - Makes the menubar disapear
        if([oldNSView isInFullScreenMode]) {
            [oldNSView exitFullScreenModeWithOptions: NULL];
        }
NS_HANDLER
NS_ENDHANDLER
        if( [oldNSView isMemberOfClass:[NewtView class]] ) {
            oldView = (NewtView *) oldNSView;

            jobject globJavaWindowObject = [oldView getJavaWindowObject];
            (*env)->DeleteGlobalRef(env, globJavaWindowObject);
            [oldView setJavaWindowObject: NULL];
        }
        /** FIXME: Tried child window: auto clip or message reception ..
        if(NULL!=pwin) {
            [oldView removeFromSuperview];
        } */
    }
    if(NULL!=newView) {
        jobject globJavaWindowObject = (*env)->NewGlobalRef(env, javaWindowObject);
        [newView setJavaWindowObject: globJavaWindowObject];
        [newView setJNIEnv: env];

        /** FIXME: Tried child window: auto clip or message reception ..
        if(NULL!=pwin) {
            NSView* pview = [pwin contentView];
            [pview addSubview: newView];
        } */
    }
    [win setContentView: newView];

    // make sure the insets are updated in the java object
    NewtMacWindow* newtw = (NewtMacWindow*)win;
    [newtw updateInsets: env];

    return oldView;
}

/*
 * Class:     com_jogamp_newt_impl_macosx_MacDisplay
 * Method:    initIDs
 * Signature: ()Z
 */
JNIEXPORT jboolean JNICALL Java_com_jogamp_newt_impl_macosx_MacDisplay_initNSApplication0
  (JNIEnv *env, jclass clazz)
{
    static int initialized = 0;

    if(initialized) return JNI_TRUE;
    initialized = 1;

    // This little bit of magic is needed in order to receive mouse
    // motion events and allow key focus to be properly transferred.
    // FIXME: are these Carbon APIs? They come from the
    // ApplicationServices.framework.
    ProcessSerialNumber psn;
    if (GetCurrentProcess(&psn) == noErr) {
        TransformProcessType(&psn, kProcessTransformToForegroundApplication);
        SetFrontProcess(&psn);
    }

    // Initialize the shared NSApplication instance
    [NSApplication sharedApplication];

    // Need this when debugging, as it is necessary to attach gdb to
    // the running java process -- "gdb java" doesn't work
    //    printf("Going to sleep for 10 seconds\n");
    //    sleep(10);

    return (jboolean) JNI_TRUE;
}

/*
 * Class:     com_jogamp_newt_impl_macosx_MacDisplay
 * Method:    dispatchMessages0
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_jogamp_newt_impl_macosx_MacDisplay_dispatchMessages0
  (JNIEnv *env, jobject unused)
{
    NSEvent* event = NULL;
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

NS_DURING

    int num_events = 0;

    // Periodically take a break
    do {
        // FIXME: ignoring event mask for the time being
        event = [NSApp nextEventMatchingMask: NSAnyEventMask
                       untilDate: [NSDate distantPast]
                       inMode: NSDefaultRunLoopMode
                       dequeue: YES];
        if (event != NULL) {
            [NSApp sendEvent: event];

            num_events++;
        }
    } while (num_events<100 && event != NULL);

NS_HANDLER
    
    // just ignore it ..

NS_ENDHANDLER

    [pool release];
}

/*
 * Class:     com_jogamp_newt_impl_macosx_MacScreen
 * Method:    getWidthImpl
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_com_jogamp_newt_impl_macosx_MacScreen_getWidthImpl0
  (JNIEnv *env, jclass clazz, jint screen_idx)
{
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

    NSArray *screens = [NSScreen screens];
    if(screen_idx<0) screen_idx=0;
    if(screen_idx>=[screens count]) screen_idx=0;
    NSScreen *screen = (NSScreen *) [screens objectAtIndex: screen_idx];
    NSRect rect = [screen frame];

    [pool release];

    return (jint) (rect.size.width);
}

/*
 * Class:     com_jogamp_newt_impl_macosx_MacScreen
 * Method:    getHeightImpl
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_com_jogamp_newt_impl_macosx_MacScreen_getHeightImpl0
  (JNIEnv *env, jclass clazz, jint screen_idx)
{
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

    NSArray *screens = [NSScreen screens];
    if(screen_idx<0) screen_idx=0;
    if(screen_idx>=[screens count]) screen_idx=0;
    NSScreen *screen = (NSScreen *) [screens objectAtIndex: screen_idx];
    NSRect rect = [screen frame];

    [pool release];

    return (jint) (rect.size.height);
}

/*
 * Class:     com_jogamp_newt_impl_macosx_MacWindow
 * Method:    initIDs
 * Signature: ()Z
 */
JNIEXPORT jboolean JNICALL Java_com_jogamp_newt_impl_macosx_MacWindow_initIDs0
  (JNIEnv *env, jclass clazz)
{
    static int initialized = 0;

    if(initialized) return JNI_TRUE;
    initialized = 1;

    // Need this when debugging, as it is necessary to attach gdb to
    // the running java process -- "gdb java" doesn't work
    //    printf("Going to sleep for 10 seconds\n");
    //    sleep(10);

    return (jboolean) [NewtMacWindow initNatives: env forClass: clazz];
}

/*
 * Class:     com_jogamp_newt_impl_macosx_MacWindow
 * Method:    createWindow0
 * Signature: (JIIIIZIIIJ)J
 */
JNIEXPORT jlong JNICALL Java_com_jogamp_newt_impl_macosx_MacWindow_createWindow0
  (JNIEnv *env, jobject jthis, jlong parent, jint x, jint y, jint w, jint h, jboolean fullscreen, jint styleMask, 
   jint bufferingType, jint screen_idx, jlong jview)
{
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    NSRect rect = NSMakeRect(x, y, w, h);

    NSArray *screens = [NSScreen screens];
    if(screen_idx<0) screen_idx=0;
    if(screen_idx>=[screens count]) screen_idx=0;
    NSScreen *screen = (NSScreen *) [screens objectAtIndex: screen_idx];

    if (fullscreen) {
        styleMask = NSBorderlessWindowMask;
        NSRect rect = [screen frame];
        w = (jint) (rect.size.width);
        h = (jint) (rect.size.height);
    }

    // Allocate the window
    NSWindow* window = [[[NewtMacWindow alloc] initWithContentRect: rect
                                               styleMask: (NSUInteger) styleMask
                                               backing: (NSBackingStoreType) bufferingType
                                               screen: screen] retain];

    NSObject *nsParentObj = (NSObject*) ((intptr_t) parent);
    NSWindow* parentWindow = NULL;
    if( nsParentObj != NULL && [nsParentObj isKindOfClass:[NSWindow class]] ) {
        parentWindow = (NSWindow*) nsParentObj;
    } else if( nsParentObj != NULL && [nsParentObj isKindOfClass:[NSView class]] ) {
        NSView* view = (NSView*) nsParentObj;
        parentWindow = [view window];
        fprintf(stderr, "createWindow0 - Parent is NSView : %p -> %p (win) \n", nsParentObj, parentWindow);
    } else {
        fprintf(stderr, "createWindow0 - Parent is neither NSWindow nor NSView : %p\n", nsParentObj);
    }
    if(NULL!=parentWindow) {
        [parentWindow addChildWindow: window ordered: NSWindowAbove];
        [window setParentWindow: parentWindow];
    }

    if (fullscreen) {
        [window setOpaque: YES];
    } else {
        // If the window is undecorated, assume we want the possibility of
        // a shaped window, so make it non-opaque and the background color clear
        if ((styleMask & NSTitledWindowMask) == 0) {
            [window setOpaque: NO];
            [window setBackgroundColor: [NSColor clearColor]];
        }
    }

    // Immediately re-position the window based on an upper-left coordinate system
    setFrameTopLeftPoint(parentWindow, window, x, y);

    // specify we want mouse-moved events
    [window setAcceptsMouseMovedEvents:YES];

    // Use given NewtView or allocate an NewtView if NULL
    NewtView* view = (0==jview)? [[NewtView alloc] initWithFrame: rect] : (NewtView*) ((intptr_t) jview) ;

    // Set the content view
    (void) changeContentView(env, jthis, parentWindow, window, view);

NS_DURING
    // Available >= 10.5 - Makes the menubar disapear
    if(fullscreen) {
         [view enterFullScreenMode: screen withOptions:NULL];
    }
NS_HANDLER
NS_ENDHANDLER

    // Set the next responder to be the window so that we can forward
    // right mouse button down events
    [view setNextResponder: window];

    [pool release];

    return (jlong) ((intptr_t) window);
}

/*
 * Class:     com_jogamp_newt_impl_macosx_MacWindow
 * Method:    makeKeyAndOrderFront
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_jogamp_newt_impl_macosx_MacWindow_makeKeyAndOrderFront0
  (JNIEnv *env, jobject unused, jlong window)
{
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    NSWindow* win = (NSWindow*) ((intptr_t) window);
    [win makeKeyAndOrderFront: win];
    [pool release];
}

/*
 * Class:     com_jogamp_newt_impl_macosx_MacWindow
 * Method:    makeKey
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_jogamp_newt_impl_macosx_MacWindow_makeKey0
  (JNIEnv *env, jobject unused, jlong window)
{
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    NSWindow* win = (NSWindow*) ((intptr_t) window);
    [win makeKeyWindow];
    [pool release];
}

/*
 * Class:     com_jogamp_newt_impl_macosx_MacWindow
 * Method:    orderOut
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_jogamp_newt_impl_macosx_MacWindow_orderOut0
  (JNIEnv *env, jobject unused, jlong window)
{
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    NSWindow* win = (NSWindow*) ((intptr_t) window);
    [win orderOut: win];
    [pool release];
}

/*
 * Class:     com_jogamp_newt_impl_macosx_MacWindow
 * Method:    close0
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_jogamp_newt_impl_macosx_MacWindow_close0
  (JNIEnv *env, jobject unused, jlong window)
{
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    NSWindow* win = (NSWindow*) ((intptr_t) window);
    NSView* view = [win contentView];
    [win orderOut: win];
NS_DURING
    if(NULL!=view) {
        // Available >= 10.5 - Makes the menubar disapear
        if([view isInFullScreenMode]) {
            [view exitFullScreenModeWithOptions: NULL];
        }
    }
NS_HANDLER
NS_ENDHANDLER
    [win close];
    [pool release];
}

/*
 * Class:     com_jogamp_newt_impl_macosx_MacWindow
 * Method:    setTitle0
 * Signature: (JLjava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_com_jogamp_newt_impl_macosx_MacWindow_setTitle0
  (JNIEnv *env, jobject unused, jlong window, jstring title)
{
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    NSWindow* win = (NSWindow*) ((intptr_t) window);
    NSString* str = jstringToNSString(env, title);
    [str autorelease];
    [win setTitle: str];
    [pool release];
}

/*
 * Class:     com_jogamp_newt_impl_macosx_MacWindow
 * Method:    contentView
 * Signature: (J)J
 */
JNIEXPORT jlong JNICALL Java_com_jogamp_newt_impl_macosx_MacWindow_contentView0
  (JNIEnv *env, jobject unused, jlong window)
{
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    NSWindow* win = (NSWindow*) ((intptr_t) window);
    jlong res = (jlong) ((intptr_t) [win contentView]);
    [pool release];
    return res;
}

/*
 * Class:     com_jogamp_newt_impl_macosx_MacWindow
 * Method:    changeContentView
 * Signature: (J)J
 */
JNIEXPORT jlong JNICALL Java_com_jogamp_newt_impl_macosx_MacWindow_changeContentView0
  (JNIEnv *env, jobject jthis, jlong parent, jlong window, jlong jview)
{
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    NSWindow* pwin = (NewtMacWindow*) ((intptr_t) parent);
    NSWindow* win = (NewtMacWindow*) ((intptr_t) window);
    NewtView* newView = (NewtView *) ((intptr_t) jview);

    NewtView* oldView = changeContentView(env, jthis, pwin, win, newView);

    [pool release];

    return (jlong) ((intptr_t) oldView);
}

/*
 * Class:     com_jogamp_newt_impl_macosx_MacWindow
 * Method:    setContentSize
 * Signature: (JII)V
 */
JNIEXPORT void JNICALL Java_com_jogamp_newt_impl_macosx_MacWindow_setContentSize0
  (JNIEnv *env, jobject unused, jlong window, jint w, jint h)
{
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    NSWindow* win = (NSWindow*) ((intptr_t) window);
    NSSize sz = NSMakeSize(w, h);
    [win setContentSize: sz];
    [pool release];
}

/*
 * Class:     com_jogamp_newt_impl_macosx_MacWindow
 * Method:    setFrameTopLeftPoint
 * Signature: (JII)V
 */
JNIEXPORT void JNICALL Java_com_jogamp_newt_impl_macosx_MacWindow_setFrameTopLeftPoint0
  (JNIEnv *env, jobject unused, jlong parent, jlong window, jint x, jint y)
{
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    NSWindow* pwin = (NSWindow*) ((intptr_t) parent);
    NSWindow* win = (NSWindow*) ((intptr_t) window);
    setFrameTopLeftPoint(pwin, win, x, y);
    [pool release];
}