#import "TGPhotoCropView.h"

#import "TGPhotoEditorAnimation.h"
#import "TGPhotoEditorInterfaceAssets.h"
#import "UIControl+HitTestEdgeInsets.h"
#import "TGPhotoEditorUtils.h"
#import "TGImageUtils.h"
#import "TGTimer.h"
#import "TGBlurEffect.h"

#import "TGPhotoCropScrollView.h"
#import "TGPhotoCropAreaView.h"
#import "TGPhotoCropRotationView.h"

const CGFloat TGPhotoCropViewOverscreenSize = 1000;

@interface TGPhotoCropView () <UIScrollViewDelegate>
{
    CGSize _originalSize;

    TGTimer *_confirmTimer;
    bool _animatingConfirm;
    bool _cropAreaChanged;
    
    UIView *_overlayWrapperView;
    UIView *_topOverlayView;
    UIView *_leftOverlayView;
    UIView *_rightOverlayView;
    UIView *_bottomOverlayView;
    
    UIControl *_areaWrapperView;
    TGPhotoCropAreaView *_areaView;
    CGRect _previousAreaFrame;
    
    TGPhotoCropScrollView *_scrollView;
    UIView *_contentWrapperView;
    UIVisualEffectView *_blurView;
    UIImageView *_imageView;
    UIView *_snapshotView;
    CGSize _snapshotSize;
    UIImageView *_paintingImageView;
    
    UIImage *_paintingImage;
    
    bool _hasArbitraryRotation;
    bool _animatingRotation;
    UIView *_rotationSnapshotView;
    TGPhotoCropRotationView *_rotationView;
    
    UIInterfaceOrientation _previousInterfaceOrientation;
}
@end

@implementation TGPhotoCropView


- (instancetype)initWithOriginalSize:(CGSize)originalSize hasArbitraryRotation:(bool)hasArbitraryRotation
{
    self = [self initWithFrame:CGRectZero];
    if (self != nil)
    {
        _hasArbitraryRotation = hasArbitraryRotation;
        
        _originalSize = originalSize;
        _cropRect = CGRectMake(0.0f, 0.0f, originalSize.width, originalSize.height);
        _cropOrientation = UIImageOrientationUp;
        
        __weak TGPhotoCropView *weakSelf = self;
        
        self.hitTestEdgeInsets = UIEdgeInsetsMake(-16, -100, -100, -100);
        
        _areaWrapperView = [[UIControl alloc] initWithFrame:CGRectZero];
        _areaWrapperView.hitTestEdgeInsets = UIEdgeInsetsMake(-16, -100, -100, -100);
        [self addSubview:_areaWrapperView];
        
        _scrollView = [[TGPhotoCropScrollView alloc] initWithFrame:CGRectZero];
        _scrollView.shouldBeginChanging = ^bool
        {
            __strong TGPhotoCropView *strongSelf = weakSelf;
            if (strongSelf == nil)
                return true;
            
            return !strongSelf->_animatingConfirm && !strongSelf->_areaView.isTracking && !strongSelf->_rotationView.isTracking;
        };
        _scrollView.didBeginChanging = ^
        {
            __strong TGPhotoCropView *strongSelf = weakSelf;
            if (strongSelf == nil)
                return;
            
            [strongSelf resetBackdropViewsAnimated:true];
            [strongSelf cancelConfirmCountdown];
            [strongSelf->_areaView setGridMode:TGPhotoCropViewGridModeMajor animated:true];
            
            [strongSelf->_scrollView resetRotationStartValues];
            
            if (strongSelf.interactionBegan != nil)
                strongSelf.interactionBegan();
        };
        _scrollView.didEndChanging = ^
        {
            __strong TGPhotoCropView *strongSelf = weakSelf;
            if (strongSelf == nil)
                return;
            
            [strongSelf startConfirmCountdown];
            
            if (strongSelf.croppingChanged != nil)
                strongSelf.croppingChanged();
        };
        [_areaWrapperView addSubview:_scrollView];
        
        if (iosMajorVersion() >= 9)
        {
            _blurView = [[UIVisualEffectView alloc] initWithEffect:[TGBlurEffect cropBlurEffect]];
            _blurView.alpha = 0.0f;
            _blurView.userInteractionEnabled = false;
            [_areaWrapperView addSubview:_blurView];
        }
        
        _overlayWrapperView = [[UIView alloc] initWithFrame:CGRectZero];
        _overlayWrapperView.userInteractionEnabled = false;
        [_areaWrapperView addSubview:_overlayWrapperView];
        
        _topOverlayView = [[UIView alloc] initWithFrame:CGRectZero];
        _topOverlayView.backgroundColor = [TGPhotoEditorInterfaceAssets cropTransparentOverlayColor];
        [_overlayWrapperView addSubview:_topOverlayView];
        
        _leftOverlayView = [[UIView alloc] initWithFrame:CGRectZero];
        _leftOverlayView.backgroundColor = [TGPhotoEditorInterfaceAssets cropTransparentOverlayColor];
        [_overlayWrapperView addSubview:_leftOverlayView];
        
        _rightOverlayView = [[UIView alloc] initWithFrame:CGRectZero];
        _rightOverlayView.backgroundColor = [TGPhotoEditorInterfaceAssets cropTransparentOverlayColor];
        [_overlayWrapperView addSubview:_rightOverlayView];
        
        _bottomOverlayView = [[UIView alloc] initWithFrame:CGRectZero];
        _bottomOverlayView.backgroundColor = [TGPhotoEditorInterfaceAssets cropTransparentOverlayColor];
        [_overlayWrapperView addSubview:_bottomOverlayView];
        
        _areaView = [[TGPhotoCropAreaView alloc] initWithFrame:self.bounds];
        _areaView.shouldBeginEditing = ^bool
        {
            __strong TGPhotoCropView *strongSelf = weakSelf;
            if (strongSelf == nil)
                return true;
            
            return !strongSelf->_animatingConfirm && !strongSelf->_scrollView.isTracking && !strongSelf->_rotationView.isTracking;
        };
        _areaView.didBeginEditing = ^
        {
            __strong TGPhotoCropView *strongSelf = weakSelf;
            if (strongSelf == nil)
                return;
            
            strongSelf->_previousAreaFrame = strongSelf->_areaView.frame;
            
            [strongSelf resetBackdropViewsAnimated:false];
            [strongSelf cancelConfirmCountdown];
            [strongSelf->_areaView setGridMode:TGPhotoCropViewGridModeMajor animated:true];
            [strongSelf setIntefaceHidden:true animated:true];

            [strongSelf->_scrollView resetRotationStartValues];
            
            if (strongSelf.interactionBegan != nil)
                strongSelf.interactionBegan();
        };
        _areaView.didEndEditing = ^
        {
            __strong TGPhotoCropView *strongSelf = weakSelf;
            if (strongSelf == nil)
                return;
            
            [strongSelf startConfirmCountdown];
        };
        _areaView.areaChanged = ^
        {
            __strong TGPhotoCropView *strongSelf = weakSelf;
            if (strongSelf == nil)
                return;
            
            [strongSelf handleCropAreaChanged];
        };
        [_areaWrapperView addSubview:_areaView];
        
        if (_hasArbitraryRotation)
        {
            _rotationView = [[TGPhotoCropRotationView alloc] initWithFrame:CGRectZero];
            _rotationView.hitTestEdgeInsets = UIEdgeInsetsMake(10, 10, 0, 10);
            [self addSubview:_rotationView];
            
            _rotationView.shouldBeginChanging = ^bool
            {
                __strong TGPhotoCropView *strongSelf = weakSelf;
                if (strongSelf == nil)
                    return true;
                
                return !strongSelf->_animatingConfirm && !strongSelf->_scrollView.isTracking && !strongSelf->_areaView.isTracking;
            };
            _rotationView.didBeginChanging = ^
            {
                __strong TGPhotoCropView *strongSelf = weakSelf;
                if (strongSelf == nil)
                    return;
                
                [strongSelf resetBackdropViewsAnimated:true];
                [strongSelf cancelConfirmCountdown];
                [strongSelf->_areaView setGridMode:TGPhotoCropViewGridModeMinor animated:true];
                
                [strongSelf->_scrollView storeRotationStartValues];
                
                if (strongSelf.interactionBegan != nil)
                    strongSelf.interactionBegan();
            };
            
            _rotationView.didEndChanging = ^
            {
                __strong TGPhotoCropView *strongSelf = weakSelf;
                if (strongSelf == nil)
                    return;
                
                [strongSelf startConfirmCountdown];
                [strongSelf->_areaView setGridMode:TGPhotoCropViewGridModeNone animated:true];
                
                if (strongSelf.croppingChanged != nil)
                    strongSelf.croppingChanged();
                
                if (strongSelf.interactionEnded != nil)
                    strongSelf.interactionEnded();
            };
            
            _rotationView.angleChanged = ^(CGFloat angle, bool resetting)
            {
                __strong TGPhotoCropView *strongSelf = weakSelf;
                if (strongSelf == nil)
                    return;
                
                [strongSelf setRotation:angle resetting:resetting];
            };
        }
    }
    return self;
}

- (void)dealloc
{
    if (_confirmTimer != nil)
    {
        [_confirmTimer invalidate];
        _confirmTimer = nil;
    }
}

#pragma mark - Transition

- (void)animateTransitionIn
{
    _scrollView.hidden = true;
    _overlayWrapperView.alpha = 0.0f;
    _areaView.alpha = 0.0f;
    _rotationView.alpha = 0.0f;
}

- (void)animateTransitionOut
{
    if (_confirmTimer != nil)
    {
        [_confirmTimer invalidate];
        _confirmTimer = nil;
    }
    
    _scrollView.hidden = true;
    _blurView.hidden = true;
    [UIView animateWithDuration:0.1f delay:0.0f options:UIViewAnimationOptionCurveEaseOut animations:^
    {
        _areaView.alpha = 0.0f;
        _rotationView.alpha = 0.0f;
    } completion:nil];
    
    _overlayWrapperView.alpha = 0.0f;
}

- (void)transitionInFinishedAnimated:(bool)animated completion:(void (^)(void))completion
{
    _overlayWrapperView.alpha = 1.0f;
    
    _scrollView.hidden = false;

    if (animated)
        _scrollView.alpha = 0.0f;
    
    [UIView animateWithDuration:0.35f delay:0.0f options:UIViewAnimationOptionCurveEaseInOut animations:^
    {
        if (animated)
            _scrollView.alpha = 1.0f;
        _areaView.alpha = 1.0f;
        _rotationView.alpha = 1.0f;
    } completion:^(__unused BOOL finished)
    {
        if (completion != nil)
            completion();
    }];
    
    [self showBackdropViewAnimated:false];
}

- (bool)isAnimating
{
    return _animatingConfirm || _animatingRotation || _scrollView.animating;
}

- (bool)isAnimatingRotation
{
    return _animatingRotation;
}

#pragma mark - Setup

- (void)setup
{
    _contentWrapperView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, _originalSize.width, _originalSize.height)];
    
    _scrollView.contentSize = _originalSize;
    [_scrollView setContentView:_contentWrapperView];
    
    _imageView = [[UIImageView alloc] initWithFrame:_contentWrapperView.bounds];
    _imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    _imageView.contentMode = UIViewContentModeScaleAspectFit;
    [_contentWrapperView addSubview:_imageView];
    
    _scrollView.imageView = _imageView;
    
    [self _layoutAreaViewAnimated:false completion:nil];
    [self _zoomToCropRectWithFrame:_scrollView.bounds animated:false completion:nil];
    
    if (_snapshotView != nil)
    {
        CGAffineTransform transform = _contentWrapperView.transform;
        _contentWrapperView.transform = CGAffineTransformIdentity;
        
        CGRect rotatedRect = [_contentWrapperView convertRect:_scrollView.bounds fromView:_scrollView];
        CGRect frame = [_scrollView zoomedRect];

        _snapshotView.frame = frame;
        _snapshotView.center = CGPointMake(CGRectGetMidX(rotatedRect), CGRectGetMidY(rotatedRect));
        _snapshotView.transform = CGAffineTransformRotate(_snapshotView.transform, -self.rotation);
        [_contentWrapperView addSubview:_snapshotView];
        
        _contentWrapperView.transform = transform;
    }
    
    if (_paintingImage != nil)
    {
        _paintingImageView = [[UIImageView alloc] initWithFrame:_imageView.frame];
        _paintingImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        _paintingImageView.image = _paintingImage;
        [_contentWrapperView addSubview:_paintingImageView];
        
        [_scrollView setPaintingImage:_paintingImage];
    }
}

- (void)setImage:(UIImage *)image
{
    _image = image;
    [_imageView setImage:image];
    
    [_snapshotView removeFromSuperview];
    _snapshotView = nil;
}

- (void)setSnapshotImage:(UIImage *)snapshotImage
{
    [_snapshotView removeFromSuperview];
    
    _snapshotSize = snapshotImage.size;
    
    UIImageView *imageSnapshotView = [[UIImageView alloc] initWithImage:snapshotImage];
    _snapshotView = imageSnapshotView;
}

- (void)setSnapshotView:(UIView *)snapshotView
{
    [_snapshotView removeFromSuperview];
    _snapshotView = snapshotView;
}

- (void)setPaintingImage:(UIImage *)paintingImage
{
    _paintingImage = paintingImage;
    
    if (_paintingImageView != nil)
    {
        _paintingImageView.hidden = (paintingImage == nil);
        _paintingImageView.image = paintingImage;
    }
}

#pragma mark - Crop Area

- (void)handleCropAreaChanged
{
    _cropAreaChanged = true;
    
    _areaView.frame = [self _cappedAreaViewRectWithRect:_areaView.frame];
    
    CGPoint translationOffset = CGPointMake(_previousAreaFrame.origin.x - _areaView.frame.origin.x,
                                            _previousAreaFrame.origin.y - _areaView.frame.origin.y);
    
    [_scrollView translateContentViewWithOffset:translationOffset];
    _previousAreaFrame = _areaView.frame;
    
    _scrollView.frame = _areaView.frame;
    [_scrollView fitContentInsideBoundsAllowScale:true animated:false completion:nil];
    
    [self _layoutRotationView];
    
    [self _layoutOverlayViewsWithFrame:_areaView.frame animated:false];
}

- (CGRect)_cappedAreaViewRectWithRect:(CGRect)cropRect
{
    CGRect cappedRect = [self convertRect:cropRect fromView:_areaWrapperView];
    CGFloat aspectRatio = _lockedAspectRatio;
    if (aspectRatio > 0 && (_cropOrientation == UIImageOrientationLeft || _cropOrientation == UIImageOrientationRight))
        aspectRatio = 1.0f / aspectRatio;
    
    if (CGRectGetMaxX(cappedRect) > self.frame.size.width)
    {
        cappedRect.origin.x = MIN(self.frame.size.width - TGPhotoCropCornerControlSize.width, cappedRect.origin.x);
        cappedRect.size.width = MAX(TGPhotoCropCornerControlSize.width, self.frame.size.width - cappedRect.origin.x);
        if (aspectRatio > 0)
            cappedRect.size.height = cappedRect.size.width * aspectRatio;
    }
    else if (CGRectGetMinX(cappedRect) < 0)
    {
        cappedRect.size.width = CGRectGetMaxX(cappedRect);
        if (aspectRatio > 0)
            cappedRect.size.height = cappedRect.size.width * aspectRatio;
        cappedRect.origin.x = 0;
    }
    
    if (CGRectGetMaxY(cappedRect) > self.frame.size.height)
    {
        cappedRect.origin.y = MIN(self.frame.size.height - TGPhotoCropCornerControlSize.height, cappedRect.origin.y);
        cappedRect.size.height = MAX(TGPhotoCropCornerControlSize.height, self.frame.size.height - cappedRect.origin.y);
        if (aspectRatio > 0)
            cappedRect.size.width = cappedRect.size.height / aspectRatio;
    }
    else if (CGRectGetMinY(cappedRect) < 0)
    {
        cappedRect.size.height = CGRectGetMaxY(cappedRect);
        if (aspectRatio > 0)
            cappedRect.size.width = cappedRect.size.height / aspectRatio;
        cappedRect.origin.y = 0;
    }
    
    return [self convertRect:cappedRect toView:_areaWrapperView];
}

#pragma mark - Backdrop

- (void)showBackdropViewAnimated:(bool)animated
{
    if (iosMajorVersion() < 9 || _imageView.image == nil)
        return;
    
    UIView *superview = self.superview.superview;
    _blurView.frame = [superview convertRect:superview.bounds toView:_blurView.superview];
    
    UIView *snapshotView = [_scrollView setSnapshotViewEnabled:true];
    snapshotView.frame = _scrollView.frame;
    [_areaWrapperView insertSubview:snapshotView aboveSubview:_blurView];
    
    if (animated)
    {
        [UIView animateWithDuration:0.16f delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^
        {
            _blurView.alpha = 1.0f;
        } completion:nil];
    }
    else
    {
        _blurView.alpha = 1.0f;
    }
}

- (void)resetBackdropViewsAnimated:(bool)animated
{
    if (iosMajorVersion() < 9)
        return;
    
    if (animated)
    {
        if (_blurView.alpha == 0)
        {
            [_scrollView setSnapshotViewEnabled:false];
            return;
        }
        
        [UIView animateWithDuration:0.16f delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^
        {
            _blurView.alpha = 0.0f;
        } completion:^(BOOL finished)
        {
            if (finished)
                [_scrollView setSnapshotViewEnabled:false];
        }];
    }
    else
    {
        [_scrollView setSnapshotViewEnabled:false];
        _blurView.alpha = 0.0f;
    }
}

#pragma mark - Area Confirmation

- (void)startConfirmCountdown
{
    if (_areaView.isTracking)
        return;
    
    __weak TGPhotoCropView *weakSelf = self;
    
    _confirmTimer = [[TGTimer alloc] initWithTimeout:1.0f repeat:false completion:^
    {
        __strong TGPhotoCropView *strongSelf = weakSelf;
        if (strongSelf == nil)
            return;
        
        [strongSelf performConfirmAnimated:true];
    } queue:dispatch_get_main_queue()];
    [_confirmTimer start];
}

- (void)cancelConfirmCountdown
{
    [_confirmTimer invalidate];
    _confirmTimer = nil;
}

- (void)performConfirmAnimated:(bool)animated
{
    [self performConfirmAnimated:animated updateInterface:true];
}

- (void)performConfirmAnimated:(bool)animated updateInterface:(bool)updateInterface
{
    if (_areaView.isTracking || _animatingConfirm)
        return;
    
    [_confirmTimer invalidate];
    _confirmTimer = nil;
    
    _cropRect = [_scrollView zoomedRect];
    CGSize minimumSizes = CGSizeMake(_originalSize.width / _scrollView.maximumZoomScale,
                                     _originalSize.height / _scrollView.maximumZoomScale);
    
    CGRect constrainedCropRect = _cropRect;
    if (_cropRect.size.width < minimumSizes.width && _cropRect.size.height < minimumSizes.height)
    {
        if (_cropRect.size.width > _cropRect.size.height)
        {
            constrainedCropRect.size.width = minimumSizes.width;
            constrainedCropRect.size.height = _cropRect.size.height * constrainedCropRect.size.width / _cropRect.size.width;
        }
        else
        {
            constrainedCropRect.size.height = minimumSizes.height;
            constrainedCropRect.size.width = _cropRect.size.width * constrainedCropRect.size.height / _cropRect.size.height;
        }
        
        CGSize rotatedContentSize = TGRotatedContentSize(_scrollView.contentSize, _scrollView.contentRotation);
        if (CGRectGetMaxX(constrainedCropRect) > rotatedContentSize.width)
            constrainedCropRect.origin.x = rotatedContentSize.width - constrainedCropRect.size.width;

        if (CGRectGetMaxY(constrainedCropRect) > rotatedContentSize.height)
            constrainedCropRect.origin.y = rotatedContentSize.height - constrainedCropRect.size.height;
    }
    _cropRect = constrainedCropRect;
    
    [_areaView setGridMode:TGPhotoCropViewGridModeNone animated:animated];
    
    if (updateInterface)
    {
        [self setIntefaceHidden:false animated:animated];
    }
    else
    {
        if (_rotationView.hidden)
            [self setCropAreaHidden:true animated:false];
    }
    
    if (!_cropAreaChanged)
    {
        [self showBackdropViewAnimated:animated];
        
        if (self.croppingChanged != nil)
            self.croppingChanged();
        
        if (self.interactionEnded != nil)
            self.interactionEnded();
        
        return;
    }
    
    _cropAreaChanged = false;
    
    [self evenlyFillAreaViewAnimated:animated reset:false completion:^
    {
        if (self.interactionEnded != nil)
            self.interactionEnded();
    }];
    
    if (self.croppingChanged != nil)
        self.croppingChanged();
}

- (void)evenlyFillAreaViewAnimated:(bool)animated reset:(bool)reset completion:(void (^)(void))completion
{
    if (animated)
    {
        [self _layoutRotationView];
        
        _animatingConfirm = true;
        
        NSMutableSet *animations = [NSMutableSet set];
        void (^onAnimationCompletion)(id) = ^(id object)
        {
            [animations removeObject:object];
            
            if (animations.count == 0)
            {
                _animatingConfirm = false;
                
                [_scrollView fitContentInsideBoundsAllowScale:false animated:true completion:^
                {
                    [self showBackdropViewAnimated:true];
                    
                    if (completion != nil)
                        completion();
                }];
            }
        };
        
        [animations addObject:@1];
        CGRect frame = [self _layoutAreaViewAnimated:true completion:^
        {
            onAnimationCompletion(@1);
        }];
        
        if (reset)
        {
            [animations addObject:@2];
            [_scrollView resetAnimatedWithFrame:frame completion:^
            {
                onAnimationCompletion(@2);
            }];
        }
        else
        {
            [animations addObject:@2];
            [self _zoomToCropRectWithFrame:frame animated:true completion:^
            {
                onAnimationCompletion(@2);
            }];
        }
    }
    else
    {
        [self _layoutAreaViewAnimated:false completion:nil];
        if (reset)
            [_scrollView reset];
        else
            [self _zoomToCropRectWithFrame:_scrollView.bounds animated:false completion:nil];
        [self showBackdropViewAnimated:true];

        if (completion != nil)
            completion();
    }
}

- (void)setCropAreaHidden:(bool)hidden animated:(bool)animated
{
    if (animated)
    {
        _areaView.hidden = false;
        [UIView animateWithDuration:0.2f delay:0.0f options:UIViewAnimationOptionCurveEaseInOut animations:^
        {
            _areaView.alpha = hidden ? 0.0f : 1.0f;
        } completion:^(BOOL finished)
        {
            if (finished)
                _areaView.hidden = hidden;
        }];
    }
    else
    {
        _areaView.hidden = hidden;
        _areaView.alpha = hidden ? 0.0f : 1.0f;
    }
}

- (void)setIntefaceHidden:(bool)hidden animated:(bool)animated
{
    if (animated)
    {
        _rotationView.hidden = false;
        [UIView animateWithDuration:0.2f delay:0.0f options:UIViewAnimationOptionCurveEaseInOut animations:^
        {
            _rotationView.alpha = hidden ? 0.0f : 1.0f;
        } completion:^(BOOL finished)
        {
            if (finished)
                _rotationView.hidden = hidden;
        }];
    }
    else
    {
        _rotationView.hidden = hidden;
        _rotationView.alpha = hidden ? 0.0f : 1.0f;
    }
}

#pragma mark - Cropping

- (UIView *)cropSnapshotView
{
    UIView *snapshotView = [_scrollView snapshotViewAfterScreenUpdates:false];
    snapshotView.transform = CGAffineTransformMakeRotation(TGRotationForOrientation(_cropOrientation));
    return snapshotView;
}

- (CGRect)cropRectFrameForView:(UIView *)view
{
    return [_areaWrapperView convertRect:_scrollView.frame toView:view];
}

- (UIImage *)croppedImageWithMaxSize:(CGSize)maxSize
{
    return TGPhotoEditorVideoCrop(_imageView.image, _paintingImage, self.cropOrientation, self.rotation, self.cropRect, self.mirrored, maxSize, _originalSize, true, true);
}

#pragma mark - Rotation

- (bool)hasArbitraryRotation
{
    return (_rotationView != nil);
}

- (void)setRotation:(CGFloat)rotation
{
    [_rotationView setAngle:rotation];
    [self setRotation:rotation resetting:false];
}

- (void)setRotation:(CGFloat)rotation resetting:(bool)resetting
{
    _rotation = rotation;
    [_scrollView setContentRotation:rotation maximize:true resetting:resetting];
}

- (void)setRotation:(CGFloat)rotation animated:(bool)animated
{
    [_rotationView setAngle:rotation animated:animated];
}

- (void)setMirrored:(bool)mirrored
{
    _mirrored = mirrored;
    [_scrollView setContentMirrored:mirrored];
}

- (void)rotate90DegreesCCWAnimated:(bool)animated
{
    if (_animatingConfirm || _scrollView.animating)
        return;
    
    [self cancelConfirmCountdown];
    
    UIView *snapshotView = nil;
    
    if (_rotationSnapshotView != nil)
    {
        snapshotView = _rotationSnapshotView;
    }
    else
    {
        if (animated)
        {
            snapshotView = [self cropSnapshotView];
            snapshotView.transform = _areaWrapperView.transform;
            snapshotView.frame = [self convertRect:_scrollView.frame fromView:_areaWrapperView];
            [self addSubview:snapshotView];
            
            _rotationSnapshotView = snapshotView;
        }
        
        _cropRect = [_scrollView zoomedRect];
    }

    _scrollView.hidden = true;
    
    [self resetBackdropViewsAnimated:false];
    
    [self setIntefaceHidden:true animated:false];
    [self setCropAreaHidden:true animated:false];
    
    [_scrollView resetRotationStartValues];
    
    self.cropOrientation = TGNextCCWOrientationForOrientation(self.cropOrientation);
    
    CGSize areaSize = [self _areaSizeForCropRect:_cropRect orientation:_cropOrientation];
    CGRect areaBounds = CGRectMake(0, 0, areaSize.width, areaSize.height);
    if (self.cropOrientation == UIImageOrientationLeft || self.cropOrientation == UIImageOrientationRight)
        areaBounds = CGRectMake(0, 0, areaSize.height, areaSize.width);
    
    _areaWrapperView.backgroundColor = [TGPhotoEditorInterfaceAssets toolbarBackgroundColor];
    
    if (animated)
    {
        _animatingRotation = true;
        
        POPSpringAnimation *centerAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewCenter];
        centerAnimation.fromValue = [NSValue valueWithCGPoint:snapshotView.center];
        centerAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2)];
        centerAnimation.springSpeed = 7;
        centerAnimation.springBounciness = 1;
        [snapshotView pop_addAnimation:centerAnimation forKey:@"center"];
        
        POPSpringAnimation *boundsAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewBounds];
        boundsAnimation.fromValue = [NSValue valueWithCGRect:snapshotView.bounds];
        boundsAnimation.toValue = [NSValue valueWithCGRect:areaBounds];
        boundsAnimation.springSpeed = 7;
        boundsAnimation.springBounciness = 1;
        [snapshotView pop_addAnimation:boundsAnimation forKey:@"bounds"];
        
        CGFloat currentRotation = [[snapshotView.layer valueForKeyPath:@"transform.rotation.z"] floatValue];
        CGFloat targetRotation = TGRotationForOrientation(self.cropOrientation);
        if (fabs(currentRotation - targetRotation) > M_PI)
            targetRotation = -2 * (CGFloat)M_PI + targetRotation;
            
        POPSpringAnimation *rotationAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerRotation];
        rotationAnimation.fromValue = @(currentRotation);
        rotationAnimation.toValue = @(targetRotation);
        rotationAnimation.springSpeed = 7;
        rotationAnimation.springBounciness = 1;
        [snapshotView.layer pop_addAnimation:rotationAnimation forKey:@"rotation"];
        
        [TGPhotoEditorAnimation performBlock:^(__unused bool allFinished)
        {
            if (!allFinished)
                return;
            
            _animatingRotation = false;
            
            [snapshotView removeFromSuperview];
            if (_rotationSnapshotView == snapshotView)
                _rotationSnapshotView = nil;
            
            _areaWrapperView.backgroundColor = [UIColor clearColor];
            
            [self evenlyFillAreaViewAnimated:false reset:false completion:nil];
            
            _scrollView.hidden = false;
            [self setIntefaceHidden:false animated:true];
            [self setCropAreaHidden:false animated:true];
            
            [self showBackdropViewAnimated:false];
            _blurView.alpha = 0.0f;
            _imageView.alpha = 0.0f;
            [UIView animateWithDuration:0.16f delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^
            {
                _blurView.alpha = 1.0f;
            } completion:^(__unused BOOL finished)
            {
                _imageView.alpha = 1.0f;
            }];
        } whenCompletedAllAnimations:@[ centerAnimation, boundsAnimation, rotationAnimation ]];
    }
    else
    {
        [snapshotView removeFromSuperview];
        if (_rotationSnapshotView == snapshotView)
            _rotationSnapshotView = nil;
        
        _areaWrapperView.backgroundColor = [UIColor clearColor];
        
        [self _layoutAreaViewAnimated:false completion:nil];
        [self _zoomToCropRectWithFrame:_scrollView.bounds animated:false completion:nil];
        
        _scrollView.hidden = false;
        [self setIntefaceHidden:false animated:false];
        [self setCropAreaHidden:false animated:false];
        
        [self showBackdropViewAnimated:false];
    }
    
    if (self.croppingChanged != nil)
        self.croppingChanged();
}

#pragma mark - 

- (void)mirror
{
    if (_animatingConfirm || _scrollView.animating)
        return;
    
    [self cancelConfirmCountdown];
    
    self.cropRect = [_scrollView zoomedRect];
    self.mirrored = !self.mirrored;
    
    [self resetBackdropViewsAnimated:false];
    [_scrollView setContentMirrored:self.mirrored];
    [self showBackdropViewAnimated:false];
    
    [self _layoutAreaViewAnimated:false completion:nil];
    [self _zoomToCropRectWithFrame:_scrollView.bounds animated:false completion:nil];
    
    if (self.croppingChanged != nil)
        self.croppingChanged();
}

#pragma mark - Aspect Ratio Lock

- (void)setLockedAspectRatio:(CGFloat)aspectRatio performResize:(bool)performResize animated:(bool)animated
{
    _lockedAspectRatio = aspectRatio;
    
    _areaView.lockAspectRatio = true;
    _areaView.aspectRatio = aspectRatio;
    
    if (!performResize)
        return;
    
    CGPoint currentCenter = CGPointMake(CGRectGetMidX(_cropRect), CGRectGetMidY(_cropRect));
    CGRect availableRect = [_scrollView availableRect];
    CGRect newCropRect = _cropRect;
    newCropRect.size.height = newCropRect.size.width * aspectRatio;
    
    if (newCropRect.size.height > availableRect.size.height)
    {
        newCropRect.size.height = availableRect.size.height;
        newCropRect.size.width = newCropRect.size.height / aspectRatio;
    }
    
    newCropRect.origin.x = currentCenter.x - newCropRect.size.width / 2;
    newCropRect.origin.y = currentCenter.y - newCropRect.size.height / 2;
    
    if (newCropRect.origin.x < availableRect.origin.x)
        newCropRect.origin.x = availableRect.origin.x;
    
    if (newCropRect.origin.y < availableRect.origin.y)
        newCropRect.origin.y = availableRect.origin.y;
    
    if (CGRectGetMaxX(newCropRect) > CGRectGetMaxX(availableRect))
        newCropRect.origin.x = CGRectGetMaxX(availableRect) - newCropRect.size.width;
    
    if (CGRectGetMaxY(newCropRect) > CGRectGetMaxY(availableRect))
        newCropRect.origin.y = CGRectGetMaxY(availableRect) - newCropRect.size.height;
    
    if (!_CGRectEqualToRectWithEpsilon(newCropRect, _cropRect, [self _cropRectEpsilon]))
         [self resetBackdropViewsAnimated:false];
    
    _cropRect = newCropRect;
    
    [self evenlyFillAreaViewAnimated:animated reset:false completion:nil];
    
    if (self.croppingChanged != nil)
        self.croppingChanged();
}

- (void)unlockAspectRatio
{
    _lockedAspectRatio = 0;
    _areaView.lockAspectRatio = false;
    _areaView.aspectRatio = 0;
    
    if (self.croppingChanged != nil)
        self.croppingChanged();
}

- (bool)isAspectRatioLocked
{
    return (_lockedAspectRatio > FLT_EPSILON);
}

- (CGFloat)_cropRectEpsilon
{
    return MAX(_originalSize.width, _originalSize.height) * 0.005f;
}

#pragma mark - Reset

- (void)resetAnimated:(bool)animated
{
    if (_animatingConfirm)
        return;
    
    _animatingConfirm = true;
    
    if (self.cropOrientation != UIImageOrientationUp && (_mirrored || ABS(_rotation) > FLT_EPSILON || !_CGRectEqualToRectWithEpsilon(_cropRect, CGRectMake(0, 0, _originalSize.width, _originalSize.height), FLT_EPSILON)))
        animated = false;
    
    _cropAreaChanged = false;
    [self cancelConfirmCountdown];
    
    _lockedAspectRatio = 0;
    _areaView.lockAspectRatio = false;
    _areaView.aspectRatio = 0;
    
    CGRect originalCropRect = CGRectMake(0.0f, 0.0f, _originalSize.width, _originalSize.height);
    
    if (!_CGRectEqualToRectWithEpsilon(_cropRect, originalCropRect, FLT_EPSILON) || fabs(_rotation) > 0 || _cropOrientation != UIImageOrientationUp)
    {
        [self resetBackdropViewsAnimated:false];
    }
    
    _cropRect = originalCropRect;
    _rotation = 0.0f;
    _cropOrientation = UIImageOrientationUp;
    _mirrored = false;
    
    if (animated && _cropOrientation != UIImageOrientationUp)
    {
        [self setIntefaceHidden:true animated:false];
        [self setCropAreaHidden:true animated:false];
    }
    
    [_rotationView resetAnimated:animated];
    [_areaView setGridMode:TGPhotoCropViewGridModeNone animated:animated];
    
    [_scrollView resetRotationStartValues];
    [_scrollView setContentMirrored:false];
    
    [self evenlyFillAreaViewAnimated:animated reset:true completion:^
    {
        [self setIntefaceHidden:false animated:true];
        [self setCropAreaHidden:false animated:true];
        
        _animatingConfirm = false;
    }];
    
    if (self.croppingChanged != nil)
        self.croppingChanged();
}

#pragma mark - Layout

- (bool)isTracking
{
    return _rotationView.isTracking || _areaView.isTracking || _scrollView.isTracking;
}

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *view = [super hitTest:point withEvent:event];
    
    if (view == self || view == _areaWrapperView)
        return _scrollView;
    
    return view;
}

- (void)setInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    _interfaceOrientation = interfaceOrientation;
    _rotationView.interfaceOrientation = interfaceOrientation;
}

- (void)layoutSubviews
{
    if (_imageView == nil)
    {
        [self setup];
    }
    else if (_previousInterfaceOrientation != self.interfaceOrientation)
    {
        bool performedConfirm = false;
        if (_confirmTimer != nil)
        {
            [UIView performWithoutAnimation:^
            {
                [self performConfirmAnimated:false];
            }];
            performedConfirm = true;
        }
        
        void (^layoutBlock)(void) = ^
        {
            if (!_areaView.isTracking && !_animatingConfirm)
                [self _layoutAreaViewAnimated:false completion:nil];
            
            [self _zoomToCropRectWithFrame:_scrollView.bounds animated:false completion:nil];
        };
        
        if (performedConfirm)
            [UIView performWithoutAnimation:layoutBlock];
        else
            layoutBlock();
    }
    else
    {
        if (!_areaView.isTracking && !_animatingConfirm)
            [self _layoutAreaViewAnimated:false completion:nil];
        
        if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
            [self _zoomToCropRectWithFrame:_scrollView.bounds animated:false completion:nil];
    }
    
    _previousInterfaceOrientation = self.interfaceOrientation;
}

- (void)_layoutRotationView
{
    CGRect initialAreaFrame = _areaView.frame;
    [self _layoutRotationViewWithWrapperFrame:_areaWrapperView.frame areaFrame:initialAreaFrame animated:false];
}

- (void)_layoutRotationViewWithWrapperFrame:(CGRect)wrapperFrame areaFrame:(CGRect)areaFrame animated:(bool)animated
{
    CGRect rotationViewFrame = CGRectZero;
    
    CGPoint areaOrigin = areaFrame.origin;
    CGSize areaSize = areaFrame.size;
    
    switch (self.cropOrientation)
    {
        case UIImageOrientationDown:
        {
            areaOrigin = CGPointMake(wrapperFrame.size.width - areaFrame.size.width, wrapperFrame.size.height - areaFrame.size.height);
            areaSize = CGSizeMake(areaFrame.size.width - areaFrame.origin.x, areaFrame.size.height - areaFrame.origin.y);
        }
            break;
        case UIImageOrientationLeft:
        {
            areaOrigin = CGPointMake(areaFrame.origin.y, wrapperFrame.size.height - areaFrame.size.width);
            areaSize = CGSizeMake(areaFrame.size.height, areaFrame.size.width - areaFrame.origin.x);
        }
            break;
        case UIImageOrientationRight:
        {
            areaOrigin = CGPointMake(wrapperFrame.size.width - areaFrame.size.height, areaFrame.origin.x);
            areaSize = CGSizeMake(areaFrame.size.height - areaFrame.origin.y, areaFrame.size.width);
        }
            break;
            
        default:
            break;
    }
    
    areaFrame.origin = areaOrigin;
    areaFrame.size = areaSize;
    
    switch (self.interfaceOrientation)
    {
        case UIInterfaceOrientationLandscapeLeft:
        {
            rotationViewFrame = CGRectMake(wrapperFrame.origin.x - 100, wrapperFrame.origin.y + areaFrame.origin.y + (areaFrame.size.height - self.frame.size.height) / 2, 100, self.frame.size.height);
        }
            break;
            
        case UIInterfaceOrientationLandscapeRight:
        {
            rotationViewFrame = CGRectMake(wrapperFrame.origin.x + areaFrame.origin.x + areaFrame.size.width, wrapperFrame.origin.y + areaFrame.origin.y + (areaFrame.size.height - self.frame.size.height) / 2, 100, self.frame.size.height);
        }
            break;
            
        default:
        {
            rotationViewFrame = CGRectMake(wrapperFrame.origin.x + areaFrame.origin.x + (areaFrame.size.width - self.frame.size.width) / 2, wrapperFrame.origin.y + areaFrame.origin.y + areaFrame.size.height, self.frame.size.width, 100);
        }
            break;
    }
    
    if (animated)
    {
        POPSpringAnimation *animation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame];
        animation.fromValue = [NSValue valueWithCGRect:_rotationView.frame];
        animation.toValue = [NSValue valueWithCGRect:rotationViewFrame];
        animation.springSpeed = 7;
        animation.springBounciness = 1;
        [_rotationView pop_addAnimation:animation forKey:@"frameAnimation"];
    }
    else
    {
        _rotationView.frame = rotationViewFrame;
    }
    
    [_rotationView setNeedsLayout];
}

- (void)_layoutOverlayViewsWithFrame:(CGRect)frame animated:(bool)animated
{
    CGRect overlayWrapperFrame = frame;
    CGRect topOverlayFrame = CGRectMake(0, -TGPhotoCropViewOverscreenSize, overlayWrapperFrame.size.width, TGPhotoCropViewOverscreenSize);
    CGRect leftOverlayFrame = CGRectMake(-TGPhotoCropViewOverscreenSize, -TGPhotoCropViewOverscreenSize, TGPhotoCropViewOverscreenSize, overlayWrapperFrame.size.height + 2 * TGPhotoCropViewOverscreenSize);
    CGRect rightOverlayFrame = CGRectMake(overlayWrapperFrame.size.width, -TGPhotoCropViewOverscreenSize, TGPhotoCropViewOverscreenSize, overlayWrapperFrame.size.height + 2 * TGPhotoCropViewOverscreenSize);
    CGRect bottomOverlayFrame = CGRectMake(0, overlayWrapperFrame.size.height, overlayWrapperFrame.size.width, TGPhotoCropViewOverscreenSize);
    
    if (animated)
    {
        POPSpringAnimation *wrapperAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame];
        wrapperAnimation.fromValue = [NSValue valueWithCGRect:_overlayWrapperView.frame];
        wrapperAnimation.toValue = [NSValue valueWithCGRect:overlayWrapperFrame];
        wrapperAnimation.springSpeed = 7;
        wrapperAnimation.springBounciness = 1;
        [_overlayWrapperView pop_addAnimation:wrapperAnimation forKey:@"frameAnimation"];
        
        POPSpringAnimation *topAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame];
        topAnimation.fromValue = [NSValue valueWithCGRect:_topOverlayView.frame];
        topAnimation.toValue = [NSValue valueWithCGRect:topOverlayFrame];
        topAnimation.springSpeed = 7;
        topAnimation.springBounciness = 1;
        [_topOverlayView pop_addAnimation:topAnimation forKey:@"frameAnimation"];
        
        POPSpringAnimation *leftAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame];
        leftAnimation.fromValue = [NSValue valueWithCGRect:_leftOverlayView.frame];
        leftAnimation.toValue = [NSValue valueWithCGRect:leftOverlayFrame];
        leftAnimation.springSpeed = 7;
        leftAnimation.springBounciness = 1;
        [_leftOverlayView pop_addAnimation:leftAnimation forKey:@"frameAnimation"];
        
        POPSpringAnimation *rightAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame];
        rightAnimation.fromValue = [NSValue valueWithCGRect:_rightOverlayView.frame];
        rightAnimation.toValue = [NSValue valueWithCGRect:rightOverlayFrame];
        rightAnimation.springSpeed = 7;
        rightAnimation.springBounciness = 1;
        [_rightOverlayView pop_addAnimation:rightAnimation forKey:@"frameAnimation"];
        
        POPSpringAnimation *bottomAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame];
        bottomAnimation.fromValue = [NSValue valueWithCGRect:_bottomOverlayView.frame];
        bottomAnimation.toValue = [NSValue valueWithCGRect:bottomOverlayFrame];
        bottomAnimation.springSpeed = 7;
        bottomAnimation.springBounciness = 1;
        [_bottomOverlayView pop_addAnimation:bottomAnimation forKey:@"frameAnimation"];
    }
    else
    {
        _overlayWrapperView.frame = overlayWrapperFrame;
        _topOverlayView.frame = topOverlayFrame;
        _leftOverlayView.frame = leftOverlayFrame;
        _rightOverlayView.frame = rightOverlayFrame;
        _bottomOverlayView.frame = bottomOverlayFrame;
    }
}

- (CGSize)_areaSizeForCropRect:(CGRect)cropRect orientation:(UIImageOrientation)orientation
{
    CGSize resultSize = cropRect.size;
    CGSize rotatedSize = resultSize;
    if (orientation == UIImageOrientationLeft || orientation == UIImageOrientationRight)
        rotatedSize = CGSizeMake(rotatedSize.height, rotatedSize.width);
    
    CGSize areaSize = TGScaleToSize(rotatedSize, self.bounds.size);
    
    return areaSize;
}

- (CGRect)_layoutAreaViewAnimated:(bool)animated completion:(void (^)(void))completion
{
    CGSize areaSize = [self _areaSizeForCropRect:_cropRect orientation:_cropOrientation];
    CGRect areaWrapperFrame = CGRectMake((self.frame.size.width - areaSize.width) / 2, (self.frame.size.height - areaSize.height) / 2, areaSize.width, areaSize.height);
    CGRect areaWrapperBounds = CGRectMake(0, 0, areaWrapperFrame.size.width, areaWrapperFrame.size.height);
    
    switch (self.cropOrientation)
    {
        case UIImageOrientationUp:
        {
            _areaWrapperView.transform = CGAffineTransformIdentity;
        }
            break;
            
        case UIImageOrientationDown:
        {
            _areaWrapperView.transform = CGAffineTransformMakeRotation((CGFloat)M_PI);
        }
            break;
            
        case UIImageOrientationLeft:
        {
            _areaWrapperView.transform = CGAffineTransformMakeRotation((CGFloat)-M_PI_2);
            areaWrapperBounds = CGRectMake(0, 0, areaWrapperBounds.size.height, areaWrapperBounds.size.width);
        }
            break;
            
        case UIImageOrientationRight:
        {
            _areaWrapperView.transform = CGAffineTransformMakeRotation((CGFloat)M_PI_2);
            areaWrapperBounds = CGRectMake(0, 0, areaWrapperBounds.size.height, areaWrapperBounds.size.width);
        }
            break;
            
        default:
            break;
    }
    
    if (animated)
    {
        POPSpringAnimation *wrapperAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame];
        wrapperAnimation.fromValue = [NSValue valueWithCGRect:_areaWrapperView.frame];
        wrapperAnimation.toValue = [NSValue valueWithCGRect:areaWrapperFrame];
        wrapperAnimation.springSpeed = 7;
        wrapperAnimation.springBounciness = 1;
        
        POPSpringAnimation *areaAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame];
        areaAnimation.fromValue = [NSValue valueWithCGRect:_areaView.frame];
        areaAnimation.toValue = [NSValue valueWithCGRect:areaWrapperBounds];
        areaAnimation.springSpeed = 7;
        areaAnimation.springBounciness = 1;
        
        POPSpringAnimation *scrollViewAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame];
        scrollViewAnimation.fromValue = [NSValue valueWithCGRect:_scrollView.frame];
        scrollViewAnimation.toValue = [NSValue valueWithCGRect:areaWrapperBounds];
        scrollViewAnimation.springSpeed = 7;
        scrollViewAnimation.springBounciness = 1;

        [TGPhotoEditorAnimation performBlock:^(__unused bool allFinished)
        {
          if (completion != nil)
              completion();
        } whenCompletedAllAnimations:@[ wrapperAnimation, areaAnimation, scrollViewAnimation ]];

        [_areaWrapperView pop_addAnimation:wrapperAnimation forKey:@"frameAnimation"];
        [_areaView pop_addAnimation:areaAnimation forKey:@"frameAnimation"];
        [_scrollView pop_addAnimation:scrollViewAnimation forKey:@"frameAnimation"];
    }
    else
    {
        _areaWrapperView.frame = areaWrapperFrame;
        _areaView.frame = areaWrapperBounds;
        _scrollView.frame = areaWrapperBounds;
        
        if (completion != nil)
            completion();
    }

    [self _layoutOverlayViewsWithFrame:areaWrapperBounds animated:animated];
    [self _layoutRotationViewWithWrapperFrame:areaWrapperFrame areaFrame:CGRectMake(0, 0, areaWrapperBounds.size.width, areaWrapperBounds.size.height) animated:animated];
    
    return areaWrapperBounds;
}

- (void)_zoomToCropRectWithFrame:(CGRect)frame animated:(bool)animated completion:(void (^)(void))completion
{
    [_scrollView zoomToRect:_cropRect withFrame:frame animated:animated completion:completion];
}

@end
