//
//  UIImage+extensions.swift
//  
//  Copyright © 2016-2019 Apple Inc. All rights reserved.
//

import UIKit

// MARK: - UIImage extensions

public extension UIImage {
    
    var isEmpty: Bool {
        return (size.width == 0) || (size.height == 0)
    }
    
    func scaledToFit(within availableSize: CGSize) -> UIImage {
        var scaledImageRect = CGRect.zero
        
        let aspectWidth = availableSize.width / self.size.width
        let aspectHeight = availableSize.height / self.size.height
        let aspectRatio = min(aspectWidth, aspectHeight)
        
        scaledImageRect.size.width = self.size.width * aspectRatio
        scaledImageRect.size.height = self.size.height * aspectRatio
        
        let rendererFormat = UIGraphicsImageRendererFormat()
        rendererFormat.scale = 1
        
        let renderer = UIGraphicsImageRenderer(size: scaledImageRect.size, format: rendererFormat)
        let scaledImage = renderer.image { _ in
            self.draw(in: scaledImageRect)
        }
        return scaledImage
    }
    
    enum ResizeMode {
        case aspectFill
        case aspectFit
        case center
        case fill
    }
    
    func resizedTo(size newSize: CGSize, using resizeMode: ResizeMode) -> UIImage? {
        let aspectWidth = newSize.width  / self.size.width
        let aspectHeight = newSize.height / self.size.height
        
        var newImageSize = self.size
        switch resizeMode {
        case .aspectFit:
            let scalingFactor = min(aspectWidth, aspectHeight)
            newImageSize = CGSize(width:  self.size.width  * scalingFactor, height: self.size.height * scalingFactor)
        case .aspectFill:
            let scalingFactor = max(aspectWidth, aspectHeight)
            newImageSize = CGSize(width:  self.size.width  * scalingFactor, height: self.size.height * scalingFactor)
        case .center:
            newImageSize = self.size
        case .fill:
            newImageSize = newSize
        }
        
        let drawRect = CGRect(origin: CGPoint(x: (newSize.width  - newImageSize.width)  / 2, y: (newSize.height - newImageSize.height) / 2), size: newImageSize)
        
        guard let cgImage = self.cgImage else { return nil }
        
        let opaque = !cgImage.hasAlphaChannel // Preserve alpha (or not).
        
        UIGraphicsBeginImageContextWithOptions(newSize, opaque, UIScreen.main.scale)
        guard let context = UIGraphicsGetCurrentContext() else { return nil }
        
        // Convert to CG coordinate system.
        context.translateBy(x: 0.0, y: newSize.height)
        context.scaleBy(x: 1.0, y: -1.0)
        
        context.draw(cgImage, in: drawRect)
        
        let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return resizedImage
    }

    func scaledAndCroppedToMatchAspectRatio(of aspectSize: CGSize) -> UIImage {
        let aspectWidth = self.size.width  / aspectSize.width
        let aspectHeight = self.size.height / aspectSize.height
        let scalingFactor = min(aspectWidth, aspectHeight)
        let newSize = CGSize(width:  aspectSize.width  * scalingFactor,
                             height: aspectSize.height * scalingFactor)
        let drawRect = CGRect(origin: CGPoint(x: (newSize.width  - size.width)  / 2,
                                              y: (newSize.height - size.height) / 2),
                              size: size)
        
        let rendererFormat = UIGraphicsImageRendererFormat()
        rendererFormat.scale = 1
        
        let renderer = UIGraphicsImageRenderer(size: newSize, format: rendererFormat)
        let scaledImage = renderer.image { _ in
            self.draw(in: drawRect)
        }
        return scaledImage
    }

    func tinted(with color: UIColor) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(self.size, false, UIScreen.main.scale)
        guard let context = UIGraphicsGetCurrentContext(), let cgImage = self.cgImage else { return self }

        // Flip the image.
        context.scaleBy(x: 1.0, y: -1.0)
        context.translateBy(x: 0.0, y: -self.size.height)
        
        let rect = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)

        // Draw the image.
        context.draw(cgImage, in: rect)
        
        // Draw the tint color blended on top of image.
        context.setBlendMode(.color)
        context.clip(to: rect, mask: self.cgImage!)
        color.setFill()
        context.fill(rect)

        // Create new UIImage from the context.
        guard let newImage = UIGraphicsGetImageFromCurrentImageContext() else { return self }
        UIGraphicsEndImageContext()
        
        return newImage
    }
    
    func imageByApplyingClippingBezierPath(_ path: UIBezierPath, cropToPath: Bool = true) -> UIImage? {
        guard
            // Mask image using path.
            let maskedImage = imageByApplyingMaskingBezierPath(path)
            else { return nil }
        
        if cropToPath {
            // Crop image to frame of path.
            guard let croppedCGImage = maskedImage.cgImage?.cropping(to: path.bounds) else { return nil }
            return UIImage(cgImage: croppedCGImage)
        } else {
            return maskedImage
        }
    }
    
    func imageByApplyingMaskingBezierPath(_ path: UIBezierPath) -> UIImage? {
        UIGraphicsBeginImageContext(size)
        guard let context = UIGraphicsGetCurrentContext() else { return nil }
        context.saveGState()
        
        path.addClip()
        draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
        
        let maskedImage = UIGraphicsGetImageFromCurrentImageContext()
        
        context.restoreGState()
        UIGraphicsEndImageContext()
        
        return maskedImage
    }
    
    // Returns a copy of the image masked by a grayscale mask image.
    //
    // White areas in the mask allow the image to show through.
    // Black areas mask the image, becoming transparent in the output image.
    // Intermediate gray areas are partially transparent.
    //
    // - Parameter maskImage: The grayscale mask image. This should be the same size as the image.
    // - Returns: The image with the mask applied.
    func imageMaskedBy(maskImage: UIImage) -> UIImage? {
        guard let cgImage = self.cgImage, let maskCGImage = maskImage.cgImage else { return nil }
        
        let rect = CGRect(origin: CGPoint.zero, size: self.size)
        
        UIGraphicsBeginImageContextWithOptions(self.size, false, UIScreen.main.scale)
        guard let context = UIGraphicsGetCurrentContext() else { return nil }
        
        // Convert to CG coordinate system.
        context.translateBy(x: 0.0, y: self.size.height)
        context.scaleBy(x: 1.0, y: -1.0)
        
        context.clip(to: rect, mask: maskCGImage)
        context.draw(cgImage, in: rect)
        
        let maskedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return maskedImage
    }
    
    // Returns a composite of the image overlaid with another image.
    // In the resulting composite image the position and size of the image and overlay image
    // are determined by the relative sizes and positions of the reference frames supplied.
    //
    // - Parameter overlayImage: The image to overlay.
    // - Parameter imageFrame: The reference rect for the image.
    // - Parameter overlayFrame: The reference rect for the overlay image.
    // - Parameter cropToImageFrame: Indicates if the overlay image should be cropped to the image frame.
    //
    // - Returns: The composited image.
    func composited(with overlayImage: UIImage, imageFrame: CGRect, overlayFrame: CGRect, cropToImageFrame: Bool) -> UIImage {
        // Get the frame of the overlay image within overlayFrame (aspect fit).
        let overlayImageSizeInFrame = overlayImage.size.scaledToFit(within: overlayFrame.size)
        let overlayImageFrame = CGRect(x: overlayFrame.origin.x + ((overlayFrame.size.width - overlayImageSizeInFrame.width) / 2.0),
                                       y: overlayFrame.origin.y + ((overlayFrame.size.height - overlayImageSizeInFrame.height) / 2.0),
                                       width: overlayImageSizeInFrame.width, height: overlayImageSizeInFrame.height)
        
        // Get the frame of the image: initially fill this image.
        let imageDrawRect = CGRect(origin: CGPoint.zero, size: self.size)
        
        // Scale and position the overlay image.
        let w = overlayImageSizeInFrame.width * self.size.width / imageFrame.size.width
        let h = overlayImageSizeInFrame.height * self.size.height / imageFrame.size.height
        let x = overlayImageFrame.origin.x * self.size.width / imageFrame.size.width
        let y = overlayImageFrame.origin.y * self.size.height / imageFrame.size.height
        let overlayDrawRect = CGRect(x: x, y: y, width: w, height: h)
        
        // Work out the composition frame.
        var compositionFrame: CGRect
        if cropToImageFrame {
            // Composition is limited to the image frame: the overlay image is cropped.
            compositionFrame = imageDrawRect
        } else {
            // Composition is extended to include the entire overlay image.
            compositionFrame = imageDrawRect.union(overlayDrawRect)
        }
        let imageDrawRectWithinComposition = imageDrawRect.offsetBy(dx: -compositionFrame.origin.x, dy: -compositionFrame.origin.y)
        let overlayDrawRectWithinComposition = overlayDrawRect.offsetBy(dx: -compositionFrame.origin.x, dy: -compositionFrame.origin.y)
        
        // Scale everything to the size of this image (i.e. self.size) so that the composition frame fits within it.
        let compositionScale = compositionFrame.size.scaleToFit(within: self.size)
        let scaledCompositionFrame = compositionFrame.scaled(by: compositionScale)
        var finalImageDrawRect = imageDrawRectWithinComposition.scaled(by: compositionScale)
        var finalOverlayDrawRect = overlayDrawRectWithinComposition.scaled(by: compositionScale)
        
        // Center the composition.
        let outputFrame = CGRect(origin: CGPoint.zero, size: self.size)
        let centeringOffsetX = (outputFrame.width - scaledCompositionFrame.width) / 2.0
        let centeringOffsetY = (outputFrame.height - scaledCompositionFrame.height) / 2.0
        finalImageDrawRect = finalImageDrawRect.offsetBy(dx: centeringOffsetX, dy: centeringOffsetY)
        finalOverlayDrawRect = finalOverlayDrawRect.offsetBy(dx: centeringOffsetX, dy: centeringOffsetY)

        // Draw the composition.
        let rendererFormat = UIGraphicsImageRendererFormat()
        rendererFormat.scale = UIScreen.main.scale
        rendererFormat.opaque = false
        let renderer = UIGraphicsImageRenderer(size: size, format: rendererFormat)
        let compositeImage = renderer.image { (ctx) in
            // Draw the image.
            self.draw(in: finalImageDrawRect)
            // Draw the overlay image.
            overlayImage.draw(in: finalOverlayDrawRect)
        }
        return compositeImage
    }
}

