//
//  AttachmentsContainerView.swift
//  Tusker
//
//  Created by Shadowfacts on 6/16/19.
//  Copyright © 2019 Shadowfacts. All rights reserved.
//

import UIKit
import Pachyderm

class AttachmentsContainerView: UIView {
    
    weak var delegate: AttachmentViewDelegate?
    
    private var attachmentTokens: [AttachmentToken] = []
    var attachments: [Attachment]!
    
    let attachmentViews: NSHashTable<AttachmentView> = .weakObjects()
    let attachmentStacks: NSHashTable<UIStackView> = .weakObjects()
    var moreView: UIView?
    private var aspectRatioConstraint: NSLayoutConstraint?
    private(set) var aspectRatio: CGFloat = 16/9 {
        didSet {
            if aspectRatio != aspectRatioConstraint?.multiplier {
                aspectRatioConstraint?.isActive = false
                aspectRatioConstraint = widthAnchor.constraint(equalTo: heightAnchor, multiplier: aspectRatio)
                aspectRatioConstraint!.isActive = true
            }
        }
    }
    
    var blurView: UIVisualEffectView?
    var hideButtonView: UIVisualEffectView?
    var contentHidden: Bool! {
        didSet {
            guard let blurView = blurView,
                let hideButtonView = hideButtonView else { return }
            
            blurView.alpha = self.contentHidden ? 1 : 0
            hideButtonView.alpha = self.contentHidden ? 0 : 1
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    private func commonInit() {
        self.isUserInteractionEnabled = true
        self.layer.cornerRadius = 5
        self.layer.masksToBounds = true
        
        createBlurView()
        createHideButton()
    }
    
    func getAttachmentView(for attachment: Attachment) -> AttachmentView? {
        return attachmentViews.allObjects.first { $0.attachment.id == attachment.id }
    }
    
    // MARK: - User Interaface
    
    func updateUI(attachments: [Attachment]) {
        let newTokens = attachments.map { AttachmentToken(attachment: $0) }
        
        guard self.attachmentTokens != newTokens else {
            return
        }
        
        self.attachments = attachments
        self.attachmentTokens = newTokens
        
        attachmentViews.allObjects.forEach { $0.removeFromSuperview() }
        attachmentViews.removeAllObjects()
        attachmentStacks.allObjects.forEach { $0.removeFromSuperview() }
        attachmentStacks.removeAllObjects()
        moreView?.removeFromSuperview()
        
        var accessibilityElements = [Any]()

        if attachments.count > 0 {
            self.isHidden = false
            
            var aspectRatio: CGFloat = 16/9
            
            switch attachments.count {
            case 1:
                let attachmentView = createAttachmentView(index: 0, hSize: .full, vSize: .full)
                attachmentView.layer.cornerRadius = 5
                attachmentView.layer.cornerCurve = .continuous
                attachmentView.layer.masksToBounds = true
                fillView(attachmentView)
                sendSubviewToBack(attachmentView)
                accessibilityElements.append(attachmentView)
                if Preferences.shared.showUncroppedMediaInline,
                   let attachmentAspectRatio = attachmentView.attachmentAspectRatio {
                    // clamp to prevent excessively short/tall attachments
                    aspectRatio = max(min(attachmentAspectRatio, 2/1), 1/2)
                }
            case 2:
                let left = createAttachmentView(index: 0, hSize: .half, vSize: .full)
                left.layer.cornerRadius = 5
                left.layer.cornerCurve = .continuous
                left.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
                left.layer.masksToBounds = true
                let right = createAttachmentView(index: 1, hSize: .half, vSize: .full)
                right.layer.cornerRadius = 5
                right.layer.cornerCurve = .continuous
                right.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
                right.layer.masksToBounds = true
                let stack = createAttachmentsStack(axis: .horizontal, arrangedSubviews: [
                    left,
                    right
                ])
                attachmentStacks.add(stack)
                fillView(stack)
                sendSubviewToBack(stack)
                NSLayoutConstraint.activate([
                    left.halfWidth()
                ])
                accessibilityElements.append(left)
                accessibilityElements.append(right)
            case 3:
                let left = createAttachmentView(index: 0, hSize: .half, vSize: .full)
                left.layer.cornerRadius = 5
                left.layer.cornerCurve = .continuous
                left.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
                left.layer.masksToBounds = true
                let topRight = createAttachmentView(index: 1, hSize: .half, vSize: .half)
                topRight.layer.cornerRadius = 5
                topRight.layer.cornerCurve = .continuous
                topRight.layer.maskedCorners = .layerMaxXMinYCorner
                topRight.layer.masksToBounds = true
                let bottomRight = createAttachmentView(index: 2, hSize: .half, vSize: .half)
                bottomRight.layer.cornerRadius = 5
                bottomRight.layer.cornerCurve = .continuous
                bottomRight.layer.maskedCorners = .layerMaxXMaxYCorner
                bottomRight.layer.masksToBounds = true
                let innerStack = createAttachmentsStack(axis: .vertical, arrangedSubviews: [
                    topRight,
                    bottomRight
                ])
                attachmentStacks.add(innerStack)
                let outerStack = createAttachmentsStack(axis: .horizontal, arrangedSubviews: [
                    left,
                    innerStack,
                ])
                attachmentStacks.add(outerStack)
                fillView(outerStack)
                sendSubviewToBack(outerStack)
                NSLayoutConstraint.activate([
                    left.halfWidth(),
                    topRight.halfHeight(),
                ])
                accessibilityElements.append(left)
                accessibilityElements.append(topRight)
                accessibilityElements.append(bottomRight)
            case 4:
                let topLeft = createAttachmentView(index: 0, hSize: .half, vSize: .half)
                topLeft.layer.cornerRadius = 5
                topLeft.layer.cornerCurve = .continuous
                topLeft.layer.maskedCorners = .layerMinXMinYCorner
                topLeft.layer.masksToBounds = true
                let bottomLeft = createAttachmentView(index: 2, hSize: .half, vSize: .half)
                bottomLeft.layer.cornerRadius = 5
                bottomLeft.layer.cornerCurve = .continuous
                bottomLeft.layer.maskedCorners = .layerMinXMaxYCorner
                bottomLeft.layer.masksToBounds = true
                let left = createAttachmentsStack(axis: .vertical, arrangedSubviews: [
                    topLeft,
                    bottomLeft
                ])
                attachmentStacks.add(left)
                let topRight = createAttachmentView(index: 1, hSize: .half, vSize: .half)
                topRight.layer.cornerRadius = 5
                topRight.layer.cornerCurve = .continuous
                topRight.layer.maskedCorners = .layerMaxXMinYCorner
                topRight.layer.masksToBounds = true
                let bottomRight = createAttachmentView(index: 3, hSize: .half, vSize: .half)
                bottomRight.layer.cornerRadius = 5
                bottomRight.layer.cornerCurve = .continuous
                bottomRight.layer.maskedCorners = .layerMaxXMaxYCorner
                bottomRight.layer.masksToBounds = true
                let right = createAttachmentsStack(axis: .vertical, arrangedSubviews: [
                    topRight,
                    bottomRight
                ])
                attachmentStacks.add(right)
                let outerStack = createAttachmentsStack(axis: .horizontal, arrangedSubviews: [
                    left,
                    right,
                ])
                attachmentStacks.add(outerStack)
                fillView(outerStack)
                sendSubviewToBack(outerStack)
                NSLayoutConstraint.activate([
                    left.halfWidth(),
                    topLeft.halfHeight(),
                    topRight.halfHeight(),
                ])
                accessibilityElements.append(topLeft)
                accessibilityElements.append(topRight)
                accessibilityElements.append(bottomLeft)
                accessibilityElements.append(bottomRight)
            default: // more than 4
                let moreView = UIView()
                self.moreView = moreView
                moreView.backgroundColor = .secondarySystemBackground
                moreView.translatesAutoresizingMaskIntoConstraints = false
                moreView.isUserInteractionEnabled = true
                moreView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(moreViewTapped)))
                moreView.layer.cornerRadius = 5
                moreView.layer.cornerCurve = .continuous
                moreView.layer.maskedCorners = .layerMaxXMaxYCorner
                moreView.layer.masksToBounds = true
                let moreLabel = UILabel()
                moreLabel.text = "\(attachments.count - 3) more..."
                moreLabel.textColor = .secondaryLabel
                moreLabel.textAlignment = .center
                moreLabel.translatesAutoresizingMaskIntoConstraints = false
                moreView.addSubview(moreLabel)
                moreView.accessibilityLabel = moreLabel.text

                let topLeft = createAttachmentView(index: 0, hSize: .half, vSize: .half)
                topLeft.layer.cornerRadius = 5
                topLeft.layer.cornerCurve = .continuous
                topLeft.layer.maskedCorners = .layerMinXMinYCorner
                topLeft.layer.masksToBounds = true
                let bottomLeft = createAttachmentView(index: 2, hSize: .half, vSize: .half)
                bottomLeft.layer.cornerRadius = 5
                bottomLeft.layer.maskedCorners = .layerMinXMaxYCorner
                bottomLeft.layer.masksToBounds = true
                let left = createAttachmentsStack(axis: .vertical, arrangedSubviews: [
                    topLeft,
                    bottomLeft
                ])
                attachmentStacks.add(left)
                let topRight = createAttachmentView(index: 1, hSize: .half, vSize: .half)
                topRight.layer.cornerRadius = 5
                topRight.layer.cornerCurve = .continuous
                topRight.layer.maskedCorners = .layerMaxXMinYCorner
                topRight.layer.masksToBounds = true
                let right = createAttachmentsStack(axis: .vertical, arrangedSubviews: [
                    topRight,
                    moreView
                ])
                attachmentStacks.add(right)
                let outerStack = createAttachmentsStack(axis: .horizontal, arrangedSubviews: [
                    left,
                    right,
                ])
                attachmentStacks.add(outerStack)
                fillView(outerStack)
                sendSubviewToBack(outerStack)
                NSLayoutConstraint.activate([
                    left.halfWidth(),
                    topLeft.halfHeight(),
                    topRight.halfHeight(),
                    moreView.leadingAnchor.constraint(equalTo: moreLabel.leadingAnchor),
                    moreLabel.trailingAnchor.constraint(equalTo: moreView.trailingAnchor),
                    moreView.topAnchor.constraint(equalTo: moreLabel.topAnchor),
                    moreLabel.bottomAnchor.constraint(equalTo: moreView.bottomAnchor),
                ])
                accessibilityElements.append(topLeft)
                accessibilityElements.append(topRight)
                accessibilityElements.append(bottomLeft)
                accessibilityElements.append(moreView)
            }
            
            self.aspectRatio = aspectRatio
        } else {
            self.isHidden = true
        }
        
        // Make sure accessibilityElements is set every time the UI is updated, otherwise it holds
        // on to strong references to the old set of attachment views
        self.accessibilityElements = accessibilityElements
    }
    
    private func createAttachmentView(index: Int, hSize: RelativeSize, vSize: RelativeSize) -> AttachmentView {
        let attachmentView = AttachmentView(attachment: attachments[index], index: index)
        attachmentView.delegate = delegate
        attachmentView.translatesAutoresizingMaskIntoConstraints = false
        attachmentView.accessibilityLabel = String(format: NSLocalizedString("Attachment %d", comment: "attachment at index accessiblity label"), index + 1)
        attachmentView.accessibilityLabel = "Attachment \(index + 1)"
        if let desc = attachments[index].description {
            attachmentView.accessibilityLabel! += ", \(desc)"
        }
        attachmentViews.add(attachmentView)
        return attachmentView
    }
    
    private func createAttachmentsStack(axis: NSLayoutConstraint.Axis, arrangedSubviews: [UIView]) -> UIStackView {
        let stack = UIStackView(arrangedSubviews: arrangedSubviews)
        stack.axis = axis
        stack.spacing = 4
        stack.translatesAutoresizingMaskIntoConstraints = false
        return stack
    }
    
    private func createBlurView() {
        let blur = UIBlurEffect(style: .dark)
        let blurView = UIVisualEffectView(effect: blur)
        blurView.alpha = 0
        blurView.translatesAutoresizingMaskIntoConstraints = false
        fillView(blurView)
        let vibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: blur, style: .label))
        vibrancyView.translatesAutoresizingMaskIntoConstraints = false
        fillView(vibrancyView, in: blurView.contentView)
        blurView.contentView.addSubview(vibrancyView)
        
        let image = UIImage(systemName: "eye")!
        let imageView = UIImageView(image: image)
        imageView.translatesAutoresizingMaskIntoConstraints = false
        let label = UILabel()
        label.font = .preferredFont(forTextStyle: .body)
        label.adjustsFontForContentSizeCategory = true
        label.text = "Sensitive Content"
        let stack = UIStackView(arrangedSubviews: [
            imageView,
            label
        ])
        stack.axis = .vertical
        stack.alignment = .center
        stack.translatesAutoresizingMaskIntoConstraints = false
        vibrancyView.contentView.addSubview(stack)
        NSLayoutConstraint.activate([
            imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor, multiplier: image.size.width / image.size.height),
            imageView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.2),
            stack.centerXAnchor.constraint(equalTo: centerXAnchor),
            stack.centerYAnchor.constraint(equalTo: centerYAnchor),
            stack.widthAnchor.constraint(equalTo: widthAnchor)
        ])
        
        self.blurView = blurView
        
        blurView.isUserInteractionEnabled = true
        blurView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(blurViewTapped)))
    }
    
    private func createHideButton() {
        let blurEffect = UIBlurEffect(style: .regular)
        let hideButtonBlurView = UIVisualEffectView(effect: blurEffect)
        hideButtonBlurView.translatesAutoresizingMaskIntoConstraints = false
        hideButtonBlurView.alpha = 1
        hideButtonBlurView.isUserInteractionEnabled = true
        hideButtonBlurView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(hideButtonTapped)))
        addSubview(hideButtonBlurView)
        self.hideButtonView = hideButtonBlurView
        
        let maskLayer = CALayer()
        let image = UIImage(systemName: "eye.slash.fill")!
        maskLayer.contents = image.cgImage!
        maskLayer.frame = CGRect(origin: .zero, size: image.size)
        hideButtonBlurView.layer.mask = maskLayer

        let hideButtonVibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: blurEffect, style: .label))
        hideButtonVibrancyView.translatesAutoresizingMaskIntoConstraints = false
        hideButtonBlurView.contentView.addSubview(hideButtonVibrancyView)
        let fillView = UIView()
        fillView.translatesAutoresizingMaskIntoConstraints = false
        fillView.backgroundColor = UIColor(displayP3Red: 0, green: 0, blue: 0, alpha: 0.5)
        hideButtonVibrancyView.contentView.addSubview(fillView)

        NSLayoutConstraint.activate([
            hideButtonBlurView.topAnchor.constraint(equalTo: topAnchor, constant: 8),
            hideButtonBlurView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
            hideButtonBlurView.widthAnchor.constraint(equalToConstant: image.size.width),
            hideButtonBlurView.heightAnchor.constraint(equalToConstant: image.size.height),
            hideButtonVibrancyView.leadingAnchor.constraint(equalTo: hideButtonBlurView.contentView.leadingAnchor),
            hideButtonVibrancyView.trailingAnchor.constraint(equalTo: hideButtonBlurView.contentView.trailingAnchor),
            hideButtonVibrancyView.topAnchor.constraint(equalTo: hideButtonBlurView.contentView.topAnchor),
            hideButtonVibrancyView.bottomAnchor.constraint(equalTo: hideButtonBlurView.contentView.bottomAnchor),
            fillView.leadingAnchor.constraint(equalTo: hideButtonBlurView.contentView.leadingAnchor),
            fillView.trailingAnchor.constraint(equalTo: hideButtonBlurView.contentView.trailingAnchor),
            fillView.topAnchor.constraint(equalTo: hideButtonBlurView.contentView.topAnchor),
            fillView.bottomAnchor.constraint(equalTo: hideButtonBlurView.contentView.bottomAnchor),
        ])
    }
    
    private func fillView(_ view: UIView, in parentView: UIView? = nil) {
        let parentView = parentView ?? self
        parentView.addSubview(view)
        NSLayoutConstraint.activate([
            view.leadingAnchor.constraint(equalTo: parentView.leadingAnchor),
            view.trailingAnchor.constraint(equalTo: parentView.trailingAnchor),
            view.topAnchor.constraint(equalTo: parentView.topAnchor),
            view.bottomAnchor.constraint(equalTo: parentView.bottomAnchor)
        ])
    }
    
    // MARK: - Interaction
    
    @objc func blurViewTapped() {
        UIView.animate(withDuration: 0.2) {
            self.contentHidden = false
        }
    }
    
    @objc func hideButtonTapped() {
        UIView.animate(withDuration: 0.2) {
            self.contentHidden = true
        }
    }
    
    @objc func showSensitiveContent() {
        guard let blurView = blurView else { return }
        
        blurView.alpha = 1
        UIView.animate(withDuration: 0.2) {
            blurView.alpha = 0
        }
    }
    
    @objc func hideSensitiveContent() {
        guard let blurView = self.blurView else { return }
        
        blurView.alpha = 0
        UIView.animate(withDuration: 0.2) {
            blurView.alpha = 1
        }
    }
    
    @objc func moreViewTapped() {
        guard attachments.count > 4 else { return }
        // the more view shows up in place of the fourth attachemtn view, show tapping it should start at the fourth attachment
        if let delegate = delegate,
           let gallery = delegate.attachmentViewGallery(startingAt: 3) {
            delegate.attachmentViewPresent(gallery, animated: true)
        }
    }

}

fileprivate enum RelativeSize {
    case full, half
    
    var multiplier: CGFloat {
        switch self {
        case .full:
            return 1
        case .half:
            return 0.5
        }
    }
}

fileprivate extension UIView {
    func halfWidth(spacing: CGFloat = 4) -> NSLayoutConstraint {
        return widthAnchor.constraint(equalTo: superview!.widthAnchor, multiplier: 0.5, constant: -spacing / 2)
    }
    
    func halfHeight(spacing: CGFloat = 4) -> NSLayoutConstraint {
        return heightAnchor.constraint(equalTo: superview!.heightAnchor, multiplier: 0.5, constant: -spacing / 2)
    }
}

// A token that represents properties of attachments that the container needs to take into account when deciding whether to update
fileprivate struct AttachmentToken: Equatable {
    let url: URL
    // to show the alt badge or not
    let hasDescription: Bool
    
    init(attachment: Attachment) {
        self.url = attachment.url
        self.hasDescription = attachment.description != nil
    }
}
