//
//  UINavigationController+M13ProgressViewBar.m
//  M13ProgressView
//

#import "UINavigationController+M13ProgressViewBar.h"
#import "UIApplication+M13ProgressSuite.h"
#import <objc/runtime.h>

//Keys to set properties since one cannot define properties in a category.
static char oldTitleKey;
static char displayLinkKey;
static char animationFromKey;
static char animationToKey;
static char animationStartTimeKey;
static char progressKey;
static char progressViewKey;
static char indeterminateKey;
static char indeterminateLayerKey;
static char isShowingProgressKey;
static char primaryColorKey;
static char secondaryColorKey;
static char backgroundColorKey;
static char backgroundViewKey;

@implementation UINavigationController (M13ProgressViewBar)

#pragma mark Title

- (void)setProgressTitle:(NSString *)title
{
    //Change the title on screen.
    NSString *oldTitle = [self getOldTitle];
    if (oldTitle == nil) {
        //We haven't changed the navigation bar yet. So store the original before changing it.
        [self setOldTitle:self.visibleViewController.navigationItem.title];
    }
    
    if (title != nil) {
        self.visibleViewController.navigationItem.title = title;
    } else {
        self.visibleViewController.navigationItem.title = oldTitle;
        [self setOldTitle:nil];
    }
}

#pragma mark Progress

- (void)setProgress:(CGFloat)progress animated:(BOOL)animated
{
    CADisplayLink *displayLink = [self getDisplayLink];
    if (animated == NO) {
        if (displayLink) {
            //Kill running animations
            [displayLink invalidate];
            [self setDisplayLink:nil];
        }
        [self setProgress:progress];
    } else {
        [self setAnimationStartTime:CACurrentMediaTime()];
        [self setAnimationFromValue:[self getProgress]];
        [self setAnimationToValue:progress];
        if (!displayLink) {
            //Create and setup the display link
            [displayLink invalidate];
            displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(animateProgress:)];
            [self setDisplayLink:displayLink];
            [displayLink addToRunLoop:NSRunLoop.mainRunLoop forMode:NSRunLoopCommonModes];
        } /*else {
           //Reuse the current display link
           }*/
    }
}

- (void)animateProgress:(CADisplayLink *)displayLink
{
    dispatch_async(dispatch_get_main_queue(), ^{
        CGFloat dt = (displayLink.timestamp - [self getAnimationStartTime]) / [self getAnimationDuration];
        if (dt >= 1.0) {
            //Order is important! Otherwise concurrency will cause errors, because setProgress: will detect an animation in progress and try to stop it by itself. Once over one, set to actual progress amount. Animation is over.
            [displayLink invalidate];
            [self setDisplayLink:nil];
            [self setProgress:[self getAnimationToValue]];
            return;
        }
        
        //Set progress
        [self setProgress:[self getAnimationFromValue] + dt * ([self getAnimationToValue] - [self getAnimationFromValue])];
        
    });
}

- (void)finishProgress
{
    UIView *progressView = [self getProgressView];
    UIView *backgroundView = [self getBackgroundView];
    if (progressView && backgroundView) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [UIView animateWithDuration:0.1 animations:^{
                CGRect progressFrame = progressView.frame;
                progressFrame.size.width = self.navigationBar.frame.size.width;
                progressView.frame = progressFrame;
            } completion:^(BOOL finished) {
                [UIView animateWithDuration:0.5 animations:^{
                    progressView.alpha = 0;
                    backgroundView.alpha = 0;
                } completion:^(BOOL finished) {
                    [progressView removeFromSuperview];
                    [backgroundView removeFromSuperview];
                    backgroundView.alpha = 1;
                    progressView.alpha = 1;
                    [self setTitle:nil];
                    [self setIsShowingProgressBar:NO];
                }];
            }];
        });
    }
}

- (void)cancelProgress
{
    UIView *progressView = [self getProgressView];
    UIView *backgroundView = [self getBackgroundView];

    if (progressView && backgroundView) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [UIView animateWithDuration:0.5 animations:^{
                progressView.alpha = 0;
                backgroundView.alpha = 0;
            } completion:^(BOOL finished) {
                [progressView removeFromSuperview];
                [backgroundView removeFromSuperview];
                progressView.alpha = 1;
                backgroundView.alpha = 1;
                [self setTitle:nil];
                [self setIsShowingProgressBar:NO];
            }];
        });
    }
}

#pragma mark Orientation

- (UIInterfaceOrientation)currentDeviceOrientation
{
    UIInterfaceOrientation orientation;

    if ([UIApplication isM13AppExtension]) {
        if ([UIScreen mainScreen].bounds.size.width < [UIScreen mainScreen].bounds.size.height) {
            orientation = UIInterfaceOrientationPortrait;
        } else {
            orientation = UIInterfaceOrientationLandscapeLeft;
        }
    } else {
        orientation = [UIApplication safeM13SharedApplication].statusBarOrientation;
    }
  
    return orientation;
}

#pragma mark Drawing

- (void)showProgress
{
    UIView *progressView = [self getProgressView];
    UIView *backgroundView = [self getBackgroundView];
    
    [UIView animateWithDuration:.1 animations:^{
        progressView.alpha = 1;
        backgroundView.alpha = 1;
    }];
    
    [self setIsShowingProgressBar:YES];
}

- (void)updateProgress
{
    [self updateProgressWithInterfaceOrientation:[self currentDeviceOrientation]];
}

- (void)updateProgressWithInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    //Create the progress view if it doesn't exist
	UIView *progressView = [self getProgressView];
    if(!progressView)
	{
		progressView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 2.5)];
        progressView.clipsToBounds = YES;
        [self setProgressView:progressView];
	}
    
    if ([self getPrimaryColor]) {
        progressView.backgroundColor = [self getPrimaryColor];
    } else {
        progressView.backgroundColor = self.navigationBar.tintColor;
    }
    
    //Create background view if it doesn't exist
    UIView *backgroundView = [self getBackgroundView];
    if (!backgroundView)
    {
        backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 2.5)];
        backgroundView.clipsToBounds = YES;
        [self setBackgroundView:backgroundView];
    }
    
    if ([self getBackgroundColor]) {
        backgroundView.backgroundColor = [self getBackgroundColor];
    } else {
        backgroundView.backgroundColor = [UIColor clearColor];
    }
    
    //Calculate the frame of the navigation bar, based off the orientation.
    UIView *topView = self.topViewController.view;
    CGSize screenSize;
    if (topView) {
        screenSize = topView.bounds.size;
    } else {
        screenSize = [UIScreen mainScreen].bounds.size;
    }
    CGFloat width = 0.0;
    CGFloat height = 0.0;
    //Calculate the width of the screen
    if (UIInterfaceOrientationIsLandscape(interfaceOrientation)) {
        //Use the maximum value
        width = MAX(screenSize.width, screenSize.height);
        if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
            height = 32.0; //Hate hardcoding values, but autolayout doesn't work, and cant retreive the new height until after the animation completes.
        } else {
            height = 44.0; //Hate hardcoding values, but autolayout doesn't work, and cant retreive the new height until after the animation completes.
        }
    } else {
        //Use the minimum value
        width = MIN(screenSize.width, screenSize.height);
        height = 44.0; //Hate hardcoding values, but autolayout doesn't work, and cant retreive the new height until after the animation completes.
    }
    
    //Check if the progress view is in its superview and if we are showing the bar.
    if (progressView.superview == nil && [self isShowingProgressBar]) {
        [self.navigationBar addSubview:backgroundView];
        [self.navigationBar addSubview:progressView];
    }
    
    //Layout
    if (![self getIndeterminate]) {
        //Calculate the width of the progress view;
        float progressWidth = (float)width * (float)[self getProgress];
        //Set the frame of the progress view
        progressView.frame = CGRectMake(0, height - 2.5, progressWidth, 2.5);
    } else {
        //Calculate the width of the progress view
        progressView.frame = CGRectMake(0, height - 2.5, width, 2.5);
    }
    backgroundView.frame = CGRectMake(0, height - 2.5, width, 2.5);
}

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    [self updateProgressWithInterfaceOrientation:toInterfaceOrientation];
    [self drawIndeterminateWithInterfaceOrientation:toInterfaceOrientation];
}

- (void)drawIndeterminate
{
    [self drawIndeterminateWithInterfaceOrientation:[self currentDeviceOrientation]];
}

- (void)drawIndeterminateWithInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    if ([self getIndeterminate]) {
        //Get the indeterminate layer
        CALayer *indeterminateLayer = [self getIndeterminateLayer];
        if (!indeterminateLayer) {
            //Create if needed
            indeterminateLayer = [CALayer layer];
            [self setIndeterminateLayer:indeterminateLayer];
        }
        
        //Calculate the frame of the navigation bar, based off the orientation.
        CGSize screenSize = [UIScreen mainScreen].bounds.size;
        CGFloat width = 0.0;
        //Calculate the width of the screen
        if (UIInterfaceOrientationIsLandscape(interfaceOrientation)) {
            //Use the maximum value
            width = MAX(screenSize.width, screenSize.height);
        } else {
            //Use the minimum value
            width = MIN(screenSize.width, screenSize.height);
        }
        
        //Create the pattern image
        CGFloat stripeWidth = 2.5;
        //Start the image context
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(stripeWidth * 4.0, stripeWidth * 4.0), NO, [UIScreen mainScreen].scale);
        //Fill the background
        if ([self getPrimaryColor]) {
            [[self getPrimaryColor] setFill];
        } else {
        [self.navigationBar.tintColor setFill];
        }
        UIBezierPath *fillPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, stripeWidth * 4.0, stripeWidth * 4.0)];
        [fillPath fill];
        //Draw the stripes
        //Set the stripe color
        if ([self getSecondaryColor]) {
            [[self getSecondaryColor] setFill];
        } else {
            CGFloat red;
            CGFloat green;
            CGFloat blue;
            CGFloat alpha;
            [self.navigationBar.barTintColor getRed:&red green:&green blue:&blue alpha:&alpha];
            //System set the tint color to a close to, but not non-zero value for each component. 
            if (alpha > .05) {
                [self.navigationBar.barTintColor setFill];
            } else {
                [[UIColor whiteColor] setFill];
            }
            
        }
        
        for (int i = 0; i < 4; i++) {
            //Create the four inital points of the fill shape
            CGPoint bottomLeft = CGPointMake(-(stripeWidth * 4.0), stripeWidth * 4.0);
            CGPoint topLeft = CGPointMake(0, 0);
            CGPoint topRight = CGPointMake(stripeWidth, 0);
            CGPoint bottomRight = CGPointMake(-(stripeWidth * 4.0) + stripeWidth, stripeWidth * 4.0);
            //Shift all four points as needed to draw all four stripes
            bottomLeft.x += i * (2 * stripeWidth);
            topLeft.x += i * (2 * stripeWidth);
            topRight.x += i * (2 * stripeWidth);
            bottomRight.x += i * (2 * stripeWidth);
            //Create the fill path
            UIBezierPath *path = [UIBezierPath bezierPath];
            [path moveToPoint:bottomLeft];
            [path addLineToPoint:topLeft];
            [path addLineToPoint:topRight];
            [path addLineToPoint:bottomRight];
            [path closePath];
            [path fill];
        }
        //Retreive the image
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        //Set the background of the progress layer
        indeterminateLayer.backgroundColor = [UIColor colorWithPatternImage:image].CGColor;
        
        //remove any indeterminate layer animations
        [indeterminateLayer removeAllAnimations];
        //Set the indeterminate layer frame and add to the sub view
        indeterminateLayer.frame = CGRectMake(0, 0, width + (4 * 2.5), 2.5);
        UIView *progressView = [self getProgressView];
        [progressView.layer addSublayer:indeterminateLayer];
        //Add the animation
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
        animation.duration = .1;
        animation.repeatCount = HUGE_VALF;
        animation.removedOnCompletion = YES;
        animation.fromValue = [NSValue valueWithCGPoint:CGPointMake(- (2 * 2.5) + (width / 2.0), 2.5 / 2.0)];
        animation.toValue = [NSValue valueWithCGPoint:CGPointMake(0 + (width / 2.0), 2.5 / 2.0)];
        [indeterminateLayer addAnimation:animation forKey:@"position"];
    } else {
        CALayer *indeterminateLayer = [self getIndeterminateLayer];
        [indeterminateLayer removeAllAnimations];
        [indeterminateLayer removeFromSuperlayer];
    }
}

#pragma mark properties

- (void)setOldTitle:(NSString *)oldTitle
{
    objc_setAssociatedObject(self, &oldTitleKey, self.visibleViewController.navigationItem.title, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)getOldTitle
{
    return objc_getAssociatedObject(self, &oldTitleKey);
}

- (void)setDisplayLink:(CADisplayLink *)displayLink
{
    objc_setAssociatedObject(self, &displayLinkKey, displayLink, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (CADisplayLink *)getDisplayLink
{
    return objc_getAssociatedObject(self, &displayLinkKey);
}

- (void)setAnimationFromValue:(CGFloat)animationFromValue
{
    objc_setAssociatedObject(self, &animationFromKey, [NSNumber numberWithFloat:(float)animationFromValue], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (CGFloat)getAnimationFromValue
{
    NSNumber *number = objc_getAssociatedObject(self, &animationFromKey);
    return number.floatValue;
}

- (void)setAnimationToValue:(CGFloat)animationToValue
{
    objc_setAssociatedObject(self, &animationToKey, [NSNumber numberWithFloat:(float)animationToValue], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (CGFloat)getAnimationToValue
{
    NSNumber *number = objc_getAssociatedObject(self, &animationToKey);
    return number.floatValue;
}

- (void)setAnimationStartTime:(NSTimeInterval)animationStartTime
{
    objc_setAssociatedObject(self, &animationStartTimeKey, [NSNumber numberWithFloat:(float)animationStartTime], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSTimeInterval)getAnimationStartTime
{
    NSNumber *number =  objc_getAssociatedObject(self, &animationStartTimeKey);
    return number.floatValue;
}

- (void)setProgress:(CGFloat)progress
{
    if (progress > 1.0) {
        progress = 1.0;
    } else if (progress < 0.0) {
        progress = 0.0;
    }
    objc_setAssociatedObject(self, &progressKey, [NSNumber numberWithFloat:(float)progress], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //Draw the update
    if ([NSThread isMainThread]) {
        [self updateProgress];
    } else {
        //Sometimes UINavigationController runs in a background thread. And drawing is not thread safe.
        dispatch_async(dispatch_get_main_queue(), ^{
            [self updateProgress];
        });
    }
}

- (void)setIsShowingProgressBar:(BOOL)isShowingProgressBar
{
    objc_setAssociatedObject(self, &isShowingProgressKey, [NSNumber numberWithBool:isShowingProgressBar], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (CGFloat)getProgress
{
    NSNumber *number =  objc_getAssociatedObject(self, &progressKey);
    return number.floatValue;
}

- (CGFloat)getAnimationDuration
{
    return .3;
}

- (void)setProgressView:(UIView *)view
{
    objc_setAssociatedObject(self, &progressViewKey, view, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIView *)getProgressView
{
    return objc_getAssociatedObject(self, &progressViewKey);
}

- (void)setBackgroundView:(UIView *)view
{
    objc_setAssociatedObject(self, &backgroundViewKey, view, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIView *)getBackgroundView
{
    return objc_getAssociatedObject(self, &backgroundViewKey);
}


- (void)setIndeterminate:(BOOL)indeterminate
{
    objc_setAssociatedObject(self, &indeterminateKey, [NSNumber numberWithBool:indeterminate], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    [self updateProgress];
    [self drawIndeterminate];
}

- (BOOL)getIndeterminate
{
    NSNumber *number = objc_getAssociatedObject(self, &indeterminateKey);
    return number.boolValue;
}

- (void)setIndeterminateLayer:(CALayer *)indeterminateLayer
{
    objc_setAssociatedObject(self, &indeterminateLayerKey, indeterminateLayer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (CALayer *)getIndeterminateLayer
{
    return objc_getAssociatedObject(self, &indeterminateLayerKey);
}

- (BOOL)isShowingProgressBar
{
    return [objc_getAssociatedObject(self, &isShowingProgressKey) boolValue];
}

- (void)setPrimaryColor:(UIColor *)primaryColor
{
    objc_setAssociatedObject(self, &primaryColorKey, primaryColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    [self getProgressView].backgroundColor = primaryColor;
    [self setIndeterminate:[self getIndeterminate]];
}

- (UIColor *)getPrimaryColor
{
    return objc_getAssociatedObject(self, &primaryColorKey);
}

- (void)setSecondaryColor:(UIColor *)secondaryColor
{
    objc_setAssociatedObject(self, &secondaryColorKey, secondaryColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    [self setIndeterminate:[self getIndeterminate]];
}

- (UIColor *)getSecondaryColor
{
    return objc_getAssociatedObject(self, &secondaryColorKey);
}

- (void)setBackgroundColor:(UIColor *)backgroundColor
{
    objc_setAssociatedObject(self, &backgroundColorKey, backgroundColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    [self setIndeterminate:[self getIndeterminate]];
}

- (UIColor *)getBackgroundColor
{
    return objc_getAssociatedObject(self, &backgroundColorKey);
}

@end