import { UIEventSource } from "../../UIEventSource"
import FeatureSource, { FeatureSourceForLayer, IndexedFeatureSource, Tiled } from "../FeatureSource"
import FilteredLayer from "../../../Models/FilteredLayer"
import { Tiles } from "../../../Models/TileRange"
import { BBox } from "../../BBox"

export default class FeatureSourceMerger
    implements FeatureSourceForLayer, Tiled, IndexedFeatureSource
{
    public features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<
        { feature: any; freshness: Date }[]
    >([])
    public readonly name
    public readonly layer: FilteredLayer
    public readonly tileIndex: number
    public readonly bbox: BBox
    public readonly containedIds: UIEventSource<Set<string>> = new UIEventSource<Set<string>>(
        new Set()
    )
    private readonly _sources: UIEventSource<FeatureSource[]>
    /**
     * Merges features from different featureSources for a single layer
     * Uses the freshest feature available in the case multiple sources offer data with the same identifier
     */
    constructor(
        layer: FilteredLayer,
        tileIndex: number,
        bbox: BBox,
        sources: UIEventSource<FeatureSource[]>
    ) {
        this.tileIndex = tileIndex
        this.bbox = bbox
        this._sources = sources
        this.layer = layer
        this.name =
            "FeatureSourceMerger(" +
            layer.layerDef.id +
            ", " +
            Tiles.tile_from_index(tileIndex).join(",") +
            ")"
        const self = this

        const handledSources = new Set<FeatureSource>()

        sources.addCallbackAndRunD((sources) => {
            let newSourceRegistered = false
            for (let i = 0; i < sources.length; i++) {
                let source = sources[i]
                if (handledSources.has(source)) {
                    continue
                }
                handledSources.add(source)
                newSourceRegistered = true
                source.features.addCallback(() => {
                    self.Update()
                })
                if (newSourceRegistered) {
                    self.Update()
                }
            }
        })
    }

    private Update() {
        let somethingChanged = false
        const all: Map<string, { feature: any; freshness: Date }> = new Map<
            string,
            { feature: any; freshness: Date }
        >()
        // We seed the dictionary with the previously loaded features
        const oldValues = this.features.data ?? []
        for (const oldValue of oldValues) {
            all.set(oldValue.feature.id, oldValue)
        }

        for (const source of this._sources.data) {
            if (source?.features?.data === undefined) {
                continue
            }
            for (const f of source.features.data) {
                const id = f.feature.properties.id
                if (!all.has(id)) {
                    // This is a new feature
                    somethingChanged = true
                    all.set(id, f)
                    continue
                }

                // This value has been seen already, either in a previous run or by a previous datasource
                // Let's figure out if something changed
                const oldV = all.get(id)
                if (oldV.freshness < f.freshness) {
                    // Jup, this feature is fresher
                    all.set(id, f)
                    somethingChanged = true
                }
            }
        }

        if (!somethingChanged) {
            // We don't bother triggering an update
            return
        }

        const newList = []
        all.forEach((value, _) => {
            newList.push(value)
        })
        this.containedIds.setData(new Set(all.keys()))
        this.features.setData(newList)
    }
}