//
//  Window.mm
//  Platform
//
//  Created by John Ryland on 5/10/17.
//  Copyright © 2017 John Ryland. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import <Cocoa/Cocoa.h>
#include <OpenGL/gl.h>
#include <mutex>
#include "Window.h"


void* CreateWindow()
{
  NSRect frame = NSMakeRect(100, 200, 640, 480);
  NSWindow* window  = [[NSWindow alloc] initWithContentRect:frame
                      styleMask: NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable
                      backing:NSBackingStoreBuffered
                      defer:NO];
  /*
  NSRect rect = [window contentRectForFrameRect:[window frame]];
  NSView* view = [[NSView alloc] initWithFrame:rect];
  [window setOpaque:YES];
  [window setContentView:view];
  */
  return (void*)CFBridgingRetain(window);
}


void DestroyWindow(void* a_platformHandle)
{
  CFBridgingRelease(a_platformHandle);
}


void SetWindowStyle(void* a_platformHandle, int a_styleFlags)
{
  NSWindow* window = (__bridge NSWindow*)a_platformHandle;
  window.styleMask = a_styleFlags;
}


void SetWindowGeometry(void* a_platformHandle, int a_x, int a_y, int a_width, int a_height)
{
  NSWindow* window = (__bridge NSWindow*)a_platformHandle;
  [window setFrame:NSMakeRect(a_x, a_y, a_width, a_height) display: window.visible];
}


void SetWindowTitle(void* a_platformHandle, const char* a_title)
{
  NSWindow* window = (__bridge NSWindow*)a_platformHandle;
  window.title = [NSString stringWithUTF8String:a_title];
}


void ShowWindow(void* a_platformHandle)
{
  NSWindow* window = (__bridge NSWindow*)a_platformHandle;
  [window makeKeyAndOrderFront:NSApp];
}


@interface MouseResponder : NSResponder
  @property NSView* m_view;
  @property MouseEvent m_mouseState;
  @property std::function<void(const MouseEvent&)> m_mouseEventCallback;
@end


@implementation MouseResponder
- (id)initWithView:(NSView*)view
{
  self = [super init];
  self.m_mouseEventCallback = [](const MouseEvent&){};
  self.m_mouseState = MouseEvent{ 0.0f, 0.0f, 0 };
  self.m_view = view;
  return self;
}
- (void)sendMouseEvent:(NSEvent*)event
{
  MouseEvent state = self.m_mouseState;
  if (event.type == NSEventTypeLeftMouseUp)
    state.m_buttons &= ~LeftButton;
  if (event.type == NSEventTypeOtherMouseUp)
    state.m_buttons &= ~MiddleButton;
  if (event.type == NSEventTypeRightMouseUp)
    state.m_buttons &= ~RightButton;
  if (event.type == NSEventTypeLeftMouseDown)
    state.m_buttons |= LeftButton;
  if (event.type == NSEventTypeOtherMouseDown)
    state.m_buttons |= MiddleButton;
  if (event.type == NSEventTypeRightMouseDown)
    state.m_buttons |= RightButton;
  NSPoint locationInView = [self.m_view convertPoint:[event locationInWindow] fromView:nil];
  state.m_x = (locationInView.x - self.m_view.frame.origin.x) / self.m_view.frame.size.width;
  state.m_y = (locationInView.y - self.m_view.frame.origin.y) / self.m_view.frame.size.height;
  self.m_mouseEventCallback(state);
  self.m_mouseState = state;
}
- (void)mouseMoved:(NSEvent *)event
{
  [self sendMouseEvent:event];
}
- (void)mouseUp:(NSEvent *)event
{
  [self sendMouseEvent:event];
}
- (void)rightMouseUp:(NSEvent *)event
{
  [self sendMouseEvent:event];
}
- (void)otherMouseUp:(NSEvent *)event
{
  [self sendMouseEvent:event];
}
- (void)mouseDown:(NSEvent *)event
{
  [self sendMouseEvent:event];
}
- (void)rightMouseDown:(NSEvent *)event
{
  [self sendMouseEvent:event];
}
- (void)otherMouseDown:(NSEvent *)event
{
  [self sendMouseEvent:event];
}
@end


@interface CustomView : NSBox
  @property MouseResponder* m_mouseHandler;
@end
@implementation CustomView
@end


void* CreateView(void* a_platformHandle)
{
  CustomView* view = [[CustomView alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 100.0)];
  NSWindow* window = (__bridge NSWindow*)a_platformHandle;
  [window.contentView addSubview:view];
  view.fillColor = [NSColor colorWithRed:0.9 green:0.0 blue:0.0 alpha:1.0];
  view.borderType = NSNoBorder;
  view.boxType = NSBoxCustom;
  view.m_mouseHandler = [[MouseResponder alloc] initWithView:view];
  view.nextResponder = view.m_mouseHandler;
  return (void*)CFBridgingRetain(view);
}


void DestroyView(void* a_platformHandle)
{
  CFBridgingRelease(a_platformHandle);
}


void SetViewGeometry(void* a_platformHandle, int a_x, int a_y, int a_width, int a_height)
{
  CustomView* view = (__bridge CustomView*)a_platformHandle;
  [view setFrame:NSMakeRect(a_x, a_y, a_width, a_height)];
}


void SetViewColor(void* a_platformHandle, float a_red, float a_green, float a_blue, float a_alpha)
{
  CustomView* view = (__bridge CustomView*)a_platformHandle;
  view.fillColor = [NSColor colorWithRed:a_red green:a_green blue:a_blue alpha:a_alpha];
}


void SetViewOnMouseEvent(void* a_platformHandle, std::function<void(const MouseEvent&)> a_callback)
{
  CustomView* view = (__bridge CustomView*)a_platformHandle;
  view.m_mouseHandler.m_mouseEventCallback = a_callback;
}


@interface CustomGLView : NSOpenGLView
  {
    CVDisplayLinkRef  m_displayLink; // display link for managing rendering thread
    std::mutex        m_mutex;
    float m_w, m_h;
  }
  @property MouseResponder* m_mouseHandler;
  @property std::function<void()> m_prepareCallback;
  @property std::function<void()> m_updateCallback;
  @property std::function<void()> m_drawCallback;
  @property std::function<void()> m_closeCallback;
  @property std::function<void(float a_width, float a_height)> m_resizeCallback;
@end


@implementation CustomGLView
- (id)initWithFrame:(NSRect)rect
{
  NSOpenGLPixelFormatAttribute attrs[] = {
    NSOpenGLPFAAccelerated,
    NSOpenGLPFADoubleBuffer,
    NSOpenGLPFADepthSize, 24,
    NSOpenGLPFAAllowOfflineRenderers, // lets OpenGL know this context is offline renderer aware
    NSOpenGLPFAMultisample, 1,
    NSOpenGLPFASampleBuffers, 1,
    NSOpenGLPFASamples, 4,
    NSOpenGLPFAColorSize, 32,
    0
  };
  NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
  self = [super initWithFrame:rect pixelFormat:pixelFormat];
  self.m_prepareCallback = [](){};
  self.m_updateCallback = [](){};
  self.m_drawCallback = [](){};
  self.m_closeCallback = [](){};
  self.m_resizeCallback = [](float a_width, float a_height){};
  return self;
}
- (void)prepareOpenGL
{
  [super prepareOpenGL];
  [self.openGLContext makeCurrentContext];
  GLint swapInterval = 1;
  [[self openGLContext] setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval];
  m_mutex.lock();
  CGLLockContext(self.openGLContext.CGLContextObj);
  m_w = float(self.frame.size.width);
  m_h = float(self.frame.size.height);
  self.m_resizeCallback(float(self.frame.size.width), float(self.frame.size.height));
  self.m_prepareCallback();
  CVDisplayLinkCreateWithActiveCGDisplays(&m_displayLink);
  CVDisplayLinkSetOutputCallback(m_displayLink, &displayLinkCallback, (__bridge void *)self);
  CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(m_displayLink, self.openGLContext.CGLContextObj, self.pixelFormat.CGLPixelFormatObj);
  CGLUnlockContext(self.openGLContext.CGLContextObj);
  CVDisplayLinkStart(m_displayLink);
  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)
{
  CustomGLView *view = (__bridge CustomGLView*)displayLinkContext;
  [view drawRect:NSMakeRect(0, 0, view->m_w, view->m_h)];
  return kCVReturnSuccess;
}
- (void)dealloc
{
  // TODO: figure out why this doesn't appear to be getting called
  m_mutex.lock();
  CVDisplayLinkRelease(m_displayLink);
  self.m_closeCallback();
  m_mutex.unlock();
}
- (void)reshape
{
  m_w = float(self.frame.size.width);
  m_h = float(self.frame.size.height);
  self.m_resizeCallback(float(self.frame.size.width), float(self.frame.size.height));
}
- (void) drawRect: (NSRect) theRect
{
  m_mutex.lock();
  [[self openGLContext] makeCurrentContext];
  if (self.m_updateCallback)
    self.m_updateCallback();
  CGLLockContext([[self openGLContext] CGLContextObj]);
  glViewport(0,0,m_w,m_h);
  glClearColor(0, 0, 0, 0);
  glClear(GL_COLOR_BUFFER_BIT);
  if (self.m_drawCallback)
    self.m_drawCallback(); // Draw the scene. This doesn't need to be in the drawRect method.
  glFlush();
  [[self openGLContext] flushBuffer];
  CGLFlushDrawable([[self openGLContext] CGLContextObj]);
  CGLUnlockContext([[self openGLContext] CGLContextObj]);
  m_mutex.unlock();
}
@end


void* CreateGLView(void* a_platformHandle)
{
  CustomGLView* view = [[CustomGLView alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 100.0)];
  NSWindow* window = (__bridge NSWindow*)a_platformHandle;
  [window.contentView addSubview:view];
  view.m_mouseHandler = [[MouseResponder alloc] initWithView:view];
  view.nextResponder = view.m_mouseHandler;
  return (void*)CFBridgingRetain(view);
}


void DestroyGLView(void* a_platformHandle)
{
  CFBridgingRelease(a_platformHandle);
}


void SetGLViewGeometry(void* a_platformHandle, int a_x, int a_y, int a_width, int a_height)
{
  CustomGLView* view = (__bridge CustomGLView*)a_platformHandle;
  [view setFrame:NSMakeRect(a_x, a_y, a_width, a_height)];
}


void SetGLViewOnPrepare(void* a_platformHandle, std::function<void()> a_callback)
{
  CustomGLView* view = (__bridge CustomGLView*)a_platformHandle;
  view.m_prepareCallback = a_callback;
}


void SetGLViewOnUpdate(void* a_platformHandle, std::function<void()> a_callback)
{
  CustomGLView* view = (__bridge CustomGLView*)a_platformHandle;
  view.m_updateCallback = a_callback;
}


void SetGLViewOnDraw(void* a_platformHandle, std::function<void()> a_callback)
{
  CustomGLView* view = (__bridge CustomGLView*)a_platformHandle;
  view.m_drawCallback = a_callback;
}


void SetGLViewOnClose(void* a_platformHandle, std::function<void()> a_callback)
{
  CustomGLView* view = (__bridge CustomGLView*)a_platformHandle;
  view.m_closeCallback = a_callback;
}


void SetGLViewOnResize(void* a_platformHandle, std::function<void(float a_width, float a_height)> a_callback)
{
  CustomGLView* view = (__bridge CustomGLView*)a_platformHandle;
  view.m_resizeCallback = a_callback;
}


void SetGLViewOnMouseEvent(void* a_platformHandle, std::function<void(const MouseEvent&)> a_callback)
{
  CustomGLView* view = (__bridge CustomGLView*)a_platformHandle;
  view.m_mouseHandler.m_mouseEventCallback = a_callback;
}


@interface CustomLabel : NSTextField
  @property MouseResponder* m_mouseHandler;
@end
@implementation CustomLabel
@end


void* CreateWindowLabel(void* a_platformHandle)
{
  CustomLabel* text = [[CustomLabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 50.0)];
  NSWindow* window = (__bridge NSWindow*)a_platformHandle;
  [window.contentView addSubview:text];
  text.bordered = false;
  text.editable = false;
  //text.autoresizingMask = NSViewWidthSizable;
  //text.lineBreakMode = NSLineBreakByClipping;
  //text.cell.scrollable = true;
  text.alignment = NSTextAlignmentCenter;
  text.m_mouseHandler = [[MouseResponder alloc] initWithView:text];
  text.nextResponder = text.m_mouseHandler;
  return (void*)CFBridgingRetain(text);
}


void* CreateViewLabel(void* a_platformHandle)
{
  CustomLabel* text = [[CustomLabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 50.0)];
  NSBox* view = (__bridge NSBox*)a_platformHandle;
  [view addSubview:text];
  text.bordered = false;
  text.editable = false;
  text.alignment = NSTextAlignmentCenter;
  text.m_mouseHandler = [[MouseResponder alloc] initWithView:text];
  text.nextResponder = text.m_mouseHandler;
  return (void*)CFBridgingRetain(text);
}


void DestroyLabel(void* a_platformHandle)
{
  CFBridgingRelease(a_platformHandle);
}


void SetLabelGeometry(void* a_platformHandle, int a_x, int a_y, int a_width, int a_height)
{
  NSTextField* text = (__bridge NSTextField*)a_platformHandle;
  [text setFrame:NSMakeRect(a_x, a_y, a_width, a_height)];
}


void SetLabelColor(void* a_platformHandle, float a_red, float a_green, float a_blue, float a_alpha)
{
  NSTextField* text = (__bridge NSTextField*)a_platformHandle;
  text.backgroundColor = [NSColor colorWithRed:a_red green:a_green blue:a_blue alpha:a_alpha];
}


void SetLabelText(void* a_platformHandle, const char* a_text)
{
  NSTextField* text = (__bridge NSTextField*)a_platformHandle;
  text.cell.title = [NSString stringWithUTF8String:a_text];
}


void SetLabelFontSize(void* a_platformHandle, int a_size)
{
  NSTextField* text = (__bridge NSTextField*)a_platformHandle;
  text.font = [NSFont fontWithName: text.font.fontName  size: a_size];
}


void SetLabelOnMouseEvent(void* a_platformHandle, std::function<void(const MouseEvent&)> a_callback)
{
  CustomLabel* text = (__bridge CustomLabel*)a_platformHandle;
  text.m_mouseHandler.m_mouseEventCallback = a_callback;
}
