#import <Cocoa/Cocoa.h>
#import <objc/runtime.h>

static NSString *const kElectrobunVibrancyViewIdentifier =
    @"ElectrobunVibrancyView";
static NSString *const kElectrobunNativeDragViewIdentifier =
    @"ElectrobunNativeDragView";
static NSString *const kElectrobunWindowBorderViewIdentifier =
    @"ElectrobunWindowBorderView";

static CGFloat gWindowCornerRadius = 15.0;
static CGFloat gTrafficLightsX = 16.0;
static CGFloat gTrafficLightsY = 12.0;

static const void *kElectrobunChromeObserverKey = &kElectrobunChromeObserverKey;

@interface ElectrobunWindowBorderView : NSView
@property(nonatomic) CGFloat cornerRadius;
@end

@implementation ElectrobunWindowBorderView
@synthesize cornerRadius = _cornerRadius;

- (instancetype)initWithFrame:(NSRect)frameRect {
  self = [super initWithFrame:frameRect];
  if (self) {
    _cornerRadius = gWindowCornerRadius;
  }
  return self;
}

- (BOOL)isOpaque {
  return NO;
}

- (BOOL)acceptsFirstMouse:(NSEvent *)event {
  (void)event;
  return NO;
}

- (NSView *)hitTest:(NSPoint)point {
  (void)point;
  return nil;
}

- (void)setFrame:(NSRect)frame {
  [super setFrame:frame];
  [self setNeedsDisplay:YES];
}

- (void)drawRect:(NSRect)dirtyRect {
  (void)dirtyRect;

  NSRect bounds = [self bounds];
  CGFloat radius = MAX(0.0, self.cornerRadius);
  NSBezierPath *borderPath = [NSBezierPath
      bezierPathWithRoundedRect:NSInsetRect(bounds, 0.5, 0.5)
                        xRadius:radius
                        yRadius:radius];

  if (@available(macOS 10.14, *)) {
    NSAppearance *appearance = [self effectiveAppearance];
    BOOL isDark =
        [[appearance bestMatchFromAppearancesWithNames:@[
          NSAppearanceNameAqua, NSAppearanceNameDarkAqua
        ]] isEqualToString:NSAppearanceNameDarkAqua];

    if (isDark) {
      [[NSColor colorWithWhite:1.0 alpha:0.18] setStroke];
    } else {
      [[NSColor colorWithWhite:1.0 alpha:0.72] setStroke];
    }
  } else {
    [[NSColor colorWithWhite:1.0 alpha:0.72] setStroke];
  }

  borderPath.lineWidth = 1.0;
  [borderPath stroke];
}
@end

@interface ElectrobunNativeDragView : NSView
@end

@implementation ElectrobunNativeDragView
- (BOOL)isOpaque {
  return NO;
}

- (void)drawRect:(NSRect)dirtyRect {
  (void)dirtyRect;
}

- (void)mouseDown:(NSEvent *)event {
  NSWindow *window = [self window];
  if (window != nil && event != nil) {
    [window performWindowDragWithEvent:event];
  }
}
@end

@interface ElectrobunWindowChromeObserver : NSObject
@property(nonatomic, weak) NSWindow *window;
@end

static bool applyTrafficLightsPosition(NSWindow *window, CGFloat x,
                                       CGFloat yFromTop);
static void refreshWindowChrome(NSWindow *window);
static BOOL isWindowFullScreen(NSWindow *window);

@implementation ElectrobunWindowChromeObserver

- (instancetype)initWithWindow:(NSWindow *)window {
  self = [super init];
  if (self) {
    _window = window;
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self
               selector:@selector(refreshChrome)
                   name:NSWindowDidResizeNotification
                 object:window];
    [center addObserver:self
               selector:@selector(refreshChrome)
                   name:NSWindowDidEndLiveResizeNotification
                 object:window];
    [center addObserver:self
               selector:@selector(refreshChrome)
                   name:NSWindowDidEnterFullScreenNotification
                 object:window];
    [center addObserver:self
               selector:@selector(refreshChrome)
                   name:NSWindowDidExitFullScreenNotification
                 object:window];
    [center addObserver:self
               selector:@selector(refreshChrome)
                   name:NSWindowDidBecomeKeyNotification
                 object:window];
    [center addObserver:self
               selector:@selector(refreshChrome)
                   name:NSWindowWillEnterFullScreenNotification
                 object:window];
  }
  return self;
}

- (void)refreshChrome {
  if (self.window != nil) {
    refreshWindowChrome(self.window);
    applyTrafficLightsPosition(self.window, gTrafficLightsX, gTrafficLightsY);
  }
}

- (void)dealloc {
  [[NSNotificationCenter defaultCenter] removeObserver:self];
}

@end

static NSVisualEffectView *findVibrancyView(NSView *contentView) {
  for (NSView *subview in [contentView subviews]) {
    if ([subview isKindOfClass:[NSVisualEffectView class]] &&
        [[subview identifier]
            isEqualToString:kElectrobunVibrancyViewIdentifier]) {
      return (NSVisualEffectView *)subview;
    }
  }

  return nil;
}

static ElectrobunNativeDragView *findNativeDragView(NSView *contentView) {
  for (NSView *subview in [contentView subviews]) {
    if ([subview isKindOfClass:[ElectrobunNativeDragView class]] &&
        [[subview identifier]
            isEqualToString:kElectrobunNativeDragViewIdentifier]) {
      return (ElectrobunNativeDragView *)subview;
    }
  }

  return nil;
}

static ElectrobunWindowBorderView *findWindowBorderView(NSView *contentView) {
  for (NSView *subview in [contentView subviews]) {
    if ([subview isKindOfClass:[ElectrobunWindowBorderView class]] &&
        [[subview identifier]
            isEqualToString:kElectrobunWindowBorderViewIdentifier]) {
      return (ElectrobunWindowBorderView *)subview;
    }
  }

  return nil;
}

static BOOL isWindowFullScreen(NSWindow *window) {
  return (window.styleMask & NSWindowStyleMaskFullScreen) != 0;
}

static void refreshWindowChrome(NSWindow *window) {
  BOOL fullScreen = isWindowFullScreen(window);
  CGFloat radius = fullScreen ? 0.0 : gWindowCornerRadius;

  NSView *contentView = [window contentView];
  if (contentView == nil) {
    return;
  }

  contentView.wantsLayer = YES;
  contentView.layer.cornerRadius = radius;
  contentView.layer.masksToBounds = YES;

  NSVisualEffectView *effectView = findVibrancyView(contentView);
  if (effectView != nil) {
    effectView.wantsLayer = YES;
    effectView.layer.cornerRadius = radius;
    effectView.layer.masksToBounds = YES;
  }

  ElectrobunWindowBorderView *borderView = findWindowBorderView(contentView);
  if (borderView != nil) {
    borderView.hidden = fullScreen;
    if (!fullScreen) {
      borderView.cornerRadius = gWindowCornerRadius;
      [borderView setNeedsDisplay:YES];
    }
  }
}

static void applyWindowCornerRadius(NSWindow *window, CGFloat radius) {
  gWindowCornerRadius = MAX(0.0, radius);
  refreshWindowChrome(window);
}

static void ensureWindowBorder(NSWindow *window) {
  NSView *contentView = [window contentView];
  if (contentView == nil) {
    return;
  }

  ElectrobunWindowBorderView *borderView = findWindowBorderView(contentView);
  if (borderView == nil) {
    borderView = [[ElectrobunWindowBorderView alloc]
        initWithFrame:[contentView bounds]];
    [borderView setIdentifier:kElectrobunWindowBorderViewIdentifier];
    [borderView
        setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
  }

  [borderView setFrame:[contentView bounds]];

  if ([borderView superview] == nil) {
    [contentView addSubview:borderView
                  positioned:NSWindowAbove
                  relativeTo:nil];
  } else {
    [borderView removeFromSuperview];
    [contentView addSubview:borderView
                  positioned:NSWindowAbove
                  relativeTo:nil];
  }

  refreshWindowChrome(window);
}

static bool applyTrafficLightsPosition(NSWindow *window, CGFloat x,
                                       CGFloat yFromTop) {
  NSButton *closeButton = [window standardWindowButton:NSWindowCloseButton];
  NSButton *minimizeButton =
      [window standardWindowButton:NSWindowMiniaturizeButton];
  NSButton *zoomButton = [window standardWindowButton:NSWindowZoomButton];

  if (closeButton == nil || minimizeButton == nil || zoomButton == nil) {
    return false;
  }

  NSView *buttonContainer = [closeButton superview];
  if (buttonContainer == nil) {
    return false;
  }

  CGFloat spacing = NSMinX(minimizeButton.frame) - NSMinX(closeButton.frame);
  if (spacing <= 0) {
    spacing = closeButton.frame.size.width + 6.0;
  }

  BOOL flipped = [buttonContainer isFlipped];
  CGFloat targetY = yFromTop;
  if (!flipped) {
    targetY = buttonContainer.frame.size.height - yFromTop -
              closeButton.frame.size.height;
  }
  targetY = MAX(0.0, targetY);

  CGFloat currentX = x;
  NSArray *buttons = @[ closeButton, minimizeButton, zoomButton ];
  for (NSButton *button in buttons) {
    [button setAutoresizingMask:NSViewNotSizable];
    [button setFrameOrigin:NSMakePoint(currentX, targetY)];
    currentX += spacing;
  }

  return true;
}

static void ensureTrafficLightsObserver(NSWindow *window) {
  ElectrobunWindowChromeObserver *existingObserver =
      objc_getAssociatedObject(window, kElectrobunChromeObserverKey);
  if (existingObserver != nil) {
    return;
  }

  ElectrobunWindowChromeObserver *observer =
      [[ElectrobunWindowChromeObserver alloc] initWithWindow:window];
  objc_setAssociatedObject(window, kElectrobunChromeObserverKey, observer,
                           OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

static void scheduleTrafficLightsPosition(NSWindow *window, NSInteger attempt) {
  if (applyTrafficLightsPosition(window, gTrafficLightsX, gTrafficLightsY)) {
    return;
  }

  if (attempt >= 10) {
    return;
  }

  dispatch_after(
      dispatch_time(DISPATCH_TIME_NOW, (int64_t)(50 * NSEC_PER_MSEC)),
      dispatch_get_main_queue(), ^{
        scheduleTrafficLightsPosition(window, attempt + 1);
      });
}

extern "C" bool enableWindowVibrancy(void *windowPtr, double cornerRadius) {
  if (windowPtr == nullptr) {
    return false;
  }

  __block BOOL success = NO;
  dispatch_sync(dispatch_get_main_queue(), ^{
    NSWindow *window = (__bridge NSWindow *)windowPtr;
    if (![window isKindOfClass:[NSWindow class]]) {
      return;
    }

    applyWindowCornerRadius(window, (CGFloat)cornerRadius);

    [window setOpaque:NO];
    [window setBackgroundColor:[NSColor clearColor]];
    [window setTitlebarAppearsTransparent:YES];
    [window setHasShadow:YES];

    NSView *contentView = [window contentView];
    if (contentView == nil) {
      return;
    }

    NSVisualEffectView *effectView = findVibrancyView(contentView);

    if (effectView == nil) {
      effectView = [[NSVisualEffectView alloc]
          initWithFrame:[contentView bounds]];
      [effectView setIdentifier:kElectrobunVibrancyViewIdentifier];
      [effectView
          setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
    }

    if (@available(macOS 10.14, *)) {
      [effectView setMaterial:NSVisualEffectMaterialHUDWindow];
    } else {
      [effectView setMaterial:NSVisualEffectMaterialSidebar];
    }
    [effectView setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
    [effectView setState:NSVisualEffectStateActive];

    if ([effectView superview] == nil) {
      NSView *relativeView = [[contentView subviews] firstObject];
      if (relativeView != nil) {
        [contentView addSubview:effectView
                      positioned:NSWindowBelow
                      relativeTo:relativeView];
      } else {
        [contentView addSubview:effectView];
      }
    }

    ensureWindowBorder(window);
    applyWindowCornerRadius(window, gWindowCornerRadius);
    ensureTrafficLightsObserver(window);
    scheduleTrafficLightsPosition(window, 0);

    [window invalidateShadow];
    success = YES;
  });

  return success;
}

extern "C" bool setWindowCornerRadius(void *windowPtr, double cornerRadius) {
  if (windowPtr == nullptr) {
    return false;
  }

  __block BOOL success = NO;
  dispatch_sync(dispatch_get_main_queue(), ^{
    NSWindow *window = (__bridge NSWindow *)windowPtr;
    if (![window isKindOfClass:[NSWindow class]]) {
      return;
    }

    applyWindowCornerRadius(window, (CGFloat)cornerRadius);
    ensureWindowBorder(window);
    [window invalidateShadow];
    success = YES;
  });

  return success;
}

extern "C" bool ensureWindowShadow(void *windowPtr) {
  if (windowPtr == nullptr) {
    return false;
  }

  __block BOOL success = NO;
  dispatch_sync(dispatch_get_main_queue(), ^{
    NSWindow *window = (__bridge NSWindow *)windowPtr;
    if (![window isKindOfClass:[NSWindow class]]) {
      return;
    }

    [window setHasShadow:YES];
    [window invalidateShadow];
    ensureWindowBorder(window);
    applyWindowCornerRadius(window, gWindowCornerRadius);
    success = YES;
  });

  return success;
}

extern "C" bool setWindowTrafficLightsPosition(void *windowPtr, double x,
                                               double yFromTop) {
  if (windowPtr == nullptr) {
    return false;
  }

  __block BOOL success = NO;
  dispatch_sync(dispatch_get_main_queue(), ^{
    NSWindow *window = (__bridge NSWindow *)windowPtr;
    if (![window isKindOfClass:[NSWindow class]]) {
      return;
    }

    gTrafficLightsX = (CGFloat)x;
    gTrafficLightsY = (CGFloat)yFromTop;
    ensureTrafficLightsObserver(window);
    success = applyTrafficLightsPosition(window, gTrafficLightsX, gTrafficLightsY);
    if (success) {
      [window invalidateShadow];
    }
  });

  return success;
}

extern "C" bool setNativeWindowDragRegion(void *windowPtr, double x,
                                            double height) {
  if (windowPtr == nullptr) {
    return false;
  }

  __block BOOL success = NO;
  dispatch_sync(dispatch_get_main_queue(), ^{
    NSWindow *window = (__bridge NSWindow *)windowPtr;
    if (![window isKindOfClass:[NSWindow class]]) {
      return;
    }

    NSView *contentView = [window contentView];
    if (contentView == nil) {
      return;
    }

    CGFloat dragX = MAX(0.0, x);
    CGFloat dragHeight = MAX(0.0, height);
    CGFloat dragWidth = MAX(0.0, contentView.bounds.size.width - dragX);
    if (dragHeight <= 0.0 || dragWidth <= 0.0) {
      return;
    }

    BOOL flipped = [contentView isFlipped];
    CGFloat dragY = flipped ? 0.0 : contentView.bounds.size.height - dragHeight;
    dragY = MAX(0.0, dragY);

    ElectrobunNativeDragView *dragView = findNativeDragView(contentView);
    if (dragView == nil) {
      dragView = [[ElectrobunNativeDragView alloc] initWithFrame:NSZeroRect];
      [dragView setIdentifier:kElectrobunNativeDragViewIdentifier];
    }

    [dragView setFrame:NSMakeRect(dragX, dragY, dragWidth, dragHeight)];
    [dragView setAutoresizingMask:NSViewWidthSizable];

    if ([dragView superview] == nil) {
      [contentView addSubview:dragView
                    positioned:NSWindowAbove
                    relativeTo:nil];
    }

    success = YES;
  });

  return success;
}
