/*
* 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 graphic from '../../util/graphic';
import {getECData} from '../../util/innerStore';
import * as layout from '../../util/layout';
import {wrapTreePathInfo} from '../helper/treeHelper';
import TreemapSeriesModel, { TreemapSeriesNodeItemOption, TreemapSeriesOption } from './TreemapSeries';
import ExtensionAPI from '../../core/ExtensionAPI';
import { TreeNode } from '../../data/Tree';
import { curry, defaults } from 'zrender/src/core/util';
import { ZRElementEvent, BoxLayoutOptionMixin, ECElement } from '../../util/types';
import Element from 'zrender/src/Element';
import Model from '../../model/Model';
import { convertOptionIdName } from '../../util/model';
import { toggleHoverEmphasis, Z2_EMPHASIS_LIFT } from '../../util/states';
import { createTextStyle } from '../../label/labelStyle';

const TEXT_PADDING = 8;
const ITEM_GAP = 8;
const ARRAY_LENGTH = 5;

interface OnSelectCallback {
    (node: TreeNode, e: ZRElementEvent): void
}

interface LayoutParam {
    pos: BoxLayoutOptionMixin
    box: {
        width: number,
        height: number
    }
    emptyItemWidth: number
    totalWidth: number
    renderList: {
        node: TreeNode,
        text: string
        width: number
    }[]
}

type BreadcrumbItemStyleModel = Model<TreemapSeriesOption['breadcrumb']['itemStyle']>;
type BreadcrumbEmphasisItemStyleModel = Model<TreemapSeriesOption['breadcrumb']['emphasis']>;
type BreadcrumbTextStyleModel = Model<TreemapSeriesOption['breadcrumb']['itemStyle']['textStyle']>;

class Breadcrumb {

    group = new graphic.Group();

    constructor(containerGroup: graphic.Group) {
        containerGroup.add(this.group);
    }

    render(
        seriesModel: TreemapSeriesModel,
        api: ExtensionAPI,
        targetNode: TreeNode,
        onSelect: OnSelectCallback
    ) {
        const model = seriesModel.getModel('breadcrumb');
        const thisGroup = this.group;

        thisGroup.removeAll();

        if (!model.get('show') || !targetNode) {
            return;
        }

        const normalStyleModel = model.getModel('itemStyle');
        const emphasisModel = model.getModel('emphasis');
        const textStyleModel = normalStyleModel.getModel('textStyle');
        const emphasisTextStyleModel = emphasisModel.getModel(['itemStyle', 'textStyle']);

        const layoutParam: LayoutParam = {
            pos: {
                left: model.get('left'),
                right: model.get('right'),
                top: model.get('top'),
                bottom: model.get('bottom')
            },
            box: {
                width: api.getWidth(),
                height: api.getHeight()
            },
            emptyItemWidth: model.get('emptyItemWidth'),
            totalWidth: 0,
            renderList: []
        };

        this._prepare(targetNode, layoutParam, textStyleModel);
        this._renderContent(
            seriesModel, layoutParam, normalStyleModel,
            emphasisModel, textStyleModel, emphasisTextStyleModel, onSelect
        );

        layout.positionElement(thisGroup, layoutParam.pos, layoutParam.box);
    }

    /**
     * Prepare render list and total width
     * @private
     */
    _prepare(targetNode: TreeNode, layoutParam: LayoutParam, textStyleModel: BreadcrumbTextStyleModel) {
        for (let node = targetNode; node; node = node.parentNode) {
            const text = convertOptionIdName(node.getModel<TreemapSeriesNodeItemOption>().get('name'), '');
            const textRect = textStyleModel.getTextRect(text);
            const itemWidth = Math.max(
                textRect.width + TEXT_PADDING * 2,
                layoutParam.emptyItemWidth
            );
            layoutParam.totalWidth += itemWidth + ITEM_GAP;
            layoutParam.renderList.push({
                node: node,
                text: text,
                width: itemWidth
            });
        }
    }

    /**
     * @private
     */
    _renderContent(
        seriesModel: TreemapSeriesModel,
        layoutParam: LayoutParam,
        normalStyleModel: BreadcrumbItemStyleModel,
        emphasisModel: BreadcrumbEmphasisItemStyleModel,
        textStyleModel: BreadcrumbTextStyleModel,
        emphasisTextStyleModel: BreadcrumbTextStyleModel,
        onSelect: OnSelectCallback
    ) {
        // Start rendering.
        let lastX = 0;
        const emptyItemWidth = layoutParam.emptyItemWidth;
        const height = seriesModel.get(['breadcrumb', 'height']);
        const availableSize = layout.getAvailableSize(layoutParam.pos, layoutParam.box);
        let totalWidth = layoutParam.totalWidth;
        const renderList = layoutParam.renderList;
        const emphasisItemStyle = emphasisModel.getModel('itemStyle').getItemStyle();

        for (let i = renderList.length - 1; i >= 0; i--) {
            const item = renderList[i];
            const itemNode = item.node;
            let itemWidth = item.width;
            let text = item.text;

            // Hdie text and shorten width if necessary.
            if (totalWidth > availableSize.width) {
                totalWidth -= itemWidth - emptyItemWidth;
                itemWidth = emptyItemWidth;
                text = null;
            }

            const el = new graphic.Polygon({
                shape: {
                    points: makeItemPoints(
                        lastX, 0, itemWidth, height,
                        i === renderList.length - 1, i === 0
                    )
                },
                style: defaults(
                    normalStyleModel.getItemStyle(),
                    {
                        lineJoin: 'bevel'
                    }
                ),
                textContent: new graphic.Text({
                    style: createTextStyle(textStyleModel, { text })
                }),
                textConfig: {
                    position: 'inside'
                },
                z2: Z2_EMPHASIS_LIFT * 1e4,    // A very large z2
                onclick: curry(onSelect, itemNode)
            });
            (el as ECElement).disableLabelAnimation = true;
            el.getTextContent().ensureState('emphasis').style = createTextStyle(emphasisTextStyleModel, { text });
            el.ensureState('emphasis').style = emphasisItemStyle;
            toggleHoverEmphasis(
                el, emphasisModel.get('focus'), emphasisModel.get('blurScope'), emphasisModel.get('disabled')
            );
            this.group.add(el);

            packEventData(el, seriesModel, itemNode);

            lastX += itemWidth + ITEM_GAP;
        }
    }

    remove() {
        this.group.removeAll();
    }
}

function makeItemPoints(x: number, y: number, itemWidth: number, itemHeight: number, head: boolean, tail: boolean) {
    const points = [
        [head ? x : x - ARRAY_LENGTH, y],
        [x + itemWidth, y],
        [x + itemWidth, y + itemHeight],
        [head ? x : x - ARRAY_LENGTH, y + itemHeight]
    ];
    !tail && points.splice(2, 0, [x + itemWidth + ARRAY_LENGTH, y + itemHeight / 2]);
    !head && points.push([x, y + itemHeight / 2]);
    return points;
}

// Package custom mouse event.
function packEventData(el: Element, seriesModel: TreemapSeriesModel, itemNode: TreeNode) {
    getECData(el).eventData = {
        componentType: 'series',
        componentSubType: 'treemap',
        componentIndex: seriesModel.componentIndex,
        seriesIndex: seriesModel.seriesIndex,
        seriesName: seriesModel.name,
        seriesType: 'treemap',
        selfType: 'breadcrumb', // Distinguish with click event on treemap node.
        nodeData: {
            dataIndex: itemNode && itemNode.dataIndex,
            name: itemNode && itemNode.name
        },
        treePathInfo: itemNode && wrapTreePathInfo(itemNode, seriesModel)
    };
}

export default Breadcrumb;