import { loadSoundTrackAsset } from "./loadSoundTrackAsset.js";
import { SoundTrackFlags } from "./SoundTrackFlags.js";

export class SoundEmitterComponentContext {
    constructor() {

        /**
         *
         * @type {SoundEmitter}
         */
        this.emitter = null;

        /**
         *
         * @type {Transform}
         */
        this.transform = null;

        /**
         *
         * @type {SoundEmitterSystem}
         */
        this.system = null;

        /**
         *
         * @type {AudioNode}
         */
        this.targetNode = null;

        /**
         *
         * @type {boolean}
         * @private
         */
        this.__isConnected = false;
    }

    /**
     *
     * @param {SoundEmitterComponentContext} other
     */
    compare(other) {
        return this.emitter.compare(other.emitter);
    }

    update() {

        const position = this.transform.position;

        /**
         *
         * @type {SoundEmitter}
         */
        const emitter = this.emitter;

        const x = position.x;
        const y = position.y;
        const z = position.z;

        emitter.updatePosition(x, y, z);
    }

    /**
     *
     * @param {SoundTrack} soundTrack
     */
    addTrack(soundTrack) {
        const system = this.system;

        const emitter = this.emitter;

        const context = system.webAudioContext;
        const assetManager = system.assetManager;
        const targetNode = emitter.nodes.volume;

        soundTrack.initializeNodes(context);

        const nodes = soundTrack.nodes;

        //connect to target
        nodes.volume.connect(targetNode);

        nodes.volume.gain.setValueAtTime(soundTrack.volume, 0);

        loadSoundTrackAsset(soundTrack, assetManager, (asset) => {

            soundTrack.buffer = asset.create();

            if (soundTrack.getFlag(SoundTrackFlags.StartWhenReady)) {

                if (this.__isConnected) {
                    soundTrack.start(context.currentTime);
                } else {
                    soundTrack.setFlag(SoundTrackFlags.Playing);
                }
            }


        }, console.error);

        soundTrack.on.ended.add(emitter.tracks.removeOneOf, emitter.tracks);
    }

    /**
     *
     * @param {SoundTrack} track
     */
    removeTrack(track) {
        /**
         *
         * @type {GainNode}
         */
        const volume = track.nodes.volume;

        volume.disconnect();

        const emitter = this.emitter;

        track.on.ended.remove(emitter.tracks.removeOneOf, emitter.tracks);
    }

    /**
     * @param {SoundTrack} t
     */
    suspendTrack(t) {
        if (t.getFlag(SoundTrackFlags.Playing)) {
            t.suspend();
        }
    }

    /**
     *
     * @param {SoundTrack} t
     */
    resumeTrack(t) {

        if (t.getFlag(SoundTrackFlags.Playing)) {
            t.start();
        }
    }

    connect() {
        if (this.__isConnected) {
            return;
        }

        const emitter = this.emitter;
        const targetNode = emitter.getTargetNode();

        targetNode.connect(this.targetNode);

        // update all track times
        emitter.tracks.forEach(this.resumeTrack);

        this.__isConnected = true;
    }

    disconnect() {
        if (!this.__isConnected) {
            return;
        }

        const emitter = this.emitter;
        const targetNode = emitter.getTargetNode();

        if (targetNode !== null) {
            targetNode.disconnect();
        }

        /*
         The reason we suspend all tracks is to be able to track progression of a track after it is disconnected
         From experiments it appears that at least on Chrome v111 disconnected sources do not advance time, which leads to dis-synchronization when we later reconnected
         To circumvent this issue we introduced "Suspended" flag to keep track of timing on disconnected tracks
         */
        emitter.tracks.forEach(this.suspendTrack, this)

        this.__isConnected = false;
    }

    link() {
        this.transform.position.onChanged.add(this.update, this);

        this.emitter.tracks.forEach(this.addTrack, this);

        this.emitter.tracks.on.added.add(this.addTrack, this);
        this.emitter.tracks.on.removed.add(this.removeTrack, this);
    }

    unlink() {
        this.transform.position.onChanged.remove(this.update, this);

        this.emitter.tracks.on.added.remove(this.addTrack, this);
        this.emitter.tracks.on.removed.remove(this.removeTrack, this);

        this.emitter.tracks.forEach(this.removeTrack, this);

        this.disconnect();
    }


}
