/*
* 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 graphic from '../../util/graphic';
import { toggleHoverEmphasis, SPECIAL_STATES, DISPLAY_STATES } from '../../util/states';
import { createTextStyle } from '../../label/labelStyle';
import { TreeNode } from '../../data/Tree';
import SunburstSeriesModel, { SunburstSeriesNodeItemOption, SunburstSeriesOption } from './SunburstSeries';
import GlobalModel from '../../model/Global';
import { PathStyleProps } from 'zrender/src/graphic/Path';
import { ColorString } from '../../util/types';
import Model from '../../model/Model';
import { getECData } from '../../util/innerStore';
import { getSectorCornerRadius } from '../helper/sectorHelper';
import { createOrUpdatePatternFromDecal } from '../../util/decal';
import ExtensionAPI from '../../core/ExtensionAPI';
import { saveOldStyle } from '../../animation/basicTransition';
import { normalizeRadian } from 'zrender/src/contain/util';
import { isRadianAroundZero } from '../../util/number';

const DEFAULT_SECTOR_Z = 2;
const DEFAULT_TEXT_Z = 4;
interface DrawTreeNode extends TreeNode {
    piece: SunburstPiece
}
/**
 * Sunburstce of Sunburst including Sector, Label, LabelLine
 */
class SunburstPiece extends graphic.Sector {

    node: TreeNode;

    private _seriesModel: SunburstSeriesModel;
    private _ecModel: GlobalModel;

    constructor(node: TreeNode, seriesModel: SunburstSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) {
        super();

        this.z2 = DEFAULT_SECTOR_Z;
        this.textConfig = {
            inside: true
        };

        getECData(this).seriesIndex = seriesModel.seriesIndex;

        const text = new graphic.Text({
            z2: DEFAULT_TEXT_Z,
            silent: node.getModel<SunburstSeriesNodeItemOption>().get(['label', 'silent'])
        });
        this.setTextContent(text);

        this.updateData(true, node, seriesModel, ecModel, api);
    }

    updateData(
        firstCreate: boolean,
        node: TreeNode,
        // state: 'emphasis' | 'normal' | 'highlight' | 'downplay',
        seriesModel: SunburstSeriesModel,
        ecModel: GlobalModel,
        api: ExtensionAPI
    ) {
        this.node = node;
        (node as DrawTreeNode).piece = this;

        seriesModel = seriesModel || this._seriesModel;
        ecModel = ecModel || this._ecModel;

        const sector = this;
        getECData(sector).dataIndex = node.dataIndex;

        const itemModel = node.getModel<SunburstSeriesNodeItemOption>();
        const emphasisModel = itemModel.getModel('emphasis');
        const layout = node.getLayout();

        const sectorShape = zrUtil.extend({}, layout);
        sectorShape.label = null;

        const normalStyle = node.getVisual('style') as PathStyleProps;
        normalStyle.lineJoin = 'bevel';

        const decal = node.getVisual('decal');
        if (decal) {
            normalStyle.decal = createOrUpdatePatternFromDecal(decal, api);
        }

        const cornerRadius = getSectorCornerRadius(itemModel.getModel('itemStyle'), sectorShape, true);
        zrUtil.extend(sectorShape, cornerRadius);

        zrUtil.each(SPECIAL_STATES, function (stateName) {
            const state = sector.ensureState(stateName);
            const itemStyleModel = itemModel.getModel([stateName, 'itemStyle']);
            state.style = itemStyleModel.getItemStyle();
            // border radius
            const cornerRadius = getSectorCornerRadius(itemStyleModel, sectorShape);
            if (cornerRadius) {
                state.shape = cornerRadius;
            }
        });

        if (firstCreate) {
            sector.setShape(sectorShape);
            sector.shape.r = layout.r0;
            graphic.initProps(
                sector,
                {
                    shape: {
                        r: layout.r
                    }
                },
                seriesModel,
                node.dataIndex
            );
        }
        else {
            // Disable animation for gradient since no interpolation method
            // is supported for gradient
            graphic.updateProps(sector, {
                shape: sectorShape
            }, seriesModel);

            saveOldStyle(sector);
        }

        sector.useStyle(normalStyle);

        this._updateLabel(seriesModel);

        const cursorStyle = itemModel.getShallow('cursor');
        cursorStyle && sector.attr('cursor', cursorStyle);

        this._seriesModel = seriesModel || this._seriesModel;
        this._ecModel = ecModel || this._ecModel;

        const focus = emphasisModel.get('focus');

        const focusOrIndices =
            focus === 'ancestor' ? node.getAncestorsIndices()
                : focus === 'descendant' ? node.getDescendantIndices()
                    : focus;

        toggleHoverEmphasis(this, focusOrIndices, emphasisModel.get('blurScope'), emphasisModel.get('disabled'));
    }

    _updateLabel(
        seriesModel: SunburstSeriesModel
    ) {
        const itemModel = this.node.getModel<SunburstSeriesNodeItemOption>();
        const normalLabelModel = itemModel.getModel('label');

        const layout = this.node.getLayout();
        const angle = layout.endAngle - layout.startAngle;

        const midAngle = (layout.startAngle + layout.endAngle) / 2;
        const dx = Math.cos(midAngle);
        const dy = Math.sin(midAngle);

        const sector = this;
        const label = sector.getTextContent();
        const dataIndex = this.node.dataIndex;
        const labelMinAngle = normalLabelModel.get('minAngle') / 180 * Math.PI;
        const isNormalShown = normalLabelModel.get('show')
            && !(labelMinAngle != null && Math.abs(angle) < labelMinAngle);
        label.ignore = !isNormalShown;

        // TODO use setLabelStyle
        zrUtil.each(DISPLAY_STATES, (stateName) => {

            const labelStateModel = stateName === 'normal' ? itemModel.getModel('label')
                : itemModel.getModel([stateName, 'label']);
            const isNormal = stateName === 'normal';

            const state = isNormal ? label : label.ensureState(stateName);
            let text = seriesModel.getFormattedLabel(dataIndex, stateName);
            if (isNormal) {
                text = text || this.node.name;
            }

            state.style = createTextStyle(labelStateModel, {}, null, stateName !== 'normal', true);
            if (text) {
                state.style.text = text;
            }
            // Not displaying text when angle is too small
            const isShown = labelStateModel.get('show');
            if (isShown != null && !isNormal) {
                state.ignore = !isShown;
            }

            const labelPosition = getLabelAttr(labelStateModel, 'position');

            const sectorState = isNormal ? sector : sector.states[stateName];
            const labelColor = sectorState.style.fill as ColorString;
            sectorState.textConfig = {
                outsideFill: labelStateModel.get('color') === 'inherit' ? labelColor : null,
                inside: labelPosition !== 'outside'
            };

            let r;
            const labelPadding = getLabelAttr(labelStateModel, 'distance') || 0;
            let textAlign = getLabelAttr(labelStateModel, 'align');
            const rotateType = getLabelAttr(labelStateModel, 'rotate');
            const flipStartAngle = Math.PI * 0.5;
            const flipEndAngle = Math.PI * 1.5;
            const midAngleNormal = normalizeRadian(rotateType === 'tangential' ? Math.PI / 2 - midAngle : midAngle);

            // For text that is up-side down, rotate 180 degrees to make sure
            // it's readable
            const needsFlip = midAngleNormal > flipStartAngle
                && !isRadianAroundZero(midAngleNormal - flipStartAngle)
                && midAngleNormal < flipEndAngle;

            if (labelPosition === 'outside') {
                r = layout.r + labelPadding;
                textAlign = needsFlip ? 'right' : 'left';
            }
            else {
                if (!textAlign || textAlign === 'center') {
                    // Put label in the center if it's a circle
                    if (angle === 2 * Math.PI && layout.r0 === 0) {
                        r = 0;
                    }
                    else {
                        r = (layout.r + layout.r0) / 2;
                    }
                    textAlign = 'center';
                }
                else if (textAlign === 'left') {
                    r = layout.r0 + labelPadding;
                    textAlign = needsFlip ? 'right' : 'left';
                }
                else if (textAlign === 'right') {
                    r = layout.r - labelPadding;
                    textAlign = needsFlip ? 'left' : 'right';
                }
            }

            state.style.align = textAlign;
            state.style.verticalAlign = getLabelAttr(labelStateModel, 'verticalAlign') || 'middle';

            state.x = r * dx + layout.cx;
            state.y = r * dy + layout.cy;

            let rotate = 0;
            if (rotateType === 'radial') {
                rotate = normalizeRadian(-midAngle)
                    + (needsFlip ? Math.PI : 0);
            }
            else if (rotateType === 'tangential') {
                rotate = normalizeRadian(Math.PI / 2 - midAngle)
                    + (needsFlip ? Math.PI : 0);
            }
            else if (zrUtil.isNumber(rotateType)) {
                rotate = rotateType * Math.PI / 180;
            }

            state.rotation = normalizeRadian(rotate);
        });


        type LabelOpt = SunburstSeriesOption['label'];
        function getLabelAttr<T extends keyof LabelOpt>(model: Model<LabelOpt>, name: T): LabelOpt[T] {
            const stateAttr = model.get(name);
            if (stateAttr == null) {
                return normalLabelModel.get(name) as LabelOpt[T];
            }
            return stateAttr;
        }

        label.dirtyStyle();
    }
}


export default SunburstPiece;
