//
//  Sound.swift
//  LeCountdown
//
//  Created by Laurent Morvillier on 30/01/2023.
//

import Foundation
import AVFoundation

protocol Localized {
    var localizedString: String { get }
}

class SoundCatalog {
    
    static let main: SoundCatalog = SoundCatalog()
    
    fileprivate var _soundsByPlaylist: [Playlist : [Sound]] = [:]
    
    init() {
        self._soundsByPlaylist = Dictionary(grouping: Sound.allCases) { $0.playlist }
    }
    
    func sounds(for playlist: Playlist) -> [Sound] {
        return self._soundsByPlaylist[playlist] ?? []
    }
    
}

enum Playlist: Int, CaseIterable, Identifiable, Localized {

    var id: Int { return self.rawValue }

    case custom
    case nature
    case fun
    case stephanBodzin
    case relax
    
    static var selectable: [Playlist] {
        return Playlist.allCases.filter { $0 != .custom }
    }

    var localizedString: String {
        switch self {
        case .nature:
            return NSLocalizedString("Nature", comment: "")
        case .fun:
            return NSLocalizedString("Fun", comment: "")
        case .stephanBodzin:
            return "Stephan Bodzin"
        case .custom:
            return NSLocalizedString("Custom", comment: "")
        case .relax:
            return NSLocalizedString("Relax", comment: "")
        }
    }
    
}

// Sound id are stored thus case order should not be changed
enum Sound: Int, CaseIterable, Identifiable, Localized {
    
    var id: Int { return self.rawValue }
    
    case trainhorn = 1 // default
    case forestStream
    // StephanBodzin
    case sbSEM_Synths_Loop4_Nothing_Like_You
    case sbClave_Loop_LLL
    case sbLoop_ToneSD_Boavista
    case sbArpeggio_Loop_River
    case sbSquareArp_Loop_River
    case sbHighChords_Loop_River
    case sbMatriarchFxs_Loop2_Collider
    // Relax
    case FF_SH_bowl_drone_tapping_C
    case FF_SH_flute_melody_ambient_stacked_profound_Dmin
    case FF_SH_bowl_drone_tap_hold_E
    case EX_ATSM_Koshi_Chimes_Aria_Tuning_Texture_Longer_Dm
    case EX_ATSM_Bell_Binaural_Flam_Eb
    case EX_ATSM_160_Metal_Tonal_Percussion_Sansula_Loop_Call_Am
    case EX_ATSM_125_Metal_Percussion_Wing_Loop_Chimey_Dm
    case EX_ATSM_140_Koshi_Chimes_Aria_Tuning_Loop_Wondering_Am
    case EX_ATSM_20_Inch_Highwall_Bowl_Hit_Ring_Ab
    
    static var `default`: Sound { .sbSEM_Synths_Loop4_Nothing_Like_You }
    
    var localizedString: String {
        switch self {
        case .trainhorn: return NSLocalizedString("Train horn", comment: "")
        case .forestStream: return NSLocalizedString("Forest stream", comment: "")
        case .sbSEM_Synths_Loop4_Nothing_Like_You: return "Nothing Like You"
        case .sbClave_Loop_LLL: return "LLL"
        case .sbLoop_ToneSD_Boavista: return "Boavista"
        case .sbArpeggio_Loop_River: return "River 1"
        case .sbSquareArp_Loop_River: return "River 2"
        case .sbHighChords_Loop_River: return "River 3"
        case .sbMatriarchFxs_Loop2_Collider: return "Collider"
        case .FF_SH_bowl_drone_tapping_C: return "Bowl 1"
        case .FF_SH_flute_melody_ambient_stacked_profound_Dmin: return "Flute"
        case .FF_SH_bowl_drone_tap_hold_E: return "Bowl 2"
        case .EX_ATSM_Koshi_Chimes_Aria_Tuning_Texture_Longer_Dm: return "Koshi Chimes 1"
        case .EX_ATSM_Bell_Binaural_Flam_Eb: return "Bell Binaural"
        case .EX_ATSM_160_Metal_Tonal_Percussion_Sansula_Loop_Call_Am: return "Sansula"
        case .EX_ATSM_125_Metal_Percussion_Wing_Loop_Chimey_Dm: return "Chimey percussion"
        case .EX_ATSM_140_Koshi_Chimes_Aria_Tuning_Loop_Wondering_Am: return "Koshi Chimes 2"
        case .EX_ATSM_20_Inch_Highwall_Bowl_Hit_Ring_Ab: return "Bowl 3"
        }
    }
    
    var fileName: String {
        switch self {
        case .trainhorn: return "train_horn.mp3"
        case .forestStream: return "forest_stream.mp3"
        case .sbSEM_Synths_Loop4_Nothing_Like_You: return "SEM_Synths_Loop4_Nothing_Like_You.wav"
        case .sbClave_Loop_LLL: return "Clave_Loop_LLL.wav"
        case .sbLoop_ToneSD_Boavista: return "Loop_ToneSD_Boavista.wav"
        case .sbArpeggio_Loop_River: return "Arpeggio_Loop_River.wav"
        case .sbSquareArp_Loop_River: return "SquareArp_Loop_River.wav"
        case .sbHighChords_Loop_River: return "HighChords_Loop_River.wav"
        case .sbMatriarchFxs_Loop2_Collider: return "MatriarchFxs_Loop2_Collider.wav"
        case .FF_SH_bowl_drone_tapping_C: return "FF_SH_bowl_drone_tapping_C.wav"
        case .FF_SH_flute_melody_ambient_stacked_profound_Dmin: return "FF_SH_flute_melody_ambient_stacked_profound_Dmin.wav"
        case .FF_SH_bowl_drone_tap_hold_E: return "FF_SH_bowl_drone_tap_hold_E.wav"
        case .EX_ATSM_Koshi_Chimes_Aria_Tuning_Texture_Longer_Dm: return "EX_ATSM_Koshi_Chimes_Aria_Tuning_Texture_Longer_Dm.wav"
        case .EX_ATSM_Bell_Binaural_Flam_Eb: return "EX_ATSM_Bell_Binaural_Flam_Eb.wav"
        case .EX_ATSM_160_Metal_Tonal_Percussion_Sansula_Loop_Call_Am: return "EX_ATSM_160_Metal_Tonal_Percussion_Sansula_Loop_Call_Am.wav"
        case .EX_ATSM_125_Metal_Percussion_Wing_Loop_Chimey_Dm: return "EX_ATSM_125_Metal_Percussion_Wing_Loop_Chimey_Dm.wav"
        case .EX_ATSM_140_Koshi_Chimes_Aria_Tuning_Loop_Wondering_Am: return "EX_ATSM_140_Koshi_Chimes_Aria_Tuning_Loop_Wondering_Am.wav"
        case .EX_ATSM_20_Inch_Highwall_Bowl_Hit_Ring_Ab: return "EX_ATSM_20_Inch_Highwall_Bowl_Hit_Ring_Ab.wav"
        }
    }
    
    var playlist: Playlist {
        switch self {
        case .trainhorn: return .fun
        case .forestStream: return .nature
        case .sbSEM_Synths_Loop4_Nothing_Like_You, .sbClave_Loop_LLL, .sbLoop_ToneSD_Boavista, .sbArpeggio_Loop_River, .sbSquareArp_Loop_River, .sbHighChords_Loop_River, .sbMatriarchFxs_Loop2_Collider:
            return .stephanBodzin
        case .FF_SH_bowl_drone_tapping_C, .FF_SH_flute_melody_ambient_stacked_profound_Dmin, .FF_SH_bowl_drone_tap_hold_E, .EX_ATSM_Koshi_Chimes_Aria_Tuning_Texture_Longer_Dm, .EX_ATSM_Bell_Binaural_Flam_Eb, .EX_ATSM_160_Metal_Tonal_Percussion_Sansula_Loop_Call_Am, .EX_ATSM_125_Metal_Percussion_Wing_Loop_Chimey_Dm, .EX_ATSM_140_Koshi_Chimes_Aria_Tuning_Loop_Wondering_Am, .EX_ATSM_20_Inch_Highwall_Bowl_Hit_Ring_Ab:
            return .relax
        }
    }
    
    var isRestricted: Bool {
        switch self {
        case .trainhorn, .forestStream, .sbSEM_Synths_Loop4_Nothing_Like_You:
            return false
        default:
            return true
        }
    }
    
    var url: URL? {
        let components = self.fileName.components(separatedBy: ".")
        if components.count == 2 {
            return Bundle.main.url(forResource: components[0], withExtension: components[1])
        } else {
            print("bad sound file name for \(self)")
            return nil
        }
    }
    
    func soundFile() throws -> SoundFile {
        return try SoundFile(fullName: self.fileName)
    }
    
    func duration() async throws -> TimeInterval {
        
        guard let url = self.url else {
            print("sound \(self) has no url")
            return -1.0
        }
        
        let audioAsset = AVURLAsset.init(url: url, options: nil)
        let duration = try await audioAsset.load(.duration)
        return CMTimeGetSeconds(duration)
    }
    
    static func computeSoundDurationsIfNecessary()  {
        Task {
            for sound in Sound.allCases {
                if Preferences.soundDurations[sound.rawValue] == nil {
                    if let duration = try? await sound.duration() {
                        Preferences.soundDurations[sound.rawValue] = duration
                    }
                }
            }
        }
    }
    
    var formattedDuration: String {
        if let duration = Preferences.soundDurations[self.rawValue] {
            return duration.minuteSecond
        } else {
            return ""
        }
    }
    
}
