//
//  Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//

import Foundation
import MediaPlayer
import YYImage
import NVActivityIndicatorView
import SessionUIKit

public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
    public enum Mode: UInt {
        case large
        case small
        case attachmentApproval
    }

    // MARK: Properties

    public let mode: Mode
    public let attachment: SignalAttachment

    public var audioPlayer: OWSAudioPlayer?
    
    private var linkPreviewInfo: (url: String, draft: OWSLinkPreviewDraft?)?
    

    public var playbackState = AudioPlaybackState.stopped {
        didSet {
            AssertIsOnMainThread()

            ensureButtonState()
        }
    }

    public var audioProgressSeconds: CGFloat = 0
    public var audioDurationSeconds: CGFloat = 0

    public var contentView: UIView?
    
    

    // MARK: Initializers

    @available(*, unavailable, message:"use other constructor instead.")
    required public init?(coder aDecoder: NSCoder) {
        notImplemented()
    }

    // Currently we only use one mode (AttachmentApproval), so we could simplify this class, but it's kind
    // of nice that it's written in a flexible way in case we'd want to use it elsewhere again in the future.
    public required init(attachment: SignalAttachment, mode: MediaMessageView.Mode) {
        if attachment.hasError { owsFailDebug(attachment.error.debugDescription) }
        
        self.attachment = attachment
        self.mode = mode
        
        super.init(frame: CGRect.zero)

        createViews()
        
        
        setupLayout()
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
    // MARK: - UI
    
    private lazy var stackView: UIStackView = {
        let stackView: UIStackView = UIStackView()
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.axis = .vertical
        stackView.alignment = .center
        stackView.distribution = .equalSpacing
        
        switch mode {
            case .attachmentApproval: stackView.spacing = 2
            case .large: stackView.spacing = 10
            case .small: stackView.spacing = 5
        }
        
        return stackView
    }()
    
    private lazy var loadingView: NVActivityIndicatorView = {
        let view: NVActivityIndicatorView = NVActivityIndicatorView(frame: CGRect.zero, type: .circleStrokeSpin, color: Colors.text, padding: nil)
        view.translatesAutoresizingMaskIntoConstraints = false
        view.isHidden = true
        
        return view
    }()
    
    private lazy var imageView: UIImageView = {
        let view: UIImageView = UIImageView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.contentMode = .scaleAspectFit
        view.layer.minificationFilter = .trilinear
        view.layer.magnificationFilter = .trilinear
        
        return view
    }()
    
    private lazy var fileTypeImageView: UIImageView = {
        let view: UIImageView = UIImageView()
        view.translatesAutoresizingMaskIntoConstraints = false
        
        return view
    }()
    
    private lazy var animatedImageView: YYAnimatedImageView = {
        let view: YYAnimatedImageView = YYAnimatedImageView()
        view.translatesAutoresizingMaskIntoConstraints = false
        
        return view
    }()
    
    lazy var videoPlayButton: UIImageView = {
        let imageView: UIImageView = UIImageView(image: UIImage(named: "CirclePlay"))
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.contentMode = .scaleAspectFit
        
        return imageView
    }()
    
    /// Note: This uses different assets from the `videoPlayButton` and has a 'Pause' state
    private lazy var audioPlayPauseButton: UIButton = {
        let button: UIButton = UIButton()
        button.translatesAutoresizingMaskIntoConstraints = false
        button.clipsToBounds = true
        button.setBackgroundImage(UIColor.white.toImage(), for: .normal)
        button.setBackgroundImage(UIColor.white.darken(by: 0.2).toImage(), for: .highlighted)
        button.addTarget(self, action: #selector(audioPlayPauseButtonPressed), for: .touchUpInside)
        
        return button
    }()
    
    private lazy var titleLabel: UILabel = {
        let label: UILabel = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.textAlignment = .center
        label.lineBreakMode = .byTruncatingMiddle
        
        if let fileName: String = attachment.sourceFilename?.trimmingCharacters(in: .whitespacesAndNewlines), fileName.count > 0 {
            label.text = fileName
        }
        else if let fileExtension: String = attachment.fileExtension {
            label.text = String(
                format: "ATTACHMENT_APPROVAL_FILE_EXTENSION_FORMAT".localized(),
                fileExtension.uppercased()
            )
        }
        
        label.isHidden = ((label.text?.count ?? 0) == 0)
        
        switch mode {
            case .attachmentApproval:
                label.font = UIFont.ows_boldFont(withSize: ScaleFromIPhone5To7Plus(16, 22))
                label.textColor = Colors.text
                
            case .large:
                label.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(18, 24))
                label.textColor = Colors.accent
                
            case .small:
                label.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(14, 14))
                label.textColor = Colors.accent
        }
        
        return label
    }()
    
    private lazy var fileSizeLabel: UILabel = {
        let fileSize: UInt = attachment.dataLength
        
        let label: UILabel = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        // Format string for file size label in call interstitial view.
        // Embeds: {{file size as 'N mb' or 'N kb'}}.
        label.text = String(format: "ATTACHMENT_APPROVAL_FILE_SIZE_FORMAT".localized(), OWSFormat.formatFileSize(UInt(fileSize)))
        label.textAlignment = .center
        
        switch mode {
            case .attachmentApproval:
                label.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(12, 18))
                label.textColor = Colors.pinIcon
                
            case .large:
                label.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(18, 24))
                label.textColor = Colors.accent
                
            case .small:
                label.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(14, 14))
                label.textColor = Colors.accent
        }
        
        return label
    }()
    
    // MARK: - Layout

    private func createViews() {
        if attachment.isAnimatedImage {
            createAnimatedPreview()
        } else if attachment.isImage {
            createImagePreview()
        } else if attachment.isVideo {
            createVideoPreview()
        } else if attachment.isAudio {
            createAudioPreview()
        } else if attachment.isUrl {
            createUrlPreview()
        } else if attachment.isText {
            // Do nothing as we will just put the text in the 'message' input
        } else {
            createGenericPreview()
        }
    }
    
    private func setupLayout() {
        // Bottom inset
    }

    // TODO: Any reason for not just using UIStackView
    private func wrapViewsInVerticalStack(subviews: [UIView]) -> UIView {
        assert(subviews.count > 0)

        let stackView = UIView()

        var lastView: UIView?
        for subview in subviews {

            stackView.addSubview(subview)
            subview.autoHCenterInSuperview()

            if lastView == nil {
                subview.autoPinEdge(toSuperviewEdge: .top)
            } else {
                subview.autoPinEdge(.top, to: .bottom, of: lastView!, withOffset: 10)
            }

            lastView = subview
        }

        lastView?.autoPinEdge(toSuperviewEdge: .bottom)

        return stackView
    }
    
    private func wrapViewsInHorizontalStack(subviews: [UIView]) -> UIView {
        assert(subviews.count > 0)

        let stackView = UIView()

        var lastView: UIView?
        for subview in subviews {

            stackView.addSubview(subview)
            subview.autoVCenterInSuperview()

            if lastView == nil {
                subview.autoPinEdge(toSuperviewEdge: .left)
            } else {
                subview.autoPinEdge(.left, to: .right, of: lastView!, withOffset: 10)
            }

            lastView = subview
        }

        lastView?.autoPinEdge(toSuperviewEdge: .right)

        return stackView
    }

//    private func stackSpacing() -> CGFloat {
//        switch mode {
//        case .large, .attachmentApproval:
//            return CGFloat(10)
//        case .small:
//            return CGFloat(5)
//        }
//    }

    private func createAudioPreview() {
        guard let dataUrl = attachment.dataUrl else {
            createGenericPreview()
            return
        }

        audioPlayer = OWSAudioPlayer(mediaUrl: dataUrl, audioBehavior: .playback, delegate: self)
        
        imageView.image = UIImage(named: "FileLarge")?.withRenderingMode(.alwaysTemplate)
        imageView.tintColor = Colors.text
        fileTypeImageView.image = UIImage(named: "table_ic_notification_sound")?
            .withRenderingMode(.alwaysTemplate)
        fileTypeImageView.tintColor = Colors.text
        setAudioIconToPlay()
        
        self.addSubview(stackView)
        self.addSubview(audioPlayPauseButton)
        
        stackView.addArrangedSubview(imageView)
        stackView.addArrangedSubview(UIView.vSpacer(0))
        stackView.addArrangedSubview(titleLabel)
        stackView.addArrangedSubview(fileSizeLabel)
        
        imageView.addSubview(fileTypeImageView)
        
        let imageSize: CGFloat = {
            switch mode {
                case .large: return 200
                case .attachmentApproval: return 150
                case .small: return 80
            }
        }()
        let audioButtonSize: CGFloat = (imageSize / 2.5)
        audioPlayPauseButton.layer.cornerRadius = (audioButtonSize / 2)
        
        NSLayoutConstraint.activate([
            stackView.centerYAnchor.constraint(equalTo: centerYAnchor),
            stackView.widthAnchor.constraint(equalTo: widthAnchor),
            stackView.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor),
            
            imageView.widthAnchor.constraint(equalToConstant: imageSize),
            imageView.heightAnchor.constraint(equalToConstant: imageSize),
            titleLabel.widthAnchor.constraint(equalTo: stackView.widthAnchor, constant: -(32 * 2)),
            fileSizeLabel.widthAnchor.constraint(equalTo: stackView.widthAnchor, constant: -(32 * 2)),
            
            fileTypeImageView.centerXAnchor.constraint(equalTo: imageView.centerXAnchor),
            fileTypeImageView.centerYAnchor.constraint(
                equalTo: imageView.centerYAnchor,
                constant: ceil(imageSize * 0.15)
            ),
            fileTypeImageView.widthAnchor.constraint(
                equalTo: fileTypeImageView.heightAnchor,
                multiplier: ((fileTypeImageView.image?.size.width ?? 1) / (fileTypeImageView.image?.size.height ?? 1))
            ),
            fileTypeImageView.widthAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 0.5),
            
            audioPlayPauseButton.centerXAnchor.constraint(equalTo: imageView.centerXAnchor),
            audioPlayPauseButton.centerYAnchor.constraint(equalTo: imageView.centerYAnchor),
            audioPlayPauseButton.widthAnchor.constraint(equalToConstant: audioButtonSize),
            audioPlayPauseButton.heightAnchor.constraint(equalToConstant: audioButtonSize)
        ])
    }

    private func createAnimatedPreview() {
        guard attachment.isValidImage else {
            createGenericPreview()
            return
        }
        guard let dataUrl = attachment.dataUrl else {
            createGenericPreview()
            return
        }
        guard let image = YYImage(contentsOfFile: dataUrl.path) else {
            createGenericPreview()
            return
        }
        guard image.size.width > 0 && image.size.height > 0 else {
            createGenericPreview()
            return
        }
        animatedImageView.image = image
        let aspectRatio: CGFloat = (image.size.width / image.size.height)
        let clampedRatio: CGFloat = CGFloatClamp(aspectRatio, 0.05, 95.0)
        
        addSubview(animatedImageView)
//        addSubviewWithScaleAspectFitLayout(view: animatedImageView, aspectRatio: aspectRatio)
        contentView = animatedImageView
        
        NSLayoutConstraint.activate([
            animatedImageView.centerXAnchor.constraint(equalTo: centerXAnchor),
            animatedImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
            animatedImageView.widthAnchor.constraint(
                equalTo: animatedImageView.heightAnchor,
                multiplier: clampedRatio
            ),
            animatedImageView.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor),
            animatedImageView.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor)
        ])
    }

//    private func addSubviewWithScaleAspectFitLayout(view: UIView, aspectRatio: CGFloat) {
//        self.addSubview(view)
//        // This emulates the behavior of contentMode = .scaleAspectFit using
//        // iOS auto layout constraints.
//        //
//        // This allows ConversationInputToolbar to place the "cancel" button
//        // in the upper-right hand corner of the preview content.
//        view.autoCenterInSuperview()
//        view.autoPin(toAspectRatio: aspectRatio)
//        view.autoMatch(.width, to: .width, of: self, withMultiplier: 1.0, relation: .lessThanOrEqual)
//        view.autoMatch(.height, to: .height, of: self, withMultiplier: 1.0, relation: .lessThanOrEqual)
//    }

    private func createImagePreview() {
        guard attachment.isValidImage else {
            createGenericPreview()
            return
        }
        guard let image = attachment.image() else {
            createGenericPreview()
            return
        }
        guard image.size.width > 0 && image.size.height > 0 else {
            createGenericPreview()
            return
        }

        imageView.image = image
//        imageView.layer.minificationFilter = .trilinear
//        imageView.layer.magnificationFilter = .trilinear
        
        let aspectRatio = image.size.width / image.size.height
        let clampedRatio: CGFloat = CGFloatClamp(aspectRatio, 0.05, 95.0)
        
//        addSubviewWithScaleAspectFitLayout(view: imageView, aspectRatio: aspectRatio)
        contentView = imageView
        
        NSLayoutConstraint.activate([
            imageView.centerXAnchor.constraint(equalTo: centerXAnchor),
            imageView.centerYAnchor.constraint(equalTo: centerYAnchor),
            imageView.widthAnchor.constraint(
                equalTo: imageView.heightAnchor,
                multiplier: clampedRatio
            ),
            imageView.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor),
            imageView.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor)
        ])
    }

    private func createVideoPreview() {
        guard attachment.isValidVideo else {
            createGenericPreview()
            return
        }
        guard let image = attachment.videoPreview() else {
            createGenericPreview()
            return
        }
        guard image.size.width > 0 && image.size.height > 0 else {
            createGenericPreview()
            return
        }

        imageView.image = image
        self.addSubview(imageView)
        
        let aspectRatio = image.size.width / image.size.height
        let clampedRatio: CGFloat = CGFloatClamp(aspectRatio, 0.05, 95.0)
        
        contentView = imageView
        
        // Attachment approval provides it's own play button to keep it
        // at the proper zoom scale.
        if mode != .attachmentApproval {
            self.addSubview(videoPlayButton)
        }
        
        NSLayoutConstraint.activate([
            imageView.centerXAnchor.constraint(equalTo: centerXAnchor),
            imageView.centerYAnchor.constraint(equalTo: centerYAnchor),
            imageView.widthAnchor.constraint(
                equalTo: imageView.heightAnchor,
                multiplier: clampedRatio
            ),
            imageView.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor),
            imageView.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor)
        ])

        // Attachment approval provides it's own play button to keep it
        // at the proper zoom scale.
        if mode != .attachmentApproval {
            self.addSubview(videoPlayButton)
            
            NSLayoutConstraint.activate([
                videoPlayButton.centerXAnchor.constraint(equalTo: centerXAnchor),
                videoPlayButton.centerYAnchor.constraint(equalTo: centerYAnchor),
                imageView.widthAnchor.constraint(equalToConstant: 72),
                imageView.heightAnchor.constraint(equalToConstant: 72)
            ])
        }
    }
    
    private func createUrlPreview() {
        // If link previews aren't enabled then use a fallback state
        guard let linkPreviewURL: String = OWSLinkPreview.previewURL(forRawBodyText: attachment.text()) else {
            titleLabel.text = "vc_share_link_previews_disabled_title".localized()
            titleLabel.isHidden = false
            
            fileSizeLabel.text = "vc_share_link_previews_disabled_explanation".localized()
            fileSizeLabel.textColor = Colors.text
            fileSizeLabel.numberOfLines = 0
            
            self.addSubview(stackView)
            
            stackView.addArrangedSubview(titleLabel)
            stackView.addArrangedSubview(UIView.vSpacer(10))
            stackView.addArrangedSubview(fileSizeLabel)
            
            NSLayoutConstraint.activate([
                stackView.centerXAnchor.constraint(equalTo: centerXAnchor),
                stackView.centerYAnchor.constraint(equalTo: centerYAnchor),
                stackView.widthAnchor.constraint(equalTo: widthAnchor, constant: -(32 * 2)),
                stackView.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor)
            ])
            return
        }
        
        linkPreviewInfo = (url: linkPreviewURL, draft: nil)

        stackView.axis = .horizontal
        stackView.distribution = .fill
        
        imageView.clipsToBounds = true
        imageView.image = UIImage(named: "Link")?.withTint(Colors.text)
        imageView.alpha = 0 // Not 'isHidden' because we want it to take up space in the UIStackView
        imageView.contentMode = .center
        imageView.backgroundColor = (isDarkMode ? .black : UIColor.black.withAlphaComponent(0.06))
        imageView.layer.cornerRadius = 8
        
        loadingView.isHidden = false
        loadingView.startAnimating()
        
        titleLabel.font = .boldSystemFont(ofSize: Values.smallFontSize)
        titleLabel.text = linkPreviewURL
        titleLabel.textAlignment = .left
        titleLabel.numberOfLines = 2
        titleLabel.isHidden = false
        
        self.addSubview(stackView)
        self.addSubview(loadingView)
        
        stackView.addArrangedSubview(imageView)
        stackView.addArrangedSubview(UIView.vhSpacer(10, 0))
        stackView.addArrangedSubview(titleLabel)
        
        let imageSize: CGFloat = {
            switch mode {
                case .large: return 120
                case .attachmentApproval, .small: return 80
            }
        }()
        
        NSLayoutConstraint.activate([
            stackView.centerXAnchor.constraint(equalTo: centerXAnchor),
            stackView.centerYAnchor.constraint(equalTo: centerYAnchor),
            stackView.widthAnchor.constraint(equalTo: widthAnchor, constant: -(32 * 2)),
            stackView.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor),
            
            imageView.widthAnchor.constraint(equalToConstant: imageSize),
            imageView.heightAnchor.constraint(equalToConstant: imageSize),
            
            loadingView.centerXAnchor.constraint(equalTo: imageView.centerXAnchor),
            loadingView.centerYAnchor.constraint(equalTo: imageView.centerYAnchor),
            loadingView.widthAnchor.constraint(equalToConstant: ceil(imageSize / 3)),
            loadingView.heightAnchor.constraint(equalToConstant: ceil(imageSize / 3))
        ])
        
        // Build the link preview
        OWSLinkPreview.tryToBuildPreviewInfo(previewUrl: linkPreviewURL)
            .done { [weak self] draft in
                // TODO: Look at refactoring this behaviour to consolidate attachment mutations
                self?.attachment.linkPreviewDraft = draft
                self?.linkPreviewInfo = (url: linkPreviewURL, draft: draft)
                
                // Update the UI
                self?.titleLabel.text = (draft.title ?? self?.titleLabel.text)
                self?.loadingView.alpha = 0
                self?.loadingView.stopAnimating()
                self?.imageView.alpha = 1
                
                if let jpegImageData: Data = draft.jpegImageData, let loadedImage: UIImage = UIImage(data: jpegImageData) {
                    self?.imageView.image = loadedImage
                    self?.imageView.contentMode = .scaleAspectFill
                }
            }
            .catch { [weak self] _ in
                self?.titleLabel.attributedText = NSMutableAttributedString(string: linkPreviewURL)
                    .rtlSafeAppend(
                        "\n\("vc_share_link_previews_error".localized())",
                        attributes: [
                            NSAttributedString.Key.font: UIFont.ows_regularFont(
                                withSize: Values.verySmallFontSize
                            ),
                            NSAttributedString.Key.foregroundColor: self?.fileSizeLabel.textColor
                        ]
                        .compactMapValues { $0 }
                    )
                self?.loadingView.alpha = 0
                self?.loadingView.stopAnimating()
                self?.imageView.alpha = 1
            }
            .retainUntilComplete()
    }

    private func createGenericPreview() {
        imageView.image = UIImage(named: "FileLarge")
        
        self.addSubview(stackView)
        
        stackView.addArrangedSubview(imageView)
        stackView.addArrangedSubview(UIView.vSpacer(5))
        stackView.addArrangedSubview(titleLabel)
        stackView.addArrangedSubview(fileSizeLabel)
        
        imageView.addSubview(fileTypeImageView)
        
        let imageSize: CGFloat = {
            switch mode {
                case .large: return 200
                case .attachmentApproval: return 150
                case .small: return 80
            }
        }()

        NSLayoutConstraint.activate([
            stackView.centerYAnchor.constraint(equalTo: centerYAnchor),
            stackView.widthAnchor.constraint(equalTo: widthAnchor),
            stackView.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor),
            
            imageView.widthAnchor.constraint(equalToConstant: imageSize),
            imageView.heightAnchor.constraint(equalToConstant: imageSize),
            titleLabel.widthAnchor.constraint(equalTo: stackView.widthAnchor, constant: -(32 * 2)),
            fileSizeLabel.widthAnchor.constraint(equalTo: stackView.widthAnchor, constant: -(32 * 2)),
            
            fileTypeImageView.centerXAnchor.constraint(equalTo: imageView.centerXAnchor),
            fileTypeImageView.centerYAnchor.constraint(
                equalTo: imageView.centerYAnchor,
                constant: 25
            ),
            fileTypeImageView.widthAnchor.constraint(
                equalTo: fileTypeImageView.heightAnchor,
                multiplier: ((fileTypeImageView.image?.size.width ?? 1) / (fileTypeImageView.image?.size.height ?? 1))
            ),
            fileTypeImageView.widthAnchor.constraint(
                equalTo: imageView.widthAnchor, constant: -75
            )
        ])
    }

    // MARK: - Event Handlers

    @objc func audioPlayPauseButtonPressed(sender: UIButton) {
        audioPlayer?.togglePlayState()
    }

    // MARK: - OWSAudioPlayerDelegate

    public func audioPlaybackState() -> AudioPlaybackState {
        return playbackState
    }

    public func setAudioPlaybackState(_ value: AudioPlaybackState) {
        playbackState = value
    }
    
    public func showInvalidAudioFileAlert() {
        OWSAlerts.showErrorAlert(message: NSLocalizedString("INVALID_AUDIO_FILE_ALERT_ERROR_MESSAGE", comment: "Message for the alert indicating that an audio file is invalid."))
    }

    public func audioPlayerDidFinishPlaying(_ player: OWSAudioPlayer, successfully flag: Bool) {
        // Do nothing
    }

    private func ensureButtonState() {
        switch playbackState {
            case .playing: setAudioIconToPause()
            default: setAudioIconToPlay()
        }
    }

    public func setAudioProgress(_ progress: CGFloat, duration: CGFloat) {
        audioProgressSeconds = progress
        audioDurationSeconds = duration
    }

    private func setAudioIconToPlay() {
        audioPlayPauseButton.setImage(UIImage(named: "Play"), for: .normal)
    }

    private func setAudioIconToPause() {
        audioPlayPauseButton.setImage(UIImage(named: "Pause"), for: .normal)
    }
}