//-----------------------------------------------------------------------------
// Copyright (c) 2013 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#import "platformOSX/platformOSX.h"
#import "platformOSX/osxTorqueView.h"
#import "game/gameInterface.h"
#import "gui/guiCanvas.h"

#pragma mark ---- OSXTorqueView Implementation ----

@interface OSXTorqueView (PrivateMethods)
- (void)windowFinishedLiveResize:(NSNotification *)notification;
- (void)getModifierKey:(U32&)modifiers event:(NSEvent *)event;
- (void)processMouseButton:(NSEvent *)event button:(KeyCodes)button action:(U8)action;
- (void)processKeyEvent:(NSEvent *)event make:(BOOL)make;
@end

@implementation OSXTorqueView

@synthesize contextInitialized = _contextInitialized;

//-----------------------------------------------------------------------------
// Custom initialization method for OSXTorqueView
- (void)initialize
{
    if (self)
    {
        // Make absolutely sure _openGLContext is nil
        _openGLContext = nil;

        NSTrackingAreaOptions trackingOptions = NSTrackingCursorUpdate |
                NSTrackingMouseMoved |
                NSTrackingMouseEnteredAndExited |
                NSTrackingInVisibleRect |
                NSTrackingActiveInActiveApp;

        _trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:trackingOptions owner:self userInfo:nil];

        [self addTrackingArea:_trackingArea];

        inputManager = (osxInputManager *) Input::getManager();
    }
}

//-----------------------------------------------------------------------------
// Default dealloc override
- (void)dealloc
{
    // End notifications
    [[NSNotificationCenter defaultCenter] removeObserver:self];

    // Drop the tracking rectangle for mouse events
    if (_trackingArea != nil)
    {
        [self removeTrackingArea:_trackingArea];
        [_trackingArea release];
    }

    // Custom memory cleanup
    if (_openGLContext != nil)
    {
        [_openGLContext release];
        _openGLContext = nil;
    }

    // "Parent" cleanup
    [super dealloc];
}

//-----------------------------------------------------------------------------
// This view an always be a first responder
- (BOOL)acceptsFirstResponder
{
    return YES;
}

//-----------------------------------------------------------------------------
// Called whent the parent finishes its live resizing
- (void)windowFinishedLiveResize:(NSNotification *)notification
{
    NSSize size = [[self window] frame].size;

    [[osxPlatState sharedPlatState] setWindowSize:(S32)size.width height:(S32)size.height];
    
    NSRect frame = NSMakeRect(0, 0, size.width, size.height);
    
    S32 barHeight = frame.size.height;
    frame = [NSWindow frameRectForContentRect:frame styleMask:NSTitledWindowMask];
    barHeight -= frame.size.height;
    
    NSRect viewFrame = NSMakeRect(0, barHeight, frame.size.width, frame.size.height);
    
    [self setFrame:viewFrame];
    [self updateContext];
}

#pragma mark ---- OSXTorqueView OpenGL Handling ----

//-----------------------------------------------------------------------------
// Allocates a new NSOpenGLContext with the specified pixel format and makes
// it the current OpenGL context automatically
- (void)createContextWithPixelFormat:(NSOpenGLPixelFormat *)pixelFormat
{
    _openGLContext = [[[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil] retain];

    AssertFatal(_openGLContext, "We could not create a valid NSOpenGL rendering context.");

    [_openGLContext setView:self];

    [_openGLContext makeCurrentContext];

    _contextInitialized = YES;
}

//-----------------------------------------------------------------------------
// Clears the current context, releases control from this view, and deallocates
// the NSOpenGLContext
- (void)clearContext
{
    if (_openGLContext != nil)
    {
        [NSOpenGLContext clearCurrentContext];
        [_openGLContext clearDrawable];

        [_openGLContext release];
        _openGLContext = nil;

        _contextInitialized = NO;
    }
}

//-----------------------------------------------------------------------------
// Perform an update on the NSOpenGLContext, which will match the surface
// size to the view's frame
- (void)updateContext
{
    if (_openGLContext != nil)
        [_openGLContext update];
}

//-----------------------------------------------------------------------------
// Perform a swap buffer if the NSOpenGLContext is initialized
- (void)flushBuffer
{
    if (_openGLContext != nil)
        [_openGLContext flushBuffer];
}

//-----------------------------------------------------------------------------
- (void)setVerticalSync:(bool)sync
{
    if (_openGLContext != nil)
    {
        GLint swapInterval = sync ? 1 : 0;
        [_openGLContext setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval];
    }
}

#pragma mark ---- OSXTorqueView Input Handling ----

//-----------------------------------------------------------------------------
// Fills out the modifiers based on key presses such as shift, alt, etc
- (void)getModifierKey:(U32&)modifiers event:(NSEvent *)event;
{
    /*
     NSAlphaShiftKeyMask = 1 << 16,
     NSShiftKeyMask      = 1 << 17,
     NSControlKeyMask    = 1 << 18,
     NSAlternateKeyMask  = 1 << 19,
     NSCommandKeyMask    = 1 << 20,
     NSNumericPadKeyMask = 1 << 21,
     NSHelpKeyMask       = 1 << 22,
     NSFunctionKeyMask   = 1 << 23,
     NSDeviceIndependentModifierFlagsMask = 0xffff0000U
     */
    
    U32 keyMods = (U32)[event modifierFlags];
    
    if (keyMods & NSShiftKeyMask)
        modifiers |= SI_SHIFT;
    
    if (keyMods & NSCommandKeyMask)
        modifiers |= SI_ALT;
    
    if (keyMods & NSAlternateKeyMask)
        modifiers |= SI_MAC_OPT;
    
    if (keyMods & NSControlKeyMask)
        modifiers |= SI_CTRL;
}

//-----------------------------------------------------------------------------
// Processes mouse up and down events, posts to the event system
- (void)processMouseButton:(NSEvent *)event button:(KeyCodes)button action:(U8)action
{
    // Get the click location
    NSPoint clickLocation = [self convertPoint:[event locationInWindow] fromView:nil];
    
    NSRect bounds = [self bounds];
    
    clickLocation.y = bounds.size.height - clickLocation.y;
    
    // Move the cursor
    Canvas->setCursorPos(Point2I((S32) clickLocation.x, (S32) clickLocation.y));
    
    // Grab any modifiers
    U32 modifiers = 0;
    [self getModifierKey:modifiers event:event];
    
    // Build the input event
    InputEvent torqueEvent;
    
    torqueEvent.deviceType = MouseDeviceType;
    torqueEvent.deviceInst = 0;
    torqueEvent.objType = SI_BUTTON;
    torqueEvent.objInst = button;
    torqueEvent.modifier = modifiers;
    torqueEvent.ascii = 0;
    torqueEvent.action = action;
    if (action == SI_BREAK)
        torqueEvent.fValues[0] = 0.0;
    else
        torqueEvent.fValues[0] = 1.0;
    
    // Post the input event
    Game->postEvent(torqueEvent);
}

//-----------------------------------------------------------------------------
// Processes keyboard up and down events, posts to the event system
- (void)processKeyEvent:(NSEvent *)event make:(BOOL)make
{
    // If input and keyboard are enabled
    if (!Input::isEnabled() && !Input::isKeyboardEnabled())
        return;
    
    unichar chars = [[event charactersIgnoringModifiers] characterAtIndex:0];
    
    // Get the key code for the event
    U32 keyCode = [event keyCode];
    
    U16 objInst = TranslateOSKeyCode(keyCode);
    
    // Grab any modifiers
    U32 modifiers = 0;
    [self getModifierKey:modifiers event:event];
    
    // Build the input event
    InputEvent torqueEvent;
    
    F32 fValue = 1.0f;
    U8 action = SI_MAKE;
    
    if (!make)
    {
        action = SI_BREAK;
        fValue = 0.0f;
    }
    else if(make && [event isARepeat])
    {
        action = SI_REPEAT;
    }
    
    torqueEvent.deviceType = KeyboardDeviceType;
    torqueEvent.deviceInst = 0;
    torqueEvent.objType = SI_KEY;
    torqueEvent.objInst = objInst;
    torqueEvent.modifier = modifiers;
    torqueEvent.ascii = 0;
    torqueEvent.action = action;
    torqueEvent.fValues[0] = fValue;
    torqueEvent.ascii = chars;
    
    // Post the input event
    Game->postEvent(torqueEvent);
}

//-----------------------------------------------------------------------------
// Default mouseDown override
- (void)mouseDown:(NSEvent *)event
{
    if (!Input::isEnabled() && !Input::isMouseEnabled())
        return;
    
    [self processMouseButton:event button:KEY_BUTTON0 action:SI_MAKE];
}

//-----------------------------------------------------------------------------
// Default rightMouseDown override
- (void)rightMouseDown:(NSEvent *)event
{
    if (!Input::isEnabled() && !Input::isMouseEnabled())
        return;
    
    [self processMouseButton:event button:KEY_BUTTON1 action:SI_MAKE];
}

//-----------------------------------------------------------------------------
// Default otherMouseDown override
- (void)otherMouseDown:(NSEvent *)event
{
    if (!Input::isEnabled() && !Input::isMouseEnabled())
        return;
    
    [self processMouseButton:event button:KEY_BUTTON2 action:SI_MAKE];
}

//-----------------------------------------------------------------------------
// Default mouseUp override
- (void)mouseUp:(NSEvent *)event
{
    if (!Input::isEnabled() && !Input::isMouseEnabled())
        return;
    
    [self processMouseButton:event button:KEY_BUTTON0 action:SI_BREAK];
}

//-----------------------------------------------------------------------------
// Default rightMouseUp override
- (void)rightMouseUp:(NSEvent *)event
{
    if (!Input::isEnabled() && !Input::isMouseEnabled())
        return;
    
    [self processMouseButton:event button:KEY_BUTTON1 action:SI_BREAK];
}

//-----------------------------------------------------------------------------
// Default otherMouseUp override
- (void)otherMouseUp:(NSEvent *)event
{
    if (!Input::isEnabled() && !Input::isMouseEnabled())
        return;
    
    [self processMouseButton:event button:KEY_BUTTON2 action:SI_BREAK];
}

//-----------------------------------------------------------------------------
// Default otherMouseDown override
- (void)mouseMoved:(NSEvent *)event
{
    if (!Input::isEnabled() && !Input::isMouseEnabled())
        return;
    
    // Get the mouse location
    NSPoint location = [self convertPoint:[event locationInWindow] fromView:nil];
    
    // NSViews increase the Y the higher the cursor
    // Torque needs that to be inverted
    NSRect bounds = [self bounds];
    location.y = bounds.size.height - location.y;
    
    // Grab any modifiers
    U32 modifiers = 0;
    [self getModifierKey:modifiers event:event];
    
    // Move the cursor
    Canvas->setCursorPos(Point2I((S32) location.x, (S32) location.y));
    
    // Build the mouse event
    MouseMoveEvent TorqueEvent;
    TorqueEvent.xPos = (S32) location.x;
    TorqueEvent.yPos = (S32) location.y;
    TorqueEvent.modifier = modifiers;
    
    // Post the event
    Game->postEvent(TorqueEvent);
}

//-----------------------------------------------------------------------------
// Default mouseDragged override
- (void)mouseDragged:(NSEvent *)event
{
    if (!Input::isEnabled() && !Input::isMouseEnabled())
        return;
    
    [self mouseMoved:event];
}

//-----------------------------------------------------------------------------
// Default rightMouseDragged override
- (void)rightMouseDragged:(NSEvent *)event
{
    if (!Input::isEnabled() && !Input::isMouseEnabled())
        return;
    
    [self mouseMoved:event];
}

//-----------------------------------------------------------------------------
// Default otherMouseDragged override
- (void)otherMouseDragged:(NSEvent *)event
{
    if (!Input::isEnabled() && !Input::isMouseEnabled())
        return;
    
    [self mouseMoved:event];
}

//-----------------------------------------------------------------------------
// Default scrollWheel override
- (void)scrollWheel:(NSEvent *)event
{
    if (!Input::isEnabled() && !Input::isMouseEnabled())
        return;
    
    F32 deltaY = [event deltaY];
    
    if (deltaY == 0)
        return;
    
    // Grab any modifiers
    U32 modifiers = 0;
    [self getModifierKey:modifiers event:event];
    
    InputEvent torqueEvent;
    
    torqueEvent.deviceType = MouseDeviceType;
    torqueEvent.deviceInst = 0;
    torqueEvent.objType = SI_ZAXIS;
    torqueEvent.objInst = 0;
    torqueEvent.modifier = modifiers;
    torqueEvent.ascii = 0;
    torqueEvent.action = SI_MOVE;
    torqueEvent.fValues[0] = deltaY;
    Game->postEvent(torqueEvent);
}

//-----------------------------------------------------------------------------
// Default keyDown override
- (void)keyDown:(NSEvent *)event
{
    // If input and keyboard are enabled
    if (!Input::isEnabled() && !Input::isKeyboardEnabled())
        return;
    
    [self processKeyEvent:event make:YES];
}

//-----------------------------------------------------------------------------
// Default keyUp override
- (void)keyUp:(NSEvent *)event
{
    // If input and keyboard are enabled
    if (!Input::isEnabled() && !Input::isKeyboardEnabled())
        return;
    
    [self processKeyEvent:event make:NO];
}

@end
