/* JellyfinPlayer/Swiftfin is subject to the terms of the Mozilla Public
 * License, v2.0. If a copy of the MPL was not distributed with this
 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
 *
 * Copyright 2021 Aiden Vigue & Jellyfin Contributors
 */

// lol can someone buy me a coffee this took forever :|

import Foundation
import JellyfinAPI

enum CPUModel {
    case A4
    case A5
    case A5X
    case A6
    case A6X
    case A7
    case A7X
    case A8
    case A8X
    case A9
    case A9X
    case A10
    case A10X
    case A11
    case A12
    case A12X
    case A12Z
    case A13
    case A14
    case A99
}

class DeviceProfileBuilder {
    public var bitrate: Int = 0

    public func setMaxBitrate(bitrate: Int) {
        self.bitrate = bitrate
    }

    public func buildProfile() -> DeviceProfile {
        let maxStreamingBitrate = bitrate
        let maxStaticBitrate = bitrate
        let musicStreamingTranscodingBitrate = bitrate

        // Build direct play profiles
            var directPlayProfiles: [DirectPlayProfile] = []
        directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv,webm", audioCodec: "aac,mp3,wav", videoCodec: "h264,mpeg4,vp9", type: .video)]

            // Device supports Dolby Digital (AC3, EAC3)
            if supportsFeature(minimumSupported: .A8X) {
                if supportsFeature(minimumSupported: .A9) {
                    directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv,webm", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", videoCodec: "hevc,h264,hev1,mpeg4,vp9", type: .video)] // HEVC/H.264 with Dolby Digital
                } else {
                    directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv,webm", audioCodec: "ac3,eac3,aac,mp3,wav,opus", videoCodec: "h264,mpeg4,vp9", type: .video)] // H.264 with Dolby Digital
                }
            }

            // Device supports Dolby Vision?
            if supportsFeature(minimumSupported: .A10X) {
                directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv,webm", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", videoCodec: "dvhe,dvh1,h264,hevc,hev1,mpeg4,vp9", type: .video)] // H.264/HEVC with Dolby Digital - No Atmos - Vision
            }

            // Device supports Dolby Atmos?
            if supportsFeature(minimumSupported: .A12) {
                directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv,webm", audioCodec: "aac,mp3,wav,ac3,eac3,flac,truehd,dts,dca,opus", videoCodec: "h264,hevc,dvhe,dvh1,h264,hevc,hev1,mpeg4,vp9", type: .video)] // H.264/HEVC with Dolby Digital & Atmos - Vision
            }

        // Build transcoding profiles
            var transcodingProfiles: [TranscodingProfile] = []
        transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "h264,mpeg4", audioCodec: "aac,mp3,wav")]

            // Device supports Dolby Digital (AC3, EAC3)
            if supportsFeature(minimumSupported: .A8X) {
                if supportsFeature(minimumSupported: .A9) {
                    transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "h264,hevc,mpeg4", audioCodec: "aac,mp3,wav,eac3,ac3,flac,opus", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)]
                } else {
                    transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "h264,mpeg4", audioCodec: "aac,mp3,wav,eac3,ac3,opus", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)]
                }
            }

            // Device supports FLAC?
            if supportsFeature(minimumSupported: .A10X) {
                transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "hevc,h264,mpeg4", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)]
            }

        var codecProfiles: [CodecProfile] = []

        let h264CodecConditions: [ProfileCondition] = [
            ProfileCondition(condition: .notEquals, property: .isAnamorphic, value: "true", isRequired: false),
            ProfileCondition(condition: .equalsAny, property: .videoProfile, value: "high|main|baseline|constrained baseline", isRequired: false),
            ProfileCondition(condition: .lessThanEqual, property: .videoLevel, value: "80", isRequired: false),
            ProfileCondition(condition: .notEquals, property: .isInterlaced, value: "true", isRequired: false)]
        let hevcCodecConditions: [ProfileCondition] = [
            ProfileCondition(condition: .notEquals, property: .isAnamorphic, value: "true", isRequired: false),
            ProfileCondition(condition: .equalsAny, property: .videoProfile, value: "high|main|main 10", isRequired: false),
            ProfileCondition(condition: .lessThanEqual, property: .videoLevel, value: "175", isRequired: false),
            ProfileCondition(condition: .notEquals, property: .isInterlaced, value: "true", isRequired: false)]

        codecProfiles.append(CodecProfile(type: .video, applyConditions: h264CodecConditions, codec: "h264"))

        if supportsFeature(minimumSupported: .A9) {
            codecProfiles.append(CodecProfile(type: .video, applyConditions: hevcCodecConditions, codec: "hevc"))
        }

        var subtitleProfiles: [SubtitleProfile] = []

        subtitleProfiles.append(SubtitleProfile(format: "ass", method: .embed))
        subtitleProfiles.append(SubtitleProfile(format: "ssa", method: .embed))
        subtitleProfiles.append(SubtitleProfile(format: "subrip", method: .embed))
        subtitleProfiles.append(SubtitleProfile(format: "sub", method: .embed))
        subtitleProfiles.append(SubtitleProfile(format: "pgssub", method: .embed))
        subtitleProfiles.append(SubtitleProfile(format: "subrip", method: .external))
        subtitleProfiles.append(SubtitleProfile(format: "sub", method: .external))
        subtitleProfiles.append(SubtitleProfile(format: "ass", method: .external))
        subtitleProfiles.append(SubtitleProfile(format: "ssa", method: .external))
        subtitleProfiles.append(SubtitleProfile(format: "vtt", method: .external))
        subtitleProfiles.append(SubtitleProfile(format: "ass", method: .external))
        subtitleProfiles.append(SubtitleProfile(format: "ssa", method: .external))

        let responseProfiles: [ResponseProfile] = [ResponseProfile(container: "m4v", type: .video, mimeType: "video/mp4")]

        let profile = DeviceProfile(maxStreamingBitrate: maxStreamingBitrate, maxStaticBitrate: maxStaticBitrate, musicStreamingTranscodingBitrate: musicStreamingTranscodingBitrate, directPlayProfiles: directPlayProfiles, transcodingProfiles: transcodingProfiles, containerProfiles: [], codecProfiles: codecProfiles, responseProfiles: responseProfiles, subtitleProfiles: subtitleProfiles)

        return profile
    }

    private func supportsFeature(minimumSupported: CPUModel) -> Bool {
        let intValues: [CPUModel: Int] = [.A4: 1, .A5: 2, .A5X: 3, .A6: 4, .A6X: 5, .A7: 6, .A7X: 7, .A8: 8, .A8X: 9, .A9: 10, .A9X: 11, .A10: 12, .A10X: 13, .A11: 14, .A12: 15, .A12X: 16, .A12Z: 16, .A13: 17, .A14: 18, .A99: 99]
        return intValues[CPUinfo()] ?? 0 >= intValues[minimumSupported] ?? 0
    }

    /**********************************************
    *  CPUInfo():
    *     Returns a hardcoded value of the current
    * devices CPU name.
    ***********************************************/
    private func CPUinfo() -> CPUModel {

        #if targetEnvironment(simulator)
        let identifier = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"]!
        #else

        var systemInfo = utsname()
        uname(&systemInfo)
        let machineMirror = Mirror(reflecting: systemInfo.machine)
        let identifier = machineMirror.children.reduce("") { identifier, element in
            guard let value = element.value as? Int8, value != 0 else { return identifier }
            return identifier + String(UnicodeScalar(UInt8(value)))
        }
        #endif

        switch identifier {
        case "iPod5,1":                                              return .A5
        case "iPod7,1":                                              return .A8
        case "iPod9,1":                                              return .A10
        case "iPhone3,1", "iPhone3,2", "iPhone3,3":                  return .A4
        case "iPhone4,1":                                            return .A5
        case "iPhone5,1", "iPhone5,2":                               return .A6
        case "iPhone5,3", "iPhone5,4":                               return .A6
        case "iPhone6,1", "iPhone6,2":                               return .A7
        case "iPhone7,2":                                            return .A8
        case "iPhone7,1":                                            return .A8
        case "iPhone8,1":                                            return .A9
        case "iPhone8,2", "iPhone8,4":                               return .A9
        case "iPhone9,1", "iPhone9,3":                               return .A10
        case "iPhone9,2", "iPhone9,4":                               return .A10
        case "iPhone10,1", "iPhone10,4":                             return .A11
        case "iPhone10,2", "iPhone10,5":                             return .A11
        case "iPhone10,3", "iPhone10,6":                             return .A11
        case "iPhone11,2", "iPhone11,6", "iPhone11,8":               return .A12
        case "iPhone12,1", "iPhone12,3", "iPhone12,5", "iPhone12,8": return .A13
        case "iPhone13,1", "iPhone13,2", "iPhone13,3", "iPhone13,4": return .A14
        case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4":             return .A5
        case "iPad3,1", "iPad3,2", "iPad3,3":                        return .A5X
        case "iPad3,4", "iPad3,5", "iPad3,6":                        return .A6X
        case "iPad4,1", "iPad4,2", "iPad4,3":                        return .A7
        case "iPad5,3", "iPad5,4":                                   return .A8X
        case "iPad6,11", "iPad6,12":                                 return .A9
        case "iPad2,5", "iPad2,6", "iPad2,7":                        return .A5
        case "iPad4,4", "iPad4,5", "iPad4,6":                        return .A7
        case "iPad4,7", "iPad4,8", "iPad4,9":                        return .A7
        case "iPad5,1", "iPad5,2":                                   return .A8
        case "iPad11,1", "iPad11,2":                                 return .A12
        case "iPad6,3", "iPad6,4":                                   return .A9X
        case "iPad6,7", "iPad6,8":                                   return .A9X
        case "iPad7,1", "iPad7,2":                                   return .A10X
        case "iPad7,3", "iPad7,4":                                   return .A10X
        case "iPad7,5", "iPad7,6", "iPad7,11", "iPad7,12":           return .A10
        case "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4":             return .A12X
        case "iPad8,5", "iPad8,6", "iPad8,7", "iPad8,8":             return .A12X
        case "iPad8,9", "iPad8,10", "iPad8,11", "iPad8,12":          return .A12Z
        case "iPad11,3", "iPad11,4", "iPad11,6", "iPad11,7":         return .A12
        case "iPad13,1", "iPad13,2":                                 return .A14
        case "AppleTV5,3":                                           return .A8
        case "AppleTV6,2":                                           return .A10X
        case "AppleTV11,1":                                           return .A12
        case "AudioAccessory1,1":                                    return .A8
        default:                                                     return .A99
        }
    }
}
