/*
 Copyright 2019 The Matrix.org Foundation C.I.C
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 
 http://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

import Foundation
import ImageIO
import MobileCoreServices

// We do not use the SwiftUTI pod anymore
// The library is embedded in MatrixKit. See Libs/SwiftUTI/README.md for more details
// import SwiftUTI

/// MXKUTI represents a Universal Type Identifier (e.g. kUTTypePNG).
/// See https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/understanding_utis/understand_utis_conc/understand_utis_conc.html#//apple_ref/doc/uid/TP40001319-CH202-SW5 for more information.
/// MXKUTI wraps UTI class from SwiftUTI library (https://github.com/mkeiser/SwiftUTI) to make it available for Objective-C.
@objcMembers
open class MXKUTI: NSObject, RawRepresentable {
    
    public typealias RawValue = String
    
    // MARK: - Properties
    
    // MARK: Private
    
    private let utiWrapper: UTI
    
    // MARK: Public
    
    /// UTI string
    public var rawValue: String {
        return utiWrapper.rawValue
    }
    
    /// Return associated prefered file extension (e.g. "png").
    public var fileExtension: String? {
        return utiWrapper.fileExtension
    }
    
    /// Return associated prefered mime-type (e.g. "image/png").
    public var mimeType: String? {
        return utiWrapper.mimeType
    }
    
    // MARK: - Setup
    
    // MARK: Private
    
    private init(utiWrapper: UTI) {
        self.utiWrapper = utiWrapper
        super.init()
    }
    
    // MARK: Public
    
    /// Initialize with UTI String.
    /// Note: Although this initializer is marked as failable, due to RawRepresentable conformity, it cannot fail.
    ///
    /// - Parameter rawValue: UTI String (e.g. "public.png").
    public required init?(rawValue: String) {
        let utiWrapper = UTI(rawValue: rawValue)
        self.utiWrapper = utiWrapper
        super.init()
    }
    
    /// Initialize with UTI CFString.
    ///
    /// - Parameter cfRawValue: UTI CFString (e.g. kUTTypePNG).
    public convenience init?(cfRawValue: CFString) {
        self.init(rawValue: cfRawValue as String)
    }
    
    /// Initialize with file extension.
    ///
    /// - Parameter fileExtension: A file extesion (e.g. "png").
    public convenience init(fileExtension: String) {
        let utiWrapper = UTI(withExtension: fileExtension)
        self.init(utiWrapper: utiWrapper)
    }
    
    /// Initialize with MIME type.
    ///
    /// - Parameter mimeType: A MIME type (e.g. "image/png").
    public convenience init?(mimeType: String) {
        let utiWrapper = UTI(withMimeType: mimeType)
        self.init(utiWrapper: utiWrapper)
    }
    
    /// Check current UTI conformance with another UTI.
    ///
    /// - Parameter otherUTI: UTI which to conform with.
    /// - Returns: true if self conforms to other UTI.
    public func conforms(to otherUTI: MXKUTI) -> Bool {
        return self.utiWrapper.conforms(to: otherUTI.utiWrapper)
    }
    
    /// Check whether the current UTI conforms to any UTIs within an array.
    ///
    /// - Parameter otherUTIs: UTI which to conform with.
    /// - Returns: true if self conforms to any of the other UTIs.
    public func conformsToAny(of otherUTIs: [MXKUTI]) -> Bool {
        for uti in otherUTIs {
            if conforms(to: uti) {
                return true
            }
        }
        
        return false
    }
}

// MARK: - Other convenients initializers
extension MXKUTI {
    
    /// Initialize with image data.
    ///
    /// - Parameter imageData: Image data.
    convenience init?(imageData: Data) {
        guard let imageSource = CGImageSourceCreateWithData(imageData as CFData, nil),
            let uti = CGImageSourceGetType(imageSource) else {
                return nil
        }
        self.init(rawValue: uti as String)
    }
    
    // swiftlint:disable unused_optional_binding
    
    /// Initialize with local file URL.
    /// This method is currently applicable only to URLs for file system resources.
    ///
    /// - Parameters:
    ///   - localFileURL: Local file URL.
    ///   - loadResourceValues: Indicate true to prefetch `typeIdentifierKey` URLResourceKey
    convenience init?(localFileURL: URL, loadResourceValues: Bool = true) {
        if loadResourceValues,
            let _ = try? FileManager.default.contentsOfDirectory(at: localFileURL.deletingLastPathComponent(), includingPropertiesForKeys: [.typeIdentifierKey], options: []),
            let uti = try? localFileURL.resourceValues(forKeys: [.typeIdentifierKey]).typeIdentifier {
            self.init(rawValue: uti)
        } else if localFileURL.pathExtension.isEmpty == false {
            let fileExtension = localFileURL.pathExtension
            self.init(fileExtension: fileExtension)
        } else {
            return nil
        }
    }
    
    // swiftlint:enable unused_optional_binding
    
    public convenience init?(localFileURL: URL) {
        self.init(localFileURL: localFileURL, loadResourceValues: true)
    }
}

// MARK: - Convenients conformance UTIs methods
extension MXKUTI {
    public var isImage: Bool {
        return self.conforms(to: MXKUTI.image)
    }
    
    public var isVideo: Bool {
        return self.conforms(to: MXKUTI.movie)
    }
    
    public var isFile: Bool {
        return self.conforms(to: MXKUTI.data)
    }
}

// swiftlint:disable force_unwrapping

// MARK: - Some system defined UTIs
extension MXKUTI {
    public static let data = MXKUTI(cfRawValue: kUTTypeData)!
    public static let text = MXKUTI(cfRawValue: kUTTypeText)!
    public static let audio = MXKUTI(cfRawValue: kUTTypeAudio)!
    public static let video = MXKUTI(cfRawValue: kUTTypeVideo)!
    public static let movie = MXKUTI(cfRawValue: kUTTypeMovie)!
    public static let image = MXKUTI(cfRawValue: kUTTypeImage)!
    public static let png = MXKUTI(cfRawValue: kUTTypePNG)!
    public static let jpeg = MXKUTI(cfRawValue: kUTTypeJPEG)!
    public static let svg = MXKUTI(cfRawValue: kUTTypeScalableVectorGraphics)!
    public static let url = MXKUTI(cfRawValue: kUTTypeURL)!
    public static let fileUrl = MXKUTI(cfRawValue: kUTTypeFileURL)!
    public static let html = MXKUTI(cfRawValue: kUTTypeHTML)!
    public static let xml = MXKUTI(cfRawValue: kUTTypeXML)!
}

// swiftlint:enable force_unwrapping

// MARK: - Convenience static methods
extension MXKUTI {
    
    public static func mimeType(from fileExtension: String) -> String? {
        return MXKUTI(fileExtension: fileExtension).mimeType
    }
    
    public static func fileExtension(from mimeType: String) -> String? {
        return MXKUTI(mimeType: mimeType)?.fileExtension
    }
}
