/*
 * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#import "GlassLayer3D.h"

#import "GlassMacros.h"
#import "RemoteLayerSupport.h"
#import "GlassScreen.h"

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

@implementation GlassLayer3D

static NSArray *allModes = nil;

- (void)_connectToRemoteServer:(NSString*)serverName
{
    // SAK: Note that RemoteLayerGetServerPort can return 0/MACH_PORT_NULL. This is
    // not an error, and means that JRSRenderServer is tracking the port for us.
    // Yes, this is odd, and I've asked Apple to clarify it for us.
    self->_serverPort = RemoteLayerGetServerPort(serverName);
    self->_remoteLayer = RemoteLayerGetRemoteFromLocal(self->_serverPort, self);
    if (self->_remoteLayer != nil)
    {
        self->_remoteLayerID = RemoteLayerGetIdForRemote(self->_remoteLayer);
        if (self->_remoteLayerID != 0)
        {
            [self->_remoteLayer retain];
        }
        else
        {
            NSLog(@"RemoteLayer ERROR: 0 _remoteLayerID for serverName:%@", serverName);
        }
    }
    else
    {
        NSLog(@"RemoteLayer ERROR: nil remoteLayer for serverName:%@", serverName);
    }
}

- (id)initWithSharedContext:(CGLContextObj)ctx
           andClientContext:(CGLContextObj)clCtx
             withHiDPIAware:(BOOL)HiDPIAware
             withIsSwPipe:(BOOL)isSwPipe
{
    LOG("GlassLayer3D initWithSharedContext]");
    self = [super init];
    if (self != nil)
    {
        self->_serverPort = MACH_PORT_NULL;
        self->_remoteLayer = nil;
        self->_remoteLayerID = 0;

        self->_painterOffscreen = [[GlassOffscreen alloc] initWithContext:clCtx andIsSwPipe:isSwPipe];
        self->_glassOffscreen = [[GlassOffscreen alloc] initWithContext:ctx andIsSwPipe:isSwPipe];
        [self->_glassOffscreen setLayer:self];
        LOG("   GlassLayer3D context: %p", ctx);

        self->isHiDPIAware = HiDPIAware;

        [self setAsynchronous:NO];
        [self setAutoresizingMask:(kCALayerWidthSizable|kCALayerHeightSizable)];
        [self setContentsGravity:kCAGravityTopLeft];

        // Initially the view is not in any window yet, so using the
        // screens[0]'s scale is a good starting point (this is most probably
        // the notebook's main LCD display which is HiDPI-capable).
        // Note that mainScreen is the screen with the current app bar focus
        // in Mavericks and later OS so it will likely not match the screen
        // we initially show windows on if an app is started from an external
        // monitor.
        [self notifyScaleFactorChanged:GetScreenScaleFactor([[NSScreen screens] objectAtIndex:0])];

        [self setMasksToBounds:YES];
        [self setNeedsDisplayOnBoundsChange:YES];
        [self setAnchorPoint:CGPointMake(0.0f, 0.0f)];

        if (allModes == nil) {
            allModes = [[NSArray arrayWithObjects:NSDefaultRunLoopMode,
                                                  NSEventTrackingRunLoopMode,
                                                  NSModalPanelRunLoopMode, nil] retain];
        }
    }
    return self;
}

- (void)dealloc
{
    [self->_glassOffscreen release];
    self->_glassOffscreen = nil;

    [self->_painterOffscreen release];
    self->_painterOffscreen = nil;

    [self->_remoteLayer dealloc];
    self->_remoteLayer = nil;

    [super dealloc];
}

- (void)notifyScaleFactorChanged:(CGFloat)scale
{
    if (self->isHiDPIAware) {
        if ([self respondsToSelector:@selector(setContentsScale:)]) {
            [self setContentsScale: scale];
        }
    }
}

//- (void)setBounds:(CGRect)bounds
//{
//    LOG("GlassLayer3D setBounds:%s", [NSStringFromRect(NSRectFromCGRect(bounds)) UTF8String]);
//    [super setBounds:bounds];
//}

- (BOOL)canDrawInCGLContext:(CGLContextObj)glContext pixelFormat:(CGLPixelFormatObj)pixelFormat forLayerTime:(CFTimeInterval)timeInterval displayTime:(const CVTimeStamp *)timeStamp
{
    return [self->_glassOffscreen isDirty];
}

- (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat
{
    return CGLRetainContext([self->_glassOffscreen getContext]);
}

- (CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask
{
    return CGLRetainPixelFormat(CGLGetPixelFormat([self->_glassOffscreen getContext]));
}

- (void)drawInCGLContext:(CGLContextObj)glContext pixelFormat:(CGLPixelFormatObj)pixelFormat forLayerTime:(CFTimeInterval)timeInterval displayTime:(const CVTimeStamp *)timeStamp
{
    // glContext is already set as current by now and locked by Quartz internaly
    LOG("GlassLayer3D drawInCGLContext]");
    LOG("   current context: %p", CGLGetCurrentContext());
#ifdef VERBOSE
    {
        GLint fbo = 0; // default to screen
        glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, (GLint*)&fbo);
        LOG("   fbo: %d", fbo);
    }
#endif
    // the viewport is already set for us here, so just blit

#if 0
    // this will stretch the offscreen to cover all the surface
    // ie., live resizing "appears" better, but the blit area is not at 1:1 scale
    [self->_glassOffscreen blit];
#else
    // we blit only in the area we rendered in
    GLint params[] = { 0, 0, 0, 0 };
    glGetIntegerv(GL_VIEWPORT, params);
    if ((params[2] > 0) && ((params[3] > 0)))
    {
        [self->_glassOffscreen blitForWidth:(GLuint)params[2] andHeight:(GLuint)params[3]];
    }
#endif

    // the default implementation of the method flushes the context.
    [super drawInCGLContext:glContext pixelFormat:pixelFormat forLayerTime:timeInterval displayTime:timeStamp];
    LOG("\n");
}

- (void)flush
{
    [(GlassOffscreen*)_glassOffscreen blitFromOffscreen:(GlassOffscreen*)_painterOffscreen];
    if ([NSThread isMainThread]) {
        [[self->_glassOffscreen getLayer] setNeedsDisplay];
    } else {
        [[self->_glassOffscreen getLayer] performSelectorOnMainThread:@selector(setNeedsDisplay)
                                                           withObject:nil
                                                        waitUntilDone:NO
                                                                modes:allModes];
    }
}

- (uint32_t)getRemoteLayerIdForServer:(NSString*)serverName
{
    if (self->_remoteLayerID == 0)
    {
        Class JRSRemoteLayer_class = objc_getClass("JRSRenderServer");
        if (JRSRemoteLayer_class != nil)
        {
            [self _connectToRemoteServer:serverName];
        }
        else
        {
            NSLog(@"GlassLayer3D: no remote CALayer server support detected!");
            // we're running on JDK without remote CALayer server (JRSRemoteLayer.h) support
        }
    }
    return self->_remoteLayerID;
}

- (void)hostRemoteLayerId:(uint32_t)layerId
{
    if (layerId > 0)
    {
        RemoteLayerHostRemoteIdInLocal(layerId, self);
    }
    else
    {
        NSLog(@"GlassLayer3D: invalid remote layer id!");
    }
}

- (GlassOffscreen*)getPainterOffscreen
{
    return self->_painterOffscreen;
}

- (GlassOffscreen*)getGlassOffscreen
{
    return self->_glassOffscreen;
}

- (void)hostOffscreen:(GlassOffscreen*)offscreen
{
    [self->_glassOffscreen release];
    self->_glassOffscreen = [offscreen retain];
    [self->_glassOffscreen setLayer:self];
}

@end
