//
//  TrendHistoryView.swift
//  Tusker
//
//  Created by Shadowfacts on 1/24/21.
//  Copyright © 2021 Shadowfacts. All rights reserved.
//

import UIKit
import Pachyderm

class TrendHistoryView: UIView {

    private var history: [History]?
    
    private let curveRadius: CGFloat = 10
    
    /// The base background color used for the graph fill.
    var effectiveBackgroundColor = UIColor.appBackground
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        createLayers()
    }
    
    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        super.traitCollectionDidChange(previousTraitCollection)
        
        createLayers()
    }
    
    func setHistory(_ history: [History]?) {
        if let history = history {
            self.history = history.sorted(by: { $0.day < $1.day })
        } else {
            self.history = nil
        }
        
        createLayers()
    }
    
    private func createLayers() {
        guard let history = history,
              history.count >= 2,
              !bounds.isEmpty else {
            return
        }
        
        let maxUses = history.max(by: { $0.uses < $1.uses })!.uses
        guard maxUses > 0 else {
            return
        }
        
        // remove old layers if this view is being re-used
        layer.sublayers?.forEach { $0.removeFromSuperlayer() }
        
        let path = UIBezierPath()
        
        let widthStep = bounds.width / CGFloat(history.count - 1)
        
        let points: [CGPoint] = history.enumerated().map { (index, entry) in
            let x = CGFloat(index) * widthStep
            let yFrac = CGFloat(entry.uses) / CGFloat(maxUses)
            let y = (1 - yFrac) * bounds.height
            return CGPoint(x: x, y: y)
        }
        
        var gapStartPoints = [CGPoint]()
        var gapEndPoints = [CGPoint]()
        
        for (index, point) in points.enumerated().dropFirst().dropLast() {
            let prev = points[index - 1]
            let next = points[index + 1]
            
            let a = atan((point.y - prev.y) / widthStep)
            let b = atan((next.y - point.y) / widthStep)
            let innerAngle = .pi - a - b
            
            let gapDistance = curveRadius / sin(innerAngle / 2)
            
            let x1 = point.x - cos(a) * gapDistance
            let y1 = point.y - sin(a) * gapDistance
            gapStartPoints.append(CGPoint(x: x1, y: y1))
            
            let x2 = point.x + cos(b) * gapDistance
            let y2 = point.y + sin(b) * gapDistance
            gapEndPoints.append(CGPoint(x: x2, y: y2))
        }
        
        path.move(to: points.first!)
        for (index, point) in points.dropFirst().dropLast().enumerated() {
            path.addLine(to: gapStartPoints[index])
            path.addQuadCurve(to: gapEndPoints[index], controlPoint: point)
        }
        path.addLine(to: points.last!)

        let borderLayer = CAShapeLayer()
        // copy the border path so we can continue mutating the UIBezierPath to create the fill path
        borderLayer.path = path.cgPath.copy()!
        borderLayer.strokeColor = tintColor.cgColor
        borderLayer.fillColor = nil
        borderLayer.lineWidth = lineWidth
        borderLayer.lineCap = .round
        
        path.addLine(to: CGPoint(x: bounds.width, y: bounds.height))
        path.addLine(to: CGPoint(x: 0, y: bounds.height))
        path.addLine(to: points.first!)
        
        let fillLayer = CAShapeLayer()
        fillLayer.path = path.cgPath
        let fillColor = self.fillColor()
        fillLayer.strokeColor = fillColor
        fillLayer.fillColor = fillColor
        fillLayer.lineWidth = lineWidth
        
        layer.addSublayer(fillLayer)
        layer.addSublayer(borderLayer)
    }
    
    private var lineWidth: CGFloat {
        (traitCollection.preferredContentSizeCategory > .large || UIAccessibility.isBoldTextEnabled) ? 4 : 2
    }
    
    // The non-transparent fill color.
    // We blend with the view's background color ourselves so that final color is non-transparent,
    // otherwise when the fill layer's border and fill overlap, there's a visibly darker patch
    // because transparent colors are being blended together.
    private func fillColor() -> CGColor {
        var backgroundRed: CGFloat = 0
        var backgroundGreen: CGFloat = 0
        var backgroundBlue: CGFloat = 0
        var tintRed: CGFloat = 0
        var tintGreen: CGFloat = 0
        var tintBlue: CGFloat = 0
        traitCollection.performAsCurrent {
            effectiveBackgroundColor.getRed(&backgroundRed, green: &backgroundGreen, blue: &backgroundBlue, alpha: nil)
            tintColor.getRed(&tintRed, green: &tintGreen, blue: &tintBlue, alpha: nil)
        }
        let blendedRed = (backgroundRed + tintRed) / 2
        let blendedGreen = (backgroundGreen + tintGreen) / 2
        let blendedBlue = (backgroundBlue + tintBlue) / 2
        return CGColor(red: blendedRed, green: blendedGreen, blue: blendedBlue, alpha: 1)
    }

}
