/**
    DiagramView.m

    Author: Matt Rice <ratmice@gmail.com>
    Date: Oct 2006

    This file is part of DBModeler.

    <license>
    DBModeler 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; either version 3 of the License, or
    (at your option) any later version.

    DBModeler 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 DBModeler; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
    </license>
**/


#include "DiagramView.h"
#include "EntityView.h"
#include "AttributeCell.h"

#ifdef NeXT_Foundation_LIBRARY
#include <Foundation/Foundation.h>
#else
#include <Foundation/NSSet.h>
#endif

#ifdef NeXT_GUI_LIBRARY
#include <AppKit/AppKit.h>
#else
#include <AppKit/NSColor.h>
#include <AppKit/NSScrollView.h>
#include <AppKit/NSEvent.h>
#include <AppKit/NSGraphics.h>
#include <AppKit/NSBezierPath.h>
#include <AppKit/NSPasteboard.h>
#include <AppKit/NSDragging.h>
#endif

#include <EOAccess/EOModel.h>
#include <EOAccess/EOAttribute.h>
#include <EOAccess/EORelationship.h>
#include <EOModeler/EOModelExtensions.h>

#include <GNUstepBase/GNUstep.h>
#include <math.h>
#include <stdlib.h>

@implementation DiagramView
- (id) initWithFrame:(NSRect)frameRect
{
  self = [super initWithFrame:frameRect];
  _shownEntities = [NSMutableDictionary new];
  _relationships = [NSMutableArray new];
  _subview_order = [NSMutableArray new];
  _bgColor = RETAIN([NSColor colorWithCalibratedRed:0.881437 green:0.941223 blue:1.0 alpha:1.0]);
  [self registerForDraggedTypes:[NSArray arrayWithObject:NSColorPboardType]];
  return self;
}

- (void) dealloc
{
  RELEASE(_shownEntities);
  RELEASE(_relationships);
  RELEASE(_subview_order);
  RELEASE(_bgColor);
  RELEASE(_model);

  [super dealloc];
}

- (BOOL) prepareForDragOperation:(id <NSDraggingInfo>)sender
{
  return YES;
}
- (NSDragOperation) draggingEntered:(id <NSDraggingInfo>)sender
{
  return [sender draggingSourceOperationMask];
}

- (BOOL) performDragOperation:(id <NSDraggingInfo>)sender
{
  NSPasteboard *pb = [sender draggingPasteboard];
  NSColor *color = [NSColor colorFromPasteboard:pb];

  ASSIGN(_bgColor, color);
  [self setNeedsDisplay:YES];
  return YES;
}

- (BOOL) isFlipped
{
  return YES;
}

- (void) setModel:(EOModel *)model
{
  ASSIGN(_model, model);
}

- (void) showEntity:(NSString *)name
{
  EntityView *ev;

  if (!(ev = [_shownEntities objectForKey:name]))
    {
      EOEntity *entity = [_model entityNamed:name];
      NSArray *attribs = [entity attributes];
      NSRect vis = [[self enclosingScrollView] documentVisibleRect];
      NSRect evFrame;
      NSPoint toPoint;
      int i, c = [attribs count];
   
      ev = [[EntityView alloc] initWithFrame:NSMakeRect(0,0,1,1)];
      [ev setTitle:[entity name]];
      [ev setNumberOfAttributes:c];

      for (i = 0; i < c; i++)
        {
          EOAttribute *attrib = [attribs objectAtIndex:i];
          AttributeCell *cell = [ev cellAtRow:i];

          [cell setStringValue:[attrib name]];
          [cell setLock:[[entity attributesUsedForLocking] containsObject:attrib]];
          [cell setProp:[[entity classProperties] containsObject:attrib]];
          [cell setKey:[[entity primaryKeyAttributes] containsObject:attrib]];
        }
 
      [ev sizeToFit];
      evFrame = [ev frame];
  
      /* this "layout mechanism" is pure evil... */
      toPoint.x = floor(vis.origin.x  + ((vis.size.width - evFrame.size.width) * rand()/(RAND_MAX + vis.origin.x)));
      toPoint.y = floor(vis.origin.y + ((vis.size.height - evFrame.size.height) * rand()/(RAND_MAX + vis.origin.y)));
      
      [ev setFrameOrigin:toPoint];
      [_shownEntities setObject:ev forKey:[entity name]];
  
      [self addSubview:ev];
      [_subview_order addObject:ev];
      [self setupRelationships];
    }
  else
    {
      EOEntity *entity = [_model entityNamed:name];
      NSArray *attribs = [entity attributes];
      int i, c = [attribs count];

      [ev setTitle:[entity name]];
      [ev setNumberOfAttributes:c];
      
      for (i = 0; i < c; i++)
        {
          EOAttribute *attrib = [attribs objectAtIndex:i];
          AttributeCell *cell = [ev cellAtRow:i];

          [cell setStringValue:[attrib name]];
          [cell setLock:[[entity attributesUsedForLocking] containsObject:attrib]];
          [cell setProp:[[entity classProperties] containsObject:attrib]];
          [cell setKey:[[entity primaryKeyAttributes] containsObject:attrib]];
        }
      [ev sizeToFit];
      [ev setNeedsDisplay:YES];
     
    }
}

int sortSubviews(id view1, id view2, void *context)
{
  DiagramView *self = context;
  unsigned idx1, idx2;

  idx1 = [self->_subview_order indexOfObject:view1];
  idx2 = [self->_subview_order indexOfObject:view2];
  
  return (idx1 < idx2) ? NSOrderedAscending : NSOrderedDescending;
}

- (void) orderViewFront:(NSView *)v
{
  int idx = [_subview_order indexOfObject:v];
  RETAIN(v);
  [_subview_order removeObjectAtIndex:idx];
  [_subview_order addObject:v];
  RELEASE(v);
  [self sortSubviewsUsingFunction:(int (*)(id, id, void *))sortSubviews context:self];
  [self setNeedsDisplay:YES];
}

- (void) setupRelationships
{
  int i,c;
  NSArray *stuff = [_shownEntities allKeys];
  [_relationships removeAllObjects];
  for (i = 0, c = [stuff count]; i < c; i++)
     {
       int j, d;
       NSString *entName = [stuff objectAtIndex:i];
       EOEntity *ent = [_model entityNamed:entName];
       NSArray *rels = [ent relationships];
       
       for (j = 0, d = [rels count]; j < d; j++)
         {
           EORelationship *rel = [rels objectAtIndex:j];
           EOEntity *dest = [rel destinationEntity];
           id srcName = [ent name];
           id destName = [dest name]; 
           EntityView *from = [_shownEntities objectForKey:srcName];
           EntityView *to = [_shownEntities objectForKey:destName];
           NSArray *srcAttribs = [rel sourceAttributes]; 
           NSArray *destAttribs = [rel destinationAttributes]; 
           int k, e;
           for (k = 0, e = [srcAttribs count]; k < e; k++)
             {
               id sAttrib = [srcAttribs objectAtIndex:k];
               id dAttrib = [destAttribs objectAtIndex:k];
               int sIdx = [[ent attributes] indexOfObject:sAttrib];
               int dIdx = [[dest attributes] indexOfObject:dAttrib];
               NSRect fromRect = [from attributeRectAtRow:sIdx];
               NSRect toRect = [to attributeRectAtRow:dIdx];
               NSRect fromViewFrame = [from frame];
               NSRect toViewFrame = [to frame];
               NSPoint midPoint;
               NSPoint tmp; 
               float arrowOffset; 
                NSBezierPath *path = [NSBezierPath bezierPath];
               BOOL fromRight;
               BOOL toRight;

               [path setLineWidth:2];

               fromRect.origin.y += fromViewFrame.origin.y; 
               toRect.origin.y += toViewFrame.origin.y; 
              
               /* which side of the EntityView the arrow line will be connecting
                * to, for the source and destination entities */
               fromRight = (fromViewFrame.origin.x - 40 < toViewFrame.origin.x + toViewFrame.size.width);
               toRight = (toViewFrame.origin.x - 40 < fromViewFrame.origin.x + fromViewFrame.size.width);
               
               if (fromRight)
                 {
                   fromRect.origin.x = fromViewFrame.origin.x + fromViewFrame.size.width + 5;
                 }
               else
                 {
                   fromRect.origin.x = fromViewFrame.origin.x - 5;
                 }
               
               if (toRight)
                 {
                   toRect.origin.x = toViewFrame.origin.x + toViewFrame.size.width;
                   toRect.origin.x += 5;
                   /* <- */
                   arrowOffset = -5.0;
                 }
               else
                 {
                   toRect.origin.x = toViewFrame.origin.x;
                   toRect.origin.x -= 5;
                   /* -> */
                   arrowOffset = 5.0;
                 }

               fromRect.origin.y = NSMidY(fromRect);
               toRect.origin.y = NSMidY(toRect);
               
               /* every line segment is drawn forwards and backwards so we dont
                * end up with lightning bolts when filling the arrow */

               /* a recursive relationship... 
                * Don't think they are particularly useful but... */
               if (fromRect.origin.y == toRect.origin.y
                   && fromRect.origin.x == toRect.origin.x)
                 {  
                   [path moveToPoint:NSMakePoint(toRect.origin.x + 15, toRect.origin.y)];
                   [path lineToPoint:NSMakePoint(toRect.origin.x + 15, toRect.origin.y + 5)];
                   [path lineToPoint:NSMakePoint(toRect.origin.x + 15, toRect.origin.y)];

                   [path moveToPoint:NSMakePoint(toRect.origin.x + 15, toRect.origin.y + 5)];
                   [path lineToPoint:NSMakePoint(toRect.origin.x + 20, toRect.origin.y + 5)];
                   [path lineToPoint:NSMakePoint(toRect.origin.x + 15, toRect.origin.y + 5)];
                   
                   [path moveToPoint:NSMakePoint(toRect.origin.x + 20, toRect.origin.y + 5)];
                   [path lineToPoint:NSMakePoint(toRect.origin.x + 20, toRect.origin.y)];
                   [path lineToPoint:NSMakePoint(toRect.origin.x + 20, toRect.origin.y + 5)];
                 }
               
               if ((fromRight || toRight) && !(fromRight && toRight))
                 {
                   /* a line like...   +-----
                    *                  |   
                    *             -----+ 
                    *  (from the right side, to the left side or vice versa)
                    */           
                   [path moveToPoint:fromRect.origin];
               
                   midPoint.x = (fromRect.origin.x + toRect.origin.x) / 2;
                   midPoint.y = fromRect.origin.y;
                   [path lineToPoint:midPoint];
               
                   [path lineToPoint:fromRect.origin];
                    [path moveToPoint:midPoint]; 
                   tmp = midPoint;
                   midPoint.x = (fromRect.origin.x + toRect.origin.x) / 2;
                   midPoint.y = toRect.origin.y;
                   [path lineToPoint:midPoint];
                   [path lineToPoint:tmp];

                   [path moveToPoint:midPoint];
                   [path lineToPoint:toRect.origin];
                   [path lineToPoint:midPoint];
                 }
               else if (fromRight && toRight)
                 {
                   /*  need to       --+   or -------+ <- joint end or start.
                     * make a line      |             |
                    * like...   -------+           --+ <- joint start or end.
                    * from the right side to the right side.
                    */
                         
                   NSPoint jointStart;
                   NSPoint jointEnd;
                   
                   if (toRect.origin.x + toRect.size.width < fromRect.origin.x + fromRect.size.width)
                     {
                       jointStart = NSMakePoint(fromRect.origin.x + 20, fromRect.origin.y);
                       jointEnd = NSMakePoint(fromRect.origin.x + 20, toRect.origin.y);
                     }
                   else
                     {
                       jointStart = NSMakePoint(toRect.origin.x + 20, fromRect.origin.y);
                       jointEnd = NSMakePoint(toRect.origin.x + 20, toRect.origin.y);
                     } 
                   [path moveToPoint:fromRect.origin];
                   [path lineToPoint:jointStart];
                   [path lineToPoint:fromRect.origin];
                   
                   [path moveToPoint:jointStart];
                   [path lineToPoint:jointEnd];
                   [path lineToPoint:jointStart];
                   
                   [path moveToPoint:jointEnd]; 
                   [path lineToPoint:toRect.origin];
                   [path lineToPoint:jointEnd];
                 }

               /* draw arrows.. */ 
               if ([rel isToMany]) 
                 {
                   [path moveToPoint:toRect.origin]; 
                   [path lineToPoint:NSMakePoint(toRect.origin.x - arrowOffset, toRect.origin.y + arrowOffset)];
                   [path lineToPoint:NSMakePoint(toRect.origin.x - arrowOffset, toRect.origin.y - arrowOffset)]; 
                   [path lineToPoint:toRect.origin]; 
                   toRect.origin.x -= arrowOffset;
                   [path moveToPoint:toRect.origin]; 
                   [path lineToPoint:NSMakePoint(toRect.origin.x - arrowOffset, toRect.origin.y + arrowOffset)];
                   [path lineToPoint:NSMakePoint(toRect.origin.x - arrowOffset, toRect.origin.y - arrowOffset)]; 
                   [path lineToPoint:toRect.origin]; 
                 }
               else
                 {
                   [path moveToPoint:toRect.origin]; 
                   [path lineToPoint:NSMakePoint(toRect.origin.x - arrowOffset, toRect.origin.y + arrowOffset)];
                   [path lineToPoint:NSMakePoint(toRect.origin.x - arrowOffset, toRect.origin.y - arrowOffset)]; 
                   [path lineToPoint:toRect.origin]; 
                 }

               [path closePath];
               [_relationships addObject:path];
             }

         }
     }
}

- (void) mouseDown:(NSEvent *)ev
{
  /* create new relationshp??*/
}

- (BOOL) autoscroll:(NSEvent *)ev
{
  NSPoint pt = [self convertPoint:[ev locationInWindow] fromView:nil];
  NSRect r = _frame;
  BOOL flag;

  if (!NSPointInRect(pt, r))
    {
      if (pt.x > r.size.width)
        r.size.width = pt.x; 
      if (pt.y > r.size.height)
        r.size.height = pt.y;
      [self setFrameSize:r.size];
    }
  flag = [super autoscroll:ev];
  return flag;
}

- (void) drawRect:(NSRect)aRect
{
  int i, c;
  [_bgColor set];
  NSRectFill([self frame]);
  [self setupRelationships];
  
  for (i = 0, c = [_relationships count]; i < c; i++)
    {
      [[NSColor blackColor] set];
      NSBezierPath *path = [_relationships objectAtIndex:i];
      [path stroke];
      [path fill];
    }
}

@end
