import {SpecialVisualization} from "../../UI/SpecialVisualizations";
import {SubstitutedTranslation} from "../../UI/SubstitutedTranslation";
import TagRenderingConfig from "./TagRenderingConfig";
import {ExtraFuncParams, ExtraFunctions} from "../../Logic/ExtraFunctions";
import LayerConfig from "./LayerConfig";

export default class DependencyCalculator {

    public static GetTagRenderingDependencies(tr: TagRenderingConfig): string[] {

        if (tr === undefined) {
            throw "Got undefined tag rendering in getTagRenderingDependencies"
        }
        const deps: string[] = []

        // All translated snippets
        const parts: string[] = [].concat(...(tr.EnumerateTranslations().map(tr => tr.AllValues())))

        for (const part of parts) {
            const specialVizs: { func: SpecialVisualization, args: string[] }[]
                = SubstitutedTranslation.ExtractSpecialComponents(part).map(o => o.special)
                .filter(o => o?.func?.getLayerDependencies !== undefined)
            for (const specialViz of specialVizs) {
                deps.push(...specialViz.func.getLayerDependencies(specialViz.args))
            }
        }
        return deps;
    }

    /**
     * Returns a set of all other layer-ids that this layer needs to function.
     * E.g. if this layers does snap to another layer in the preset, this other layer id will be mentioned
     */
    public static getLayerDependencies(layer: LayerConfig): { neededLayer: string, reason: string, context?: string, neededBy: string }[] {
        const deps: { neededLayer: string, reason: string, context?: string, neededBy: string }[] = []

        for (let i = 0; layer.presets !== undefined && i < layer.presets.length; i++) {
            const preset = layer.presets[i];
            preset.preciseInput?.snapToLayers?.forEach(id => {
                deps.push({
                    neededLayer: id,
                    reason: "a preset snaps to this layer",
                    context: "presets[" + i + "]",
                    neededBy: layer.id
                });
            })
        }

        for (const tr of layer.AllTagRenderings()) {
            for (const dep of DependencyCalculator.GetTagRenderingDependencies(tr)) {
                deps.push({
                    neededLayer: dep,
                    reason: "a tagrendering needs this layer",
                    context: tr.id,
                    neededBy: layer.id
                })
            }
        }

        if (layer.calculatedTags?.length > 0) {
            const obj = {
                type: "Feature",
                geometry: {
                    type: "Point",
                    coordinates: [0, 0]
                },
                properties: {
                    id: "node/1"
                }
            }
            let currentKey = undefined
            let currentLine = undefined
            const params: ExtraFuncParams = {
                getFeatureById: _ => undefined,
                getFeaturesWithin: (layerId, _) => {

                    if (layerId === '*') {
                        // This is a wildcard
                        return []
                    }

                    // The important line: steal the dependencies!
                    deps.push({
                        neededLayer: layerId, reason: "a calculated tag loads features from this layer",
                        context: "calculatedTag[" + currentLine + "] which calculates the value for " + currentKey,
                        neededBy: layer.id
                    })

                    return []
                },
                memberships: undefined
            }
            // Init the extra patched functions...
            ExtraFunctions.FullPatchFeature(params, obj)
            // ... Run the calculated tag code, which will trigger the getFeaturesWithin above...
            for (let i = 0; i < layer.calculatedTags.length; i++) {
                const [key, code] = layer.calculatedTags[i];
                currentLine = i; // Leak the state... 
                currentKey = key;
                try {

                    const func = new Function("feat", "return " + code + ";");
                    const result = func(obj)
                    obj.properties[key] = JSON.stringify(result);
                } catch (e) {
                }
            }
        }

        return deps
    }
}