//
//  URLBar.swift
//  SBrowser
//
//  Created by James Magahern on 7/23/20.
//

import UIKit

protocol URLBarDelegate: AnyObject
{
    func urlBarRequestedFocusEscape(_ urlBar: URLBar)
}

class TextFieldWithKeyCommands: UITextField
{
    internal var _keyCommands: [UIKeyCommand]? = []
    override var keyCommands: [UIKeyCommand]? {
        get { _keyCommands }
        set { _keyCommands = newValue }
    }
}

class TextFieldControlsView: UIView
{
    let clearButton = UIButton(frame: .zero)
    let autocorrectButton = UIButton(frame: .zero)
    
    static let padding = CGFloat(4.0)
    static let spacing = CGFloat(2.0)

    init() {
        super.init(frame: .zero)
        
        var clearButtonConfig = UIButton.Configuration.borderless()
        clearButtonConfig.image = .init(systemName: "xmark.circle.fill")
        clearButtonConfig.preferredSymbolConfigurationForImage = UIImage.SymbolConfiguration.init(pointSize: 12.0)
        clearButton.configuration = clearButtonConfig
        clearButton.tintColor = .secondaryLabel
        clearButton.translatesAutoresizingMaskIntoConstraints = false
        addSubview(clearButton)
        
        var autocorrectButtonConfig = UIButton.Configuration.borderedTinted()
        autocorrectButtonConfig.image = .init(systemName: "textformat")
        autocorrectButton.changesSelectionAsPrimaryAction = true
        autocorrectButton.configuration = autocorrectButtonConfig
        autocorrectButton.translatesAutoresizingMaskIntoConstraints = false
        addSubview(autocorrectButton)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func sizeThatFits(_ size: CGSize) -> CGSize {
        CGSize(width: (size.height * 2) + Self.spacing, height: size.height)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        let size = bounds.height
        autocorrectButton.frame = CGRect(
            x: bounds.width - size,
            y: 0.0,
            width: size, height: size
        ).insetBy(dx: Self.padding, dy: Self.padding)
        
        clearButton.frame = CGRect(
            x: autocorrectButton.frame.minX - size - Self.spacing,
            y: 0.0,
            width: size, height: size
        ).insetBy(dx: Self.padding, dy: Self.padding)
    }
}

class URLBar: ReliefButton
{
    let textField = TextFieldWithKeyCommands(frame: .zero)
    let refreshButton = UIButton(frame: .zero)
    let errorButton = UIButton(frame: .zero)
    let documentButton = UIButton(frame: .zero)
    
    let controlsView = TextFieldControlsView()
    
    weak var delegate: URLBarDelegate?
    
    public enum LoadProgress: Equatable {
        case idle
        case complete
        case loading(progress: Double)
        case error(error: Error)
        
        public static func == (lhs: URLBar.LoadProgress, rhs: URLBar.LoadProgress) -> Bool {
            switch lhs {
            case .idle:
                if case .idle = rhs { return true }
                else { return false }
            case .complete:
                if case .complete = rhs { return true }
                else { return false }
            case let .loading(progress: mine):
                if case .loading(progress: let theirs) = rhs {
                    return mine == theirs
                } else {
                    return false
                }
            case .error:
                if case .error(error: _) = rhs { return true }
                else { return false }
            }
        }
    }
    
    public var loadProgress: LoadProgress = .idle {
        didSet {
            if oldValue != loadProgress {
                updateProgressIndicator()
            }
        }
    }
    
    override var isPointerInteractionEnabled: Bool {
        get { false } set {}
    }
    
    private let fadeMaskView = UIImageView(frame: .zero)
    
    private let progressIndicatorView = ProgressIndicatorView()
    private var progressIndicatorAnimating = false
    
    private let documentImage = UIImage(systemName: "ellipsis.circle")
    private let refreshImage = UIImage(systemName: "arrow.clockwise")
    private let stopImage = UIImage(systemName: "xmark")
    
    private let backgroundCornerRadius: CGFloat = 0
    
    private let documentSeparatorView = UIView(frame: .zero)
    
    override init() {
        super.init()
        
        backgroundColor = .clear
        constrainedToSquare = false
        
        backgroundView.addSubview(progressIndicatorView)
        
        textField.backgroundColor = .clear
        textField.textContentType = .URL
        textField.keyboardType = .webSearch
        textField.autocorrectionType = .no
        textField.autocapitalizationType = .none
        textField.font = .systemFont(ofSize: 13.0)
        textField.clearButtonMode = .never
        textField.placeholder = "URL or search term"
        textField.keyCommands = [
            UIKeyCommand(action: #selector(Self.downKeyPressed), input: UIKeyCommand.inputDownArrow)
                .prioritizeOverSystem()
        ]
        addSubview(textField)
        
        refreshButton.tintColor = .secondaryLabel
        refreshButton.setImage(refreshImage, for: .normal)
        refreshButton.isPointerInteractionEnabled = true
        addSubview(refreshButton)
        
        errorButton.backgroundColor = .systemRed
        errorButton.layer.cornerRadius = 3.0
        errorButton.layer.masksToBounds = true
        errorButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 11.0)
        errorButton.setTitleColor(.white, for: .normal)
        errorButton.setTitle("ERR", for: .normal)
        errorButton.isPointerInteractionEnabled = true
        addSubview(errorButton)
        
        documentButton.tintColor = .secondaryLabel
        documentButton.setImage(documentImage, for: .normal)
        documentButton.isPointerInteractionEnabled = true
        addSubview(documentButton)
        
        documentSeparatorView.backgroundColor = .secondarySystemFill
        addSubview(documentSeparatorView)
        
        controlsView.autoresizingMask = []
        controlsView.clearButton.addAction(.init(handler: { [textField] _ in
            textField.text = ""
        }), for: .primaryActionTriggered)
        
        controlsView.autocorrectButton.addAction(.init(handler: { [unowned self] _ in
            self.setAutocorrectEnabled(controlsView.autocorrectButton.isSelected)
        }), for: .touchUpInside)
        
        if traitCollection.userInterfaceIdiom != .mac {
            textField.rightView = controlsView
            textField.rightViewMode = .whileEditing
        }
        
        setErrorButtonAnimating(false)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    @objc
    private func downKeyPressed(_ sender: Any?) {
        self.delegate?.urlBarRequestedFocusEscape(self)
    }
    
    private func setAutocorrectEnabled(_ enabled: Bool) {
        textField.autocorrectionType = enabled ? .yes : .no
        textField.reloadInputViews()
    }
    
    private func updateProgressIndicator() {
        setErrorButtonAnimating(false)
        
        if progressIndicatorAnimating {
            return
        }
        
        UIView.animate(withDuration: 0.4) { [unowned self] in
            switch self.loadProgress {
            case .idle:
                self.refreshButton.isHidden = true
                self.setErrorButtonAnimating(false)
                self.progressIndicatorView.alpha = 0.0
                self.progressIndicatorView.progress = 0.0
                
            case .complete:
                self.refreshButton.isHidden = false
                self.refreshButton.setImage(self.refreshImage, for: .normal)
                self.progressIndicatorView.progress = 1.0
                self.progressIndicatorAnimating = true
                UIView.animate(withDuration: 0.5, delay: 0.5, options: AnimationOptions()) {
                    self.progressIndicatorView.alpha = 0.0
                } completion: { _ in
                    // Reset back to zero
                    self.progressIndicatorView.progress = 0.0
                    self.progressIndicatorAnimating = false
                }
                
            case .loading(let progress):
                self.refreshButton.isHidden = false
                self.refreshButton.setImage(self.stopImage, for: .normal)
                self.progressIndicatorView.progress = progress
                self.progressIndicatorView.alpha = 1.0
                
            case .error(let error):
                self.refreshButton.isHidden = false
                self.setErrorButtonAnimating(true)
                self.progressIndicatorView.alpha = 0.0
                self.progressIndicatorView.progress = 0.0
                
                if let nserror = error as NSError? {
                    self.errorButton.setTitle("\(nserror.code)", for: .normal)
                } else {
                    self.errorButton.setTitle("ERR", for: .normal)
                }
            }
        }
        
        self.setNeedsLayout()
    }
    
    override func setBackgroundInverted(_ inverted: Bool) {
        // Do not invert. 
        super.setBackgroundInverted(false)
    }
    
    override var intrinsicContentSize: CGSize {
        let preferredHeight = CGFloat(34)
        return CGSize(width: 1000.0, height: preferredHeight)
    }
    
    private func fadeBackgroundImageForSize(_ size: CGSize, cutoffLocation: CGFloat) -> UIImage? {
        guard size.width > .leastNonzeroMagnitude && size.height > .leastNonzeroMagnitude else { return nil }
        
        var image: UIImage? = nil
        UIGraphicsBeginImageContext(CGSize(width: size.width, height: 1.0))
        if let context = UIGraphicsGetCurrentContext() {
            let gradientColorsArray = [
                UIColor(white: 1.0, alpha: 1.0).cgColor,
                UIColor(white: 1.0, alpha: 1.0).cgColor,
                UIColor(white: 1.0, alpha: 0.08).cgColor,
                UIColor(white: 1.0, alpha: 0.08).cgColor
            ]
            
            let cutoffLocation = CGFloat.minimum(0.9, cutoffLocation)
            let locations: [CGFloat] = [
                0.0, cutoffLocation, cutoffLocation + 0.10, 1.0
            ]
            
            if let gradient = CGGradient(colorsSpace: nil, colors: gradientColorsArray as CFArray, locations: locations) {
                context.drawLinearGradient(gradient, start: .zero, end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions())
            }
            
            image = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext();
        }
        
        return image
    }
    
    private func setErrorButtonAnimating(_ animating: Bool) {
        let animationKey = "blinkAnimation"
        if animating {
            let blinkAnimation = CAKeyframeAnimation(keyPath: "opacity")
            blinkAnimation.calculationMode = .discrete
            blinkAnimation.values = [ 1.0, 0.1, 1.0 ]
            blinkAnimation.keyTimes = [ 0.0, 0.5, 1.0 ]
            blinkAnimation.duration = 1.0
            blinkAnimation.repeatCount = .infinity
            
            self.errorButton.layer.add(blinkAnimation, forKey: animationKey)
        } else {
            self.errorButton.layer.removeAnimation(forKey: animationKey)
        }
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        if let animationKeys = self.layer.animationKeys(), animationKeys.count > 0 {
            // Kill the shadowPath while animating, since we cannot animate shadow paths apparently. 
            shadowView.layer.shadowPath = nil
        }
        
        progressIndicatorView.frame = backgroundView.bounds
        
        // Document button
        documentButton.frame = CGRect(x: 0.0, y: 0.0, width: bounds.height, height: bounds.height)
        
        // Document separator
        documentSeparatorView.frame = CGRect(
            x: documentButton.frame.maxX, y: 0.0,
            width: 1.0, height: bounds.height
        )
        documentSeparatorView.frame = documentSeparatorView.frame.insetBy(dx: 0.0, dy: 3.0)
        
        // Text field controls
        controlsView.frame = CGRect(origin: controlsView.frame.origin, size: controlsView.sizeThatFits(bounds.size))
        
        // Text field
        let textFieldPadding: CGFloat = 6.0
        let textFieldOrigin = CGPoint(x: documentButton.frame.maxX + textFieldPadding, y: 0.0)
        textField.frame = CGRect(
            origin: textFieldOrigin,
            size: CGSize(
                width: bounds.width - textFieldOrigin.x - textFieldPadding,
                height: bounds.height
            )
        )
        
        var fadeCutoffLocation: CGFloat = 0.8
        
        // Refresh button
        let refreshButtonSize = CGSize(width: textField.frame.height, height: textField.frame.height)
        refreshButton.frame = CGRect(origin: CGPoint(x: bounds.width - refreshButtonSize.width, y: 0), size: refreshButtonSize)
        
        // Refresh vs. controls view visibility
        let isFocused = textField.isFirstResponder
        if isFocused {
            controlsView.alpha = 1.0
            refreshButton.alpha = 0.0
        } else {
            controlsView.alpha = 0.0
            refreshButton.alpha = 1.0
        }
        
        // Error button
        if case .error(error: _) = loadProgress, !isFocused {
            errorButton.isHidden = false
            errorButton.sizeToFit()
            errorButton.frame = CGRect(
                x: refreshButton.frame.minX - errorButton.frame.width - 8.0,
                y: 0.0,
                width: errorButton.frame.width + 8.0,
                height: 22.0
            )
            errorButton.frame = errorButton.frame.centeredY(inRect: bounds)
            fadeCutoffLocation = (errorButton.frame.minX / bounds.width) - 0.1
        } else {
            errorButton.isHidden = true
        }
        
        // Fade mask
        fadeMaskView.frame = textField.bounds
        fadeMaskView.image = fadeBackgroundImageForSize(fadeMaskView.frame.size, cutoffLocation: fadeCutoffLocation)
        if !isFocused {
            textField.mask = fadeMaskView
        } else {
            textField.mask = nil
        }
    }
}

class ProgressIndicatorView: UIView
{
    public var progress: Double = 0.0 {
        didSet { layoutSubviews() }
    }
    
    private let progressFillView = UIView(frame: .zero)
    
    convenience init() {
        self.init(frame: .zero)
        
        progressFillView.backgroundColor = .systemBlue
        progressFillView.alpha = 0.3
        addSubview(progressFillView)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        let width = CGFloat(progress) * bounds.width
        progressFillView.frame = CGRect(origin: .zero, size: CGSize(width: width, height: bounds.height))
    }
}
