//
//  Wheel.swift
//  MobileFirstFramework
//
//  Created by Ryan on 10/24/19.
//  Copyright © 2019 Verizon Wireless. All rights reserved.
//

import UIKit

@objcMembers open class Wheel: View, MVMCoreUIViewConstrainingProtocol {
    
    var heightConstraint: NSLayoutConstraint?
    weak var gradientLayer: CALayer?
    var graphModel: WheelModel? {
        return model as? WheelModel
    }
    
// MARK: setup
    open override func setupView() {
        super.setupView()
        heightConstraint = heightAnchor.constraint(equalToConstant: 0)
        heightConstraint?.isActive = true
        widthAnchor.constraint(equalTo: heightAnchor).isActive = true
    }

    override open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
        super.set(with: model, delegateObject, additionalData)
        guard let model = model as? WheelModel else { return }
        createGraphCircle(model)
        rotationAnimation(model)
    }
    
    class func getAngle(_ piValue: Double) -> Double {
        return piValue / (2.0 * Double.pi) * 360.0
    }
    
    class func getPiValue(_ angle: Double) -> Double {
        return angle / 360.0 * 2.0 * Double.pi
    }
    
// MARK: circle
    open func createGraphCircle(_ graphObject: WheelModel) {
        if let sublayers = layer.sublayers {
            for sublayer in sublayers {
                sublayer.removeAllAnimations()
                sublayer.removeFromSuperlayer()
            }
        }
        heightConstraint?.constant = graphObject.diameter
        
        // iOS 12 uses the conic gradient and a mask for simplicity.
        if #available(iOS 12, *) {
            let gradient = CAGradientLayer()
            gradient.type = .conic
            gradient.startPoint = CGPoint(x: 0.5, y: 0.5)
            gradient.endPoint = CGPoint(x: 0.5, y: 0.0)
            gradient.frame = CGRect(x: 0, y: 0, width: graphObject.diameter, height: graphObject.diameter)
            gradient.colors = graphObject.colors.map({ (color) -> CGColor in
                return color.cgColor
            })
            gradientLayer = gradient
            layer.addSublayer(gradient)
            
            let center = CGPoint(x: gradient.bounds.midX, y: gradient.bounds.midY)
            let radius = (graphObject.diameter - graphObject.lineWidth) / 2.0
            let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: (3 / 2 * .pi), endAngle: -(1 / 2 * .pi), clockwise: false)
            let mask = CAShapeLayer()
            mask.fillColor = UIColor.clear.cgColor
            mask.strokeColor = UIColor.white.cgColor
            mask.lineWidth = graphObject.lineWidth
            mask.path = path.cgPath
            gradient.mask = mask
            return
        }
        
        //create circle path
        let radius = graphObject.diameter / 2.0
        
        //begin point will be at the bottom, clockwise direction
        let path = UIBezierPath(arcCenter: CGPoint(x: radius
            , y: radius), radius: radius - graphObject.lineWidth/2.0, startAngle: CGFloat(Wheel.getPiValue(90.0)), endAngle: CGFloat(Wheel.getPiValue(90.0 + 360.0)), clockwise: true)
        path.lineWidth = graphObject.lineWidth
        
        let circleLayer = CAShapeLayer()
        circleLayer.path = path.cgPath
        circleLayer.lineCap = .round
        circleLayer.lineWidth = graphObject.lineWidth
        circleLayer.fillColor = UIColor.clear.cgColor
        circleLayer.strokeColor = UIColor.black.cgColor
        
        //create gradient layer
        let gradientLayer = createGradientLayer(graphObject)
        gradientLayer.mask = circleLayer
        layer.addSublayer(gradientLayer)
        self.gradientLayer = gradientLayer
    }
    
/*
     create three gradient layer for circle layout.
     _____________
     |     →     |  top layer for smooth gradient
     -------------
     |     |     |
     |  ↑  |  ↓  |
     |     |     |
     -------------
*/
    func createGradientLayer(_ graphObject: WheelModel) -> CALayer {
        let containLayer = CALayer()
        containLayer.frame = CGRect(x: 0, y: 0, width: graphObject.diameter, height: graphObject.diameter)
        let radius = graphObject.diameter / 2.0
        
        //create graident layers
        guard graphObject.colors.count > 1 else {
            containLayer.backgroundColor = graphObject.colors.first?.cgColor
            return containLayer
        }
        var topGradientHeight : CGFloat = 0.0
        var leftColors = graphObject.colors.prefix(through: graphObject.colors.count/2)
        let rightColors = graphObject.colors.suffix(from: graphObject.colors.count/2)
        
        // make the top layer higher than line width for smooth look
        topGradientHeight = min(max(graphObject.lineWidth, 1.0/(1.0+CGFloat(graphObject.colors.count))*graphObject.diameter), graphObject.diameter)
        let topLayer = CAGradientLayer()
        topLayer.frame = CGRect(x: 0.0, y: 0.0, width: graphObject.diameter, height: topGradientHeight)
        //make the graident edge more smoothy
        topLayer.startPoint = CGPoint(x: 0.25, y: 0.0)
        topLayer.endPoint = CGPoint(x: 0.75, y: 0.0)
        //if number of colors is even, need to display gradient layer, otherwise make top layer as solid color layer
        if graphObject.colors.count % 2 == 0 {
            leftColors.removeLast()
            let firstColor = leftColors.last!.cgColor
            let secondColor = rightColors.first!.cgColor
            topLayer.colors = [firstColor, secondColor]
        } else {
            topLayer.backgroundColor = leftColors.last?.cgColor
        }
        containLayer.addSublayer(topLayer)
        
        let leftLayer = CAGradientLayer()
        leftLayer.frame = CGRect(x: 0, y: topGradientHeight, width: radius, height: graphObject.diameter - topGradientHeight)
        leftLayer.startPoint = CGPoint(x: 0, y: 1)
        leftLayer.endPoint = CGPoint(x: 0, y: 0)
        
        //count of graidentLayer.colors must be bigger than 1, otherwise set backgroundColor
        if leftColors.count > 1 {
            leftLayer.colors = leftColors.map({ (color) -> CGColor in
                return color.cgColor
            })
        } else {
            leftLayer.backgroundColor = leftColors.first?.cgColor
        }
        containLayer.addSublayer(leftLayer)
        
        let rightLayer = CAGradientLayer()
        rightLayer.frame = CGRect(x: radius, y: topGradientHeight, width: radius, height: graphObject.diameter - topGradientHeight)
        rightLayer.startPoint = CGPoint(x: 0, y: 0)
        rightLayer.endPoint = CGPoint(x: 0, y: 1)
        if rightColors.count > 1 {
            rightLayer.colors = rightColors.map({ (color) -> CGColor in
                return color.cgColor
            })
        } else {
            rightLayer.backgroundColor = rightColors.first?.cgColor
        }
        containLayer.addSublayer(rightLayer)
        
        return containLayer
    }
//MARK: MVMCoreUIViewConstrainingProtocol
    public func needsToBeConstrained() -> Bool {
        return true
    }
    
//MARK: Animation
    func rotationAnimation(_ object: WheelModel) {
        MVMCoreDispatchUtility.performBlock(onMainThread:{
            let rotation = CABasicAnimation(keyPath: "transform.rotation")
            let animationHandler = WheelAnimationHandler.shared
            let startAngle =  animationHandler.getAnimationStartAngle(object.duration, CACurrentMediaTime())
            if startAngle == 0.0 {
                animationHandler.storeAnimation(object.duration, CACurrentMediaTime())
            }
            var fromValue = Wheel.getPiValue(0.0 + startAngle), toValue = Wheel.getPiValue(360.0 + startAngle)
            if !object.clockwise {
                fromValue = Wheel.getPiValue(360.0 - startAngle)
                toValue = Wheel.getPiValue(0.0 - startAngle)
            }
            rotation.fromValue = fromValue
            rotation.toValue = toValue
            rotation.duration = object.duration
            rotation.timingFunction = CAMediaTimingFunction(name: .linear)
            rotation.fillMode = .both
            rotation.isRemovedOnCompletion = false
            rotation.repeatCount = Float.greatestFiniteMagnitude
            
            self.gradientLayer?.add(rotation, forKey: "rotation")
        })
    }
}
