//
//  Auto.swift
//  SwiftBilibili
//
//  Created by luowen on 2020/9/27.
//  Copyright © 2020 luowen. All rights reserved.
//

#if canImport(Foundation)

import Foundation

#if os(iOS)

import UIKit

public enum Auto {

    /// 设置转换闭包
    ///
    /// - Parameter conversion: 转换闭包
    public static func set(conversion: @escaping ((Double) -> Double)) {
        conversionClosure = conversion
    }

    /// 转换 用于数值的等比例计算 如需自定义可重新设置
    static var conversionClosure: ((Double) -> Double) = { (origin) in
        guard UIDevice.current.userInterfaceIdiom == .phone else {
            return origin
        }

        let base = 375.0
        let screenWidth = Double(UIScreen.main.bounds.width)
        let screenHeight = Double(UIScreen.main.bounds.height)
        let width = min(screenWidth, screenHeight)
        let result = origin * (width / base)
        let scale = Double(UIScreen.main.scale)
        return (result * scale).rounded(.up) / scale
    }
}

extension Auto {

    static func conversion(_ value: Double) -> Double {
        return conversionClosure(value)
    }
}

protocol AutoCalculationable {

    /// 自动计算
    ///
    /// - Returns: 结果
    func auto() -> Self
}

extension Double: AutoCalculationable {

    func auto() -> Double {
        return Auto.conversion(self)
    }
}

extension BinaryInteger {

    public func auto() -> Double {
        let temp = Double("\(self)") ?? 0
        return temp.auto()
    }
    public func auto<T>() -> T where T : BinaryInteger {
        let temp = Double("\(self)") ?? 0
        return temp.auto()
    }
    public func auto<T>() -> T where T : BinaryFloatingPoint {
        let temp = Double("\(self)") ?? 0
        return temp.auto()
    }
}

extension BinaryFloatingPoint {

    public func auto() -> Double {
        let temp = Double("\(self)") ?? 0
        return temp.auto()
    }
    public func auto<T>() -> T where T : BinaryInteger {
        let temp = Double("\(self)") ?? 0
        return T(temp.auto())
    }
    public func auto<T>() -> T where T : BinaryFloatingPoint {
        let temp = Double("\(self)") ?? 0
        return T(temp.auto())
    }
}

extension CGPoint: AutoCalculationable {

    public func auto() -> CGPoint {
        return CGPoint(x: x.auto(), y: y.auto())
    }
}

extension CGSize: AutoCalculationable {

    public func auto() -> CGSize {
        return CGSize(width: width.auto(), height: height.auto())
    }
}

extension CGRect: AutoCalculationable {

    public func auto() -> CGRect {
        return CGRect(origin: origin.auto(), size: size.auto())
    }
}

extension CGVector: AutoCalculationable {

    public func auto() -> CGVector {
        return CGVector(dx: dx.auto(), dy: dy.auto())
    }
}

extension UIOffset: AutoCalculationable {

    public func auto() -> UIOffset {
        return UIOffset(horizontal: horizontal.auto(), vertical: vertical.auto())
    }
}

extension UIEdgeInsets: AutoCalculationable {

    public func auto() -> UIEdgeInsets {
        return UIEdgeInsets(
            top: top.auto(),
            left: left.auto(),
            bottom: bottom.auto(),
            right: right.auto()
        )
    }
}

extension NSLayoutConstraint {

    @IBInspectable private var autoConstant: Bool {
        get { return false }
        set {
            guard newValue else { return }

            constant = constant.auto()
        }
    }
}

extension UIView {

    @IBInspectable private var autoCornerRadius: CGFloat {
        get { return layer.cornerRadius }
        set {
            let value: CGFloat = newValue.auto()
            layer.masksToBounds = true
            layer.cornerRadius = abs(CGFloat(Int(value * 100)) / 100)
        }
    }
}

extension UILabel {

    @IBInspectable private var autoFont: Bool {
        get { return false }
        set {
            guard newValue else { return }
            guard let text = attributedText?.mutableCopy() as? NSMutableAttributedString else {
                return
            }

            font = font.withSize(font.pointSize.auto())
            attributedText = text.reset(font: { $0.auto() })
        }
    }

    @IBInspectable private var autoLine: Bool {
        get { return false }
        set {
            guard newValue else { return }
            guard let text = attributedText else { return }

            attributedText = text.reset(line: { $0.auto() })
        }
    }

    @IBInspectable private var autoShadowOffset: Bool {
        get { return false }
        set {
            guard newValue else { return }

            shadowOffset = shadowOffset.auto()
        }
    }
}

extension UITextView {

    @IBInspectable private var autoFont: Bool {
        get { return false }
        set {
            guard newValue else { return }
            guard let font = font else { return }

            self.font = font.withSize(font.pointSize.auto())
        }
    }
}

extension UITextField {

    @IBInspectable private var autoFont: Bool {
        get { return false }
        set {
            guard newValue else { return }
            guard let font = font else { return }

            self.font = font.withSize(font.pointSize.auto())
        }
    }
}

extension UIImageView {

    @IBInspectable private var autoImage: Bool {
        get { return false }
        set {
            guard newValue else { return }

            if let width = image?.size.width {
                image = image?.scaled(to: width.auto())
            }
            if let width = highlightedImage?.size.width {
                highlightedImage = highlightedImage?.scaled(to: width.auto())
            }
        }
    }
}

extension UIButton {

    @IBInspectable private var autoTitle: Bool {
        get { return false }
        set {
            guard newValue else { return }

            let states: [UIControl.State] = [
                .normal,
                .highlighted,
                .selected,
                .disabled
            ]

            if
                title(for: state) != nil,
                let label = titleLabel,
                let font = label.font {
                label.font = font.withSize(font.pointSize.auto())
            }

            let titles = states.enumerated()
                .compactMap { (i, state) -> (Int,NSAttributedString)? in
                guard let t = attributedTitle(for: state) else { return nil }
                return (i, t)
            }
            titles.filtered(duplication: { $0.1 }).forEach {
                setAttributedTitle(
                    $0.1.reset(font: { $0.auto() }),
                    for: states[$0.0]
                )
            }
        }
    }

    @IBInspectable private var autoImage: Bool {
        get { return false }
        set {
            guard newValue else { return }

            let states: [UIControl.State] = [
                .normal,
                .highlighted,
                .selected,
                .disabled
            ]

            let images = states.enumerated()
                .compactMap { (i, state) -> (Int, UIImage)? in
                guard let v = image(for: state) else { return nil }
                return (i, v)
            }
            images.filtered(duplication: { $0.1 }).forEach {
                setImage(
                    $0.1.scaled(to: $0.1.size.width.auto()),
                    for: states[$0.0]
                )
            }

            let backgrounds = states.enumerated()
                .compactMap { (i, state) -> (Int, UIImage)? in
                guard let v = backgroundImage(for: state) else { return nil }
                return (i, v)
             }
            backgrounds.filtered(duplication: { $0.1 }).forEach {
                setBackgroundImage(
                    $0.1.scaled(to: $0.1.size.width.auto()),
                    for: states[$0.0]
                )
            }
        }
    }

    @IBInspectable private var autoTitleInsets: Bool {
        get { return false }
        set {
            guard newValue else { return }

            titleEdgeInsets = titleEdgeInsets.auto()
        }
    }

    @IBInspectable private var autoImageInsets: Bool {
        get { return false }
        set {
            guard newValue else { return }

            imageEdgeInsets = imageEdgeInsets.auto()
        }
    }

    @IBInspectable private var autoContentInsets: Bool {
        get { return false }
        set {
            guard newValue else { return }

            contentEdgeInsets = contentEdgeInsets.auto()
        }
    }
}

@available(iOS 9.0, *)
extension UIStackView {

    @IBInspectable private var autoSpacing: Bool {
        get { return false }
        set {
            guard newValue else { return }

            spacing = spacing.auto()
        }
    }
}

fileprivate extension NSAttributedString {

    func reset(font size: (CGFloat) -> CGFloat) -> NSAttributedString {
        let string = NSMutableAttributedString(attributedString: self)
        enumerateAttributes(
            in: NSRange(location: 0, length: length),
            options: .longestEffectiveRangeNotRequired
        ) { (attributes, range, _) in
            var temp = attributes
            if let font = attributes[.font] as? UIFont {
                temp[.font] = font.withSize(size(font.pointSize))
            }
            string.setAttributes(temp, range: range)
        }
        return string
    }

    func reset(line spacing: (CGFloat) -> CGFloat) -> NSAttributedString {
        let string = NSMutableAttributedString(attributedString: self)
        enumerateAttributes(
            in: NSRange(location: 0, length: length),
            options: .longestEffectiveRangeNotRequired
        ) { (attributes, range, _) in
            var temp = attributes
            if let paragraph = attributes[.paragraphStyle] as? NSMutableParagraphStyle {
                paragraph.lineSpacing = spacing(paragraph.lineSpacing)
                temp[.paragraphStyle] = paragraph
            }
            string.setAttributes(temp, range: range)
        }
        return string
    }
}

fileprivate extension UIImage {

    func scaled(to width: CGFloat, opaque: Bool = false) -> UIImage? {
        guard self.size.width > 0 else {
            return nil
        }

        let scale = width / self.size.width
        let size = CGSize(width: width, height: self.size.height * scale)
        UIGraphicsBeginImageContextWithOptions(size, false, 0)
        draw(in: CGRect(origin: .zero, size: size))
        let new = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return new
    }
}

fileprivate extension Array {

    func filtered<E: Equatable>(duplication closure: (Element) throws -> E) rethrows -> [Element] {
        return try reduce(into: [Element]()) { (result, e) in
            let contains = try result.contains { try closure($0) == closure(e) }
            result += contains ? [] : [e]
        }
    }
}

public extension Double {

    func rounded(_ decimalPlaces: Int) -> Double {
        let divisor = pow(10.0, Double(max(0, decimalPlaces)))
        return (self * divisor).rounded() / divisor
    }
}

public extension BinaryFloatingPoint {

    func rounded(_ decimalPlaces: Int) -> Double {
        let temp = Double("\(self)") ?? 0
        return temp.rounded(decimalPlaces)
    }

    func rounded<T>(_ decimalPlaces: Int) -> T where T: BinaryFloatingPoint {
        let temp = Double("\(self)") ?? 0
        return T(temp.rounded(decimalPlaces))
    }
}

public extension CGPoint {

    func rounded(_ decimalPlaces: Int) -> CGPoint {
        return CGPoint(x: x.rounded(decimalPlaces), y: y.rounded(decimalPlaces))
    }
}

public extension CGSize {

    func rounded(_ decimalPlaces: Int) -> CGSize {
        return CGSize(width: width.rounded(decimalPlaces), height: height.rounded(decimalPlaces))
    }
}

public extension CGRect {

    func rounded(_ decimalPlaces: Int) -> CGRect {
        return CGRect(origin: origin.rounded(decimalPlaces), size: size.rounded(decimalPlaces))
    }
}

public extension UIEdgeInsets {

    func rounded(_ decimalPlaces: Int) -> UIEdgeInsets {
        return UIEdgeInsets(
            top: top.rounded(decimalPlaces),
            left: left.rounded(decimalPlaces),
            bottom: bottom.rounded(decimalPlaces),
            right: right.rounded(decimalPlaces)
        )
    }
}

#endif

#endif
