/* 
 * 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 "MFView.h"
#import "MFMForms.h"
#include "base/string_utilities.h"

#import "MFContainerBase.h" // to get forw declaration of setFreezeRelayout:
#import "NSColor_extras.h"

enum ViewFlags
{
  WidthFixedFlag = (1<<8),
  HeightFixedFlag = (1<<9),
  ViewFlagsMask = (WidthFixedFlag|HeightFixedFlag)
};

@class MFCheckBoxImpl;
@implementation NSView(MForms)

- (id)innerView
{
  return self;
}

- (void)setTag:(NSInteger)tag
{
  NSAssert(0, @"setTag must be overriden");
}

- (void)setFixedFrameSize:(NSSize)size
{
  NSRect frame= [self frame];
  NSUInteger flags= [self tag] & ~ViewFlagsMask;
  
  if (size.width > 0)
  {
    frame.size.width= size.width;
    flags|= WidthFixedFlag;
  }
  if (size.height > 0)
  {
    frame.size.height= size.height;
    flags|= HeightFixedFlag;
  }
  
  [self setTag: flags];
  [self setFrame: frame];
}


- (BOOL)widthIsFixed
{
  return ([self tag] & WidthFixedFlag) != 0;
}


- (BOOL)heightIsFixed
{
  return ([self tag] & HeightFixedFlag) != 0;
}


- (NSSize)minimumSize
{
  return NSMakeSize(0, 0);
}

- (NSSize)fixedFrameSize
{
  return [self frame].size;
}

- (NSSize)preferredSize
{
  NSSize size= [self minimumSize];
  NSSize fsize = [self fixedFrameSize];

  // 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, fsize.width);
  if ([self heightIsFixed])
    size.height= MAX(size.height, fsize.height);
  return size;
}


- (NSSize)preferredSizeForWidth:(float)width
{
  NSSize size= [self minimumSizeForWidth: [self widthIsFixed] ? NSWidth([self frame]) : width];
  
  if ([self heightIsFixed])
    size.height= [self fixedFrameSize].height;
  if ([self widthIsFixed])
    size.width= [self fixedFrameSize].width;
  
  return size;  
}


- (NSSize)minimumSizeForWidth:(float)width
{
  return [self minimumSize];
}


- (void)subviewMinimumSizeChanged
{
  if ([[self window] respondsToSelector:@selector(subviewMinimumSizeChanged)])
  {
    // assume this is the toplevel view so just forward to the window
    [(id)[self window] subviewMinimumSizeChanged];
  }
  else
  {
    for (id subview in [self subviews])
      [subview resizeSubviewsWithOldSize: NSMakeSize(0, 0)];
  }
}


- (void)drawBounds:(NSRect)rect
{
  NSFrameRect(rect);
  [NSBezierPath strokeLineFromPoint:NSMakePoint(NSMinX(rect), NSMinY(rect))
                            toPoint:NSMakePoint(NSMaxX(rect), NSMaxY(rect))];
  [NSBezierPath strokeLineFromPoint:NSMakePoint(NSMinX(rect), NSMaxY(rect))
                            toPoint:NSMakePoint(NSMaxX(rect), NSMinY(rect))];
}

@end


NSView *nsviewForView(mforms::View *view)
{
  id obj = view->get_data();

  return obj;
}


static void view_destroy(::mforms::View *self)
{
  id view = self->get_data();
  if (view && [view respondsToSelector: @selector(destroy)])
    [view performSelector: @selector(destroy)];
  else
    [view autorelease];
  if ([view respondsToSelector: @selector(superview)] && [view superview])
    [view removeFromSuperview];
}


static int view_get_x(::mforms::View *self)
{
  id view = self->get_data();
  if (view)
  {
    NSView* widget = view;
    return NSMinX([widget frame]);
  }
  return 0;
}

static int view_get_y(::mforms::View *self)
{
  id view = self->get_data();
  if (view)
  {
    NSView* widget = view;
    return NSMinY([widget frame]);
  }
  return 0;
}

static void view_set_size(::mforms::View *self, int w, int h)
{
  id view = self->get_data();
  if (view)
  {
    [view setFixedFrameSize: NSMakeSize(w,h)];
  }
}

static void view_set_position(::mforms::View *self, int x, int y)
{
  id view = self->get_data();
  if (view)
  {
    [view setFrameOrigin: NSMakePoint(x, y)];
  }
}

static void view_client_to_screen(::mforms::View *self, int& x, int& y)
{
  id view = self->get_data();
  if (view)
  {
    NSPoint pointInWindowCoordinates;
    NSPoint pointInScreenCoords;
    
    pointInWindowCoordinates = [view convertPoint: NSMakePoint(x, y) toView: nil];
    pointInScreenCoords = [[view window] convertBaseToScreen: pointInWindowCoordinates];
    x = pointInScreenCoords.x;
    y = pointInScreenCoords.y;
  }
}

static void view_set_enabled(::mforms::View *self, bool flag)
{
  id view = self->get_data();
  if (view)
  {
    if ([view respondsToSelector: @selector(setEnabled:)])
      [view setEnabled:flag?YES:NO];
  }
}

static bool view_is_enabled(::mforms::View *self)
{
  id view = self->get_data();
  if (view)
  {
    if ([view respondsToSelector: @selector(isEnabled)])
      return [view isEnabled];
  }
  return false;
}

static int view_get_width(::mforms::View *self)
{
  id view = self->get_data();
  if ( view )
  {
    if ([view isKindOfClass: [NSWindow class]])
      return NSWidth([view contentRectForFrameRect:[view frame]]);
    return NSWidth([view frame]);
  }
  return 0;
}

static int view_get_height(::mforms::View *self)
{
  id view = self->get_data();
  if ( view )
  {
    if ([view isKindOfClass: [NSWindow class]])
      return NSHeight([view contentRectForFrameRect:[view frame]]);
    return NSHeight([view frame]);
  }
  return 0;
}

static int view_get_preferred_width(::mforms::View *self)
{
  id view = self->get_data();
  if ( view )
  {
    return [view preferredSize].width;
  }
  return 0;
}

static int view_get_preferred_height(::mforms::View *self)
{
  id view = self->get_data();
  if ( view )
  {
    return [view preferredSize].height;
  }
  return 0;
}


static void view_show(::mforms::View *self, bool show)
{
  id view = self->get_data();
  
  if ( view && [view isHidden] != !show)
  {
    [view setHidden:!show];

    if ([view respondsToSelector: @selector(superview)])
      [[view superview] subviewMinimumSizeChanged];
    if (show && [view respondsToSelector:@selector(window)])
      [[view window] recalculateKeyViewLoop];
  }
}

static bool view_is_shown(::mforms::View *self)
{
  id view = self->get_data();
  if (view)
    return ![view isHidden];
  return false;
}

static void view_set_tooltip(::mforms::View *self, const std::string &text)
{
  id view = self->get_data();
  if (view)
  {
    [view setToolTip: wrap_nsstring(text)];
  }
}

static void view_set_font(::mforms::View *self, const std::string &fontDescription)
{
  id view = self->get_data();
  if (view && [view respondsToSelector: @selector(setFont)])
  {
    std::string name;
    float size;
    bool bold;
    bool italic;
    if (base::parse_font_description(fontDescription, name, size, bold, italic))
    {
      int traitMask = 0;
      if (bold)
        traitMask |= NSBoldFontMask;
      if (italic)
        traitMask |= NSItalicFontMask;
      NSFontManager* fontManager = [NSFontManager sharedFontManager];
      NSFont* font = [fontManager fontWithFamily: [NSString stringWithUTF8String: name.c_str()]
                                          traits: traitMask
                                          weight: 0
                                            size: size];
      [view setFont: font];
    }
  }
}

static void view_set_name(mforms::View *self, const std::string&)
{
}


static void view_relayout(mforms::View *self)
{
  if ([[NSThread currentThread] isMainThread])
    [self->get_data() subviewMinimumSizeChanged];
  else
  {
    id view = self->get_data();
    [view performSelectorOnMainThread: @selector(subviewMinimumSizeChanged) withObject: view waitUntilDone: false];
  }
}

static void view_set_needs_repaint(mforms::View *self)
{
  [self->get_data() setNeedsDisplay: YES];
}

static void view_suspend_layout(::mforms::View *self, bool flag)
{
  if ([self->get_data() respondsToSelector: @selector(setFreezeRelayout:)])
    [self->get_data() setFreezeRelayout: flag];
}


static void view_set_front_color(::mforms::View *self, const std::string &color)
{
  // Foreground color means text color, so that is supported only by text storage and text layer controls.
  if ([self->get_data() respondsToSelector: @selector(setTextColor:)])
    [self->get_data() setTextColor: [NSColor colorFromHexString: [NSString stringWithUTF8String: color.c_str()]]];
}

static std::string view_get_front_color(::mforms::View *self)
{
  if ([self->get_data() respondsToSelector: @selector(textColor)])
  {
    return [[[self->get_data() textColor] hexString] UTF8String];
  }
  return "";
}


static void view_set_back_color(::mforms::View *self, const std::string &color)
{
  if ([self->get_data() respondsToSelector: @selector(setBackgroundColor:)])
  {
    [self->get_data() setBackgroundColor: [NSColor colorFromHexString: [NSString stringWithUTF8String: color.c_str()]]];
    if ([self->get_data() respondsToSelector: @selector(setDrawsBackground:)])
      [self->get_data() setDrawsBackground: !color.empty()];
  }
}

static std::string view_get_back_color(::mforms::View *self)
{
  if ([self->get_data() respondsToSelector: @selector(backgroundColor)])
  {
    if ([self->get_data() respondsToSelector: @selector(drawsBackground)]
       && ![self->get_data() drawsBackground])
      return "";
    return [[[self->get_data() backgroundColor] hexString] UTF8String];
  }
  return "";
}

static void view_set_back_image(::mforms::View *self, const std::string &path, mforms::Alignment align)
{
  if ([self->get_data() respondsToSelector: @selector(setBackgroundImage:withAlignment:)])
    [self->get_data() setBackgroundImage: wrap_nsstring(path) withAlignment: align];
}


static void view_flush_events(::mforms::View *)
{}

static void view_set_padding(::mforms::View *self, int left, int top, int right, int bottom)
{
  if ([self->get_data() respondsToSelector: @selector(setPaddingLeft:right:top:bottom:)])
    [self->get_data() setPaddingLeft:left right:right top:top bottom:bottom];
}

static void view_focus(::mforms::View *self)
{
  id view = self->get_data();
  [[view window] makeFirstResponder: view];
}

void cf_view_init()
{
  ::mforms::ControlFactory *f= ::mforms::ControlFactory::get_instance();
  
  f->_view_impl.destroy              = &view_destroy;
  
  f->_view_impl.get_width            = &view_get_width;
  f->_view_impl.get_height           = &view_get_height;
  f->_view_impl.get_preferred_width  = &view_get_preferred_width;
  f->_view_impl.get_preferred_height = &view_get_preferred_height;
  f->_view_impl.set_size             = &view_set_size;
  
  f->_view_impl.get_x                = &view_get_x;
  f->_view_impl.get_y                = &view_get_y;
  f->_view_impl.set_position         = &view_set_position;
  f->_view_impl.client_to_screen     = &view_client_to_screen;
  
  f->_view_impl.show                 = &view_show;
  f->_view_impl.is_shown             = &view_is_shown;
  
  f->_view_impl.set_enabled          = &view_set_enabled;
  f->_view_impl.is_enabled           = &view_is_enabled;
  f->_view_impl.set_name             = &view_set_name;
  
  f->_view_impl.set_tooltip          = &view_set_tooltip;
  f->_view_impl.set_font             = &view_set_font;
  f->_view_impl.relayout             = &view_relayout;
  f->_view_impl.set_needs_repaint    = &view_set_needs_repaint;

  f->_view_impl.suspend_layout       = &view_suspend_layout;
  f->_view_impl.set_front_color      = &view_set_front_color;
  f->_view_impl.set_back_color       = &view_set_back_color;
  f->_view_impl.get_front_color      = &view_get_front_color;
  f->_view_impl.get_back_color       = &view_get_back_color;
  f->_view_impl.set_back_image       = &view_set_back_image;
  f->_view_impl.flush_events         = &view_flush_events;
  f->_view_impl.set_padding          = &view_set_padding;
  f->_view_impl.focus                = &view_focus;
}
