/*
* 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.
*/

/**
 * @module echarts/chart/helper/SymbolDraw
 */

import * as graphic from '../../util/graphic';
import SymbolClz from './Symbol';
import { isObject } from 'zrender/src/core/util';

/**
 * @constructor
 * @alias module:echarts/chart/helper/SymbolDraw
 * @param {module:zrender/graphic/Group} [symbolCtor]
 */
function SymbolDraw(symbolCtor) {
    this.group = new graphic.Group();

    this._symbolCtor = symbolCtor || SymbolClz;
}

var symbolDrawProto = SymbolDraw.prototype;

function symbolNeedsDraw(data, point, idx, opt) {
    return point && !isNaN(point[0]) && !isNaN(point[1])
        && !(opt.isIgnore && opt.isIgnore(idx))
        // We do not set clipShape on group, because it will cut part of
        // the symbol element shape. We use the same clip shape here as
        // the line clip.
        && !(opt.clipShape && !opt.clipShape.contain(point[0], point[1]))
        && data.getItemVisual(idx, 'symbol') !== 'none';
}

/**
 * Update symbols draw by new data
 * @param {module:echarts/data/List} data
 * @param {Object} [opt] Or isIgnore
 * @param {Function} [opt.isIgnore]
 * @param {Object} [opt.clipShape]
 */
symbolDrawProto.updateData = function (data, opt) {
    opt = normalizeUpdateOpt(opt);

    var group = this.group;
    var seriesModel = data.hostModel;
    var oldData = this._data;
    var SymbolCtor = this._symbolCtor;

    var seriesScope = makeSeriesScope(data);

    // There is no oldLineData only when first rendering or switching from
    // stream mode to normal mode, where previous elements should be removed.
    if (!oldData) {
        group.removeAll();
    }

    data.diff(oldData)
        .add(function (newIdx) {
            var point = data.getItemLayout(newIdx);
            if (symbolNeedsDraw(data, point, newIdx, opt)) {
                var symbolEl = new SymbolCtor(data, newIdx, seriesScope);
                symbolEl.attr('position', point);
                data.setItemGraphicEl(newIdx, symbolEl);
                group.add(symbolEl);
            }
        })
        .update(function (newIdx, oldIdx) {
            var symbolEl = oldData.getItemGraphicEl(oldIdx);
            var point = data.getItemLayout(newIdx);
            if (!symbolNeedsDraw(data, point, newIdx, opt)) {
                group.remove(symbolEl);
                return;
            }
            if (!symbolEl) {
                symbolEl = new SymbolCtor(data, newIdx);
                symbolEl.attr('position', point);
            }
            else {
                symbolEl.updateData(data, newIdx, seriesScope);
                graphic.updateProps(symbolEl, {
                    position: point
                }, seriesModel);
            }

            // Add back
            group.add(symbolEl);

            data.setItemGraphicEl(newIdx, symbolEl);
        })
        .remove(function (oldIdx) {
            var el = oldData.getItemGraphicEl(oldIdx);
            el && el.fadeOut(function () {
                group.remove(el);
            });
        })
        .execute();

    this._data = data;
};

symbolDrawProto.isPersistent = function () {
    return true;
};

symbolDrawProto.updateLayout = function () {
    var data = this._data;
    if (data) {
        // Not use animation
        data.eachItemGraphicEl(function (el, idx) {
            var point = data.getItemLayout(idx);
            el.attr('position', point);
        });
    }
};

symbolDrawProto.incrementalPrepareUpdate = function (data) {
    this._seriesScope = makeSeriesScope(data);
    this._data = null;
    this.group.removeAll();
};

/**
 * Update symbols draw by new data
 * @param {module:echarts/data/List} data
 * @param {Object} [opt] Or isIgnore
 * @param {Function} [opt.isIgnore]
 * @param {Object} [opt.clipShape]
 */
symbolDrawProto.incrementalUpdate = function (taskParams, data, opt) {
    opt = normalizeUpdateOpt(opt);

    function updateIncrementalAndHover(el) {
        if (!el.isGroup) {
            el.incremental = el.useHoverLayer = true;
        }
    }
    for (var idx = taskParams.start; idx < taskParams.end; idx++) {
        var point = data.getItemLayout(idx);
        if (symbolNeedsDraw(data, point, idx, opt)) {
            var el = new this._symbolCtor(data, idx, this._seriesScope);
            el.traverse(updateIncrementalAndHover);
            el.attr('position', point);
            this.group.add(el);
            data.setItemGraphicEl(idx, el);
        }
    }
};

function normalizeUpdateOpt(opt) {
    if (opt != null && !isObject(opt)) {
        opt = {isIgnore: opt};
    }
    return opt || {};
}

symbolDrawProto.remove = function (enableAnimation) {
    var group = this.group;
    var data = this._data;
    // Incremental model do not have this._data.
    if (data && enableAnimation) {
        data.eachItemGraphicEl(function (el) {
            el.fadeOut(function () {
                group.remove(el);
            });
        });
    }
    else {
        group.removeAll();
    }
};

function makeSeriesScope(data) {
    var seriesModel = data.hostModel;
    return {
        itemStyle: seriesModel.getModel('itemStyle').getItemStyle(['color']),
        hoverItemStyle: seriesModel.getModel('emphasis.itemStyle').getItemStyle(),
        symbolRotate: seriesModel.get('symbolRotate'),
        symbolOffset: seriesModel.get('symbolOffset'),
        hoverAnimation: seriesModel.get('hoverAnimation'),
        labelModel: seriesModel.getModel('label'),
        hoverLabelModel: seriesModel.getModel('emphasis.label'),
        cursorStyle: seriesModel.get('cursor')
    };
}

export default SymbolDraw;