
const {ccclass, property} = cc._decorator;

@ccclass
export class SoundManager extends cc.Component {

    /**
     * 背景音乐通道
     */
    private musicSoundChannel:AudioChannel;
    /**
     * 固定通道
     */
    private soundChannelMap:Map<string,AudioChannel>;
    /**
     * 重复利用通道
     */
    private recycleSoundChannelList:AudioChannel[];
    private recycleSoundChannelPool:AudioChannel[];

    onLoad():void{
        SoundManager.instance=this;
        this.soundChannelMap=new Map<string,AudioChannel>();
        this.recycleSoundChannelList=[];
        this.recycleSoundChannelPool=[];
    }

    start () {
        //背景音乐通道
        this.musicSoundChannel=new AudioChannel();
        this.musicSoundChannel.aSComponent=this.addComponent(cc.AudioSource);
        //最大重复利用通道4个
        let channel:AudioChannel;
        for (let index = 0; index < 6; index++) {
            channel=new AudioChannel();
            channel.aSComponent=this.addComponent(cc.AudioSource);
            this.recycleSoundChannelPool.push(channel);
        }
    }

    /**
     * 播放音频
     * @param assetBundle 
     * @param soundPath 
     */
    public PlayMusic(assetBundle:string,soundPath:string):void{
        this.musicSoundChannel.Play(assetBundle,soundPath,true);
    }

    /**
     * 固定通道播放音效
     * @param channel       通道
     * @param assetBundle   资源包
     * @param soundPath     音效地址
     * @param isLoop        是否循环播放
     * @param force         是否在相同音频的情况下强制从头开始播放
     */
    public PlaySound(channel:string,assetBundle:string,soundPath:string,isLoop:boolean=false,force:boolean=false):void{
        let audioChannel:AudioChannel;
        if(this.soundChannelMap.has(channel)==false){
            audioChannel=new AudioChannel();
            audioChannel.aSComponent=this.addComponent(cc.AudioSource);
            this.soundChannelMap.set(channel,audioChannel);
        }else{
            audioChannel=this.soundChannelMap.get(channel);
        }
        audioChannel.Play(assetBundle,soundPath,isLoop,force);
    }

    /**
     * 清理通道
     * @param channel
     */
    public ClearSound(channel:string):void{
        if(this.soundChannelMap.has(channel)==false){
            return;
        }
        let audioChannel:AudioChannel=this.soundChannelMap.get(channel);
        audioChannel.Dispose();
        this.soundChannelMap.delete(channel);
    }

    /**
     * 停止某个固定通道的音效
     * @param channel 
     */
    public StopSound(channel:string):void{
        if(this.soundChannelMap.has(channel)==false){
            return;
        }
        let audioChannel:AudioChannel=this.soundChannelMap.get(channel);
        audioChannel.Stop();
    }

    /**
     * 某个固定频道是否在播放中
     * @param channel 
     */
    public ChannelIsPlaying(channel:string):boolean{
        if(this.soundChannelMap.has(channel)==false){
            return false;
        }
        let audioChannel:AudioChannel=this.soundChannelMap.get(channel);
        return audioChannel.isPlaying;
    }

    /**
     * 通道播放音效
     * @param assetBundle   资源包
     * @param soundPath     音效地址
     */
    public PlaySoundRecycleChannel(assetBundle:string,soundPath:string):void{
        let audioChannel:AudioChannel;
        //检测是否有已经播放完成的通道
        for (let index = 0; index < this.recycleSoundChannelList.length; index++) {
            const element = this.recycleSoundChannelList[index];
            if(element.isPlaying==false){
                this.recycleSoundChannelPool.push(element);
                this.recycleSoundChannelList.splice(index,1);
            }
        }
        //如果池中没有，则不播放该音效！
        if(this.recycleSoundChannelPool.length==0){
            return;
        }
        audioChannel=this.recycleSoundChannelPool.pop();
        this.recycleSoundChannelList.push(audioChannel);
        //标记为被占有
        audioChannel.Play(assetBundle,soundPath,false);
    }


    // update (deltaTime: number) {
    //     // Your update function goes here.
    // }


    private static instance:SoundManager;
    public static get single():SoundManager{
        return this.instance;
    }
}


class AudioChannel{
    /**
     * 音频资源
     */
    private static audioClipMap:Map<string,cc.AudioClip>=new Map<string,cc.AudioClip>();

    aSComponent:cc.AudioSource;

    private assetBundleName:string;
    private soundUrl:string;
    private isLoop:boolean;
    private force:boolean;
    /**
     * 0 空闲 1加载中 2播放中
     */
    private state:number=0;
    constructor(){

    }

    Play(assetBundleName:string,soundUrl:string,isLoop:boolean,force:boolean=false):void{
        if(this.assetBundleName==assetBundleName&&this.soundUrl==soundUrl&&this.state==1){
            return;
        }
        this.assetBundleName=assetBundleName;
        this.soundUrl=soundUrl;
        this.isLoop=isLoop;
        this.force=force;
        this.state=1;

        if(AudioChannel.audioClipMap.has(this.soundUrl)){
            let clip:cc.AudioClip=AudioChannel.audioClipMap.get(this.soundUrl);
            console.log("播放音频缓存！");
            this.__play(clip);
        }else{
            if(this.assetBundle==null){
                cc.assetManager.loadBundle(this.assetBundleName,this.OnAssetBundleLoadComplete.bind(this));
            }else{
                this.LoadAudioClip();
            }
        }
    }

    private OnAssetBundleLoadComplete(err:Error,bundle:cc.AssetManager.Bundle):void{
        if(err){
            throw new Error("加载AssetBundle："+this.assetBundleName+"出错！");
        }
        this.LoadAudioClip();
    }

    private LoadAudioClip():void{
        this.assetBundle.load(this.soundUrl,cc.AudioClip,this.OnAudioLoadComplete.bind(this));
    }

    private OnAudioLoadComplete(err:Error,asset:cc.AudioClip):void{
        if(err){
            console.error("加载音乐错误!");
            return;
        }
        AudioChannel.audioClipMap.set(this.soundUrl,asset);
        if(this.state==0){
            return;
        }
        this.__play(asset);
    }

    private __play(audioClip:cc.AudioClip):void{        
        this.state=2;
        if(this.aSComponent==null){
            return;
        }
        if(this.aSComponent.clip!=audioClip){
            this.aSComponent.stop();
            this.aSComponent.clip=audioClip;
        }
        this.aSComponent.loop=this.isLoop;
        if(this.force){
            this.aSComponent.setCurrentTime(0);
        }
        this.aSComponent.play();
    }

    Stop():void{
        this.state=0;
        this.soundUrl=null;
        this.aSComponent.stop();
    }

    Dispose():void{
        this.soundUrl=null;
        this.aSComponent.destroy();
        this.aSComponent=null;
    }

    get isPlaying():boolean{
        return this.state>0;
    }

    private get assetBundle():cc.AssetManager.Bundle{
        return cc.assetManager.getBundle(this.assetBundleName);
    }
}