
import {eachSource, eachLayer, eachProperty} from '../visit';
import type {LayerSpecification, StyleSpecification} from '../types.g';

function eachLayout(layer: LayerSpecification, callback: (_: LayerSpecification['layout'], __: string) => void) {
    for (const k in layer) {
        if (k.indexOf('layout') === 0) {
            callback(layer[k], k);
        }
    }
}

function eachPaint(layer: LayerSpecification, callback: (_: LayerSpecification['paint'], __: string) => void) {
    for (const k in layer) {
        if (k.indexOf('paint') === 0) {
            callback(layer[k], k);
        }
    }
}

function resolveConstant(style: StyleSpecification, value: any) {
    if (typeof value === 'string' && value[0] === '@') {
        return resolveConstant(style, (style as any).constants[value]);
    } else {
        return value;
    }
}

function isFunction(value) {
    return Array.isArray(value.stops);
}

function renameProperty(obj: Object, from: string, to: string) {
    obj[to] = obj[from]; delete obj[from];
}

export default function migrateV8(style: StyleSpecification) {
    style.version = 8;

    // Rename properties, reverse coordinates in source and layers
    eachSource(style, (source) => {
        if (source.type === 'video' && source['url'] !== undefined) {
            renameProperty(source, 'url', 'urls');
        }
        if (source.type === 'video') {
            source.coordinates.forEach((coord) => {
                return coord.reverse();
            });
        }
    });

    eachLayer(style, (layer) => {
        eachLayout(layer, (layout) => {
            if (layout['symbol-min-distance'] !== undefined) {
                renameProperty(layout, 'symbol-min-distance', 'symbol-spacing');
            }
        });

        eachPaint(layer, (paint) => {
            if (paint['background-image'] !== undefined) {
                renameProperty(paint, 'background-image', 'background-pattern');
            }
            if (paint['line-image'] !== undefined) {
                renameProperty(paint, 'line-image', 'line-pattern');
            }
            if (paint['fill-image'] !== undefined) {
                renameProperty(paint, 'fill-image', 'fill-pattern');
            }
        });
    });

    // Inline Constants
    eachProperty(style, {paint: true, layout: true}, (property) => {
        const value = resolveConstant(style, property.value);

        if (isFunction(value)) {
            value.stops.forEach((stop) => {
                stop[1] = resolveConstant(style, stop[1]);
            });
        }

        property.set(value);
    });
    delete style['constants'];

    eachLayer(style, (layer) => {
        // get rid of text-max-size, icon-max-size
        // turn text-size, icon-size into layout properties
        // https://github.com/mapbox/mapbox-gl-style-spec/issues/255

        eachLayout(layer, (layout) => {
            delete layout['text-max-size'];
            delete layout['icon-max-size'];
        });

        eachPaint(layer, (paint) => {
            if (paint['text-size']) {
                if (!layer.layout) layer.layout = {};
                layer.layout['text-size'] = paint['text-size'];
                delete paint['text-size'];
            }

            if (paint['icon-size']) {
                if (!layer.layout) layer.layout = {};
                layer.layout['icon-size'] = paint['icon-size'];
                delete paint['icon-size'];
            }
        });
    });

    function migrateFontStack(font) {
        function splitAndTrim(string) {
            return string.split(',').map((s) => {
                return s.trim();
            });
        }

        if (Array.isArray(font)) {
            // Assume it's a previously migrated font-array.
            return font;

        } else if (typeof font === 'string') {
            return splitAndTrim(font);

        } else if (typeof font === 'object') {
            font.stops.forEach((stop) => {
                stop[1] = splitAndTrim(stop[1]);
            });
            return font;

        } else {
            throw new Error('unexpected font value');
        }
    }

    eachLayer(style, (layer) => {
        eachLayout(layer, (layout) => {
            if (layout['text-font']) {
                layout['text-font'] = migrateFontStack(layout['text-font']);
            }
        });
    });

    // Reverse order of symbol layers. This is an imperfect migration.
    //
    // The order of a symbol layer in the layers list affects two things:
    // - how it is drawn relative to other layers (like oneway arrows below bridges)
    // - the placement priority compared to other layers
    //
    // It's impossible to reverse the placement priority without breaking the draw order
    // in some cases. This migration only reverses the order of symbol layers that
    // are above all other types of layers.
    //
    // Symbol layers that are at the top of the map preserve their priority.
    // Symbol layers that are below another type (line, fill) of layer preserve their draw order.

    let firstSymbolLayer = 0;
    for (let i = style.layers.length - 1; i >= 0; i--) {
        const layer = style.layers[i];
        if (layer.type !== 'symbol') {
            firstSymbolLayer = i + 1;
            break;
        }
    }

    const symbolLayers = style.layers.splice(firstSymbolLayer);
    symbolLayers.reverse();
    style.layers = style.layers.concat(symbolLayers);

    return style;
}
