/* 
 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; version 2 of the
 * License.
 * 
 * This program 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 for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */


#import "MFPopup.h"
#import "MFMForms.h"

#include <cairo/cairo-quartz.h>

#pragma mark -
#pragma mark PopupContentView

@interface PopupContentView : NSView
{
  mforms::Popup *mOwner;
}
@end

//--------------------------------------------------------------------------------------------------

@implementation PopupContentView

- (id)initWithOwner: (mforms::Popup*)popup
{
  self = [self initWithFrame: NSMakeRect(0, 0, 10, 10)];
  if (self)
  {
    mOwner = popup;
  }
  return self;
}

//--------------------------------------------------------------------------------------------------

- (void)drawRect: (NSRect)rect
{
  if (mOwner == nil)
    return;
  
  NSRect frame = [self frame];
  
  [[NSColor clearColor] set];
  NSRectFill([self frame]);

  CGContextRef cgref = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
  cairo_surface_t *surface = cairo_quartz_surface_create_for_cg_context(cgref, NSWidth(frame), NSHeight(frame));
  
  cairo_t *cr = cairo_create(surface);
  try
  {
    mOwner->repaint(cr, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
  }
  catch(...)
  {
    cairo_destroy(cr);
    cairo_surface_destroy(surface);
    throw;
  }
  cairo_destroy(cr);
  cairo_surface_destroy(surface);
}

//--------------------------------------------------------------------------------------------------

- (BOOL)isFlipped
{
  return YES;
}

//--------------------------------------------------------------------------------------------------

@end

#pragma mark -
#pragma mark MFPopupImpl

@implementation MFPopupImpl

@synthesize popupStyle = mStyle;

- (id)initWithObject: (mforms::Popup*)popup style: (mforms::PopupStyle)aStyle
{
  NSUInteger mask = NSBorderlessWindowMask;
  if (aStyle == mforms::PopupBezel)
    mask |= NSHUDWindowMask;
  self = [super initWithContentRect: NSMakeRect(0, 0, 800, 300)
                          styleMask: mask
                            backing: NSBackingStoreBuffered
                              defer: NO];
  if (self)
  {
    mResult = 0;
    mDone = NO;
    mOwner = popup;
    mOwner->set_data(self);
    self.popupStyle = aStyle;
    
    [self setAcceptsMouseMovedEvents: YES];
    [self setMovableByWindowBackground: NO];
    [self setOpaque: NO];

    NSView* content = [[[PopupContentView alloc] initWithOwner: popup] autorelease];
    [self setContentView: content];

    switch (aStyle)
    {
      case mforms::PopupBezel:
      {
        [self setHidesOnDeactivate: YES];
        [self setHasShadow : YES];
        NSRect windowFrame = [self frame];
        windowFrame.origin.x = 26;
        windowFrame.origin.y = 14;
        windowFrame.size.width -= 52;
        windowFrame.size.height -= 28;
        [content setFrame: windowFrame];
        break;
      }
        
      default:
        [self setHidesOnDeactivate: NO];
        [self setHasShadow : NO];
        content.frame = self.frame;
        break;
    }
  }
  return self;
}


- (void)dealloc
{
  [NSObject cancelPreviousPerformRequestsWithTarget: self];
  [super dealloc];
}
//--------------------------------------------------------------------------------------------------

- (BOOL)isFlipped
{
  return YES;
}

//--------------------------------------------------------------------------------------------------

- (void)hidePopup
{
  if (mOwner == nil) // Set to nil if we were called already (orderOut triggeres the resignKey event).
    return;

  mforms::Popup *owner = mOwner;
  mOwner = nil;
  switch (self.popupStyle)
  {
    case mforms::PopupBezel:
      [[NSAnimationContext currentContext] setDuration: 0.25];
      [[self animator] setAlphaValue: 0];
      [self performSelector: @selector(orderPopupOut) withObject: nil afterDelay: 0.5
                    inModes: [NSArray arrayWithObjects: NSModalPanelRunLoopMode, NSDefaultRunLoopMode, nil]];
      break;

    default:
      [self orderOut: nil];
      break;
  }

  owner->closed();
}

//--------------------------------------------------------------------------------------------------

- (void)orderPopupOut
{
  [self orderOut: nil];
}

//--------------------------------------------------------------------------------------------------

/**
 * Helper to avoid code duplication.
 */
- (void)doMouseUp: (NSEvent*)event
{
  if (mOwner == nil)
    return;
  
  NSPoint p = [[self contentView] convertPoint: [event locationInWindow] fromView: nil];
  mforms::MouseButton mouseButton;
  switch ([event buttonNumber])
  {
    case NSRightMouseDown:
      mouseButton = mforms::MouseButtonRight;
      break;

    case NSOtherMouseDown:
      mouseButton = mforms::MouseButtonOther;
      break;

    default:
      mouseButton = mforms::MouseButtonLeft;
  }
  
  switch ([event clickCount])
  {
    case 1:
      mOwner->mouse_up(mouseButton, p.x, p.y);
      if (!mDone && mOwner != nil)
        mOwner->mouse_click(mouseButton, p.x, p.y);
      break;
    case 2:
      mOwner->mouse_double_click(mouseButton, p.x, p.y);
      break;
  }
}

//--------------------------------------------------------------------------------------------------

/**
 * Helper to avoid code duplication.
 */
- (void)doMouseDown: (NSEvent*)event
{
  if (mOwner == nil)
    return;
  
  NSPoint p = [[self contentView] convertPoint: [event locationInWindow] fromView: nil];
  mforms::MouseButton mouseButton;
  switch ([event buttonNumber])
  {
    case NSRightMouseDown:
      mouseButton = mforms::MouseButtonRight;
      break;

    case NSOtherMouseDown:
      mouseButton = mforms::MouseButtonOther;
      break;

    default:
      mouseButton = mforms::MouseButtonLeft;
  }
  
  mOwner->mouse_down(mouseButton, p.x, p.y);
}

//--------------------------------------------------------------------------------------------------

- (void)mouseDown: (NSEvent*)event
{
  [self doMouseDown: event];
}

//--------------------------------------------------------------------------------------------------

- (void)mouseUp: (NSEvent*)event
{
  [self doMouseUp: event];
}

//--------------------------------------------------------------------------------------------------

- (void)otherMouseDown: (NSEvent*)event
{
  [self doMouseDown: event];
}

//--------------------------------------------------------------------------------------------------

- (void)otherMouseUp: (NSEvent*)event
{
  [self doMouseUp: event];
}

//--------------------------------------------------------------------------------------------------

- (void) rightMouseDown: (NSEvent*) event
{
  [self doMouseDown: event];
}

//--------------------------------------------------------------------------------------------------

- (void)rightMouseUp: (NSEvent*)event
{
  [self doMouseUp: event];
}

//--------------------------------------------------------------------------------------------------

- (void)mouseMoved: (NSEvent *)event
{
  if (mOwner == nil)
    return;
  
  NSPoint p = [[self contentView] convertPoint: [event locationInWindow] fromView: nil];
  mOwner->mouse_move(mforms::MouseButtonNone, p.x, p.y);
}
	
//--------------------------------------------------------------------------------------------------

- (void)mouseEntered: (NSEvent *)event
{
  if (mOwner != nil)
    mOwner->mouse_enter();
}

//--------------------------------------------------------------------------------------------------

- (void)mouseExited: (NSEvent *)event
{
  if (mOwner != nil)
    mOwner->mouse_leave();
}

//--------------------------------------------------------------------------------------------------

- (void)keyDown: (NSEvent *)event
{
  unsigned short code = [event keyCode];
  if (code == 53) // Esc
  {
    mResult = 0;
    mDone = YES;
    [self hidePopup];
  }
}

//--------------------------------------------------------------------------------------------------

- (BOOL)acceptsFirstResponder
{
  return YES;
}

//--------------------------------------------------------------------------------------------------

- (BOOL)canBecomeKeyWindow
{
  return YES;
}

//--------------------------------------------------------------------------------------------------

- (NSInteger)runModalAtPosition: (NSPoint)position
{
  // By design the window's upper right corner is to set at the given position.
  NSRect frame = [self frame];
  position.x -= frame.size.width;
  [self setFrameTopLeftPoint: position];
  
  [self makeKeyAndOrderFront: nil];
  
  [[NSAnimationContext currentContext] setDuration: 0.25];
  [[self animator] setAlphaValue: 1];

  mDone = NO;

  // Local loop to simulate a modal window.
  while (!mDone) 
  {
    NSEvent *theEvent = [self nextEventMatchingMask:NSMouseMovedMask|NSMouseEnteredMask|NSMouseExitedMask|
                         NSLeftMouseDownMask|NSLeftMouseUpMask|NSRightMouseDownMask|NSRightMouseUpMask|
                         NSOtherMouseDownMask|NSOtherMouseUpMask|NSKeyDownMask|NSKeyUpMask];
    if ([theEvent type] == NSLeftMouseUp &&
        ![[self contentView] mouse: [theEvent locationInWindow] inRect:[[self contentView] bounds]])
    {
      [self hidePopup];
      mDone = YES;
    }
    else
    {
      [self sendEvent: theEvent];
    }
  }
  
  return mResult;
}

//--------------------------------------------------------------------------------------------------

- (void)showAtPosition: (NSPoint)position
{
  [self setFrameTopLeftPoint: position];
  [self makeKeyAndOrderFront: nil];

  mDone = NO;
}

//--------------------------------------------------------------------------------------------------

- (void)resignKeyWindow
{
  [super resignKeyWindow];
  [self performSelector: @selector(hidePopup) withObject: nil afterDelay: 0
                inModes: [NSArray arrayWithObjects: NSModalPanelRunLoopMode, NSDefaultRunLoopMode, nil]];
}

//--------------------------------------------------------------------------------------------------

static bool popup_create(mforms::Popup *self, mforms::PopupStyle style)
{
  [[[MFPopupImpl alloc] initWithObject: self style: style] autorelease];
  
  return true;  
}

//--------------------------------------------------------------------------------------------------

static void popup_destroy(mforms::Popup *self)
{
  self->set_data(nil); // Should release the panel.
}

//--------------------------------------------------------------------------------------------------

static void popup_set_needs_repaint(mforms::Popup *self)
{
  [[self->get_data() contentView] setNeedsDisplay: YES];
}

//--------------------------------------------------------------------------------------------------

static void popup_set_size(mforms::Popup *self, int w, int h)
{
  NSRect frame = [self->get_data() frame];
  frame.size.width = w;
  frame.size.height = h;
  [self->get_data() setFrame: frame display: YES];
}

//--------------------------------------------------------------------------------------------------

static int popup_show(mforms::Popup *self, int x, int y)
{
  MFPopupImpl *popup = (MFPopupImpl*)self->get_data();
  switch (popup.popupStyle)
  {
    case mforms::PopupBezel:
      return [popup runModalAtPosition: NSMakePoint(x, y)];
      break;

    default:
      [popup showAtPosition: NSMakePoint(x, y)];
      return 0;
      break;
  }
}

//--------------------------------------------------------------------------------------------------

static base::Rect popup_get_content_rect(mforms::Popup *self)
{
  NSRect frame = [[self->get_data() contentView] frame];
  return base::Rect(frame.origin.x, frame.origin.y, frame.size.width, frame.size.height);
}

//--------------------------------------------------------------------------------------------------

static void popup_set_modal_result(mforms::Popup *self, int result)
{
  MFPopupImpl *popup = (MFPopupImpl*)self->get_data();
  popup->mResult = result;
  popup->mDone = YES;
  [popup hidePopup];
}

//--------------------------------------------------------------------------------------------------

void cf_popup_init()
{
  mforms::ControlFactory *f = mforms::ControlFactory::get_instance();
  
  f->_popup_impl.create = &popup_create;
  f->_popup_impl.destroy = &popup_destroy;
  f->_popup_impl.set_needs_repaint = &popup_set_needs_repaint;
  f->_popup_impl.set_size = &popup_set_size;
  f->_popup_impl.show = &popup_show;
  f->_popup_impl.get_content_rect = &popup_get_content_rect;
  f->_popup_impl.set_modal_result = &popup_set_modal_result;
}

@end

