import UIKit

public final class LNCyclePageControl: UIControl {
    public var numberOfPages: Int = 0 {
        didSet {
            guard numberOfPages != oldValue else { return }
            if currentPage >= numberOfPages {
                currentPage = 0
            }
            updateIndicatorViews()
            if !indicatorViews.isEmpty { setNeedsLayout() }
        }
    }

    public var currentPage: Int = 0 {
        didSet {
            guard oldValue != currentPage, currentPage < indicatorViews.count else { return }
            if currentPage < 0 { currentPage = 0; return }
            if !currentPageIndicatorSize.equalTo(pageIndicatorSize) {
                setNeedsLayout()
            }
            updateIndicatorViewsBehavior()
            if isUserInteractionEnabled {
                sendActions(for: .valueChanged)
            }
        }
    }

    public var hidesForSinglePage = false

    public var pageIndicatorSpacing: CGFloat = 10 {
        didSet {
            guard oldValue != pageIndicatorSpacing else { return }
            if !indicatorViews.isEmpty { setNeedsLayout() }
        }
    }

    public var contentInset: UIEdgeInsets = .zero {
        didSet { setNeedsLayout() }
    }

    public var contentSize: CGSize {
        let count = indicatorViews.count
        let width = CGFloat(max(count - 1, 0)) * (pageIndicatorSize.width + pageIndicatorSpacing) + pageIndicatorSize.width + contentInset.left + contentInset.right
        let height = currentPageIndicatorSize.height + contentInset.top + contentInset.bottom
        return CGSize(width: width, height: height)
    }

    public var pageIndicatorTintColor: UIColor? = UIColor(white: 0.5, alpha: 1) {
        didSet { updateIndicatorViewsBehavior() }
    }

    public var currentPageIndicatorTintColor: UIColor? = .white {
        didSet { updateIndicatorViewsBehavior() }
    }

    public var pageIndicatorImage: UIImage? {
        didSet { updateIndicatorViewsBehavior() }
    }

    public var currentPageIndicatorImage: UIImage? {
        didSet { updateIndicatorViewsBehavior() }
    }

    public var indicatorImageContentMode: UIView.ContentMode = .center {
        didSet {
            indicatorViews.forEach { $0.contentMode = indicatorImageContentMode }
        }
    }

    public var pageIndicatorSize: CGSize = CGSize(width: 6, height: 6) {
        didSet {
            guard !pageIndicatorSize.equalTo(oldValue) else { return }
            if currentPageIndicatorSize == .zero || (currentPageIndicatorSize.width < pageIndicatorSize.width && currentPageIndicatorSize.height < pageIndicatorSize.height) {
                currentPageIndicatorSize = pageIndicatorSize
            }
            if !indicatorViews.isEmpty { setNeedsLayout() }
        }
    }

    public var currentPageIndicatorSize: CGSize = CGSize(width: 6, height: 6) {
        didSet {
            guard !currentPageIndicatorSize.equalTo(oldValue) else { return }
            if !indicatorViews.isEmpty { setNeedsLayout() }
        }
    }

    public var animateDuration: TimeInterval = 0.3

    private var indicatorViews: [UIImageView] = []
    private var forceUpdate = false

    public override init(frame: CGRect) {
        super.init(frame: frame)
        configure()
    }

    public required init?(coder: NSCoder) {
        super.init(coder: coder)
        configure()
    }

    private func configure() {
        isUserInteractionEnabled = false
        currentPageIndicatorSize = pageIndicatorSize
    }

    public override func willMove(toSuperview newSuperview: UIView?) {
        super.willMove(toSuperview: newSuperview)
        if newSuperview != nil {
            forceUpdate = true
            updateIndicatorViews()
            forceUpdate = false
        }
    }

    public func setCurrentPage(_ page: Int, animate: Bool) {
        if animate {
            UIView.animate(withDuration: animateDuration) {
                self.currentPage = page
                self.layoutIfNeeded()
            }
        } else {
            currentPage = page
        }
    }

    public override func layoutSubviews() {
        super.layoutSubviews()
        layoutIndicatorViews()
    }

    private func updateIndicatorViews() {
        if superview == nil, !forceUpdate { return }

        if indicatorViews.count < numberOfPages {
            for _ in indicatorViews.count..<numberOfPages {
                let iv = UIImageView()
                iv.contentMode = indicatorImageContentMode
                addSubview(iv)
                indicatorViews.append(iv)
            }
        } else if indicatorViews.count > numberOfPages {
            for idx in stride(from: indicatorViews.count - 1, through: numberOfPages, by: -1) {
                indicatorViews[idx].removeFromSuperview()
                indicatorViews.remove(at: idx)
            }
        }

        updateIndicatorViewsBehavior()
    }

    private func updateIndicatorViewsBehavior() {
        guard !indicatorViews.isEmpty else { return }
        if superview == nil, !forceUpdate { return }

        if hidesForSinglePage, indicatorViews.count == 1 {
            indicatorViews[0].isHidden = true
            return
        }

        for (idx, view) in indicatorViews.enumerated() {
            if let pageIndicatorImage {
                view.contentMode = indicatorImageContentMode
                view.image = (idx == currentPage) ? currentPageIndicatorImage : pageIndicatorImage
                view.backgroundColor = .clear
            } else {
                view.image = nil
                view.backgroundColor = (idx == currentPage) ? currentPageIndicatorTintColor : pageIndicatorTintColor
            }
            view.isHidden = false
        }
    }

    private func layoutIndicatorViews() {
        guard !indicatorViews.isEmpty else { return }

        var originX: CGFloat = 0
        var centerY: CGFloat = 0
        var spacing = pageIndicatorSpacing

        switch contentHorizontalAlignment {
        case .center:
            originX = (bounds.width - CGFloat(indicatorViews.count - 1) * (pageIndicatorSize.width + pageIndicatorSpacing) - currentPageIndicatorSize.width) * 0.5
        case .left:
            originX = contentInset.left
        case .right:
            originX = bounds.width - (CGFloat(indicatorViews.count - 1) * (pageIndicatorSize.width + pageIndicatorSpacing) + currentPageIndicatorSize.width) - contentInset.right
        case .fill:
            originX = contentInset.left
            if indicatorViews.count > 1 {
                spacing = (bounds.width - contentInset.left - contentInset.right - pageIndicatorSize.width - CGFloat(indicatorViews.count - 1) * pageIndicatorSize.width) / CGFloat(indicatorViews.count - 1)
            }
        default:
            break
        }

        switch contentVerticalAlignment {
        case .center:
            centerY = bounds.height * 0.5
        case .top:
            centerY = contentInset.top + currentPageIndicatorSize.height * 0.5
        case .bottom:
            centerY = bounds.height - currentPageIndicatorSize.height * 0.5 - contentInset.bottom
        case .fill:
            centerY = (bounds.height - contentInset.top - contentInset.bottom) * 0.5 + contentInset.top
        default:
            break
        }

        for (idx, view) in indicatorViews.enumerated() {
            let size = (idx == currentPage) ? currentPageIndicatorSize : pageIndicatorSize
            view.layer.cornerRadius = (pageIndicatorImage == nil) ? (size.height * 0.5) : 0
            view.clipsToBounds = true
            view.frame = CGRect(x: originX, y: centerY - size.height * 0.5, width: size.width, height: size.height)
            originX += size.width + spacing
        }
    }
}
