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

import * as zrUtil from 'zrender/src/core/util';
import * as textContain from 'zrender/src/contain/text';
import * as graphic from '../../util/graphic';
import { enterEmphasis, leaveEmphasis } from '../../util/states';
import Model from '../../model/Model';
import DataDiffer from '../../data/DataDiffer';
import * as listComponentHelper from '../helper/listComponent';
import ComponentView from '../../view/Component';
import ToolboxModel from './ToolboxModel';
import GlobalModel from '../../model/Global';
import ExtensionAPI from '../../core/ExtensionAPI';
import { DisplayState, Dictionary, Payload } from '../../util/types';
import {
    ToolboxFeature,
    getFeature,
    ToolboxFeatureModel,
    ToolboxFeatureOption,
    UserDefinedToolboxFeature
} from './featureManager';
import { getUID } from '../../util/component';
import Displayable from 'zrender/src/graphic/Displayable';
import ZRText from 'zrender/src/graphic/Text';

type IconPath = ToolboxFeatureModel['iconPaths'][string];

type ExtendedPath = IconPath & {
    __title: string
};

class ToolboxView extends ComponentView {
    static type = 'toolbox' as const;

    _features: Dictionary<ToolboxFeature | UserDefinedToolboxFeature>;

    _featureNames: string[];

    render(
        toolboxModel: ToolboxModel,
        ecModel: GlobalModel,
        api: ExtensionAPI,
        payload: Payload & {
            newTitle?: ToolboxFeatureOption['title']
        }
    ) {
        const group = this.group;
        group.removeAll();

        if (!toolboxModel.get('show')) {
            return;
        }

        const itemSize = +toolboxModel.get('itemSize');
        const isVertical = toolboxModel.get('orient') === 'vertical';
        const featureOpts = toolboxModel.get('feature') || {};
        const features = this._features || (this._features = {});

        const featureNames: string[] = [];
        zrUtil.each(featureOpts, function (opt, name) {
            featureNames.push(name);
        });

        (new DataDiffer(this._featureNames || [], featureNames))
            .add(processFeature)
            .update(processFeature)
            .remove(zrUtil.curry(processFeature, null))
            .execute();

        // Keep for diff.
        this._featureNames = featureNames;

        function processFeature(newIndex: number, oldIndex?: number) {
            const featureName = featureNames[newIndex];
            const oldName = featureNames[oldIndex];
            const featureOpt = featureOpts[featureName];
            const featureModel = new Model(featureOpt, toolboxModel, toolboxModel.ecModel) as ToolboxFeatureModel;
            let feature: ToolboxFeature | UserDefinedToolboxFeature;

            // FIX#11236, merge feature title from MagicType newOption. TODO: consider seriesIndex ?
            if (payload && payload.newTitle != null && payload.featureName === featureName) {
                featureOpt.title = payload.newTitle;
            }

            if (featureName && !oldName) { // Create
                if (isUserFeatureName(featureName)) {
                    feature = {
                        onclick: featureModel.option.onclick,
                        featureName: featureName
                    } as UserDefinedToolboxFeature;
                }
                else {
                    const Feature = getFeature(featureName);
                    if (!Feature) {
                        return;
                    }
                    feature = new Feature();
                }
                features[featureName] = feature;
            }
            else {
                feature = features[oldName];
                // If feature does not exist.
                if (!feature) {
                    return;
                }
            }
            feature.uid = getUID('toolbox-feature');
            feature.model = featureModel;
            feature.ecModel = ecModel;
            feature.api = api;

            const isToolboxFeature = feature instanceof ToolboxFeature;
            if (!featureName && oldName) {
                isToolboxFeature
                    && (feature as ToolboxFeature).dispose
                    && (feature as ToolboxFeature).dispose(ecModel, api);
                return;
            }

            if (!featureModel.get('show') || (isToolboxFeature && (feature as ToolboxFeature).unusable)) {
                isToolboxFeature
                    && (feature as ToolboxFeature).remove
                    && (feature as ToolboxFeature).remove(ecModel, api);
                return;
            }

            createIconPaths(featureModel, feature, featureName);

            featureModel.setIconStatus = function (this: ToolboxFeatureModel, iconName: string, status: DisplayState) {
                const option = this.option;
                const iconPaths = this.iconPaths;
                option.iconStatus = option.iconStatus || {};
                option.iconStatus[iconName] = status;
                if (iconPaths[iconName]) {
                    (status === 'emphasis' ? enterEmphasis : leaveEmphasis)(iconPaths[iconName]);
                }
            };

            if (feature instanceof ToolboxFeature) {
                if (feature.render) {
                    feature.render(featureModel, ecModel, api, payload);
                }
            }
        }

        function createIconPaths(
            featureModel: ToolboxFeatureModel,
            feature: ToolboxFeature | UserDefinedToolboxFeature,
            featureName: string
        ) {
            const iconStyleModel = featureModel.getModel('iconStyle');
            const iconStyleEmphasisModel = featureModel.getModel(['emphasis', 'iconStyle']);

            // If one feature has multiple icons, they are organized as
            // {
            //     icon: {
            //         foo: '',
            //         bar: ''
            //     },
            //     title: {
            //         foo: '',
            //         bar: ''
            //     }
            // }
            const icons = (feature instanceof ToolboxFeature && feature.getIcons)
                ? feature.getIcons() : featureModel.get('icon');
            const titles = featureModel.get('title') || {};
            let iconsMap: Dictionary<string>;
            let titlesMap: Dictionary<string>;
            if (zrUtil.isString(icons)) {
                iconsMap = {};
                iconsMap[featureName] = icons;
            }
            else {
                iconsMap = icons;
            }
            if (zrUtil.isString(titles)) {
                titlesMap = {};
                titlesMap[featureName] = titles as string;
            }
            else {
                titlesMap = titles;
            }
            const iconPaths: ToolboxFeatureModel['iconPaths'] = featureModel.iconPaths = {};
            zrUtil.each(iconsMap, function (iconStr, iconName) {
                const path = graphic.createIcon(
                    iconStr,
                    {},
                    {
                        x: -itemSize / 2,
                        y: -itemSize / 2,
                        width: itemSize,
                        height: itemSize
                    }
                ) as Displayable;  // TODO handling image
                path.setStyle(iconStyleModel.getItemStyle());

                const pathEmphasisState = path.ensureState('emphasis');
                pathEmphasisState.style = iconStyleEmphasisModel.getItemStyle();

                // Text position calculation
                const textContent = new ZRText({
                    style: {
                        text: titlesMap[iconName],
                        align: iconStyleEmphasisModel.get('textAlign'),
                        borderRadius: iconStyleEmphasisModel.get('textBorderRadius'),
                        padding: iconStyleEmphasisModel.get('textPadding'),
                        fill: null
                    },
                    ignore: true
                });
                path.setTextContent(textContent);

                graphic.setTooltipConfig({
                    el: path,
                    componentModel: toolboxModel,
                    itemName: iconName,
                    formatterParamsExtra: {
                        title: titlesMap[iconName]
                    }
                });

                (path as ExtendedPath).__title = titlesMap[iconName];
                (path as graphic.Path).on('mouseover', function () {
                    // Should not reuse above hoverStyle, which might be modified.
                    const hoverStyle = iconStyleEmphasisModel.getItemStyle();
                    const defaultTextPosition = isVertical
                        ? (
                            toolboxModel.get('right') == null && toolboxModel.get('left') !== 'right'
                                ? 'right' as const
                                : 'left' as const
                          )
                        : (
                            toolboxModel.get('bottom') == null && toolboxModel.get('top') !== 'bottom'
                                ? 'bottom' as const
                                : 'top' as const
                          );
                    textContent.setStyle({
                        fill: (iconStyleEmphasisModel.get('textFill')
                            || hoverStyle.fill || hoverStyle.stroke || '#000') as string,
                        backgroundColor: iconStyleEmphasisModel.get('textBackgroundColor')
                    });
                    path.setTextConfig({
                        position: iconStyleEmphasisModel.get('textPosition') || defaultTextPosition
                    });
                    textContent.ignore = !toolboxModel.get('showTitle');

                    // Use enterEmphasis and leaveEmphasis provide by ec.
                    // There are flags managed by the echarts.
                    api.enterEmphasis(this);
                })
                .on('mouseout', function () {
                    if (featureModel.get(['iconStatus', iconName]) !== 'emphasis') {
                        api.leaveEmphasis(this);
                    }
                    textContent.hide();
                });
                (featureModel.get(['iconStatus', iconName]) === 'emphasis' ? enterEmphasis : leaveEmphasis)(path);

                group.add(path);
                (path as graphic.Path).on('click', zrUtil.bind(
                    feature.onclick, feature, ecModel, api, iconName
                ));

                iconPaths[iconName] = path;
            });
        }

        listComponentHelper.layout(group, toolboxModel, api);
        // Render background after group is layout
        // FIXME
        group.add(listComponentHelper.makeBackground(group.getBoundingRect(), toolboxModel));

        // Adjust icon title positions to avoid them out of screen
        isVertical || group.eachChild(function (icon: IconPath) {
            const titleText = (icon as ExtendedPath).__title;
            // const hoverStyle = icon.hoverStyle;

            // TODO simplify code?
            const emphasisState = icon.ensureState('emphasis');
            const emphasisTextConfig = emphasisState.textConfig || (emphasisState.textConfig = {});
            const textContent = icon.getTextContent();
            const emphasisTextState = textContent && textContent.ensureState('emphasis');
            // May be background element
            if (emphasisTextState && !zrUtil.isFunction(emphasisTextState) && titleText) {
                const emphasisTextStyle = emphasisTextState.style || (emphasisTextState.style = {});
                const rect = textContain.getBoundingRect(
                    titleText, ZRText.makeFont(emphasisTextStyle)
                );
                const offsetX = icon.x + group.x;
                const offsetY = icon.y + group.y + itemSize;

                let needPutOnTop = false;
                if (offsetY + rect.height > api.getHeight()) {
                    emphasisTextConfig.position = 'top';
                    needPutOnTop = true;
                }
                const topOffset = needPutOnTop ? (-5 - rect.height) : (itemSize + 10);
                if (offsetX + rect.width / 2 > api.getWidth()) {
                    emphasisTextConfig.position = ['100%', topOffset];
                    emphasisTextStyle.align = 'right';
                }
                else if (offsetX - rect.width / 2 < 0) {
                    emphasisTextConfig.position = [0, topOffset];
                    emphasisTextStyle.align = 'left';
                }
            }
        });
    }

    updateView(
        toolboxModel: ToolboxModel,
        ecModel: GlobalModel,
        api: ExtensionAPI,
        payload: unknown
    ) {
        zrUtil.each(this._features, function (feature) {
            feature instanceof ToolboxFeature
                && feature.updateView && feature.updateView(feature.model, ecModel, api, payload);
        });
    }

    // updateLayout(toolboxModel, ecModel, api, payload) {
    //     zrUtil.each(this._features, function (feature) {
    //         feature.updateLayout && feature.updateLayout(feature.model, ecModel, api, payload);
    //     });
    // },

    remove(ecModel: GlobalModel, api: ExtensionAPI) {
        zrUtil.each(this._features, function (feature) {
            feature instanceof ToolboxFeature
                && feature.remove && feature.remove(ecModel, api);
        });
        this.group.removeAll();
    }

    dispose(ecModel: GlobalModel, api: ExtensionAPI) {
        zrUtil.each(this._features, function (feature) {
            feature instanceof ToolboxFeature
                && feature.dispose && feature.dispose(ecModel, api);
        });
    }
}


function isUserFeatureName(featureName: string): boolean {
    return featureName.indexOf('my') === 0;
}
export default ToolboxView;
