//
//  ADPhotoKitUI.swift
//  ADPhotoKit
//
//  Created by MAC on 2021/3/20.
//

import Foundation
import UIKit
import Photos

/// Style to display picker.
public enum ADPickerStyle {
    /// The display relationship between the album list and the thumbnail interface is push.
    case normal
    /// The album list is embedded in the navigation of the thumbnail interface, click the drop-down display.
    case embed
}

/// Asset fetch result.
public struct ADAssetResult {
    /// Image fetch with asset. It's `nil` if `browserOpts` not contain `.fetchImage` or error occur when fetching.
    public var image: UIImage?
    
    #if Module_ImageEdit
    /// Image edited info. It can be `nil` if image is not edit.
    public var imageEditInfo: ADImageEditInfo?
    #endif
}

/// Parsing the input `Set<ADPhotoSelectParams>` to `ADConstraintParams` and pass through the internal methods.
public struct ADConstraintParams {
    
    /// Limit the max count you can select. Set `nil` means no limit. Default is no limit.
    public var maxCount: Int?
    
    /// Limit the min image count you can select. Set `nil` means no limit. Default is no limit.
    public var minImageCount: Int?
    /// Limit the max image count you can select. Set `nil` means no limit. Default is no limit.
    public var maxImageCount: Int?
    
    /// Limit the min video count you can select. Set `nil` means no limit. Default is no limit.
    public var minVideoCount: Int?
    /// Limit the max video count you can select. Set `nil` means no limit. Default is no limit.
    public var maxVideoCount: Int?
    
    /// Limit the min video time you can select. Set `nil` means no limit. Default is no limit.
    public var minVideoTime: Int?
    /// Limit the max video time you can select. Set `nil` means no limit. Default is no limit.
    public var maxVideoTime: Int?
    
    /// Limit the min video time you can record. Set `nil` means no limit. Default is no limit.
    public var minRecordTime: Int?
    /// Limit the max video time you can record. Set `nil` means no limit. Default is no limit.
    public var maxRecordTime: Int?
}

extension ADSelectAssetModel {
    func result(with image: UIImage?) -> ADAssetResult? {
        #if Module_ImageEdit
        return ADAssetResult(image: image, imageEditInfo: imageEditInfo)
        #else
        if let img = image {
            return ADAssetResult(image: img)
        }else{
            return nil
        }
        #endif
    }
}

/// Main class of ADPhotoKit UI. It provide methods to show asset picker or asset browser.
public class ADPhotoKitUI {
    
    /// Wrap of select asset.
    /// - Parameters:
    ///     - asset: Asset select from system.
    ///     - result: Result fetch with asset. It's `nil` if not contain `ImageEdit` subspec and `browserOpts` not contain `.fetchImage`.
    ///     - error: Error info when fetch error. It's not `nil` when error occur when fetching.
    /// - Note: If `browserOpts` not contain `.fetchImage`, fetch will not perform and asset will return immediately, `result.image` and `error` will be `nil`.
    public typealias Asset = (asset: PHAsset, result: ADAssetResult?, error: Error?)
    /// Return select assets and if original or not.
    public typealias AssetSelectHandler = (([Asset],Bool) -> Void)
    /// Return browsable asset array.
    public typealias AssetableSelectHandler = (([ADAssetBrowsable]) -> Void)
    /// Cancel select.
    public typealias AssetCancelHandler = (() -> Void)
    
    /// Show picker with select assets.
    /// - Parameters:
    ///   - on: The controller to show picker.
    ///   - style: Style to display picker.
    ///   - models: Asset models have been selected.
    ///   - albumOpts: Options to limit album type and order. It is `ADAlbumSelectOptions.default` by default.
    ///   - assetOpts: Options to control the asset select condition and ui. It is `ADAssetSelectOptions.default` by default.
    ///   - browserOpts: Options to control browser controller. It is `ADAssetBrowserOptions.default` by default.
    ///   - params: Params to control the asset select condition.
    ///   - selected: Called after selection finish.
    ///   - canceled: Called when cancel select.
    public class func imagePicker(present on: UIViewController,
                                    style: ADPickerStyle = .normal,
                                    models: [ADSelectAssetModel] = [],
                                    albumOpts: ADAlbumSelectOptions = .default,
                                    assetOpts: ADAssetSelectOptions = .default,
                                    browserOpts: ADAssetBrowserOptions = .default,
                                    params: Set<ADPhotoSelectParams> = [],
                                    selected: @escaping AssetSelectHandler,
                                    canceled: AssetCancelHandler? = nil) {
        let configuration = ADPhotoKitConfig(albumOpts: albumOpts, assetOpts: assetOpts, browserOpts: browserOpts, params: params, pickerSelect: selected, browserSelect: nil, canceled: canceled)
        if let asset = models.randomElement() {
            configuration.selectMediaImage = ADAssetModel(asset: asset.asset).type.isImage
        }
        config = configuration
        if style == .normal {
            ADPhotoManager.cameraRollAlbum(options: albumOpts) { (model) in
                let album = ADAlbumListController(config: configuration, selects: models)
                let nav = ADPhotoNavController(rootViewController: album)
                album.pushThumbnail(with: model, style: style, animated: false)
                nav.modalPresentationStyle = .fullScreen
                on.present(nav, animated: true, completion: nil)
            }
        }else{
            ADPhotoManager.cameraRollAlbum(options: albumOpts) { (model) in
                let thumbnail = ADThumbnailViewController(config: configuration, album: model, style: style, selects: models)
                let nav = ADPhotoNavController(rootViewController: thumbnail)
                nav.modalPresentationStyle = .fullScreen
                on.present(nav, animated: true, completion: nil)
            }
        }
    }
    
    /// Show controller to browser and select assets.
    /// - Parameters:
    ///   - on: The controller to show browser.
    ///   - assets: Assets to browser.
    ///   - selects: Assets heave been selected.
    ///   - index: Current browser asset index.
    ///   - options: Options to control browser controller. It is `ADAssetBrowserOptions.default` by default.
    ///   - selected: Called after selection finish.
    ///   - canceled: Called when cancel select.
    public class func assetBrowser(present on: UIViewController,
                                    assets:  [ADAssetBrowsable],
                                    selects: [ADAssetBrowsable] = [],
                                    index: Int? = nil,
                                    options: ADAssetBrowserOptions = .default,
                                    selected: @escaping AssetableSelectHandler,
                                    canceled: AssetCancelHandler? = nil) {
        if assets.count == 0 {
            fatalError("assets count must>0")
        }
        let configuration = ADPhotoKitConfig(browserOpts: options, pickerSelect: nil, browserSelect: selected, canceled: canceled)
        config = configuration
        let browser = ADAssetBrowserController(config: configuration, assets: assets, selects: selects, index: index)
        let nav = ADPhotoNavController(rootViewController: browser)
        nav.modalPresentationStyle = .fullScreen
        on.present(nav, animated: true, completion: nil)
    }
    
    /// Config pass through.
    public static var config: ADPhotoKitConfig!
}

extension ADPhotoKitUI {
    
    static func springAnimation() -> CAKeyframeAnimation {
        let animate = CAKeyframeAnimation(keyPath: "transform")
        animate.duration = 0.4
        animate.isRemovedOnCompletion = true
        animate.fillMode = .forwards
        
        animate.values = [CATransform3DMakeScale(0.7, 0.7, 1),
                          CATransform3DMakeScale(1.2, 1.2, 1),
                          CATransform3DMakeScale(0.8, 0.8, 1),
                          CATransform3DMakeScale(1, 1, 1)]
        return animate
    }
    
}

/// Parsing the input config to `ADPhotoKitConfig` and pass through the internal methods.
public class ADPhotoKitConfig {

    public let albumOpts: ADAlbumSelectOptions
    public let assetOpts: ADAssetSelectOptions
    public let browserOpts: ADAssetBrowserOptions
    public let params: ADConstraintParams
    
    let pickerSelect: ADPhotoKitUI.AssetSelectHandler?
    let browserSelect: ADPhotoKitUI.AssetableSelectHandler?
    let canceled: ADPhotoKitUI.AssetCancelHandler?
    
    var selectMediaImage: Bool?
    
    var isOriginal: Bool = false
    
    let fetchImageQueue: OperationQueue = OperationQueue()
    
    init(albumOpts: ADAlbumSelectOptions = .default,
         assetOpts: ADAssetSelectOptions = .default,
         browserOpts: ADAssetBrowserOptions,
         params: Set<ADPhotoSelectParams> = [],
         pickerSelect: ADPhotoKitUI.AssetSelectHandler?,
         browserSelect: ADPhotoKitUI.AssetableSelectHandler?,
         canceled: ADPhotoKitUI.AssetCancelHandler?) {
        self.albumOpts = albumOpts
        self.assetOpts = assetOpts
        self.browserOpts = browserOpts
        self.pickerSelect = pickerSelect
        self.browserSelect = browserSelect
        self.canceled = canceled
        
        var value = ADConstraintParams()
        for item in params {
            switch item {
            case let .maxCount(max):
                value.maxCount = max
            case let .imageCount(min, max):
                value.minImageCount = min
                value.maxImageCount = max
                if let l = min, let r = max {
                    assert(l <= r, "min count must less than or equal max")
                }
            case let .videoCount(min, max):
                value.minVideoCount = min
                value.maxVideoCount = max
                if let l = min, let r = max {
                    assert(l <= r, "min count must less than or equal max")
                }
            case let .videoTime(min, max):
                value.minVideoTime = min
                value.maxVideoTime = max
                if let l = min, let r = max {
                    assert(l <= r, "min time must less than or equal max")
                }
            case let .recordTime(min, max):
                value.minRecordTime = min
                value.maxRecordTime = max
                if let l = min, let r = max {
                    assert(l <= r, "min time must less than or equal max")
                }
            }
        }
        self.params = value
        
        fetchImageQueue.maxConcurrentOperationCount = 3
    }
}

extension ADAssetListDataSource {
    
    func fetchSelectImages(original: Bool, asGif: Bool, completion: @escaping (()->Void)) {
        var hud = ADPhotoUIConfigurable.progressHUD()
        
        var timeout: Bool = false
        hud.timeoutBlock = {
            timeout = true
            ADPhotoKitUI.config.fetchImageQueue.cancelAllOperations()
        }
        
        hud.show(timeout: ADPhotoKitConfiguration.default.fetchTimeout)
        
        var result: [ADPhotoKitUI.Asset?] = Array(repeating: nil, count: selects.count)
        var operations: [Operation] = []
        for (i,item) in selects.enumerated() {
            let op = ADAssetOperation(model: item, isOriginal: original, selectAsGif: asGif, progress: nil) { (asset) in
                result[i] = asset
            }
            operations.append(op)
        }
        
        DispatchQueue.main.async {
            ADPhotoKitUI.config.fetchImageQueue.addOperations(operations, waitUntilFinished: true)
            hud.hide()
            if !timeout {
                ADPhotoKitUI.config.pickerSelect?(result.compactMap { $0 }, original)
                completion()
            }
        }
    }
    
}
