/*
 * Copyright (c) 2010 David McIntosh <dmcintosh@df12.net>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#import "GanttCell.h"

#define DEBUG_MOUSE NO

@interface NSView(GanttCellAdditions)

- (float)zoom;

@end

@implementation NSView(GanttCellAdditions)

- (float)zoom
{
    // so if we have a bounds of 0.5 and a frame of 1, we are at 2x zoom
    return ([self frame].size.width/[self bounds].size.width);
}

@end

@implementation GanttCell

- (id)init
{
    if ((self = [super init]))
    {
        bounds = NSZeroRect;
        barRect = NSZeroRect;
        progressRect = NSZeroRect;
        dragState = GC_DRAG_NONE;
        yOffset = 0.0;
        [self invalidateView];
    }
    return self;
}

- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
    [[NSColor darkGrayColor] set];
    /* [NSBezierPath fillRect:NSMakeRect(
        cellFrame.origin.x, cellFrame.origin.y, cellFrame.size.width, 1)]; */
    yOffset = cellFrame.origin.y;
    [self drawInteriorWithFrame:NSMakeRect(
        cellFrame.origin.x, cellFrame.origin.y+1,
         cellFrame.size.width, cellFrame.size.height-1) inView:controlView];
    [super drawWithFrame:cellFrame inView:controlView];
}

- (void)setIndex:(NSInteger)val
{
    _index = val;
}

- (NSInteger)index
{
    return _index;
}

- (void)invalidateView
{
    _viewIsValid = NO;
}

- (void)setBounds:(NSRect)aRect
{
    bounds = aRect;
    [self invalidateView];
}

- (NSRect)barRect
{
    if (_endDate == nil || _startDate == nil)
        return NSZeroRect;
    if (NSEqualRects(bounds, NSZeroRect))
        return NSZeroRect;
    if (_viewIsValid)
        return barRect;

    barRect.size.height = 10.0;
    barRect.origin.y = 4.0;

    barRect.origin.x = 
        [_startDate timeIntervalSinceDate:_originDate] / GanttHour;
    barRect.size.width = 
        [_endDate timeIntervalSinceDate:_startDate] / GanttHour;
    _viewIsValid = YES;
    return barRect;
}

- (NSRect)progressRectForZoom:(float)zoom
{
    NSRect bar;

    bar = [self barRect];
    if (NSEqualRects(bar, NSZeroRect))
        return NSZeroRect;

    progressRect.size.height = bar.size.height - 2.0;
    progressRect.origin.y = bar.origin.y + 1.0;

    progressRect.origin.x = bar.origin.x + (1.0/zoom);
    progressRect.size.width = 
        ( bar.size.width - (2.0/zoom) ) * _completion;

    return progressRect;
}

- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
    NSRect bar, progress;
    float zoom = [controlView zoom];
    [self setBounds:[controlView bounds]];

    if (_endDate != nil && _startDate != nil)
    {
        [[NSColor blueColor] set];
        bar = [self barRect];
        bar.origin.y += cellFrame.origin.y;
        [NSBezierPath fillRect:bar];

        if (_completion > 0.0)
        {
            [[NSColor blackColor] set];
            progress = [self progressRectForZoom:zoom];
            progress.origin.y += cellFrame.origin.y;
            [NSBezierPath fillRect:progress];
        }
    }
}

- (NSRect)resizeLeftRectInView:(NSView *)aView
{
    NSRect bar = [self barRect];
    float zoom = [aView zoom];

    if (NSEqualRects(barRect, NSZeroRect))
        return NSZeroRect;

    return NSMakeRect(bar.origin.x - (4.0 / zoom), 
        bar.origin.y - 2.0, 
        4.0 / zoom, 
        bar.size.height + 4.0);
}

- (NSRect)resizeRightRectInView:(NSView *)aView
{
    NSRect bar = [self barRect];
    float zoom = [aView zoom];

    if (NSEqualRects(barRect, NSZeroRect))
        return NSZeroRect;

    return NSMakeRect(bar.origin.x + bar.size.width,
        bar.origin.y - 2.0, 
        4.0 / zoom, 
        bar.size.height + 4.0);
}

- (NSRect)interiorRect
{
    NSRect bar;

    bar = [self barRect];
    if (NSEqualRects(barRect, NSZeroRect))
        return NSZeroRect;

    return NSMakeRect(bar.origin.x + 1.0,
        bar.origin.y,
        bar.size.width - 2.0,
        bar.size.height);
}

- (void)setStartDate:(NSDate *)aDate
{
    // UKLog(@"New start date: %@", aDate);
    ASSIGN(_startDate, aDate);
    [self invalidateView];
}

- (NSDate *)startDate
{
    return _startDate;
}
    
- (void)setEndDate:(NSDate *)aDate
{
    ASSIGN(_endDate, aDate);
    [self invalidateView];
}

- (NSDate *)endDate
{
    return _endDate;
}
    
- (void)setOriginDate:(NSDate *)aDate
{
    ASSIGN(_originDate, aDate);
    [self invalidateView];
}
    
- (void)setGridInterval:(NSTimeInterval)anInterval
{
    _gridInterval = anInterval;
}

- (NSTimeInterval)gridInterval
{
    return _gridInterval;
}

- (void)setCompletion:(float)val
{
    _completion = val;
}

- (float)completion
{
    return _completion;
}
    
- (BOOL)startTrackingAt:(NSPoint)startPoint inView:(NSView *)controlView
{
    // UKLog(@"Called with: %@ yOffset: %0.2f", NSStringFromPoint(startPoint), yOffset);
    [self setBounds:[controlView bounds]];
    if ([controlView mouse:startPoint inRect:
        NSOffsetRect([self resizeLeftRectInView:controlView], 0.0, yOffset)])
    {
        dragState = GC_DRAG_START;
        [self setAction:@selector(userUpdatedStartDate:)];
        [self setContinuous:YES];
        return YES;
    }
    else if ([controlView mouse:startPoint inRect:
        NSOffsetRect([self resizeRightRectInView:controlView], 0.0, yOffset)])
    {
        dragState = GC_DRAG_END;
        [self setAction:@selector(userUpdatedEndDate:)];
        [self setContinuous:YES];
        return YES;
    }
    else if ([controlView mouse:startPoint inRect:
        NSOffsetRect([self barRect], 0.0, yOffset)])
    {
        if (DEBUG_MOUSE)
            UKLog(@"Start Completion Drag. Tag: %d", [self tag]);
        [self setAction:@selector(userUpdatedCompletion:)];
        [self setContinuous:YES];
        dragState = GC_DRAG_COMPLETION;
        return YES;
    }

        
    return NO;
}

- (void)updateForPoint:(NSPoint)currentPoint inView:(NSView *)controlView
{
    float completion;
    NSRect bar = [self barRect];
    NSTimeInterval cursorIntv, gridIntv;
    NSDate *date, *maxStartDate, *minEndDate;

    if (DEBUG_MOUSE)
        UKLog(@"Called with: %@", NSStringFromPoint(currentPoint));

    if (NSEqualRects(NSZeroRect, bar))
        return;

    switch (dragState)
    {
        case GC_DRAG_COMPLETION:
            completion = (currentPoint.x - (bar.origin.x + 1.0)) / bar.size.width;
            if (completion < 0.0)
                completion = 0.0;
            else if (completion > 1.0)
                completion = 1.0;
            if (DEBUG_MOUSE)
                UKLog(@"completion: %0.2f", completion);
            [self setCompletion:completion];
            // [controlView updateCellInside:self];
            break;
        case GC_DRAG_START:
            cursorIntv = (currentPoint.x / GanttPointsPerHour) * (60.0 * 60.0);
            gridIntv = [self gridInterval];
            maxStartDate = [_endDate dateByAddingTimeInterval:(-gridIntv)];
            date = [_originDate dateByAddingTimeInterval:cursorIntv];
            date = [date min:maxStartDate];
            [self setStartDate:[date dateByRoundingToNearest:gridIntv]];
            [[controlView window] invalidateCursorRectsForView:controlView];
            break;
        case GC_DRAG_END:
            cursorIntv = (currentPoint.x / GanttPointsPerHour) * (60.0 * 60.0);
            gridIntv = [self gridInterval];
            date = [_originDate dateByAddingTimeInterval:cursorIntv];
            minEndDate = [_startDate dateByAddingTimeInterval:(gridIntv)];
            date = [[date max:minEndDate] dateByRoundingToNearest:gridIntv];
            [self setEndDate:date];
            UKLog(@"Tracking %@", date);
            [[controlView window] invalidateCursorRectsForView:controlView];
            break;
        default:
            break;
    }
}

- (BOOL)continueTracking:(NSPoint)lastPoint at:(NSPoint)currentPoint 
    inView:(NSView *)controlView
{
    [self updateForPoint:currentPoint inView:controlView];
    [controlView setNeedsDisplay:YES];
    return YES; // [super continueTracking:lastPoint at:currentPoint inView:controlView];
}

- (void)stopTracking:(NSPoint)lastPoint at:(NSPoint)stopPoint
    inView:(NSView *)controlView mouseIsUp:(BOOL)flag
{
    if (flag)
        dragState = GC_DRAG_NONE;
    [self updateForPoint:stopPoint inView:controlView];
    [controlView mouseUp:nil];
}

- (void)resetCursorRectsForFrame:(NSRect)frame
                   inView:(NSView *)view
{
    NSRect bar, resizeLeftRect, resizeRightRect, interiorRect;

    bar = [self barRect];

    // UKLog(@"Bar is %@", NSStringFromRect(bar));

    if (NSEqualRects(bar, NSZeroRect))
    {
        return;
    }

    resizeLeftRect = NSOffsetRect([self resizeLeftRectInView:view], 0.0, frame.origin.y);
    resizeLeftRect = NSIntersectionRect(resizeLeftRect, frame);
    if (!NSEqualRects(resizeLeftRect, NSZeroRect))
        [view addCursorRect:resizeLeftRect
                        cursor:[NSCursor resizeLeftRightCursor]];

    resizeRightRect = NSOffsetRect(
        [self resizeRightRectInView:view], 0.0, frame.origin.y);
    resizeRightRect = NSIntersectionRect(resizeRightRect, frame);
    if (!NSEqualRects(resizeRightRect, NSZeroRect))
        [view addCursorRect:resizeRightRect
                        cursor:[NSCursor resizeLeftRightCursor]];

    interiorRect = NSOffsetRect([self interiorRect], 0.0, frame.origin.y);
    interiorRect = NSIntersectionRect(interiorRect, frame);
    if (!NSEqualRects(interiorRect, NSZeroRect))
        [view addCursorRect:interiorRect
                        cursor:[NSCursor pointingHandCursor]];
}

- (void)dealloc
{
    DESTROY(_startDate);
    DESTROY(_endDate);
    DESTROY(_originDate);
    [super dealloc];
}

@end
