/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/

/**
 * @file Visual solution, for consistent option specification.
 */

import * as zrUtil from 'zrender/src/core/util';
import VisualMapping from './VisualMapping';

var each = zrUtil.each;

function hasKeys(obj) {
    if (obj) {
        for (var name in obj) {
            if (obj.hasOwnProperty(name)) {
                return true;
            }
        }
    }
}

/**
 * @param {Object} option
 * @param {Array.<string>} stateList
 * @param {Function} [supplementVisualOption]
 * @return {Object} visualMappings <state, <visualType, module:echarts/visual/VisualMapping>>
 */
export function createVisualMappings(option, stateList, supplementVisualOption) {
    var visualMappings = {};

    each(stateList, function (state) {
        var mappings = visualMappings[state] = createMappings();

        each(option[state], function (visualData, visualType) {
            if (!VisualMapping.isValidType(visualType)) {
                return;
            }
            var mappingOption = {
                type: visualType,
                visual: visualData
            };
            supplementVisualOption && supplementVisualOption(mappingOption, state);
            mappings[visualType] = new VisualMapping(mappingOption);

            // Prepare a alpha for opacity, for some case that opacity
            // is not supported, such as rendering using gradient color.
            if (visualType === 'opacity') {
                mappingOption = zrUtil.clone(mappingOption);
                mappingOption.type = 'colorAlpha';
                mappings.__hidden.__alphaForOpacity = new VisualMapping(mappingOption);
            }
        });
    });

    return visualMappings;

    function createMappings() {
        var Creater = function () {};
        // Make sure hidden fields will not be visited by
        // object iteration (with hasOwnProperty checking).
        Creater.prototype.__hidden = Creater.prototype;
        var obj = new Creater();
        return obj;
    }
}

/**
 * @param {Object} thisOption
 * @param {Object} newOption
 * @param {Array.<string>} keys
 */
export function replaceVisualOption(thisOption, newOption, keys) {
    // Visual attributes merge is not supported, otherwise it
    // brings overcomplicated merge logic. See #2853. So if
    // newOption has anyone of these keys, all of these keys
    // will be reset. Otherwise, all keys remain.
    var has;
    zrUtil.each(keys, function (key) {
        if (newOption.hasOwnProperty(key) && hasKeys(newOption[key])) {
            has = true;
        }
    });
    has && zrUtil.each(keys, function (key) {
        if (newOption.hasOwnProperty(key) && hasKeys(newOption[key])) {
            thisOption[key] = zrUtil.clone(newOption[key]);
        }
        else {
            delete thisOption[key];
        }
    });
}

/**
 * @param {Array.<string>} stateList
 * @param {Object} visualMappings <state, Object.<visualType, module:echarts/visual/VisualMapping>>
 * @param {module:echarts/data/List} list
 * @param {Function} getValueState param: valueOrIndex, return: state.
 * @param {object} [scope] Scope for getValueState
 * @param {string} [dimension] Concrete dimension, if used.
 */
// ???! handle brush?
export function applyVisual(stateList, visualMappings, data, getValueState, scope, dimension) {
    var visualTypesMap = {};
    zrUtil.each(stateList, function (state) {
        var visualTypes = VisualMapping.prepareVisualTypes(visualMappings[state]);
        visualTypesMap[state] = visualTypes;
    });

    var dataIndex;

    function getVisual(key) {
        return data.getItemVisual(dataIndex, key);
    }

    function setVisual(key, value) {
        data.setItemVisual(dataIndex, key, value);
    }

    if (dimension == null) {
        data.each(eachItem);
    }
    else {
        data.each([dimension], eachItem);
    }

    function eachItem(valueOrIndex, index) {
        dataIndex = dimension == null ? valueOrIndex : index;

        var rawDataItem = data.getRawDataItem(dataIndex);
        // Consider performance
        if (rawDataItem && rawDataItem.visualMap === false) {
            return;
        }

        var valueState = getValueState.call(scope, valueOrIndex);
        var mappings = visualMappings[valueState];
        var visualTypes = visualTypesMap[valueState];

        for (var i = 0, len = visualTypes.length; i < len; i++) {
            var type = visualTypes[i];
            mappings[type] && mappings[type].applyVisual(
                valueOrIndex, getVisual, setVisual
            );
        }
    }
}

/**
 * @param {module:echarts/data/List} data
 * @param {Array.<string>} stateList
 * @param {Object} visualMappings <state, Object.<visualType, module:echarts/visual/VisualMapping>>
 * @param {Function} getValueState param: valueOrIndex, return: state.
 * @param {number} [dim] dimension or dimension index.
 */
export function incrementalApplyVisual(stateList, visualMappings, getValueState, dim) {
    var visualTypesMap = {};
    zrUtil.each(stateList, function (state) {
        var visualTypes = VisualMapping.prepareVisualTypes(visualMappings[state]);
        visualTypesMap[state] = visualTypes;
    });

    function progress(params, data) {
        if (dim != null) {
            dim = data.getDimension(dim);
        }

        function getVisual(key) {
            return data.getItemVisual(dataIndex, key);
        }

        function setVisual(key, value) {
            data.setItemVisual(dataIndex, key, value);
        }

        var dataIndex;
        while ((dataIndex = params.next()) != null) {
            var rawDataItem = data.getRawDataItem(dataIndex);

            // Consider performance
            if (rawDataItem && rawDataItem.visualMap === false) {
                continue;
            }

            var value = dim != null
                ? data.get(dim, dataIndex, true)
                : dataIndex;

            var valueState = getValueState(value);
            var mappings = visualMappings[valueState];
            var visualTypes = visualTypesMap[valueState];

            for (var i = 0, len = visualTypes.length; i < len; i++) {
                var type = visualTypes[i];
                mappings[type] && mappings[type].applyVisual(value, getVisual, setVisual);
            }
        }
    }

    return {progress: progress};
}
