/*
* 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 SeriesModel from '../../model/Series';
import prepareSeriesDataSchema from '../../data/helper/createDimensions';
import {getDimensionTypeByAxis} from '../../data/helper/dimensionHelper';
import SeriesData from '../../data/SeriesData';
import * as zrUtil from 'zrender/src/core/util';
import {groupData, SINGLE_REFERRING} from '../../util/model';
import LegendVisualProvider from '../../visual/LegendVisualProvider';
import {
    SeriesOption,
    SeriesOnSingleOptionMixin,
    OptionDataValueDate,
    OptionDataValueNumeric,
    ItemStyleOption,
    BoxLayoutOptionMixin,
    ZRColor,
    Dictionary,
    SeriesLabelOption,
    CallbackDataParams,
    DefaultStatesMixinEmphasis
} from '../../util/types';
import SingleAxis from '../../coord/single/SingleAxis';
import GlobalModel from '../../model/Global';
import Single from '../../coord/single/Single';
import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup';

const DATA_NAME_INDEX = 2;

interface ThemeRiverSeriesLabelOption extends SeriesLabelOption {
    margin?: number
}

type ThemerRiverDataItem = [OptionDataValueDate, OptionDataValueNumeric, string];

interface ThemeRiverStatesMixin {
    emphasis?: DefaultStatesMixinEmphasis
}
export interface ThemeRiverStateOption<TCbParams = never> {
    label?: ThemeRiverSeriesLabelOption
    itemStyle?: ItemStyleOption<TCbParams>
}

export interface ThemeRiverSeriesOption
    extends SeriesOption<ThemeRiverStateOption<CallbackDataParams>, ThemeRiverStatesMixin>,
    ThemeRiverStateOption<CallbackDataParams>,
    SeriesOnSingleOptionMixin, BoxLayoutOptionMixin {
    type?: 'themeRiver'

    color?: ZRColor[]

    coordinateSystem?: 'singleAxis'

    /**
     * gap in axis's orthogonal orientation
     */
    boundaryGap?: (string | number)[]
    /**
     * [date, value, name]
     */
    data?: ThemerRiverDataItem[]
}

class ThemeRiverSeriesModel extends SeriesModel<ThemeRiverSeriesOption> {
    static readonly type = 'series.themeRiver';
    readonly type = ThemeRiverSeriesModel.type;

    static readonly dependencies = ['singleAxis'];

    nameMap: zrUtil.HashMap<number, string>;

    coordinateSystem: Single;

    /**
     * @override
     */
    init(option: ThemeRiverSeriesOption) {
        // eslint-disable-next-line
        super.init.apply(this, arguments as any);

        // Put this function here is for the sake of consistency of code style.
        // Enable legend selection for each data item
        // Use a function instead of direct access because data reference may changed
        this.legendVisualProvider = new LegendVisualProvider(
            zrUtil.bind(this.getData, this), zrUtil.bind(this.getRawData, this)
        );
    }

    /**
     * If there is no value of a certain point in the time for some event,set it value to 0.
     *
     * @param {Array} data  initial data in the option
     * @return {Array}
     */
    fixData(data: ThemeRiverSeriesOption['data']) {
        let rawDataLength = data.length;
        /**
         * Make sure every layer data get the same keys.
         * The value index tells which layer has visited.
         * {
         *  2014/01/01: -1
         * }
         */
        const timeValueKeys: Dictionary<number> = {};

        // grouped data by name
        const groupResult = groupData(data, (item: ThemerRiverDataItem) => {
            if (!timeValueKeys.hasOwnProperty(item[0] + '')) {
                timeValueKeys[item[0] + ''] = -1;
            }
            return item[2];
        });
        const layerData: {name: string, dataList: ThemerRiverDataItem[]}[] = [];
        groupResult.buckets.each(function (items, key) {
            layerData.push({
                name: key, dataList: items
            });
        });
        const layerNum = layerData.length;

        for (let k = 0; k < layerNum; ++k) {
            const name = layerData[k].name;
            for (let j = 0; j < layerData[k].dataList.length; ++j) {
                const timeValue = layerData[k].dataList[j][0] + '';
                timeValueKeys[timeValue] = k;
            }

            for (const timeValue in timeValueKeys) {
                if (timeValueKeys.hasOwnProperty(timeValue) && timeValueKeys[timeValue] !== k) {
                    timeValueKeys[timeValue] = k;
                    data[rawDataLength] = [timeValue, 0, name];
                    rawDataLength++;
                }
            }

        }
        return data;
    }

    /**
     * @override
     * @param  option  the initial option that user gave
     * @param  ecModel  the model object for themeRiver option
     */
    getInitialData(option: ThemeRiverSeriesOption, ecModel: GlobalModel): SeriesData {

        const singleAxisModel = this.getReferringComponents('singleAxis', SINGLE_REFERRING).models[0];

        const axisType = singleAxisModel.get('type');

        // filter the data item with the value of label is undefined
        const filterData = zrUtil.filter(option.data, function (dataItem) {
            return dataItem[2] !== undefined;
        });

        // ??? TODO design a stage to transfer data for themeRiver and lines?
        const data = this.fixData(filterData || []);
        const nameList = [];
        const nameMap = this.nameMap = zrUtil.createHashMap();
        let count = 0;

        for (let i = 0; i < data.length; ++i) {
            nameList.push(data[i][DATA_NAME_INDEX]);
            if (!nameMap.get(data[i][DATA_NAME_INDEX] as string)) {
                nameMap.set(data[i][DATA_NAME_INDEX] as string, count);
                count++;
            }
        }

        const { dimensions } = prepareSeriesDataSchema(data, {
            coordDimensions: ['single'],
            dimensionsDefine: [
                {
                    name: 'time',
                    type: getDimensionTypeByAxis(axisType)
                },
                {
                    name: 'value',
                    type: 'float'
                },
                {
                    name: 'name',
                    type: 'ordinal'
                }
            ],
            encodeDefine: {
                single: 0,
                value: 1,
                itemName: 2
            }
        });

        const list = new SeriesData(dimensions, this);
        list.initData(data);

        return list;
    }

    /**
     * The raw data is divided into multiple layers and each layer
     *     has same name.
     */
    getLayerSeries() {
        const data = this.getData();
        const lenCount = data.count();
        const indexArr = [];

        for (let i = 0; i < lenCount; ++i) {
            indexArr[i] = i;
        }

        const timeDim = data.mapDimension('single');

        // data group by name
        const groupResult = groupData(indexArr, function (index) {
            return data.get('name', index) as string;
        });
        const layerSeries: {
            name: string
            indices: number[]
        }[] = [];
        groupResult.buckets.each(function (items: number[], key: string) {
            items.sort(function (index1: number, index2: number) {
                return data.get(timeDim, index1) as number - (data.get(timeDim, index2) as number);
            });
            layerSeries.push({
                name: key,
                indices: items
            });
        });

        return layerSeries;
    }

    /**
     * Get data indices for show tooltip content
     */
    getAxisTooltipData(dim: string | string[], value: number, baseAxis: SingleAxis) {
        if (!zrUtil.isArray(dim)) {
            dim = dim ? [dim] : [];
        }

        const data = this.getData();
        const layerSeries = this.getLayerSeries();
        const indices = [];
        const layerNum = layerSeries.length;
        let nestestValue;

        for (let i = 0; i < layerNum; ++i) {
            let minDist = Number.MAX_VALUE;
            let nearestIdx = -1;
            const pointNum = layerSeries[i].indices.length;
            for (let j = 0; j < pointNum; ++j) {
                const theValue = data.get(dim[0], layerSeries[i].indices[j]) as number;
                const dist = Math.abs(theValue - value);
                if (dist <= minDist) {
                    nestestValue = theValue;
                    minDist = dist;
                    nearestIdx = layerSeries[i].indices[j];
                }
            }
            indices.push(nearestIdx);
        }

        return {dataIndices: indices, nestestValue: nestestValue};
    }

    formatTooltip(
        dataIndex: number,
        multipleSeries: boolean,
        dataType: string
    ) {
        const data = this.getData();
        const name = data.getName(dataIndex);
        const value = data.get(data.mapDimension('value'), dataIndex);

        return createTooltipMarkup('nameValue', { name: name, value: value });
    }

    static defaultOption: ThemeRiverSeriesOption = {
        // zlevel: 0,
        z: 2,

        colorBy: 'data',
        coordinateSystem: 'singleAxis',

        // gap in axis's orthogonal orientation
        boundaryGap: ['10%', '10%'],

        // legendHoverLink: true,

        singleAxisIndex: 0,

        animationEasing: 'linear',

        label: {
            margin: 4,
            show: true,
            position: 'left',
            fontSize: 11
        },

        emphasis: {

            label: {
                show: true
            }
        }
    };
}

export default ThemeRiverSeriesModel;
