#import "TGPhotoPaintColorPicker.h"

#import "TGPaintUtils.h"
#import "TGImageUtils.h"

#import "TGPaintSwatch.h"

const CGFloat TGPhotoPaintColorWeightGestureRange = 320.0f;
const CGFloat TGPhotoPaintVerticalThreshold = 5.0f;
const CGFloat TGPhotoPaintPreviewOffset = -70.0f;
const CGFloat TGPhotoPaintPreviewScale = 2.0f;
const CGFloat TGPhotoPaintDefaultBrushWeight = 0.22f;
const CGFloat TGPhotoPaintDefaultColorLocation = 1.0f;

@interface TGPhotoPaintColorPickerKnobCircleView : UIView
{
    CGFloat _strokeIntensity;
}

@property (nonatomic, strong) UIColor *color;
@property (nonatomic, assign) bool strokesLowContrastColors;

@end

@interface TGPhotoPaintColorPickerKnob : UIView
{
    UIView *_wrapperView;
    UIImageView *_shadowView;
    TGPhotoPaintColorPickerKnobCircleView *_backgroundView;
    TGPhotoPaintColorPickerKnobCircleView *_colorView;
    
    bool _dragging;
    CGFloat _weight;
}

- (void)setColor:(UIColor *)color;
- (void)setWeight:(CGFloat)weight;

@property (nonatomic, assign) bool dragging;
@property (nonatomic, assign) bool changingWeight;
@property (nonatomic, assign) UIInterfaceOrientation orientation;

@end

@interface TGPhotoPaintColorPickerBackground : UIView

+ (NSArray *)colors;
+ (NSArray *)locations;

@end

@interface TGPhotoPaintColorPicker () <UIGestureRecognizerDelegate>
{
    TGPhotoPaintColorPickerBackground *_backgroundView;
    TGPhotoPaintColorPickerKnob *_knobView;
    
    UIPanGestureRecognizer *_panGestureRecognizer;
    UILongPressGestureRecognizer *_pressGestureRecognizer;
    
    CGPoint _gestureStartLocation;
    
    CGFloat _location;
    bool _dragging;
    
    CGFloat _weight;
}
@end

@implementation TGPhotoPaintColorPicker

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self != nil)
    {
        _backgroundView = [[TGPhotoPaintColorPickerBackground alloc] initWithFrame:self.bounds];
        _backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        [self addSubview:_backgroundView];
        
        _knobView = [[TGPhotoPaintColorPickerKnob alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 24.0f, 24.0f)];
        [_knobView setColor:[TGPhotoPaintColorPicker colorForLocation:0.0f]];
        [self addSubview:_knobView];
        
        _panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
        _panGestureRecognizer.delegate = self;
        [self addGestureRecognizer:_panGestureRecognizer];
        
        _pressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handlePress:)];
        _pressGestureRecognizer.delegate = self;
        _pressGestureRecognizer.minimumPressDuration = 0.1;
        [self addGestureRecognizer:_pressGestureRecognizer];
        
        _location = [self restoreLastColorLocation];
        _weight = 0.22f;
    }
    return self;
}

- (CGFloat)restoreLastColorLocation
{
    NSNumber *lastColor = [[NSUserDefaults standardUserDefaults] objectForKey:@"TG_paintLastColorLocation_v0"];
    if (lastColor != nil)
        return [lastColor floatValue];
    
    return TGPhotoPaintDefaultColorLocation;
}

- (void)storeCurrentColorLocation
{
    [[NSUserDefaults standardUserDefaults] setObject:@(_location) forKey:@"TG_paintLastColorLocation_v0"];
}

- (void)setOrientation:(UIInterfaceOrientation)orientation
{
    _orientation = orientation;
    _knobView.orientation = orientation;
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    if (CGRectContainsPoint(CGRectInset(self.bounds, -30.0f, -10.0f), point))
        return true;
    
    return [super pointInside:point withEvent:event];
}

- (TGPaintSwatch *)swatch
{
    return [TGPaintSwatch swatchWithColor:[TGPhotoPaintColorPicker colorForLocation:_location] colorLocation:_location brushWeight:_weight];
}

- (void)setSwatch:(TGPaintSwatch *)swatch
{
    [self setLocation:swatch.colorLocaton];
    [self setWeight:swatch.brushWeight];
}

- (UIColor *)color
{
    return [TGPhotoPaintColorPicker colorForLocation:_location];
}

- (void)setLocation:(CGFloat)location
{
    [self setLocation:location animated:false];
}

- (void)setLocation:(CGFloat)location animated:(bool)animated
{
    _location = location;
    [_knobView setColor:[TGPhotoPaintColorPicker colorForLocation:_location]];
    
    if (animated)
    {
        [UIView animateWithDuration:0.3 delay:0.0 usingSpringWithDamping:0.85f initialSpringVelocity:0.0f options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionLayoutSubviews animations:^
        {
            [self layoutSubviews];
        } completion:nil];
    }
    else
    {
        [self setNeedsLayout];
    }
}

- (void)setWeight:(CGFloat)weight
{
    _weight = weight;
    [_knobView setWeight:weight];
}

- (void)setDragging:(bool)dragging
{
    _dragging = dragging;
    [_knobView setDragging:dragging];
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)__unused gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)__unused otherGestureRecognizer
{
    return true;
}

- (bool)_hasBeganChangingWeight:(CGPoint)location
{
    switch (self.orientation)
    {
        case UIInterfaceOrientationLandscapeLeft:
            if (location.x > self.frame.size.width + TGPhotoPaintVerticalThreshold)
                return true;
            
        case UIInterfaceOrientationLandscapeRight:
            if (location.x < -TGPhotoPaintVerticalThreshold)
                return true;
            
        default:
            if (location.y < -TGPhotoPaintVerticalThreshold)
                return true;
    }
    
    return false;
}

- (CGFloat)_weightLocation:(CGPoint)location
{
    CGFloat weightLocation = 0.0f;
    switch (self.orientation)
    {
        case UIInterfaceOrientationLandscapeLeft:
            weightLocation = (location.x - self.frame.size.width - TGPhotoPaintVerticalThreshold) / TGPhotoPaintColorWeightGestureRange;
            break;
            
        case UIInterfaceOrientationLandscapeRight:
            weightLocation = ((location.x * -1) - TGPhotoPaintVerticalThreshold) / TGPhotoPaintColorWeightGestureRange;
            break;
            
        default:
            weightLocation = ((location.y * -1) - TGPhotoPaintVerticalThreshold) / TGPhotoPaintColorWeightGestureRange;
            break;
    }
    
    return MAX(0.0f, MIN(1.0f, weightLocation));
}

- (void)handlePan:(UIPanGestureRecognizer *)gestureRecognizer
{
    CGPoint location = [gestureRecognizer locationInView:gestureRecognizer.view];
    
    switch (gestureRecognizer.state)
    {
        case UIGestureRecognizerStateBegan:
        {
            _gestureStartLocation = location;
            [self setDragging:true];
            
            if (self.beganPicking != nil)
                self.beganPicking();
        }
            break;
            
        case UIGestureRecognizerStateChanged:
        {
            CGFloat colorLocation = MAX(0.0f, MIN(1.0f, self.frame.size.width > self.frame.size.height ? location.x / gestureRecognizer.view.frame.size.width : location.y / gestureRecognizer.view.frame.size.height));
            [self setLocation:colorLocation];
            
            if ([self _hasBeganChangingWeight:location])
            {
                [_knobView setChangingWeight:true];
                CGFloat weightLocation = [self _weightLocation:location];
                [self setWeight:weightLocation];
            }
            
            if (self.valueChanged != nil)
                self.valueChanged();
        }
            break;
            
        case UIGestureRecognizerStateEnded:
        {
            [_knobView setChangingWeight:false];
            [self setDragging:false];
            
            if (self.finishedPicking != nil)
                self.finishedPicking();
            
            [self storeCurrentColorLocation];
        }
            break;
            
        case UIGestureRecognizerStateCancelled:
        {
            [_knobView setChangingWeight:false];
        }
            break;
            
        default:
            break;
    }
}

- (void)handlePress:(UILongPressGestureRecognizer *)gestureRecognizer
{
    CGPoint location = [gestureRecognizer locationInView:gestureRecognizer.view];
    
    switch (gestureRecognizer.state)
    {
        case UIGestureRecognizerStateBegan:
        {
            if (!CGRectContainsPoint(_knobView.frame, location))
            {
                CGFloat colorLocation = MAX(0.0f, MIN(1.0f, self.frame.size.width > self.frame.size.height ? location.x / gestureRecognizer.view.frame.size.width : location.y / gestureRecognizer.view.frame.size.height));
                [self setLocation:colorLocation animated:true];
            }
            
            [self setDragging:true];
            
            if (self.beganPicking != nil)
                self.beganPicking();
        }
            break;
            
        case UIGestureRecognizerStateEnded:
        {
            [self setDragging:false];
            
            if (self.finishedPicking != nil)
                self.finishedPicking();
            
            [self storeCurrentColorLocation];
        }
            break;
            
        default:
            break;
    }
}

+ (UIColor *)colorForLocation:(CGFloat)location
{
    NSArray *locations = [TGPhotoPaintColorPickerBackground locations];
    NSArray *colors = [TGPhotoPaintColorPickerBackground colors];
    
    if (location < FLT_EPSILON)
        return [UIColor colorWithCGColor:(CGColorRef)colors.firstObject];
    else if (location > 1 - FLT_EPSILON)
        return [UIColor colorWithCGColor:(CGColorRef)colors.lastObject];
    
    __block NSInteger leftIndex = -1;
    __block NSInteger rightIndex = -1;
    
    [locations enumerateObjectsUsingBlock:^(NSNumber *value, NSUInteger index, BOOL *stop)
    {
        if (index > 0)
        {
            if (value.doubleValue > location)
            {
                leftIndex = index - 1;
                rightIndex = index;
                *stop = true;
            }
        }
    }];
    
    CGFloat leftLocation = [locations[leftIndex] doubleValue];
    UIColor *leftColor = [UIColor colorWithCGColor:(CGColorRef)colors[leftIndex]];
    
    CGFloat rightLocation = [locations[rightIndex] doubleValue];
    UIColor *rightColor = [UIColor colorWithCGColor:(CGColorRef)colors[rightIndex]];
    
    CGFloat factor = (location - leftLocation) / (rightLocation - leftLocation);
    return [self _interpolateColor:leftColor withColor:rightColor factor:factor];
}

+ (void)_colorComponentsForColor:(UIColor *)color red:(CGFloat *)red green:(CGFloat *)green blue:(CGFloat *)blue
{
    NSInteger componentsCount = CGColorGetNumberOfComponents(color.CGColor);
    const CGFloat *components = CGColorGetComponents(color.CGColor);
    
    CGFloat r = 0.0f;
    CGFloat g = 0.0f;
    CGFloat b = 0.0f;
    CGFloat a = 1.0f;
    
    if (componentsCount == 4)
    {
        r = components[0];
        g = components[1];
        b = components[2];
        a = components[3];
    }
    else
    {
        r = g = b = components[0];
    }

    *red = r;
    *green = g;
    *blue = b;
}

+ (UIColor *)_interpolateColor:(UIColor *)color1 withColor:(UIColor *)color2 factor:(CGFloat)factor
{
    factor = MIN(MAX(factor, 0.0), 1.0);

    CGFloat r1 = 0, r2 = 0;
    CGFloat g1 = 0, g2 = 0;
    CGFloat b1 = 0, b2 = 0;
    
    [self _colorComponentsForColor:color1 red:&r1 green:&g1 blue:&b1];
    [self _colorComponentsForColor:color2 red:&r2 green:&g2 blue:&b2];
    
    CGFloat r = r1 + (r2 - r1) * factor;
    CGFloat g = g1 + (g2 - g1) * factor;
    CGFloat b = b1 + (b2 - b1) * factor;
    
    return [UIColor colorWithRed:r green:g blue:b alpha:1.0f];
}

- (void)layoutSubviews
{
    CGFloat pos = self.frame.size.width > self.frame.size.height ? -_knobView.frame.size.width / 2.0f + self.frame.size.width * _location : -_knobView.frame.size.height / 2.0f + self.frame.size.height * _location;
    
    _knobView.frame = self.frame.size.width > self.frame.size.height ? CGRectMake(pos, (self.frame.size.height - _knobView.frame.size.height) / 2.0f, _knobView.frame.size.width, _knobView.frame.size.height) : CGRectMake((self.frame.size.width - _knobView.frame.size.width) / 2.0f, pos, _knobView.frame.size.width, _knobView.frame.size.height);
}

@end

@implementation TGPhotoPaintColorPickerKnobCircleView

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self != nil)
    {
        self.backgroundColor = [UIColor clearColor];
        self.contentMode = UIViewContentModeRedraw;
        self.opaque = false;
    }
    return self;
}

- (void)setColor:(UIColor *)color
{
    _color = color;
    
    if (self.strokesLowContrastColors)
    {
        CGFloat strokeIntensity = 0.0f;
        CGFloat hue;
        CGFloat saturation;
        CGFloat brightness;
        CGFloat alpha;
        
        bool success = [color getHue:&hue saturation:&saturation brightness:&brightness alpha:&alpha];
        if (success && hue < FLT_EPSILON && saturation < FLT_EPSILON && brightness > 0.92f)
            strokeIntensity = (brightness - 0.92f) / 0.08f;
        
        _strokeIntensity = strokeIntensity;
    }
    
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextSetFillColorWithColor(context, self.color.CGColor);
    CGContextFillEllipseInRect(context, rect);
    
    if (_strokeIntensity > FLT_EPSILON)
    {
        CGContextSetLineWidth(context, 1.0f);
        CGContextSetStrokeColorWithColor(context, [UIColor colorWithWhite:0.88f alpha:_strokeIntensity].CGColor);
        CGContextStrokeEllipseInRect(context, CGRectInset(rect, 1.0f, 1.0f));
    }
}

@end


const CGFloat TGPhotoPaintColorSmallCircle = 4.0f;
const CGFloat TGPhotoPaintColorLargeCircle = 20.0f;

@implementation TGPhotoPaintColorPickerKnob

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self != nil)
    {
        self.userInteractionEnabled = false;
        
        _wrapperView = [[UIView alloc] initWithFrame:self.bounds];
        [self addSubview:_wrapperView];
        
        static UIImage *shadowImage = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^
        {
            UIGraphicsBeginImageContextWithOptions(CGSizeMake(48.0f, 48.0f), false, 0.0f);
            CGContextRef context = UIGraphicsGetCurrentContext();
            
            CGContextSetShadowWithColor(context, CGSizeMake(0, 1.5f), 4.0f, [UIColor colorWithWhite:0.0f alpha:0.7f].CGColor);
            CGContextSetFillColorWithColor(context, UIColorRGBA(0x000000, 1.0f).CGColor);
            CGContextFillEllipseInRect(context, CGRectMake(4.0f, 4.0f, 40.0f, 40.0f));
            shadowImage = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();
        });
        
        _shadowView = [[UIImageView alloc] initWithImage:shadowImage];
        _shadowView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        _shadowView.frame = CGRectInset(_wrapperView.bounds, -2, -2);
        [_wrapperView addSubview:_shadowView];
        
        _backgroundView = [[TGPhotoPaintColorPickerKnobCircleView alloc] initWithFrame:self.bounds];
        _backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        _backgroundView.color = [UIColor whiteColor];
        [_wrapperView addSubview:_backgroundView];
        
        _colorView = [[TGPhotoPaintColorPickerKnobCircleView alloc] init];
        _colorView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
        _colorView.center = TGPaintCenterOfRect(self.bounds);
        _colorView.color = [UIColor blueColor];
        _colorView.strokesLowContrastColors = true;
        [self setWeight:0.5f];
        [_wrapperView addSubview:_colorView];
    }
    return self;
}

- (void)setColor:(UIColor *)color
{
    [_colorView setColor:color];
}

- (void)setWeight:(CGFloat)size
{
    _weight = size;
    if (_dragging)
        [self updateLocationAnimated:true updateColorSize:false];
    
    CGFloat diameter = [self _circleDiameterForBrushWeight:size zoomed:_dragging];
    [_colorView setBounds:CGRectMake(0, 0, diameter, diameter)];
}

- (void)setDragging:(bool)dragging
{
    if (dragging == _dragging)
        return;
    
    _dragging = dragging;
    [self updateLocationAnimated:true updateColorSize:true];
}

- (CGFloat)_circleDiameterForBrushWeight:(CGFloat)size zoomed:(bool)zoomed
{
    CGFloat result = TGPhotoPaintColorSmallCircle + (TGPhotoPaintColorLargeCircle - TGPhotoPaintColorSmallCircle) * size;
    result = zoomed ? TGRetinaFloor(result) * TGPhotoPaintPreviewScale : floor(result);
    return result;
}

- (void)updateLocationAnimated:(bool)animated updateColorSize:(bool)updateColorSize
{
    void (^changeBlock)(void) = ^
    {
        CGPoint center = TGPaintCenterOfRect(self.bounds);
        CGFloat scale = 1.0f;
        if (_dragging)
        {
            scale = TGPhotoPaintPreviewScale;
         
            CGFloat offset = TGPhotoPaintPreviewOffset;
            if (self.changingWeight)
                offset -= _weight * TGPhotoPaintColorWeightGestureRange;
            
            switch (self.orientation)
            {
                case UIInterfaceOrientationLandscapeLeft:
                    center.x -= offset;
                    break;
                
                case UIInterfaceOrientationLandscapeRight:
                    center.x += offset;
                    break;
                    
                default:
                    center.y += offset;
                    break;
            }
        }
        
        _wrapperView.center = center;
        _wrapperView.bounds = CGRectMake(0, 0, 24.0f * scale, 24.0f * scale);
        
        if (updateColorSize)
        {
            CGFloat diameter = [self _circleDiameterForBrushWeight:_weight zoomed:_dragging];
            [_colorView setBounds:CGRectMake(0, 0, diameter, diameter)];
        }
    };
    
    if (animated)
    {
        [UIView animateWithDuration:0.3 delay:0.0 usingSpringWithDamping:0.85f initialSpringVelocity:0.0f options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionLayoutSubviews animations:changeBlock completion:nil];
    }
    else
    {
        changeBlock();
    }
}

@end


static void addRoundedRectToPath(CGContextRef context, CGRect rect, CGFloat ovalWidth, CGFloat ovalHeight)
{
    CGFloat fw, fh;
    if (ovalWidth == 0 || ovalHeight == 0)
    {
        CGContextAddRect(context, rect);
        return;
    }
    
    CGContextSaveGState(context);
    CGContextTranslateCTM (context, CGRectGetMinX(rect), CGRectGetMinY(rect));
    CGContextScaleCTM (context, ovalWidth, ovalHeight);
    fw = CGRectGetWidth (rect) / ovalWidth;
    fh = CGRectGetHeight (rect) / ovalHeight;
    CGContextMoveToPoint(context, fw, fh/2);
    CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 1);
    CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 1);
    CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 1);
    CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 1);
    CGContextClosePath(context);
    CGContextRestoreGState(context);
}

@implementation TGPhotoPaintColorPickerBackground

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self != nil)
    {
        self.backgroundColor = [UIColor clearColor];
        self.contentMode = UIViewContentModeRedraw;
        self.opaque = false;
    }
    return self;
}

- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    CGFloat radius = rect.size.width > rect.size.height ? rect.size.height / 2.0f : rect.size.width / 2.0f;
    addRoundedRectToPath(context, self.frame, radius, radius);
    CGContextClip(context);
    
    CFArrayRef colors = (__bridge CFArrayRef)[TGPhotoPaintColorPickerBackground colors];
    CGFloat locations[[TGPhotoPaintColorPickerBackground colors].count];
    [TGPhotoPaintColorPickerBackground fillLocations:locations];
    
    CGColorSpaceRef colorSpc = CGColorSpaceCreateDeviceRGB();
    CGGradientRef gradient = CGGradientCreateWithColors(colorSpc, colors, locations);
    
    if (rect.size.width > rect.size.height)
    {
        CGContextDrawLinearGradient(context, gradient, CGPointMake(0.0f, rect.size.height / 2.0f), CGPointMake(rect.size.width, rect.size.height / 2.0f), kCGGradientDrawsAfterEndLocation);
    }
    else
    {
        CGContextDrawLinearGradient(context, gradient, CGPointMake(rect.size.width / 2.0f, 0.0f), CGPointMake(rect.size.width / 2.0f, rect.size.height), kCGGradientDrawsAfterEndLocation);
    }
    
    CGContextSetBlendMode(context, kCGBlendModeClear);
    CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor);
    
    CGColorSpaceRelease(colorSpc);
    CGGradientRelease(gradient);
}

+ (NSArray *)colors
{
    static dispatch_once_t onceToken;
    static NSArray *colors;
    dispatch_once(&onceToken, ^
    {
        colors = @
        [
            (id)UIColorRGB(0xea2739).CGColor, //red
            (id)UIColorRGB(0xdb3ad2).CGColor, //pink
            (id)UIColorRGB(0x3051e3).CGColor, //blue
            (id)UIColorRGB(0x49c5ed).CGColor, //cyan
            (id)UIColorRGB(0x80c864).CGColor, //green
            (id)UIColorRGB(0xfcde65).CGColor, //yellow
            (id)UIColorRGB(0xfc964d).CGColor, //orange
            (id)[UIColor blackColor].CGColor, //black
            (id)[UIColor whiteColor].CGColor  //white
        ];
    });
    return colors;
}

+ (NSArray *)locations
{
    static dispatch_once_t onceToken;
    static NSArray *locations;
    dispatch_once(&onceToken, ^
    {
        locations = @
        [
            @0.0f,  //red
            @0.14f, //pink
            @0.24f, //blue
            @0.39f, //cyan
            @0.49f, //green
            @0.62f, //yellow
            @0.73f, //orange
            @0.85f, //black
            @1.0f   //white
        ];
    });
    return locations;
}

+ (void)fillLocations:(CGFloat *)buf
{
    NSArray *locations = [self locations];
    [locations enumerateObjectsUsingBlock:^(NSNumber *location, NSUInteger index, __unused BOOL *stop)
    {
        buf[index] = location.doubleValue;
    }];
}

@end
