/* 
 * Copyright (c) 2009, 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 "MFDrawBox.h"
#import "MFMForms.h"
#include <cairo/cairo-quartz.h>

@implementation MFDrawBoxImpl

@synthesize drawsBackground = mDrawsBackground;
@synthesize backgroundColor = mBackgroundColor;

- (id)initWithObject: (mforms::DrawBox*)aBox
{
  self= [super initWithFrame:NSMakeRect(10, 10, 10, 10)];
  if (self)
  {
    mOwner= aBox;
    mOwner->set_data(self);
    mTag= 0;
  }
  return self;
}

- (void) dealloc
{
  [super dealloc];
}

- (mforms::Object*)mformsObject
{
  return mOwner;
}

- (BOOL)isFlipped
{
  return YES;
}

- (void)setTag: (NSInteger)tag
{
  mTag= tag;
}

- (NSInteger)tag
{
  return mTag;
}

- (void)setBackgroundColor: (NSColor *)value
{
  if (mBackgroundColor != value)
  {
    [mBackgroundColor release];
    mBackgroundColor = [value retain];
    [self setNeedsDisplay: YES];
  }
}

- (void)setDrawsBackground:(BOOL)flag
{
  if (mDrawsBackground != flag)
  {
    mDrawsBackground = flag;
    [self setNeedsDisplay: YES];
  }
}

- (void)cancelOperation: (id)sender
{
  mOwner->cancel_operation();	
}

- (void)drawRect:(NSRect)rect
{
  NSRect bounds = self.bounds;

  if (mDrawsBackground && mBackgroundColor != nil && ![mBackgroundColor isEqualTo: [NSColor clearColor]])
  {
    [mBackgroundColor set];
    NSRectFill(bounds);
  }
  
  CGContextRef cgref = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];

  cairo_surface_t *surface= cairo_quartz_surface_create_for_cg_context(cgref, NSWidth(bounds), NSHeight(bounds));
  
  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);
}

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

/**
 * Helper to avoid code duplication.
 */
- (void) doMouseUp: (NSEvent*) event
{
  NSPoint p = [self convertPoint: [event locationInWindow] fromView: nil];
  NSInteger button= [event buttonNumber];
  switch ([event clickCount])
  {
    case 1:
      mOwner->mouse_click(button, p.x, p.y);
      break;
    case 2:
      mOwner->mouse_double_click(button, p.x, p.y);
      break;
  }
  // mouse up is always sent
  mOwner->mouse_up(button, p.x, p.y);
}

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

/**
 * Helper to avoid code duplication.
 */
- (void) doMouseDown: (NSEvent*) event
{
  NSPoint p = [self convertPoint: [event locationInWindow] fromView: nil];
  NSInteger button= [event buttonNumber];
  mOwner->mouse_down(button, 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
{
  NSPoint p = [self convertPoint: [event locationInWindow] fromView: nil];
  mOwner->mouse_move(p.x, p.y);
}

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

- (void)mouseEntered:(NSEvent *)event
{
  mOwner->mouse_enter();
}

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

- (void)mouseExited:(NSEvent *)event
{
  mOwner->mouse_leave();
}

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

- (BOOL)acceptsFirstResponder
{
  return YES;
}

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

- (void) updateTrackingAreas
{
  [super updateTrackingAreas];

  // Create one tracking area which covers the whole control and make it get mouse events
  // only when the mouse is within the area but regardless of the first responder status.
  [self removeTrackingArea: mTrackingArea];
  [mTrackingArea release];
  
  NSTrackingAreaOptions options= NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | 
                                 NSTrackingCursorUpdate | NSTrackingActiveInActiveApp |
                                 NSTrackingInVisibleRect;
  mTrackingArea= [[NSTrackingArea alloc] initWithRect: [self bounds] options: options owner: self userInfo: nil];
  [self addTrackingArea: mTrackingArea];
}

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

- (void)invalidate
{
  [self setNeedsDisplay: YES];
}

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

- (NSSize)preferredSize
{
  if (mOwner == NULL || mOwner->is_destroying())
    return NSZeroSize;
  
  int w, h;
  mOwner->get_layout_size(&w, &h);
  NSSize size= NSMakeSize(w, h);
  
  // If a fixed size is set honour that but don't go below the
  // minimal required size.
  if ([self widthIsFixed])
    size.width = MAX(size.width, NSWidth([self frame]));
  if ([self heightIsFixed])
    size.height = MAX(size.height, NSHeight([self frame]));
  return size;
}

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

- (NSSize)minimumSize
{
  return [self preferredSize];
}

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

- (void)setPaddingLeft: (float)left right: (float)right top: (float)top bottom: (float)bottom
{
  mPaddingLeft = left;
  mPaddingRight = right;
  mPaddingTop = top;
  mPaddingBottom = bottom;
}

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

- (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize
{
  if (!mOwner->is_destroying())
  {
    NSSize frameSize = self.frame.size;

    for (std::map<mforms::View*, mforms::Alignment>::const_iterator iterator = mSubviews.begin();
         iterator != mSubviews.end(); ++iterator)
    {
      NSView *view = iterator->first->get_data();
      float x, y;
      NSSize viewSize = view.frame.size;

      switch (iterator->second)
      {
        case mforms::BottomLeft:
        case mforms::MiddleLeft:
        case mforms::TopLeft:
          x = mPaddingLeft;
          break;

        case mforms::BottomCenter:
        case mforms::MiddleCenter:
        case mforms::TopCenter:
          x = (frameSize.width - viewSize.width) / 2;
          break;

        case mforms::BottomRight:
        case mforms::MiddleRight:
        case mforms::TopRight:
          x = frameSize.width - mPaddingRight - viewSize.width;
          break;

        default:
          x = 0;
          break;
      }

      switch (iterator->second)
      {
        case mforms::BottomLeft:
        case mforms::BottomCenter:
        case mforms::BottomRight:
          if (![self isFlipped])
            y = mPaddingBottom;
          else
            y = frameSize.height - mPaddingBottom - viewSize.height;
          break;

        case mforms::MiddleLeft:
        case mforms::MiddleCenter:
        case mforms::MiddleRight:
          y = (frameSize.height - viewSize.height) / 2;
          break;

        case mforms::TopLeft:
        case mforms::TopCenter:
        case mforms::TopRight:
          if ([self isFlipped])
            y = mPaddingTop;
          else
            y = frameSize.height - mPaddingTop - viewSize.height;
          break;

        default:
          y = 0;
          break;
      }

      [view setFrameOrigin: NSMakePoint(x, y)];
    }
  }
}

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

static bool drawbox_create(mforms::DrawBox *self)
{
  [[[MFDrawBoxImpl alloc] initWithObject: self] autorelease];
  
  return true;  
}

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

static void drawbox_set_needs_repaint(mforms::DrawBox *self)
{
  // Invalidate the draw box in a thread safe manner.
  [self->get_data() performSelectorOnMainThread: @selector(invalidate)
                                     withObject: nil
                                  waitUntilDone: NO];
}

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

static void drawbox_add(mforms::DrawBox *self, mforms::View *view, mforms::Alignment alignment)
{
  MFDrawBoxImpl *box = self->get_data();
  box->mSubviews[view] = alignment;
  [box addSubview: view->get_data()];

  [box subviewMinimumSizeChanged];
  [box resizeSubviewsWithOldSize: box.frame.size];
}

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

static void drawbox_remove(mforms::DrawBox *self, mforms::View *view)
{
  MFDrawBoxImpl *box = self->get_data();
  box->mSubviews.erase(view);
  [view->get_data() removeFromSuperview];

  [box subviewMinimumSizeChanged];
  [box resizeSubviewsWithOldSize: box.frame.size];
}

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

static void drawbox_move(mforms::DrawBox *self, mforms::View *view, int x, int y)
{
  MFDrawBoxImpl *box = self->get_data();
  box->mSubviews[view] = mforms::NoAlign;
  [box setFrameOrigin: NSMakePoint(x, y)];
}

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

void cf_drawbox_init()
{
  mforms::ControlFactory *f = mforms::ControlFactory::get_instance();
  
  f->_drawbox_impl.create = &drawbox_create;
  f->_drawbox_impl.set_needs_repaint = &drawbox_set_needs_repaint;
  f->_drawbox_impl.add = &drawbox_add;
  f->_drawbox_impl.remove = &drawbox_remove;
  f->_drawbox_impl.move = &drawbox_move;
}

@end


