import {DebugError} from "@engine/debug/debugError";
import {AudioPlayer} from "../audioPlayer";
import {Game} from "../../core/game";
import {ResourceLink} from "@engine/resources/resourceLink";
import {BasicAudioContext} from "@engine/media/context/basicAudioContext";
import {Clazz, ICloneable, Optional} from "@engine/core/declarations";
import {Sound} from "@engine/media/sound";

export class WebAudioContextHolder {
    public static getAudioContextClass():Clazz<AudioContext>{
        return (window as any).AudioContext ||
            (window as any).webkitAudioContext;
    }

    public static getNewAudioContext():AudioContext{
        const c:AudioContext = new (WebAudioContextHolder.getAudioContextClass())();
        CtxHolder.fixAutoPlayPolicy(c);
        return c;
    }
}

class CtxHolder {

    public static getCtx():AudioContext{
        if (CtxHolder.ctx && !CtxHolder.res) {
            CtxHolder.res = new CtxHolder.ctx();
            CtxHolder.fixAutoPlayPolicy(CtxHolder.res);
        }
        return CtxHolder.res;
    }

    public static fixAutoPlayPolicy(res:AudioContext):void { // chrome allow playing only with user gesture
        const listener =()=>{
            res.resume();
            document.removeEventListener('click',listener);
        };
        document.addEventListener('click',listener);
    }
    private static ctx:Clazz<AudioContext> = WebAudioContextHolder.getAudioContextClass();
    private static res:AudioContext;
}



const decode =(buffer:ArrayBuffer):Promise<AudioBuffer>=>{
    return new Promise<AudioBuffer>((resolve, reject)=>{
        CtxHolder.getCtx().decodeAudioData(
            buffer,
            (decoded:AudioBuffer)=> {
                resolve(decoded);
            },
            (err:DOMException)=>{
                reject(err.message);
            }
        );
    });

};


export class WebAudioContext extends BasicAudioContext implements ICloneable<WebAudioContext>{

    public static isAcceptable():boolean {
        return !!(window && CtxHolder.getCtx());
    }

    public static createContext():AudioContext{
        return CtxHolder.getCtx();
    }

    public _ctx: AudioContext;
    public _currSource: Optional<AudioBufferSourceNode>;
    public _gainNode: GainNode;
    public _stereoPanNode: Optional<StereoPannerNode>;
    public _free: boolean = true;

    public readonly type: string = 'webAudioContext';

    constructor(protected game:Game,protected audioPLayer:AudioPlayer) {
        super(game,audioPLayer);
        this._ctx = CtxHolder.getCtx();
        this._gainNode = this._ctx.createGain();
        if (this._ctx.createStereoPanner) {
            this._stereoPanNode = this._ctx.createStereoPanner();
            this._stereoPanNode.pan.value = 0.5;
            this._stereoPanNode.connect(this._ctx.destination);
            this._gainNode.connect(this._stereoPanNode);
        } else {
            this._gainNode.connect(this._ctx.destination);
        }

    }

    public async load(buffer:ArrayBuffer, link:ResourceLink<void>):Promise<void> {
        if (AudioPlayer.cache[link.getUrl()]) {
            return undefined;
        }
        AudioPlayer.cache[link.getUrl()] = await decode(buffer);
    }


    public isCached(l:ResourceLink<void>):boolean{
        return !!AudioPlayer.cache[l.getUrl()];
    }

    public isFree(): boolean {
        return this._free;
    }

    public play(sound:Sound):void {
        this.setLastTimeId();
        this._free = false;
        const currSource:AudioBufferSourceNode = this._ctx.createBufferSource();
        currSource.buffer = AudioPlayer.cache[sound.getResourceLink().getUrl()] as AudioBuffer;
        currSource.connect(this._gainNode);
        currSource.start(0,sound.offset,sound.duration);
        this._currSource = currSource;
        currSource.onended = ()=> {
            this.stop();
        };

        this.loop(sound.loop);
        this.setGain(sound.gain);
        this.setStereoPan(sound.stereoPan);

    }

    public stop():void {
        const currSource:Optional<AudioBufferSourceNode> = this._currSource;
        if (currSource!==undefined) {
            currSource.stop();
            currSource.disconnect(this._gainNode);
            // tslint:disable-next-line:no-null-keyword
            currSource.onended = null;
        }
        this._currSource = undefined;
        this._free = true;
    }

    public setGain(val:number):void {
        this._gainNode.gain.value = val;
    }

    public setVelocity(val:number):void {
        this._currSource!.playbackRate.value = val;
    }

    public setStereoPan(val:number):void {
        if (this._stereoPanNode!==undefined) this._stereoPanNode.pan.value = val;
    }

    public loop(val:boolean):void {
        this._currSource!.loop = val;
    }

    public pause():void {
        this._ctx.suspend();
    }

    public resume():void {
        this._ctx.resume();
    }

    public clone():WebAudioContext{
        return new WebAudioContext(this.game,this.audioPLayer);
    }
}
