export class UIEventSource<T>{
    
    public data: T;
    private _callbacks = [];

    constructor(data: T) {
        this.data = data;
    }


    public addCallback(callback: ((latestData : T) => void)) {
        this._callbacks.push(callback);
        return this;
    }

    public setData(t: T): void {
        if (this.data === t) {
            return;
        }
        this.data = t;
        this.ping();
    }

    public ping(): void {
        for (const callback of this._callbacks) {
            callback(this.data);
        }
    }

    public static flatten<X>(source: UIEventSource<UIEventSource<X>>, possibleSources: UIEventSource<any>[]): UIEventSource<X> {
        const sink = new UIEventSource<X>(source.data?.data);

        source.addCallback((latestData) => {
           sink.setData(latestData?.data);
        });

        for (const possibleSource of possibleSources) {
            possibleSource.addCallback(() => {
                sink.setData(source.data?.data);
                
            })
        }
        
        return sink;
    }
    
    public map<J>(f: ((T) => J),
                  extraSources: UIEventSource<any>[] = [],
                  g: ((J) => T) = undefined ): UIEventSource<J> {
        const self = this;
        
        const newSource = new UIEventSource<J>(
            f(this.data)
        );
        
        const update = function () {
            newSource.setData(f(self.data));
            newSource.ping();
        }

        this.addCallback(update);
        for (const extraSource of extraSources) {
            extraSource.addCallback(update);
        }
      
        if(g !== undefined) {
            newSource.addCallback((latest) => {
                self.setData((g(latest)));
            })
        }
        

        return newSource;

    }

    
    public syncWith(otherSource: UIEventSource<T>) : UIEventSource<T>{
        this.addCallback((latest) => otherSource.setData(latest));
        const self = this;
        otherSource.addCallback((latest) => self.setData(latest));
        if(this.data === undefined){
           this.setData(otherSource.data);
        }else{
            otherSource.setData(this.data);
        }
        return this;
    }

}