//
//  RTSpinKitView.m
//  SpinKit
//
//  Created by Ramon Torres on 1/1/14.
//  Copyright (c) 2014 Ramon Torres. All rights reserved.
//

#import "RTSpinKitView.h"

#include <tgmath.h>

static CGFloat kRTSpinKitDegToRad = 0.0174532925;

static CATransform3D RTSpinKit3DRotationWithPerspective(CGFloat perspective,
                                                        CGFloat angle,
                                                        CGFloat x,
                                                        CGFloat y,
                                                        CGFloat z)
{
    CATransform3D transform = CATransform3DIdentity;
    transform.m34 = perspective;
    return CATransform3DRotate(transform, angle, x, y, z);
}

@interface RTSpinKitView ()
@property (nonatomic, assign) RTSpinKitViewStyle style;
@property (nonatomic, assign) BOOL stopped;
@end

@implementation RTSpinKitView

-(instancetype)initWithStyle:(RTSpinKitViewStyle)style {
    return [self initWithStyle:style color:[UIColor grayColor]];
}

-(instancetype)initWithStyle:(RTSpinKitViewStyle)style color:(UIColor*)color {
    self = [super init];
    if (self) {
        _style = style;
        _color = color;

        [self sizeToFit];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(applicationWillEnterForeground)
                                                     name:UIApplicationWillEnterForegroundNotification
                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(applicationDidEnterBackground)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];
        
        if (style == RTSpinKitViewStylePlane) {
            CALayer *plane = [CALayer layer];
            plane.frame = CGRectInset(self.bounds, 2.0, 2.0);
            plane.backgroundColor = color.CGColor;
            plane.anchorPoint = CGPointMake(0.5, 0.5);
            plane.anchorPointZ = 0.5;
            [self.layer addSublayer:plane];

            CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
            anim.removedOnCompletion = NO;
            anim.repeatCount = HUGE_VALF;
            anim.duration = 1.2;
            anim.keyTimes = @[@(0.0), @(0.5), @(1.0)];

            anim.timingFunctions = @[
                [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
                [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
                [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]
            ];

            anim.values = @[
                [NSValue valueWithCATransform3D:RTSpinKit3DRotationWithPerspective(1.0/120.0, 0, 0, 0, 0)],
                [NSValue valueWithCATransform3D:RTSpinKit3DRotationWithPerspective(1.0/120.0, M_PI, 0.0, 1.0,0.0)],
                [NSValue valueWithCATransform3D:RTSpinKit3DRotationWithPerspective(1.0/120.0, M_PI, 0.0, 0.0,1.0)]
            ];

            [plane addAnimation:anim forKey:@"spinkit-anim"];
        }
        else if (style == RTSpinKitViewStyleBounce) {
            NSTimeInterval beginTime = CACurrentMediaTime();

            for (NSInteger i=0; i < 2; i+=1) {
                CALayer *circle = [CALayer layer];
                circle.frame = CGRectInset(self.bounds, 2.0, 2.0);
                circle.backgroundColor = color.CGColor;
                circle.anchorPoint = CGPointMake(0.5, 0.5);
                circle.opacity = 0.6;
                circle.cornerRadius = CGRectGetHeight(circle.bounds) * 0.5;
                circle.transform = CATransform3DMakeScale(0.0, 0.0, 0.0);

                CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
                anim.removedOnCompletion = NO;
                anim.repeatCount = HUGE_VALF;
                anim.duration = 2.0;
                anim.beginTime = beginTime - (1.0 * i);
                anim.keyTimes = @[@(0.0), @(0.5), @(1.0)];

                anim.timingFunctions = @[
                    [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
                    [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
                    [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]
                ];

                anim.values = @[
                    [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.0, 0.0, 0.0)],
                    [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 0.0)],
                    [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.0, 0.0, 0.0)]
                ];

                [self.layer addSublayer:circle];
                [circle addAnimation:anim forKey:@"spinkit-anim"];
            }
        }
        else if (style == RTSpinKitViewStyleWave) {
            NSTimeInterval beginTime = CACurrentMediaTime() + 1.2;
            CGFloat barWidth = CGRectGetWidth(self.bounds) / 5.0;
    
            for (NSInteger i=0; i < 5; i+=1) {
                CALayer *layer = [CALayer layer];
                layer.backgroundColor = color.CGColor;
                layer.frame = CGRectMake(barWidth * i, 0.0, barWidth - 3.0, CGRectGetHeight(self.bounds));
                layer.transform = CATransform3DMakeScale(1.0, 0.3, 0.0);

                CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
                anim.removedOnCompletion = NO;
                anim.beginTime = beginTime - (1.2 - (0.1 * i));
                anim.duration = 1.2;
                anim.repeatCount = HUGE_VALF;

                anim.keyTimes = @[@(0.0), @(0.2), @(0.4), @(1.0)];

                anim.timingFunctions = @[
                    [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
                    [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
                    [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
                    [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]
                ];

                anim.values = @[
                    [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 0.4, 0.0)],
                    [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 0.0)],
                    [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 0.4, 0.0)],
                    [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 0.4, 0.0)]
                ];

                [self.layer addSublayer:layer];
                [layer addAnimation:anim forKey:@"spinkit-anim"];
            }
        }
        else if (style == RTSpinKitViewStyleWanderingCubes) {
            NSTimeInterval beginTime = CACurrentMediaTime();
            CGFloat cubeSize = floor(CGRectGetWidth(self.bounds) / 3.0);
            CGFloat widthMinusCubeSize = CGRectGetWidth(self.bounds) - cubeSize;

            for (NSInteger i=0; i<2; i+=1) {
                CALayer *cube = [CALayer layer];
                cube.backgroundColor = color.CGColor;
                cube.frame = CGRectMake(0.0, 0.0, cubeSize, cubeSize);
                cube.anchorPoint = CGPointMake(0.5, 0.5);

                CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
                anim.removedOnCompletion = NO;
                anim.beginTime = beginTime - (i * 0.9);
                anim.duration = 1.8;
                anim.repeatCount = HUGE_VALF;

                anim.keyTimes = @[@(0.0), @(0.25), @(0.50), @(0.75), @(1.0)];

                anim.timingFunctions = @[
                    [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
                    [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
                    [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
                    [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
                    [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]
                ];

                CATransform3D t0 = CATransform3DIdentity;

                CATransform3D t1 = CATransform3DMakeTranslation(widthMinusCubeSize, 0.0, 0.0);
                t1 = CATransform3DRotate(t1, -90.0 * kRTSpinKitDegToRad, 0.0, 0.0, 1.0);
                t1 = CATransform3DScale(t1, 0.5, 0.5, 1.0);
                
                CATransform3D t2 = CATransform3DMakeTranslation(widthMinusCubeSize, widthMinusCubeSize, 0.0);
                t2 = CATransform3DRotate(t2, -180.0 * kRTSpinKitDegToRad, 0.0, 0.0, 1.0);
                t2 = CATransform3DScale(t2, 1.0, 1.0, 1.0);
                
                CATransform3D t3 = CATransform3DMakeTranslation(0.0, widthMinusCubeSize, 0.0);
                t3 = CATransform3DRotate(t3, -270.0 * kRTSpinKitDegToRad, 0.0, 0.0, 1.0);
                t3 = CATransform3DScale(t3, 0.5, 0.5, 1.0);
                
                CATransform3D t4 = CATransform3DMakeTranslation(0.0, 0.0, 0.0);
                t4 = CATransform3DRotate(t4, -360.0 * kRTSpinKitDegToRad, 0.0, 0.0, 1.0);
                t4 = CATransform3DScale(t4, 1.0, 1.0, 1.0);


                anim.values = @[[NSValue valueWithCATransform3D:t0],
                                [NSValue valueWithCATransform3D:t1],
                                [NSValue valueWithCATransform3D:t2],
                                [NSValue valueWithCATransform3D:t3],
                                [NSValue valueWithCATransform3D:t4]];

                [self.layer addSublayer:cube];
                [cube addAnimation:anim forKey:@"spinkit-anim"];
            }
        }
        else if (style == RTSpinKitViewStylePulse) {
            NSTimeInterval beginTime = CACurrentMediaTime();

            CALayer *circle = [CALayer layer];
            circle.frame = CGRectInset(self.bounds, 2.0, 2.0);
            circle.backgroundColor = color.CGColor;
            circle.anchorPoint = CGPointMake(0.5, 0.5);
            circle.opacity = 1.0;
            circle.cornerRadius = CGRectGetHeight(circle.bounds) * 0.5;
            circle.transform = CATransform3DMakeScale(0.0, 0.0, 0.0);

            CAKeyframeAnimation *scaleAnim = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
            scaleAnim.values = @[
                [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.0, 0.0, 0.0)],
                [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 0.0)]
            ];

            CAKeyframeAnimation *opacityAnim = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
            opacityAnim.values = @[@(1.0), @(0.0)];

            CAAnimationGroup *animGroup = [CAAnimationGroup animation];
            animGroup.removedOnCompletion = NO;
            animGroup.beginTime = beginTime;
            animGroup.repeatCount = HUGE_VALF;
            animGroup.duration = 1.0;
            animGroup.animations = @[scaleAnim, opacityAnim];
            animGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];

            [self.layer addSublayer:circle];
            [circle addAnimation:animGroup forKey:@"spinkit-anim"];
        }
    }
    return self;
}

-(void)setIsCancelAble:(BOOL)isCancelAble {
    _isCancelAble = isCancelAble;
    if (isCancelAble) {
        return;
    }
    CALayer *layer = [self.layer.sublayers firstObject];
    [layer removeFromSuperlayer];
    CGRect f = self.frame;
    f.size.width = UI_SCREEN_WIDTH;
    f.size.height = UI_SCREEN_HEITHT;
    self.frame = f;
    self.backgroundColor = [UIColor clearColor];
    
    CGFloat x = UI_SCREEN_WIDTH / 2 - 37.0 / 2;
    CGFloat y = UI_SCREEN_HEITHT / 2 - 37.0 / 2;
    UIView *loadingView = [[UIView alloc] initWithFrame:CGRectMake(x, y, 37.0, 37.0)];
    [loadingView.layer addSublayer:layer];
    
    [self addSubview:loadingView];
}

-(void)applicationWillEnterForeground {
    if (self.stopped) {
        [self pauseLayers];
    } else {
        [self resumeLayers];
    }
}

-(void)applicationDidEnterBackground {
    [self pauseLayers];
}

-(void)startAnimating {
    self.hidden = NO;
    self.stopped = NO;
    [self resumeLayers];
}

-(void)stopAnimating {
    if (self.hidesWhenStopped) {
        self.hidden = YES;
    }

    self.stopped = YES;
    [self pauseLayers];
}

-(void)pauseLayers {
    CALayer *layer = self.layer;
    if (!self.isCancelAble) {
        UIView *v = [self.subviews firstObject];
        layer = v.layer;
    }
    
    CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
    layer.speed = 0.0;
    layer.timeOffset = pausedTime;
}

-(void)resumeLayers {
    CALayer *layer = self.layer;
    if (!self.isCancelAble) {
        UIView *v = [self.subviews firstObject];
        layer = v.layer;
    }
    
    CFTimeInterval pausedTime = [layer timeOffset];
    layer.speed = 1.0;
    layer.timeOffset = 0.0;
    layer.beginTime = 0.0;
    CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
    layer.beginTime = timeSincePause;
}

-(CGSize)sizeThatFits:(CGSize)size {
    return CGSizeMake(37.0, 37.0);
}

-(void)setColor:(UIColor *)color {
    _color = color;

    for (CALayer *l in self.layer.sublayers) {
        l.backgroundColor = color.CGColor;
    }
}

-(void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

@end