//
//  CDSDrawView.m
//  PDFViewer-Mac
//
//  Copyright © 2014-2023 PDF Technologies, Inc. All Rights Reserved.
//
//  THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW
//  AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE ComPDFKit LICENSE AGREEMENT.
//  UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES.
//  This notice may not be removed from this file.
//

#import "CDSDrawView.h"

static NSInteger _index;
static CGPoint _points[5];

@interface CDSDrawView ()

@property (nonatomic,assign) BOOL cursorIsHidden;
@property (nonatomic,assign) BOOL mouseIsInView;

@property (nonatomic,retain) NSTouch *activeTouch;

@property (nonatomic,retain) NSBezierPath *bezierPath;

@property (nonatomic,assign) BOOL isInView;

@end

@implementation CDSDrawView

- (void)dealloc {
 
}

- (id)initWithFrame:(NSRect)frameRect {
    if (self = [super initWithFrame:frameRect]) {
        self.drawImage = [[NSImage alloc] initWithSize:self.frame.size];
        self.drawColor = [NSColor colorWithDeviceRed:0 green:0 blue:0 alpha:1];
        self.strokeRadius = 0.3;
        
        _bezierPath = [[NSBezierPath alloc] init];
        
        self.wantsLayer = YES;
        self.layer.borderWidth = 1.0;
        self.layer.borderColor =[NSColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.05].CGColor;

    }
    return self;
}

- (id)initWithCoder:(NSCoder *)coder {
    if (self = [super initWithCoder:coder]) {
        self.drawImage = [[NSImage alloc] initWithSize:self.frame.size];
        self.drawColor = [NSColor colorWithDeviceRed:0 green:0 blue:0 alpha:1];
        self.strokeRadius = 0.3;
        
        _bezierPath = [[NSBezierPath alloc] init];
        
        self.wantsLayer = YES;
        self.layer.borderWidth = 1.0;
        self.layer.borderColor =[NSColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.05].CGColor;

    }
    return self;
}

- (NSBezierPath *)drawBezierPath {
    _drawBezierPath = [_bezierPath copy];
    
    CGRect rect = self.bezierPath.bounds;
    NSAffineTransform *transform = [NSAffineTransform transform];
    [transform translateXBy:- rect.origin.x + +self.bezierPath.lineWidth/2.0 yBy:-rect.origin.y + self.bezierPath.lineWidth/2.0];
    [_drawBezierPath transformUsingAffineTransform:transform];
    
    return _drawBezierPath;
}

/*
 ** - (BOOL) acceptsFirstResponder
 **
 ** Make sure the view will receive
 ** events.
 **
 ** Input: none
 **
 ** Output: YES to accept, NO to reject
 */
- (BOOL)acceptsFirstResponder {
    return YES;
}

- (void)setIsAcceptsTouch:(BOOL)isAcceptsTouch {
    _isAcceptsTouch = isAcceptsTouch;
    
    // Accept trackpad events
    [self setAcceptsTouchEvents:isAcceptsTouch];
    
    size_t screenHeight = CGDisplayPixelsHigh(CGMainDisplayID());
    NSRect frameToWindow = [self convertRect:self.bounds toView:nil];
    NSRect frameToScreen = [self.window convertRectToScreen:frameToWindow];
    if (isAcceptsTouch) {
        // If the mouse cursor is not already hidden,
        if (!self.cursorIsHidden) {
            frameToScreen.origin.x = frameToScreen.origin.x+5;
            frameToScreen.origin.y = screenHeight-frameToScreen.origin.y-5;
            CGWarpMouseCursorPosition(frameToScreen.origin);

            // Detach the mouse cursor from the mouse
            // hardware so that moving the mouse (or a
            // single finger) will not move the cursor
            CGAssociateMouseAndMouseCursorPosition(false);
            
            // Hide the mouse cursor
            [NSCursor hide];
            
            // Remember that we detached and hid the
            // mouse cursor
            self.cursorIsHidden = YES;
        }
    } else {
        frameToScreen.origin.y = screenHeight-frameToScreen.origin.y;
        CGWarpMouseCursorPosition(frameToScreen.origin);

        // Attach the mouse cursor to the mouse
        // hardware so that moving the mouse (or a
        // single finger) will move the cursor
        CGAssociateMouseAndMouseCursorPosition(true);
        
        // Show the mouse cursor
        [NSCursor unhide];
        
        // Remember that we attached and unhid the
        // mouse cursor so that the next touch that
        // begins will detach and hide it
        self.cursorIsHidden = NO;
    }
}

- (void)clearImage {
    self.drawImage = nil;
    [self.bezierPath removeAllPoints];
    [self setNeedsDisplay:YES];
}

- (void)setDrawColor:(NSColor *)drawColor {
    if (_drawColor != drawColor) {
        _drawColor = drawColor;
    }
    [self setNeedsDisplay:YES];
}

- (void)setStrokeRadius:(float)strokeRadius {
    _strokeRadius = strokeRadius;
    [self setNeedsDisplay:YES];
}


- (NSImage *)signatureImage {
    CGRect rect = CGRectZero;

    if (self.bezierPath.empty) {
        return nil;
    } else {
        rect = self.bezierPath.bounds;
    }
    
    CGSize size = CGSizeMake(rect.size.width+self.bezierPath.lineWidth,
                             rect.size.height+self.bezierPath.lineWidth);
    NSImage *image = [[NSImage alloc] initWithSize:size];
    [image lockFocus];
    
    [self.drawColor set];
    [self.drawBezierPath setLineWidth:self.strokeRadius * 2];
    [self.drawBezierPath setLineCapStyle:kCGLineCapRound];
    [self.drawBezierPath setLineJoinStyle:kCGLineJoinRound];
    [self.drawBezierPath stroke];

    [image unlockFocus];

    return image;
}

#pragma mark Draw
- (void)drawRect:(NSRect)dirtyRect {
    [super drawRect:dirtyRect];
    
    [NSGraphicsContext saveGraphicsState];
    [[NSColor clearColor] set];

    if (([[self window] firstResponder] == self) && self.mouseIsInView) {
        NSSetFocusRingStyle(NSFocusRingAbove);
    }
    [[NSBezierPath bezierPathWithRect:[self bounds]] fill];
    
    [NSGraphicsContext restoreGraphicsState];

    if (self.drawImage) {
        CGRect imageFrame = [self imageFrameInRect:self.bounds];
        [self.drawImage drawInRect:imageFrame];
    }
    
    [self.drawColor set];
    [self.bezierPath setLineWidth:self.strokeRadius * 2];
    [self.bezierPath setLineCapStyle:kCGLineCapRound];
    [self.bezierPath setLineJoinStyle:kCGLineJoinRound];
    [self.bezierPath stroke];
}

- (CGRect)imageFrameInRect:(CGRect)rect {
    CGRect imageRect;
    if (self.drawImage.size.width < rect.size.width &&
        self.drawImage.size.height < rect.size.height) {
        imageRect.origin.x = (rect.size.width-self.drawImage.size.width)/2.0;
        imageRect.origin.y = (rect.size.height-self.drawImage.size.height)/2.0;
        imageRect.size = self.drawImage.size;
    } else {
        if (self.drawImage.size.width/self.drawImage.size.height >
            rect.size.width/rect.size.height) {
            imageRect.size.width = rect.size.width;
            imageRect.size.height = rect.size.width*self.drawImage.size.height/self.drawImage.size.width;
        } else {
            imageRect.size.height = rect.size.height;
            imageRect.size.width = rect.size.height*self.drawImage.size.width/self.drawImage.size.height;
        }
        imageRect.origin.x = (rect.size.width-imageRect.size.width)/2.0;
        imageRect.origin.y = (rect.size.height-imageRect.size.height)/2.0;
    }
    return imageRect;
}

#pragma mark Touch

- (void)touchesBeganWithEvent:(NSEvent *)event {
    [super touchesBeganWithEvent:event];
    
    NSSet *touches = [event touchesMatchingPhase:NSTouchPhaseBegan inView:self];
    self.activeTouch = [touches anyObject];
    
    CGPoint point = [self.activeTouch normalizedPosition];
    point.x = point.x * self.bounds.size.width;
    point.y = point.y * self.bounds.size.height;
    _index = 0;
    _points[0] = point;
    [self setNeedsDisplay:YES];
    
    if (!self.cursorIsHidden) {
        CGAssociateMouseAndMouseCursorPosition(false);
        
        [NSCursor hide];
        self.cursorIsHidden = YES;
    }
}

- (void)touchesMovedWithEvent:(NSEvent *)event {
    [super touchesMovedWithEvent:event];
    
    NSSet *touches = [event touchesMatchingPhase:NSTouchPhaseMoved inView:self];
    
    BOOL isTouch = NO;
    for (NSTouch *touch in touches) {
        if (touch.identity == self.activeTouch.identity) {
            isTouch = YES;
            self.activeTouch = touch;
        }
    }
    
    if (!isTouch) {
        return;
    }
    
    NSPoint point = [self.activeTouch normalizedPosition];
    point.x = point.x * self.bounds.size.width;
    point.y = point.y * self.bounds.size.height;
    
    _index++;
    _points[_index] = point;
    
    if (_index == 4) {
        _points[3] = CGPointMake((_points[2].x + _points[4].x)/2.0,
                                 (_points[2].y + _points[4].y)/2.0);
        [self.bezierPath moveToPoint:_points[0]];
        
        [self.bezierPath curveToPoint:_points[3]
                        controlPoint1:_points[1]
                        controlPoint2:_points[2] ];
        _points[0] = _points[3];
        _points[1] = _points[4];
        _index = 1;
        [self setNeedsDisplay:YES];
    }
}

- (void)touchesEndedWithEvent:(NSEvent *)event {
    [super touchesEndedWithEvent:event];
    
    NSSet *touches = [event touchesMatchingPhase:NSTouchPhaseMoved inView:self];
    
    for (NSTouch *touch in touches) {
        if (touch.identity == self.activeTouch.identity) {
            self.activeTouch = nil;
        }
    }
    
    if (_index < 4) {
        for (int i=0; i<_index; i++) {
            [self.bezierPath moveToPoint:_points[i]];
        }
        [self setNeedsDisplay:YES];
    }
}

- (void)touchesCancelledWithEvent:(NSEvent *)event {
    [super touchesCancelledWithEvent:event];
    
    for (int i=0; i<_index; i++) {
        [self.bezierPath moveToPoint:_points[i]];
    }
    self.activeTouch = nil;
    [self setNeedsDisplay:YES];
}

#pragma mark - Mouse

- (void)viewDidMoveToWindow {
    if ([self window] != nil) {
        [self addTrackingRect:[self bounds]
                        owner:self
                     userData:NULL
                 assumeInside:NO];
    }
}

- (void)mouseEntered:(NSEvent *)theEvent {
    [[self window] makeFirstResponder:self];

    self.mouseIsInView = YES;
    [self setNeedsDisplay:YES];
}

- (void)mouseExited:(NSEvent *)theEvent {
    self.mouseIsInView = NO;
    
    [self setNeedsDisplay:YES];
}

- (void)mouseDown:(NSEvent *)theEvent {
    if ([self acceptsTouchEvents]) {
        return;
    }
    
    CGPoint point = [self convertPoint:[theEvent locationInWindow] fromView:nil];
    if (CGRectContainsPoint(self.bounds, point)) {
        self.isInView = YES;

        _index = 0;
        _points[0] = point;
    }
}

- (void)mouseDragged:(NSEvent *)theEvent {
    if ([self acceptsTouchEvents]) {
        return;
    }
    
    NSPoint point = [self convertPoint:[theEvent locationInWindow] fromView:nil];
    if (CGRectContainsPoint(self.bounds, point)) {
        _index++;
        _points[_index] = point;
        
        if (_index == 4) {
            if (!self.isInView) {
                _points[3] = CGPointMake((_points[2].x + _points[4].x)/2.0,
                                         (_points[2].y + _points[4].y)/2.0);
                [self.bezierPath moveToPoint:_points[0]];
                
                [self.bezierPath curveToPoint:_points[3]
                                controlPoint1:_points[1]
                                controlPoint2:_points[2] ];
            }
            self.isInView = NO;
            _points[0] = _points[3];
            _points[1] = _points[4];
            _index = 1;
            [self setNeedsDisplay:YES];
        }
    } else {
        self.isInView = YES;
        
        _points[0] = _points[3];
        _points[1] = _points[4];
        _index = 1;
        [self setNeedsDisplay:YES];
    }
}

- (void)mouseUp:(NSEvent *)theEvent {
    if ([self acceptsTouchEvents]) {
        return;
    }
    
    if (_index < 4) {
        for (int i=0; i<_index; i++) {
            [self.bezierPath moveToPoint:_points[i]];
        }
        [self setNeedsDisplay:YES];
    }
}

- (void)keyDown:(NSEvent *)theEvent {
    NSString *chars = [theEvent characters];
    unichar character = [chars characterAtIndex: 0];
    
    if (character == 27) {
        if (self.isAcceptsTouch) {
            self.isAcceptsTouch = NO;
            if ([self.delegate respondsToSelector:@selector(drawViewDidFinishTouchMode:)]) {
                [self.delegate drawViewDidFinishTouchMode:self];
            }
            return;
        }
    }
    
    [super keyDown:theEvent];
}

@end