<commit_before>//  Copyright © 2016 One by Aol : Publishers. All rights reserved.

import AVFoundation

private let assetKey = "availableMediaCharacteristicsWithMediaSelectionOptions"

public protocol UUIDPhantom: Hashable {
    var uuid: UUID { get }
}

extension UUIDPhantom {
    public var hashValue: Int { return uuid.hashValue }
    public static func ==(left: Self, right: Self) -> Bool {
        return left.uuid == right.uuid
    }
}

public struct AvailableMediaOptions {
    public struct Option: UUIDPhantom {
        public let uuid: UUID
        public let name: String
        
        public init(uuid: UUID = UUID(), name: String) {
            self.uuid = uuid
            self.name = name
        }
    }
    
    public static let empty = AvailableMediaOptions(unselectedOptions: [],
                                                    selectedOption: nil)
    public let unselectedOptions: [Option]
    public let selectedOption: Option?
}

final class MediaCharacteristicRenderer {
    typealias Option = AvailableMediaOptions.Option
    
    struct Props {
        let item: AVPlayerItem
        let didStartMediaOptionsDiscovery: () -> ()
        let didDiscoverAudibleOptions: (AvailableMediaOptions) -> ()
        let didDiscoverLegibleOptions: (AvailableMediaOptions) -> ()
        var selectedAudibleOption: Option?
        var selectedLegibleOption: Option?
    }
    
    struct MediaOptionCache {
        let item: AVPlayerItem
        var audibleOptions: [Option: AVMediaSelectionOption] = [:]
        var legibleOptions: [Option: AVMediaSelectionOption] = [:]
        
        init(item: AVPlayerItem) { self.item = item }
    }
    
    var mediaOptionCache: MediaOptionCache?
    
    var props: Props? {
        didSet {
            /// Verify that we have item to look for
            guard let item = props?.item else {
                mediaOptionCache = nil
                return
            }
            
            let optionsForSelect: [AVMediaSelectionGroup: AVMediaSelectionOption]? = {
                guard let props = props else { return nil }
                guard let mediaOptionCache = mediaOptionCache else { return nil }
                var options: [AVMediaSelectionGroup: AVMediaSelectionOption] = [:]
                
                if let group = item.asset.mediaSelectionGroup(forMediaCharacteristic: AVMediaCharacteristicAudible) {
                    guard let selectedOption = props.selectedAudibleOption else { return nil }
                    options[group] = mediaOptionCache.audibleOptions[selectedOption]
                }
                if let group = item.asset.mediaSelectionGroup(forMediaCharacteristic: AVMediaCharacteristicLegible) {
                    guard let selectedOption = props.selectedLegibleOption else { return nil }
                    options[group] = mediaOptionCache.legibleOptions[selectedOption]
                }
                
                return options
            }()
            
            optionsForSelect?.forEach { item.select($0.value, in: $0.key) }
            
            
            guard item != mediaOptionCache?.item else { return }
            
            mediaOptionCache = MediaOptionCache(item: item)
            
            item.asset.loadValuesAsynchronously(forKeys: [assetKey]) { [weak self] in
                guard let `self` = self else { return }
                guard case .loaded = item.asset.statusOfValue(forKey: assetKey, error: nil) else { return }
                
                func mapToPair(option: AVMediaSelectionOption) -> (Option, AVMediaSelectionOption) {
                    return (Option(name: option.displayName), option)
                }
                func first<T, U>(pair: (T, U)) -> T { return pair.0 }
                
                func audibleOptions() -> AvailableMediaOptions {
                    func map(group: AVMediaSelectionGroup) -> AvailableMediaOptions {
                        let audibleOptionsPairs = group.options.map(mapToPair)
                        self.mediaOptionCache?.audibleOptions = Dictionary(uniqueKeysWithValues: audibleOptionsPairs)
                        let selectedAudibleOptionPair = audibleOptionsPairs.first {
                            $0.1 == item.selectedMediaOption(in: group)
                        }
                        return .init(unselectedOptions: audibleOptionsPairs.map(first),
                                     selectedOption: selectedAudibleOptionPair?.0)
                    }
                    return item.asset
                        .mediaSelectionGroup(forMediaCharacteristic: AVMediaCharacteristicAudible)
                        .flatMap(map) ?? .empty
                }
                self.props?.didDiscoverAudibleOptions(audibleOptions())
                
                func legibleOptions() -> AvailableMediaOptions {
                    func map(group: AVMediaSelectionGroup) -> AvailableMediaOptions {
                        let pairs = group.options
                            .filter(AVMediaSelectionOption.hasLanguageTag)
                            .map(mapToPair)
                            // Add 'None' option on top. Selected by default
                            .extend(element: (Option(name: "None"), AVMediaSelectionOption()),
                                    onTop: true)
                        self.mediaOptionCache?.legibleOptions = Dictionary(uniqueKeysWithValues: pairs)
                        return .init(unselectedOptions: pairs.map(first),
                                     selectedOption: pairs.first?.0)
                    }
                    return item.asset
                        .mediaSelectionGroup(forMediaCharacteristic: AVMediaCharacteristicLegible)
                        .flatMap(map) ?? .empty
                }
                self.props?.didDiscoverLegibleOptions(legibleOptions())
            }
        }
    }
}

extension Array {
    internal func extend(element: Element, onTop: Bool = true) -> [Element] {
        var new = self
        onTop ? new.insert(element, at: 0) : new.append(element)
        return new
    }
}<commit_msg>Change internal to fileprivate for Array.extend()<commit_after>//  Copyright © 2016 One by Aol : Publishers. All rights reserved.

import AVFoundation

private let assetKey = "availableMediaCharacteristicsWithMediaSelectionOptions"

public protocol UUIDPhantom: Hashable {
    var uuid: UUID { get }
}

extension UUIDPhantom {
    public var hashValue: Int { return uuid.hashValue }
    public static func ==(left: Self, right: Self) -> Bool {
        return left.uuid == right.uuid
    }
}

public struct AvailableMediaOptions {
    public struct Option: UUIDPhantom {
        public let uuid: UUID
        public let name: String
        
        public init(uuid: UUID = UUID(), name: String) {
            self.uuid = uuid
            self.name = name
        }
    }
    
    public static let empty = AvailableMediaOptions(unselectedOptions: [],
                                                    selectedOption: nil)
    public let unselectedOptions: [Option]
    public let selectedOption: Option?
}

final class MediaCharacteristicRenderer {
    typealias Option = AvailableMediaOptions.Option
    
    struct Props {
        let item: AVPlayerItem
        let didStartMediaOptionsDiscovery: () -> ()
        let didDiscoverAudibleOptions: (AvailableMediaOptions) -> ()
        let didDiscoverLegibleOptions: (AvailableMediaOptions) -> ()
        var selectedAudibleOption: Option?
        var selectedLegibleOption: Option?
    }
    
    struct MediaOptionCache {
        let item: AVPlayerItem
        var audibleOptions: [Option: AVMediaSelectionOption] = [:]
        var legibleOptions: [Option: AVMediaSelectionOption] = [:]
        
        init(item: AVPlayerItem) { self.item = item }
    }
    
    var mediaOptionCache: MediaOptionCache?
    
    var props: Props? {
        didSet {
            /// Verify that we have item to look for
            guard let item = props?.item else {
                mediaOptionCache = nil
                return
            }
            
            let optionsForSelect: [AVMediaSelectionGroup: AVMediaSelectionOption]? = {
                guard let props = props else { return nil }
                guard let mediaOptionCache = mediaOptionCache else { return nil }
                var options: [AVMediaSelectionGroup: AVMediaSelectionOption] = [:]
                
                if let group = item.asset.mediaSelectionGroup(forMediaCharacteristic: AVMediaCharacteristicAudible) {
                    guard let selectedOption = props.selectedAudibleOption else { return nil }
                    options[group] = mediaOptionCache.audibleOptions[selectedOption]
                }
                if let group = item.asset.mediaSelectionGroup(forMediaCharacteristic: AVMediaCharacteristicLegible) {
                    guard let selectedOption = props.selectedLegibleOption else { return nil }
                    options[group] = mediaOptionCache.legibleOptions[selectedOption]
                }
                
                return options
            }()
            
            optionsForSelect?.forEach { item.select($0.value, in: $0.key) }
            
            
            guard item != mediaOptionCache?.item else { return }
            
            mediaOptionCache = MediaOptionCache(item: item)
            
            item.asset.loadValuesAsynchronously(forKeys: [assetKey]) { [weak self] in
                guard let `self` = self else { return }
                guard case .loaded = item.asset.statusOfValue(forKey: assetKey, error: nil) else { return }
                
                func mapToPair(option: AVMediaSelectionOption) -> (Option, AVMediaSelectionOption) {
                    return (Option(name: option.displayName), option)
                }
                func first<T, U>(pair: (T, U)) -> T { return pair.0 }
                
                func audibleOptions() -> AvailableMediaOptions {
                    func map(group: AVMediaSelectionGroup) -> AvailableMediaOptions {
                        let audibleOptionsPairs = group.options.map(mapToPair)
                        self.mediaOptionCache?.audibleOptions = Dictionary(uniqueKeysWithValues: audibleOptionsPairs)
                        let selectedAudibleOptionPair = audibleOptionsPairs.first {
                            $0.1 == item.selectedMediaOption(in: group)
                        }
                        return .init(unselectedOptions: audibleOptionsPairs.map(first),
                                     selectedOption: selectedAudibleOptionPair?.0)
                    }
                    return item.asset
                        .mediaSelectionGroup(forMediaCharacteristic: AVMediaCharacteristicAudible)
                        .flatMap(map) ?? .empty
                }
                self.props?.didDiscoverAudibleOptions(audibleOptions())
                
                func legibleOptions() -> AvailableMediaOptions {
                    func map(group: AVMediaSelectionGroup) -> AvailableMediaOptions {
                        let pairs = group.options
                            .filter(AVMediaSelectionOption.hasLanguageTag)
                            .map(mapToPair)
                            // Add 'None' option on top. Selected by default
                            .extend(element: (Option(name: "None"), AVMediaSelectionOption()),
                                    onTop: true)
                        self.mediaOptionCache?.legibleOptions = Dictionary(uniqueKeysWithValues: pairs)
                        return .init(unselectedOptions: pairs.map(first),
                                     selectedOption: pairs.first?.0)
                    }
                    return item.asset
                        .mediaSelectionGroup(forMediaCharacteristic: AVMediaCharacteristicLegible)
                        .flatMap(map) ?? .empty
                }
                self.props?.didDiscoverLegibleOptions(legibleOptions())
            }
        }
    }
}

extension Array {
    fileprivate func extend(element: Element, onTop: Bool = true) -> [Element] {
        var new = self
        onTop ? new.insert(element, at: 0) : new.append(element)
        return new
    }
}
{'fraction_non_alphanumeric': 0.07497360084477296, 'fraction_numerical': 0.0019494760783039559, 'mean_word_length': 1.6759400130406434, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3520854', 'n_tokens_mistral': 2965, 'n_tokens_neox': 2829, 'n_words': 689}