import { Group } from '../util/graphic';
import { isObject, isArray, each, clone } from '../util';

/**
 * 管理shape类
 *
 * @class ShapeStorage
 */
class ShapeStorage {
    _shapeMap = {};

    group = new Group();

    /**
     * 直接设置shape
     *
     * @param {string} name
     * @param {Object} shape
     * @returns {Object} shape
     */
    set(name, shape) {
        let group = this.group;
        let _shape = this.getShape(name);

        if (_shape) {
            if (shape instanceof ShapeStorage) {
                group.remove(_shape.group);
            } else {
                group.remove(_shape);
            }
        }

        if (shape instanceof ShapeStorage) {
            group.add(shape.group);
            shape.group.groupShape = shape; // group设置groupShape指向实际的shape
        } else {
            group.add(shape);
        }

        this._shapeMap[name] = shape;

        return shape;
    }

    /**
     * 根据name获取通过setShape或setShapeGroup方法设置的shape
     *
     * @param {string} name
     * @param {string} [key] 仅在shape为Group时有效
     * @returns {Object} shape | shapeGroup
     */
    getShape(name, key) {
        let shape = this._shapeMap[name];

        if (shape && shape instanceof Group) {
            if (typeof key === 'undefined') {
                return shape;
            } else {
                shape = shape.childOfName(key);

                return shape
                    ? shape.groupShape
                        ? shape.groupShape
                        : shape
                    : false;
            }
        } else {
            return shape;
        }
    }

    /**
     * 遍历shape
     *
     * @param {Function} func 遍历函数
     */
    eachShape(func) {
        each(this._shapeMap, func);
    }

    /**
     * 设置单个shape,若不存在则创建,若存在则更新
     *
     * @param {string} name 存储的名字
     * @param {Object} Shape 要创建的Shape类
     * @param {Object} shapeAttr 要创建shape的相关属性attr
     * @param {Object} animateOption 动画相关属性
     * @param {boolean} [animateOption.animation] 是否动画
     * @param {Object} [animateOption.animateFrom] 动画起始状态属性,
     * @param {number} [animateOption.duration] 动画时长
     * @param {string|Function} [animateOption.easing] 动画类型
     * @param {Object} [animateOption.animateList] 要过渡的动画属性列表
     * @param {Function} initCallback 初始化shape的回调
     * @returns {Object} shape
     */
    setShape(
        name,
        Shape,
        shapeAttr,
        {
            animation,
            animateFrom,
            duration = 500,
            easing = 'cubicOut',
            animateList
        } = {},
        initCallback
    ) {
        let group = this.group;
        let shape = this.getShape(name);
        let isInit = false;

        if (animation && typeof animateList === 'undefined') {
            animateList = animateFrom;
        }

        if (!shape) {
            shape = isArray(shapeAttr)
                ? new Shape(...shapeAttr)
                : new Shape(shapeAttr);

            if (shape instanceof ShapeStorage) {
                group.add(shape.group);
                shape.group.groupShape = shape; // group设置groupShape指向实际的shape
            } else {
                group.add(shape);
            }

            isInit = true;

            if (animation && animateFrom) {
                let animateTo = targetClone(animateFrom, shapeAttr);

                shape.attr(animateFrom);
                shape.animateTo(animateTo, duration, easing);
            }
        } else {
            if (animation) {
                let animateTo = targetClone(animateList, shapeAttr);
                let animateFrom = targetClone(
                    animateList,
                    shape._shapeMap ? shape._shapeMap : shape
                );

                shape.attr(shapeAttr);
                shape.attr(animateFrom);
                shape.animateTo(animateTo, duration, easing);
            } else {
                shape.attr(shapeAttr);
            }
        }

        this._shapeMap[name] = shape;
        isInit && initCallback && initCallback(shape);

        return shape;
    }

    /**
     * 设置一组shape,根据所设置的key创建新或移除
     *
     * @param {String} name 存储的名字
     * @param {Object} Shape 要创建的Shape类
     * @param {Array} shapeAttrArray 要创建shape的相关属性attr数组,!!!若需要动画,需指定每个shape的唯一key,以对对应的shape添加过渡动画!!!
     * @param {Object} animateOption 动画相关属性
     * @param {boolean} [animateOption.animation] 是否动画
     * @param {Object} [animateOption.animateFrom] 动画起始状态属性,
     * @param {number} [animateOption.duration] 动画时长
     * @param {string|Function} [animateOption.easing] 动画类型
     * @param {Object} [animateOption.animateList] 要过渡的动画属性列表
     * @param {Object} [animateOption.animateLeave] remove时动画属性列表
     * @param {Function} initCallback shapeGroup创建时的回调
     * @param {Function} createCallback shapeGroup内新增shape时的回调
     * @returns {Object} shapeGroup
     */
    setShapeGroup(
        name,
        Shape,
        shapeAttrArray,
        {
            animation,
            animateFrom,
            duration = 500,
            easing = 'cubicOut',
            animateList,
            animateLeave
        } = {},
        initCallback,
        createCallback
    ) {
        let group = this.group;
        let shapeGroup = this.getShape(name);
        let len = shapeAttrArray.length;
        let shape;
        let isInit = false;

        if (!shapeGroup) {
            shapeGroup = new Group();
            group.add(shapeGroup);
            isInit = true;
        }

        let newChildren = keyAttr(shapeAttrArray, name, animation);
        let oldChildren = keyShape(shapeGroup);

        if (animation && typeof animateList === 'undefined') {
            animateList = animateFrom;
        }

        mergeOldNew(oldChildren, newChildren);

        each(newChildren, function(
            { action: { type, payload }, ...shapeAttr },
            key
        ) {
            switch (type) {
                case 'CREATE':
                    shape = new Shape(shapeAttr);

                    if (shape instanceof ShapeStorage) {
                        shapeGroup.add(shape.group);
                        shape.group.groupShape = shape;
                    } else {
                        shapeGroup.add(shape);
                    }

                    if (animation && animateFrom) {
                        let _animateFrom = isArray(animateFrom)
                            ? animateFrom[payload.index]
                            : clone(animateFrom);
                        let animateTo = targetClone(_animateFrom, shapeAttr);

                        shape.attr(_animateFrom);
                        shape.animateTo(animateTo, duration, easing);
                    }

                    createCallback && createCallback(shape);
                    break;
                case 'UPDATE':
                    shape = payload.oldChild;
                    shape.groupShape && (shape = shape.groupShape);

                    if (animation) {
                        let _animateList = isArray(animateList)
                            ? animateList[payload.index]
                            : animateList;
                        let animateTo = targetClone(_animateList, shapeAttr);
                        let animateFrom = targetClone(
                            _animateList,
                            shape._shapeMap ? shape._shapeMap : shape
                        );

                        shape.attr(shapeAttr);
                        shape.attr(animateFrom);
                        shape.animateTo(animateTo, duration, easing);
                    } else {
                        shape.attr(shapeAttr);
                    }

                    break;
                case 'REMOVE':
                    if (animation && animateLeave) {
                        payload.oldChild.animateTo(
                            animateLeave,
                            duration,
                            function() {
                                shapeGroup.remove(payload.oldChild);
                            }
                        );
                    } else {
                        shapeGroup.remove(payload.oldChild);
                    }
                    break;
            }
        });

        this._shapeMap[name] = shapeGroup;
        isInit && initCallback && initCallback(shapeGroup);
        return shapeGroup;
    }

    /**
     * 移除指定的shape
     *
     * @param {string} name
     */
    removeShape(name) {
        let shape = this.getShape(name);

        if (shape) {
            this.group.remove(shape.group ? shape.group : shape);
            delete this._shapeMap[name];
        }
    }

    /**
     * 移除全部
     */
    remove() {
        this.group.removeAll();
        this.group.dirty();

        for (let name in this._shapeMap) {
            let shape = this._shapeMap[name];

            shape.group ? shape.group.off() : shape.off();
            delete this._shapeMap[name];
        }
    }
}

function targetClone(target, cloneFrom) {
    if (isObject(target)) {
        let cloneTo = isArray(target) ? [] : {};
        for (let p in target) {
            if (isObject(target[p])) {
                cloneFrom[p] &&
                    (cloneTo[p] = targetClone(target[p], cloneFrom[p]));
            } else {
                cloneTo[p] = cloneFrom[p];
            }
        }
        return cloneTo;
    } else {
        return cloneFrom;
    }
}

function keyAttr(children, name, animation) {
    let keys = {};
    let len = children.length;
    let warned = false;

    for (let i = 0; i < len; i++) {
        let child = children[i];

        child._index = i;

        if (typeof child.key === 'undefined') {
            child.name = i;
            keys[i] = child;

            if (!warned && animation) {
                console.warn(
                    `在调用setShapeGroup方法创建${name}时,未添加key属性!`
                );
                warned = true;
            }
        } else {
            child.name = child.key;
            keys[child.key] = child;
            delete child.key;
        }
    }

    return keys;
}

function keyShape(children) {
    let keys = {};

    children.eachChild(function(child, index) {
        if (typeof child.name === 'undefined') {
            child.name = index;
            keys[index] = child;
        } else {
            keys[child.name] = child;
        }
    });

    return keys;
}

function mergeOldNew(oldChildren, newChildren) {
    each(newChildren, function(child, key) {
        if (oldChildren[key]) {
            child.action = {
                type: 'UPDATE',
                payload: {
                    oldChild: oldChildren[key],
                    index: child._index
                }
            };
        } else {
            child.action = {
                type: 'CREATE',
                payload: {
                    index: child._index
                }
            };
        }

        delete child._index;
    });

    each(oldChildren, function(child, key) {
        if (!newChildren[key]) {
            newChildren[key] = child;
            newChildren[key].action = {
                type: 'REMOVE',
                payload: {
                    oldChild: child
                }
            };
        }
    });

    return newChildren;
}

export default ShapeStorage;