//
//  MediaContentView.swift
//  ATGMediaBrowser
//
//  Created by Suraj Thomas K on 7/10/18.
//  Copyright © 2018 Al Tayer Group LLC.
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy of this software
//  and associated documentation files (the "Software"), to deal in the Software without
//  restriction, including without limitation the rights to use, copy, modify, merge, publish,
//  distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
//  Software is furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all copies or
//  substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
//  BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
//  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

import UIKit

/// Holds the value of minimumZoomScale and maximumZoomScale of the image.
public struct ZoomScale {

    /// Minimum zoom level, the image can be zoomed out to.
    public var minimumZoomScale: CGFloat

    /// Maximum zoom level, the image can be zoomed into.
    public var maximumZoomScale: CGFloat

    /// Default zoom scale. minimum is 1.0 and maximum is 3.0
    public static let `default` = ZoomScale(
        minimum: 1.0,
        maximum: 3.0
    )

    /// Identity zoom scale. Pass this to disable zoom.
    public static let identity = ZoomScale(
        minimum: 1.0,
        maximum: 1.0
    )

    /**
     Initializer.
     - parameter minimum: The minimum zoom level.
     - parameter maximum: The maximum zoom level.
     */
    public init(minimum: CGFloat, maximum: CGFloat) {

        minimumZoomScale = minimum
        maximumZoomScale = maximum
    }
}

internal class MediaContentView: UIScrollView {

    // MARK: - Exposed variables
    internal static var interItemSpacing: CGFloat = 0.0
    internal var index: Int {
        didSet {
            resetZoom()
        }
    }
    internal static var contentTransformer: ContentTransformer = DefaultContentTransformers.horizontalMoveInOut

    internal var position: CGFloat {
        didSet {
            updateTransform()
        }
    }
    internal var image: UIImage? {
        didSet {
            updateImageView()
        }
    }
    internal var isLoading: Bool = false {
        didSet {
            indicatorContainer.isHidden = !isLoading
            if isLoading {
                indicator.startAnimating()
            } else {
                indicator.stopAnimating()
            }
        }
    }
    internal var zoomLevels: ZoomScale? {
        didSet {
            zoomScale = ZoomScale.default.minimumZoomScale
            minimumZoomScale = zoomLevels?.minimumZoomScale ?? ZoomScale.default.minimumZoomScale
            maximumZoomScale = zoomLevels?.maximumZoomScale ?? ZoomScale.default.maximumZoomScale
        }
    }

    // MARK: - Private enumerations

    private enum Constants {

        static let indicatorViewSize: CGFloat = 60.0
    }

    // MARK: - Private variables

    private lazy var imageView: UIImageView = {
        let imageView = UIImageView()
        imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        imageView.contentMode = .scaleAspectFit
        imageView.clipsToBounds = true
        return imageView
    }()

    private lazy var indicator: UIActivityIndicatorView = {
        let indicatorView = UIActivityIndicatorView()
        indicatorView.style = UIActivityIndicatorView.Style.large
        indicatorView.hidesWhenStopped = true
        return indicatorView
    }()

    private lazy var indicatorContainer: UIView = {
        let container = UIView()
        container.backgroundColor = .darkGray
        container.layer.cornerRadius = Constants.indicatorViewSize * 0.5
        container.layer.masksToBounds = true
        return container
    }()

    private lazy var doubleTapGestureRecognizer: UITapGestureRecognizer = { [unowned self] in
        let gesture = UITapGestureRecognizer(target: self, action: #selector(didDoubleTap(_:)))
        gesture.numberOfTapsRequired = 2
        gesture.numberOfTouchesRequired = 1
        return gesture
    }()

    init(index itemIndex: Int, position: CGFloat, frame: CGRect) {

        self.index = itemIndex
        self.position = position

        super.init(frame: frame)

        initializeViewComponents()
    }

    required init?(coder aDecoder: NSCoder) {

        fatalError("Do nto use `init?(coder:)`")
    }
}

// MARK: - View Composition and Events

extension MediaContentView {

    private func initializeViewComponents() {

        addSubview(imageView)
        imageView.frame = frame

        setupIndicatorView()

        configureScrollView()

        addGestureRecognizer(doubleTapGestureRecognizer)

        updateTransform()
    }

    private func configureScrollView() {

        isMultipleTouchEnabled = true
        showsHorizontalScrollIndicator = false
        showsVerticalScrollIndicator = false
        contentSize = imageView.bounds.size
        canCancelContentTouches = false
        zoomLevels = ZoomScale.default
        delegate = self
        bouncesZoom = false
    }

    private func resetZoom() {

        setZoomScale(1.0, animated: false)
        imageView.transform = CGAffineTransform.identity
        contentSize = imageView.frame.size
        contentOffset = .zero
    }

    private func setupIndicatorView() {

        addSubview(indicatorContainer)
        indicatorContainer.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            indicatorContainer.widthAnchor.constraint(equalToConstant: Constants.indicatorViewSize),
            indicatorContainer.heightAnchor.constraint(equalToConstant: Constants.indicatorViewSize),
            indicatorContainer.centerXAnchor.constraint(equalTo: centerXAnchor),
            indicatorContainer.centerYAnchor.constraint(equalTo: centerYAnchor)
        ])

        indicatorContainer.addSubview(indicator)
        indicator.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            indicator.leadingAnchor.constraint(equalTo: indicatorContainer.leadingAnchor),
            indicator.trailingAnchor.constraint(equalTo: indicatorContainer.trailingAnchor),
            indicator.topAnchor.constraint(equalTo: indicatorContainer.topAnchor),
            indicator.bottomAnchor.constraint(equalTo: indicatorContainer.bottomAnchor)
        ])

        indicatorContainer.setNeedsLayout()
        indicatorContainer.layoutIfNeeded()

        indicatorContainer.isHidden = true
    }

    internal func updateTransform() {

        MediaContentView.contentTransformer(self, position)
    }

    internal func handleChangeInViewSize(to size: CGSize) {

        let oldScale = zoomScale
        zoomScale = 1.0
        imageView.frame = CGRect(origin: .zero, size: size)

        updateImageView()
        updateTransform()
        setZoomScale(oldScale, animated: false)

        contentSize = imageView.frame.size
    }

    @objc private func didDoubleTap(_ recognizer: UITapGestureRecognizer) {

        let locationInImage = recognizer.location(in: imageView)

        let isImageCoveringScreen = imageView.frame.size.width > bounds.size.width &&
            imageView.frame.size.height > bounds.size.height
        let zoomTo = (isImageCoveringScreen || zoomScale == maximumZoomScale) ? minimumZoomScale : maximumZoomScale

        guard zoomTo != zoomScale else {
            return
        }

        let width = bounds.size.width / zoomTo
        let height = bounds.size.height / zoomTo

        let zoomRect = CGRect(
            x: locationInImage.x - width * 0.5,
            y: locationInImage.y - height * 0.5,
            width: width,
            height: height
        )

        zoom(to: zoomRect, animated: true)
    }
}

// MARK: - UIScrollViewDelegate

extension MediaContentView: UIScrollViewDelegate {

    internal func viewForZooming(in scrollView: UIScrollView) -> UIView? {

        let shouldAllowZoom = (image != nil && position == 0.0)
        return shouldAllowZoom ? imageView : nil
    }

    internal func scrollViewDidZoom(_ scrollView: UIScrollView) {

        centerImageView()
    }

    private func centerImageView() {

        var imageViewFrame = imageView.frame

        if imageViewFrame.size.width < bounds.size.width {
            imageViewFrame.origin.x = (bounds.size.width - imageViewFrame.size.width) / 2.0
        } else {
            imageViewFrame.origin.x = 0.0
        }

        if imageViewFrame.size.height < bounds.size.height {
            imageViewFrame.origin.y = (bounds.size.height - imageViewFrame.size.height) / 2.0
        } else {
            imageViewFrame.origin.y = 0.0
        }

        imageView.frame = imageViewFrame
    }

    private func updateImageView() {

        imageView.image = image

        if let contentImage = image {

            let imageViewSize = bounds.size
            let imageSize = contentImage.size
            var targetImageSize = imageViewSize

            if imageSize.width / imageSize.height > imageViewSize.width / imageViewSize.height {
                targetImageSize.height = imageViewSize.width / imageSize.width * imageSize.height
            } else {
                targetImageSize.width = imageViewSize.height / imageSize.height * imageSize.width
            }

            imageView.frame = CGRect(origin: .zero, size: targetImageSize)
        }
        centerImageView()
    }
}
