//
//  OpenGLView.mm
//  AppLauncher
//
//  Created by John Ryland on 1/10/17.
//  Copyright © 2017 John Ryland. All rights reserved.
//

#import <Cocoa/Cocoa.h>
#include <OpenGL/gl.h>
#include <string>
#include <mutex>
#include "OpenGLContext.h"
#include "../Framework/Framework.h"


@interface OpenGLView : NSOpenGLView
{
  CVDisplayLinkRef  m_displayLink; // display link for managing rendering thread
  MouseEvent        m_mouseState;
  //DemoContext*      m_currentDemo;
  OpenGLContext     m_context;
  std::mutex        m_mutex;
}
- (void)setDemo:(const char *)demoStr;
@end


@implementation OpenGLView

- (void)prepareOpenGL
{
  m_mutex.lock();
  // Synchronize buffer swaps with vertical refresh rate
  GLint swapInterval = 1;
  [[self openGLContext] setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval];
  
  CGLLockContext(self.openGLContext.CGLContextObj);
  vec2f shape{ float(self.frame.size.width), float(self.frame.size.height) };
  m_context.onResize(shape);
  m_context.prepare();
  CGLUnlockContext(self.openGLContext.CGLContextObj);

  // Create a display link capable of being used with all active displays
  CVDisplayLinkCreateWithActiveCGDisplays(&m_displayLink);
  
  // Set the renderer output callback function
  CVDisplayLinkSetOutputCallback(m_displayLink, &displayLinkCallback, (__bridge void *)self);
  
  // Set the display link for the current renderer
  CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(m_displayLink, self.openGLContext.CGLContextObj, self.pixelFormat.CGLPixelFormatObj);
  
  // Activate the display link
  CVDisplayLinkStart(m_displayLink);
  
  m_mouseState.m_x = 0.0f;
  m_mouseState.m_y = 0.0f;
  m_mouseState.m_buttons = 0;
  m_mutex.unlock();
}

// This is the renderer output callback function
static CVReturn displayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime,
                                             CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
{
  OpenGLView *view = (__bridge OpenGLView*)displayLinkContext;
  view->m_mutex.lock();
  [view.openGLContext makeCurrentContext];
  view->m_context.update();
  CGLLockContext(view.openGLContext.CGLContextObj); // This is needed because this isn't running on the main thread.
  glClearColor(0, 0, 0, 0);
  glClear(GL_COLOR_BUFFER_BIT);
  view->m_context.draw(); // Draw the scene. This doesn't need to be in the drawRect method.
  glFlush();
  CGLUnlockContext(view.openGLContext.CGLContextObj);
  //CGLFlushDrawable(view.openGLContext.CGLContextObj); // This does glFlush() for you.
  view->m_mutex.unlock();
  return kCVReturnSuccess;
}

- (void)dealloc
{
  // TODO: figure out why this doesn't appear to be getting called
  m_mutex.lock();
  // Release the display link
  CVDisplayLinkRelease(m_displayLink);
  m_context.onClose();
  m_mutex.unlock();
}

- (void)reshape
{
  vec2f shape{ float(self.frame.size.width), float(self.frame.size.height) };
  m_context.onResize(shape);
}

- (void)mouseUp:(NSEvent *)event
{
  if (event.type == NSEventTypeLeftMouseUp)
    m_mouseState.m_buttons &= ~LeftButton;
  if (event.type == NSEventTypeOtherMouseUp)
    m_mouseState.m_buttons &= ~MiddleButton;
  if (event.type == NSEventTypeRightMouseUp)
    m_mouseState.m_buttons &= ~RightButton;
  NSPoint locationInView = [self convertPoint:[event locationInWindow] fromView:nil];
  m_mouseState.m_x = (locationInView.x - self.frame.origin.x) / self.frame.size.width;
  m_mouseState.m_y = (locationInView.y - self.frame.origin.y) / self.frame.size.height;
  m_context.onMouseUp(m_mouseState);
}

- (void)mouseDown:(NSEvent *)event
{
  if (event.type == NSEventTypeLeftMouseDown)
    m_mouseState.m_buttons |= LeftButton;
  if (event.type == NSEventTypeOtherMouseDown)
    m_mouseState.m_buttons |= MiddleButton;
  if (event.type == NSEventTypeRightMouseDown)
    m_mouseState.m_buttons |= RightButton;
  NSPoint locationInView = [self convertPoint:[event locationInWindow] fromView:nil];
  m_mouseState.m_x = (locationInView.x - self.frame.origin.x) / self.frame.size.width;
  m_mouseState.m_y = (locationInView.y - self.frame.origin.y) / self.frame.size.height;
  m_context.onMouseDown(m_mouseState);
}

- (void)mouseMoved:(NSEvent *)event
{
  NSPoint locationInView = [self convertPoint:[event locationInWindow] fromView:nil];
  m_mouseState.m_x = (locationInView.x - self.frame.origin.x) / self.frame.size.width;
  m_mouseState.m_y = (locationInView.y - self.frame.origin.y) / self.frame.size.height;
  m_context.onMouseMove(m_mouseState);
}

- (void)setDemo:(const char *)demoStr
{
  m_mutex.lock();
  [self.openGLContext makeCurrentContext];
  CGLLockContext(self.openGLContext.CGLContextObj); // This is needed because this isn't running on the main thread.
  m_context.setDemo(demoStr);
  vec2f shape{ float(self.frame.size.width), float(self.frame.size.height) };
  m_context.onResize(shape);
  CGLUnlockContext(self.openGLContext.CGLContextObj);
  m_mutex.unlock();
}

@end


@interface MenuButton : NSBox
@end


@implementation MenuButton

- (void)mouseDown:(NSEvent *)event
{
  [((OpenGLView*)self.superview.superview.subviews[1].subviews[0]) setDemo:((NSTextField*)self.subviews[0].subviews[0]).cell.title.UTF8String];
}

@end
