//
//  ExampleTool.swift
//  Tool
//
//  Created by ldc on 2020/10/22.
//

import UIKit
import CommonCrypto
import QuickLook
import SVProgressHUD
import CoreServices
import AVFoundation
import Foundation

// MARK: Data
public extension Data {
    
    /// 对应的十六进制字符串，每个字节转为两个十六进制字符，字节间用空格隔开
    var hexString: String  {
        
        return hex.joined(separator: " ")
    }
    
    var hex: [String] {
        
        return self.map({String.init(format: "%02x", $0)})
    }
    
    // BOM:
    // 00 00 fe ff  utf32-BE
    // ff fe 00 00  utf32-LE
    // ef bb bf     utf8
    // fe ff        utf16-BE
    // ff fe        utf16-LE
    // without BOM  utf8
    var txt: String? {
        
        var content = self
        var encoding = String.Encoding.utf8
        if count >= 4 {
            switch (self[0], self[1], self[2], self[3]) {
            case (0x0, 0x0, 0xfe, 0xff):
                content.removeSubrange(0..<4)
                encoding = .utf32BigEndian
            case (0xff, 0xfe, 0x0, 0x0):
                content.removeSubrange(0..<4)
                encoding = .utf32LittleEndian
            case (0xef, 0xbb, 0xbf, _):
                content.removeSubrange(0..<3)
                encoding = .utf8
            case (0xfe, 0xff, _, _):
                content.removeSubrange(0..<2)
                encoding = .utf16BigEndian
            case (0xff, 0xfe, _, _):
                content.removeSubrange(0..<2)
                encoding = .utf16LittleEndian
            default:
                break
            }
        }else if count >= 2 {
            switch (self[0], self[1]) {
            case (0xef, 0xbb):
                if count >= 3 && self[2] == 0xbf {
                    content.removeSubrange(0..<3)
                }
            case (0xfe, 0xff):
                content.removeSubrange(0..<2)
                encoding = .utf16BigEndian
            case (0xff, 0xfe):
                content.removeSubrange(0..<2)
                encoding = .utf16LittleEndian
            default:
                break
            }
        }
        if let txt = String.init(data: content, encoding: encoding) {
            return txt
        }else {
            return nil
        }
    }
}

public extension Data {
    
    var md5: String { return encryptionString(type: .md5) }
    
    var sha1: String { return encryptionString(type: .sha1) }
    
    var sha256: String { encryptionString(type: .sha256) }
    
    private enum Encryption {
        case md5, sha1, sha256
    }
    
    private func encryptionString(type: Encryption) -> String {
        
        var length: Int
        switch type {
        case .md5:
            length = Int(CC_MD5_DIGEST_LENGTH)
        case .sha1:
            length = Int(CC_SHA1_DIGEST_LENGTH)
        case .sha256:
            length = Int(CC_SHA256_DIGEST_LENGTH)
        }
        let digest = UnsafeMutablePointer<UInt8>.allocate(capacity: length)
        switch type {
        case .md5:
            _ = CC_MD5((self as NSData).bytes, CC_LONG.init(self.count), digest)
        case .sha1:
            _ = CC_SHA1((self as NSData).bytes, CC_LONG.init(self.count), digest)
        case .sha256:
            _ = CC_SHA256((self as NSData).bytes, CC_LONG.init(self.count), digest)
        }
        let digestData = Data.init(bytes: digest, count: length)
        return digestData.hex.joined()
    }
}

public extension Data {
    
    func removeZeroAtEnd() -> Data {
        
        if let index = self.firstIndex(of: 0) {
            if index == 0 {
                return Data()
            }else {
                return Data(self[0...index-1])
            }
        }
        return self
    }
    
    static func create<T>(_ value: T, as type: T.Type) -> Data {
        
        let pointer = UnsafeMutablePointer<T>.allocate(capacity: 1)
        pointer.pointee = value
        let data = Data.init(bytes: UnsafeRawPointer(pointer), count: MemoryLayout<T>.stride)
        pointer.deallocate()
        return data
    }
    
    mutating func append<T>(_ value: T, as type: T.Type) -> Void {
        
        let pointer = UnsafeMutablePointer<T>.allocate(capacity: 1)
        pointer.pointee = value
        let raw = pointer.withMemoryRebound(to: UInt8.self, capacity: 1) { $0 }
        append(raw, count: MemoryLayout<T>.stride)
        pointer.deallocate()
    }
    
    mutating func storeBytes<T>(_ value: T, toByteOffset offset: Int = 0, as type: T.Type) {
        
        let pointer = UnsafeMutablePointer<T>.allocate(capacity: 1)
        pointer.pointee = value
        let size = MemoryLayout<T>.stride
        replaceSubrange(offset..<offset + size, with: UnsafePointer(pointer), count: size)
        pointer.deallocate()
    }
    
    func readValue<T>(offset: Int = 0, as type: T.Type) -> T? {
        
        guard offset >= 0 else { return nil }
        let size = MemoryLayout<T>.stride
        if offset + size > count {
            return nil
        }
        let bytes = (self as NSData).bytes + offset
        let pointer = bytes.bindMemory(to: type, capacity: 1)
        return pointer.pointee
    }
}

// MARK: CmdSample

struct CmdSample {
    var title = ""
    var detailTitle = ""
    var closure: (() -> Void)?
}

protocol CmdSampleGenerator {
    
    var samples: [CmdSample] { set get }
    
    var messageListener: ((String) -> Void)? { set get }
}

// MARK: AlertController

fileprivate weak var AlertController: UIAlertController?

public extension UIViewController {
    
    func bk_presentAlertController(
        title: String?,
        message: String?,
        preferredStyle: UIAlertController.Style,
        actions: [UIAlertAction])
    -> Void
    {
        
        let closure = { () in
            let temp = UIAlertController.init(title: title, message: message, preferredStyle: preferredStyle)
            for action in actions {
                temp.addAction(action)
            }
            temp.popoverPresentationController?.sourceView = self.view
            temp.popoverPresentationController?.sourceRect = CGRect.init(x: 0, y: self.view.bounds.height/2, width: self.view.bounds.width, height: 1)
            self.present(temp, animated: true, completion: nil)
            AlertController = temp
        }
        if let alert = AlertController, let _ = alert.presentingViewController {
            
            alert.dismiss(animated: true, completion: closure)
        }else {
            closure()
        }
    }
    
    func bk_presentWarningAlertController(
        title: String,
        message: String,
        style: UIAlertAction.Style = .default,
        closure: ((UIAlertAction) -> Void)? = nil)
    -> Void
    {
        
        let action = UIAlertAction.init(title: "OK", style: style, handler: closure)
        bk_presentAlertController(title: title, message: message, preferredStyle: .alert, actions: [action])
    }
    
    func bk_presentDecisionAlertController(
        title: String?,
        message: String?,
        decisionTitle: String?,
        decisionClosure: @escaping (UIAlertAction) -> Void,
        cancelClosure: ((UIAlertAction) -> Void)? = nil)
    -> Void
    {
        
        let action1 = UIAlertAction.init(title: decisionTitle ?? "OK", style: .default, handler: decisionClosure)
        let action2 = UIAlertAction.init(title: "Cancel", style: .cancel, handler: cancelClosure)
        bk_presentAlertController(title: title, message: message, preferredStyle: .alert, actions: [action1, action2])
    }
}

extension UIAlertController {
    
    struct Key {
        static var textDidChangeClosure = 0
    }
    
    @objc func textFieldTextDidChange() {
        
        textFieldTextDidChangeClosure?(textFields, actions)
    }
    
    var textFieldTextDidChangeClosure: (([UITextField]?, [UIAlertAction]) -> Void)? {
        
        set {
            objc_setAssociatedObject(self, &Key.textDidChangeClosure, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
        }
        
        get {
            return objc_getAssociatedObject(self, &Key.textDidChangeClosure) as? ([UITextField]?, [UIAlertAction]) -> Void
        }
    }
}

// MARK: UIView

public extension UIView {
    
    var viewController: UIViewController? {
        
        var result: UIViewController?
        var responder: UIResponder? = self
        while true  {
            if let _ = responder {
                if let temp = responder as? UIViewController {
                    result = temp
                    break
                }else {
                    responder = responder?.next
                }
            }else {
                break
            }
        }
        return result
    }
}

// MARK: Timer

public extension Timer {
    
    private struct Key {
        static var block = 0
        static var deadline = 0
    }
    
    private var closure: ((Timer) -> Void)? {
        
        set {
            objc_setAssociatedObject(self, &Key.block, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
        }
        
        get {
            return objc_getAssociatedObject(self, &Key.block) as? (Timer) -> Void
        }
    }
    
    var deadline: TimeInterval {
        
        set {
            objc_setAssociatedObject(self, &Key.deadline, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
        
        get {
            return objc_getAssociatedObject(self, &Key.deadline) as? TimeInterval ?? 0
        }
    }
    
    @objc static func timerClosure(timer: Timer) {
        
        if let closure = timer.closure {
            timer.deadline -= timer.timeInterval
            if timer.isValid && timer.deadline <= 0 {
                timer.bk_invalidate()
            }
            closure(timer)
        }else {
            timer.bk_invalidate()
        }
    }
    
    @discardableResult
    static func timer(_ interval: TimeInterval, repeatCount: Int = 1, block: @escaping (Timer) -> Void) -> Timer {
        
        let timer = scheduledTimer(timeInterval: interval, target: self, selector: #selector(self.timerClosure(timer:)), userInfo: nil, repeats: true)
        timer.deadline = interval*TimeInterval(repeatCount)
        timer.closure = block
        return timer
    }
    
    func bk_invalidate() -> Void {
        
        closure = nil
        deadline = 0
        invalidate()
    }
}

// MARK: FilePreviewViewController
class FilePreviewViewController: QLPreviewController {
    
    let url: URL
    
    init(_ url: URL) {
        self.url = url
        super.init(nibName: nil, bundle: nil)
        dataSource = self
        delegate = self
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

extension FilePreviewViewController: QLPreviewControllerDataSource, QLPreviewControllerDelegate {
    func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
        1
    }
    
    func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
        return url as QLPreviewItem
    }
}

// MARK: HDocumentPickerViewController

class HDocumentPickerViewController: UIDocumentPickerViewController {
    
    var didPickDocumentClosure: ((URL?) -> Void)?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = UIColor.white
        modalTransitionStyle = .coverVertical
        self.delegate = self
    }
}

extension HDocumentPickerViewController : UIDocumentPickerDelegate {
    
    func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
        print("documentPickerWasCancelled")
    }
    
    func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
        
        guard let url = urls.first else {
            didPickDocumentClosure?(nil)
            return
        }
        didPickDocument(at: url)
    }
    
    func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
        
        didPickDocument(at: url)
    }
    
    func didPickDocument(at url: URL) -> Void {
        
        didPickDocumentClosure?(url)
    }
}

// MARK: UIViewController

extension UIViewController {
    
    func presentInputAlertController(msg: String, defaultText: String? = nil, inputDesc: String, closure: @escaping ((String) -> Void)) -> Void {
        
        let temp = UIAlertController(title: "Tips", message: msg, preferredStyle: .alert)
        temp.addTextField {
            $0.placeholder = inputDesc
            $0.keyboardType = .numbersAndPunctuation
            $0.text = defaultText
        }
        let confirm = UIAlertAction(title: "OK", style: .default) { [weak temp] (_) in
            
            guard let textField = temp?.textFields?[0] else { return }
            guard let text = textField.text, !text.isEmpty else { return }
            closure(text)
        }
        let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
        temp.addAction(confirm)
        temp.addAction(cancel)
        present(temp, animated: true, completion: nil)
    }
}

extension UIViewController {
    
    func stringInput(_ title: String, completeClosure: @escaping ((String) -> Void)) -> Void {
        
        let temp = UIAlertController.init(title: title, message: nil, preferredStyle: .alert)
        var action = UIAlertAction.init(title: "取消", style: .cancel, handler: nil)
        temp.addAction(action)
        action = UIAlertAction.init(title: "确定", style: .default, handler: { [weak temp] (_) in
            guard let temp = temp, let textFields = temp.textFields, textFields.count > 0 else { return }
            guard let value = textFields[0].text, !value.isEmpty else { return }
            completeClosure(value)
        })
        action.isEnabled = false
        temp.addAction(action)
        temp.addTextField { [weak temp] in
            guard let temp = temp else { return }
            NotificationCenter.default.addObserver(temp, selector: #selector(UIAlertController.textFieldTextDidChange), name: UITextField.textDidChangeNotification, object: $0)
        }
        temp.textFieldTextDidChangeClosure = {
            guard let textFields = $0, textFields.count > 0 else { return }
            guard let ssid = textFields[0].text else { return }
            if ssid.isEmpty {
                $1[1].isEnabled = false
            }else {
                $1[1].isEnabled = true
            }
        }
        self.present(temp, animated: true, completion: nil)
    }
    
    func valuePick<T>(_ values: [T], closure: @escaping (T) -> Void) -> Void {
        
        var actions = [UIAlertAction]()
        var action: UIAlertAction
        
        for i in values {
            action = UIAlertAction(title: "\(i)", style: .default, handler: { action in
                closure(i)
            })
            actions.append(action)
        }
        
        action = UIAlertAction.init(title: "取消", style: .cancel, handler: nil)
        actions.append(action)
        
        self.bk_presentAlertController(
            title: nil,
            message: "选择操作",
            preferredStyle: .actionSheet,
            actions: actions
        )
    }
    
    func filePick(_ extensionName: String, closure: @escaping (URL, Data) -> Void) -> Void {
        
        var temp: HDocumentPickerViewController
        if #available(iOS 14, *) {
            temp = HDocumentPickerViewController.init(
                forOpeningContentTypes: [
//                    UTType.init(tag: extensionName, tagClass: UTTagClass.filenameExtension, conformingTo: UTType.data)!
                    UTType.data,
                ], asCopy: true)
        }else {
            temp = HDocumentPickerViewController.init(documentTypes: [kUTTypeData as String], in: .import)
        }
        temp.modalPresentationStyle = .fullScreen
        temp.didPickDocumentClosure = {
            guard let url = $0 else {
                SVProgressHUD.showError(withStatus: "获取文件失败")
                return
            }
            DispatchQueue.global().async {
                do {
                    let data = try Data(contentsOf: url)
                    DispatchQueue.main.async {
                        closure(url, data)
                    }
                }catch {
                    DispatchQueue.main.async {
                        self.show(error.localizedDescription)
                    }
                }
            }
        }
        self.present(temp, animated: true, completion: nil)
    }
    
    func show(_ msg: CustomStringConvertible) -> Void {
        
        self.bk_presentWarningAlertController(title: "", message: msg.description)
    }
}

// MARK: UIImage

extension UIImage {
    
    /// 图片剪切
    ///
    /// - Parameter pixelRect: 截切位置大小
    /// - Returns: 剪切结果
    func crop(pixelRect: CGRect) -> UIImage {
        
        let rect = CGRect.init(x: ceil(pixelRect.origin.x),
                               y: ceil(pixelRect.origin.y),
                               width: floor(pixelRect.width),
                               height: floor(pixelRect.height))
        let pixelWidth = size.width*scale
        let pixelHeight = size.height*scale
        let drawRect = CGRect.init(x: -rect.origin.x, y: -rect.origin.y, width: pixelWidth, height: pixelHeight)
        //MARK: UIGraphicsBeginImageContextWithOptions size为非整数时，画布大小会向上取整
        UIGraphicsBeginImageContextWithOptions(rect.size, true, 1)
        UIColor.white.setFill()
        UIRectFill(CGRect.init(origin: .zero, size: rect.size))
        // 当rect.size为非整数时，绘制区域会向下取整
        self.draw(in: drawRect)
        let result = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return result
    }
    
    /// 将图片剪切至某个宽高比，取出中间部分
    ///
    /// - Parameter aspectRatio: 宽高比
    /// - Returns: 剪切结果
    func crop(aspectRatio: CGFloat) -> UIImage {
        
        if size.width/size.height == aspectRatio {
            return self
        }
        let pixelWidth = size.width*scale
        let pixelHeight = size.height*scale
        let rect = AVMakeRect(aspectRatio: CGSize.init(width: aspectRatio, height: 1), insideRect: CGRect.init(origin: .zero, size: CGSize.init(width: pixelWidth, height: pixelHeight)))
        return crop(pixelRect: rect)
    }
    
    /// 修改图片大小
    ///
    /// - Parameter size: 修改的大小
    /// - Returns: 修改后图片
    func resize(size: CGSize) -> UIImage {
        
        UIGraphicsBeginImageContextWithOptions(size, true, 1)
        draw(in: CGRect.init(origin: CGPoint.zero, size: size))
        let result = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return result
    }
    
    /// 将图片自适应绘制到画布
    ///
    /// - Parameter size: 大小
    /// - Returns: 修改后图片
    func fit(size: CGSize) -> UIImage {
        
        UIGraphicsBeginImageContextWithOptions(size, true, 1)
        let scale = min(size.width / self.size.width, size.height / self.size.height)
        let drawSize = CGSize(width: floor(self.size.width * scale), height: floor(self.size.height * scale))
        draw(in: CGRect.init(
            x: floor((size.width - drawSize.width) / 2),
            y: floor((size.height - drawSize.height) / 2),
            width: drawSize.width,
            height: drawSize.height
        ))
        let result = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return result
    }
}

extension UserDefaults {
    
    func setDefaultIp(_ ip: String) -> Void {
        setValue(ip, forKey: "target_ip")
        synchronize()
    }
    
    func getDefaultIp() -> String? {
        return value(forKey: "target_ip") as? String
    }
}

public struct AppInfo {
    
    public static var version: String = { Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "" }()
    
    public static var bundleId: String = { Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String ?? "" }()
    
    public static var displayName: String = {
        Bundle.main.localizedInfoDictionary?["CFBundleDisplayName"] as? String ?? Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String ?? ""
    }()
    
    public static var build: String = { Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "0" }()
}

public struct SandBox {
    
    public static let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
    
    public static let cache = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0]
}

extension Result {
    
    static func from<T, S>(successValue: T?, errorValue: S?) -> Result<T,S> {
        if let errorValue = errorValue {
            return .failure(errorValue)
        }
        return .success(successValue!)
    }
    
    static func from<S>(errorValue: S?) -> Result<Void,S> {
        if let errorValue = errorValue {
            return .failure(errorValue)
        }
        return .success(())
    }
}
