//
//  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
    
    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: "")
        }
    }
    
}

// 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
    
    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"
        }
    }
    
    var soundName: 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"
        }
    }
    
    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
        }
    }
    
    var url: URL? {
        
        let components = self.soundName.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.soundName)
    }
    
    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 ""
        }
    }
    
}
