/*
* 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 env from 'zrender/src/core/env';
import { DataFormatMixin } from '../../model/mixin/dataFormat';
import ComponentModel from '../../model/Component';
import SeriesModel from '../../model/Series';
import {
    DisplayStateHostOption,
    ComponentOption,
    AnimationOptionMixin,
    Dictionary,
    CommonTooltipOption,
    ScaleDataValue,
    CallbackDataParams,
    SeriesDataType
} from '../../util/types';
import Model from '../../model/Model';
import GlobalModel from '../../model/Global';
import SeriesData from '../../data/SeriesData';
import { makeInner, defaultEmphasis } from '../../util/model';
import { createTooltipMarkup } from '../tooltip/tooltipMarkup';

function fillLabel(opt: DisplayStateHostOption) {
    defaultEmphasis(opt, 'label', ['show']);
}

export type MarkerStatisticType = 'average' | 'min' | 'max' | 'median';

/**
 * Option to specify where to put the marker.
 */
export interface MarkerPositionOption {
    // Priority: x/y > coord(xAxis, yAxis) > type

    // Absolute position, px or percent string
    x?: number | string
    y?: number | string

    /**
     * Coord on any coordinate system
     */
    coord?: (ScaleDataValue | MarkerStatisticType)[]

    // On cartesian coordinate system
    xAxis?: ScaleDataValue
    yAxis?: ScaleDataValue

    // On polar coordinate system
    radiusAxis?: ScaleDataValue
    angleAxis?: ScaleDataValue

    // Use statistic method
    type?: MarkerStatisticType
    /**
     * When using statistic method with type.
     * valueIndex and valueDim can be specify which dim the statistic is used on.
     */
    valueIndex?: number
    valueDim?: string


    /**
     * Value to be displayed as label. Totally optional
     */
    value?: string | number
}

export interface MarkerOption extends ComponentOption, AnimationOptionMixin {

    silent?: boolean

    data?: unknown[]

    tooltip?: CommonTooltipOption<unknown> & {
        trigger?: 'item' | 'axis' | boolean | 'none'
    }
}

// { [componentType]: MarkerModel }
const inner = makeInner<Dictionary<MarkerModel>, SeriesModel>();

abstract class MarkerModel<Opts extends MarkerOption = MarkerOption> extends ComponentModel<Opts> {

    static type = 'marker';
    type = MarkerModel.type;

    /**
     * If marker model is created by self from series
     */
    createdBySelf = false;

    static readonly dependencies = ['series', 'grid', 'polar', 'geo'];

    __hostSeries: SeriesModel;

    private _data: SeriesData;

    /**
     * @overrite
     */
    init(option: Opts, parentModel: Model, ecModel: GlobalModel) {

        if (__DEV__) {
            if (this.type === 'marker') {
                throw new Error('Marker component is abstract component. Use markLine, markPoint, markArea instead.');
            }
        }
        this.mergeDefaultAndTheme(option, ecModel);
        this._mergeOption(option, ecModel, false, true);
    }

    isAnimationEnabled(): boolean {
        if (env.node) {
            return false;
        }

        const hostSeries = this.__hostSeries;
        return this.getShallow('animation') && hostSeries && hostSeries.isAnimationEnabled();
    }

    /**
     * @overrite
     */
    mergeOption(newOpt: Opts, ecModel: GlobalModel) {
        this._mergeOption(newOpt, ecModel, false, false);
    }

    _mergeOption(newOpt: Opts, ecModel: GlobalModel, createdBySelf?: boolean, isInit?: boolean) {
        const componentType = this.mainType;
        if (!createdBySelf) {
            ecModel.eachSeries(function (seriesModel) {

                // mainType can be markPoint, markLine, markArea
                const markerOpt = seriesModel.get(
                    this.mainType as any, true
                ) as Opts;

                let markerModel = inner(seriesModel)[componentType];
                if (!markerOpt || !markerOpt.data) {
                    inner(seriesModel)[componentType] = null;
                    return;
                }
                if (!markerModel) {
                    if (isInit) {
                        // Default label emphasis `position` and `show`
                        fillLabel(markerOpt);
                    }
                    zrUtil.each(markerOpt.data, function (item) {
                        // FIXME Overwrite fillLabel method ?
                        if (item instanceof Array) {
                            fillLabel(item[0]);
                            fillLabel(item[1]);
                        }
                        else {
                            fillLabel(item);
                        }
                    });

                    markerModel = this.createMarkerModelFromSeries(
                        markerOpt, this, ecModel
                    );
                    // markerModel = new ImplementedMarkerModel(
                    //     markerOpt, this, ecModel
                    // );

                    zrUtil.extend(markerModel, {
                        mainType: this.mainType,
                        // Use the same series index and name
                        seriesIndex: seriesModel.seriesIndex,
                        name: seriesModel.name,
                        createdBySelf: true
                    });

                    markerModel.__hostSeries = seriesModel;
                }
                else {
                    markerModel._mergeOption(markerOpt, ecModel, true);
                }
                inner(seriesModel)[componentType] = markerModel;
            }, this);
        }
    }

    formatTooltip(
        dataIndex: number,
        multipleSeries: boolean,
        dataType: string
    ) {
        const data = this.getData();
        const value = this.getRawValue(dataIndex);
        const itemName = data.getName(dataIndex);

        return createTooltipMarkup('section', {
            header: this.name,
            blocks: [createTooltipMarkup('nameValue', {
                name: itemName,
                value: value,
                noName: !itemName,
                noValue: value == null
            })]
        });
    }

    getData(): SeriesData<this> {
        return this._data as SeriesData<this>;
    }

    setData(data: SeriesData) {
        this._data = data;
    }

    getDataParams(
        dataIndex: number,
        dataType?: SeriesDataType
    ): CallbackDataParams {
        const params = DataFormatMixin.prototype.getDataParams.call(this, dataIndex, dataType);
        const hostSeries = this.__hostSeries;
        if (hostSeries) {
            params.seriesId = hostSeries.id;
            params.seriesName = hostSeries.name;
            params.seriesType = hostSeries.subType;
        }
        return params;
    }

    /**
     * Create slave marker model from series.
     */
    abstract createMarkerModelFromSeries(
        markerOpt: Opts,
        masterMarkerModel: MarkerModel,
        ecModel: GlobalModel
    ): MarkerModel;

    static getMarkerModelFromSeries(
        seriesModel: SeriesModel,
        // Support three types of markers. Strict check.
        componentType: 'markLine' | 'markPoint' | 'markArea'
    ) {
        return inner(seriesModel)[componentType];
    }
}

interface MarkerModel<Opts extends MarkerOption = MarkerOption> extends DataFormatMixin {}
zrUtil.mixin(MarkerModel, DataFormatMixin.prototype);

export default MarkerModel;
