/*
* 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 { parseDate, isNumeric, numericToNumber } from './number';
import { TooltipRenderMode, ColorString, ZRColor, DimensionType } from './types';
import { Dictionary } from 'zrender/src/core/types';
import { GradientObject } from 'zrender/src/graphic/Gradient';
import { format as timeFormat, pad } from './time';
import { deprecateReplaceLog } from './log';

/**
 * Add a comma each three digit.
 */
export function addCommas(x: string | number): string {
    if (!isNumeric(x)) {
        return zrUtil.isString(x) ? x : '-';
    }
    const parts = (x + '').split('.');
    return parts[0].replace(/(\d{1,3})(?=(?:\d{3})+(?!\d))/g, '$1,')
            + (parts.length > 1 ? ('.' + parts[1]) : '');
}

export function toCamelCase(str: string, upperCaseFirst?: boolean): string {
    str = (str || '').toLowerCase().replace(/-(.)/g, function (match, group1) {
        return group1.toUpperCase();
    });

    if (upperCaseFirst && str) {
        str = str.charAt(0).toUpperCase() + str.slice(1);
    }

    return str;
}

export const normalizeCssArray = zrUtil.normalizeCssArray;


const replaceReg = /([&<>"'])/g;
const replaceMap: Dictionary<string> = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    '\'': '&#39;'
};

export function encodeHTML(source: string): string {
    return source == null
        ? ''
        : (source + '').replace(replaceReg, function (str, c) {
            return replaceMap[c];
        });
}


/**
 * Make value user readable for tooltip and label.
 * "User readable":
 *     Try to not print programmer-specific text like NaN, Infinity, null, undefined.
 *     Avoid to display an empty string, which users can not recognize there is
 *     a value and it might look like a bug.
 */
export function makeValueReadable(
    value: unknown,
    valueType: DimensionType,
    useUTC: boolean
): string {
    const USER_READABLE_DEFUALT_TIME_PATTERN = '{yyyy}-{MM}-{dd} {hh}:{mm}:{ss}';

    function stringToUserReadable(str: string): string {
        return (str && zrUtil.trim(str)) ? str : '-';
    }
    function isNumberUserReadable(num: number): boolean {
        return !!(num != null && !isNaN(num) && isFinite(num));
    }

    const isTypeTime = valueType === 'time';
    const isValueDate = value instanceof Date;
    if (isTypeTime || isValueDate) {
        const date = isTypeTime ? parseDate(value) : value;
        if (!isNaN(+date)) {
            return timeFormat(date, USER_READABLE_DEFUALT_TIME_PATTERN, useUTC);
        }
        else if (isValueDate) {
            return '-';
        }
        // In other cases, continue to try to display the value in the following code.
    }

    if (valueType === 'ordinal') {
        return zrUtil.isStringSafe(value)
            ? stringToUserReadable(value)
            : zrUtil.isNumber(value)
            ? (isNumberUserReadable(value) ? value + '' : '-')
            : '-';
    }
    // By default.
    const numericResult = numericToNumber(value);
    return isNumberUserReadable(numericResult)
        ? addCommas(numericResult)
        : zrUtil.isStringSafe(value)
        ? stringToUserReadable(value)
        : typeof value === 'boolean'
        ? value + ''
        : '-';
}


const TPL_VAR_ALIAS = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];

const wrapVar = function (varName: string, seriesIdx?: number): string {
    return '{' + varName + (seriesIdx == null ? '' : seriesIdx) + '}';
};

export interface TplFormatterParam extends Dictionary<any> {
    // Param name list for mapping `a`, `b`, `c`, `d`, `e`
    $vars: string[];
}
/**
 * Template formatter
 * @param {Array.<Object>|Object} paramsList
 */
export function formatTpl(
    tpl: string,
    paramsList: TplFormatterParam | TplFormatterParam[],
    encode?: boolean
): string {
    if (!zrUtil.isArray(paramsList)) {
        paramsList = [paramsList];
    }
    const seriesLen = paramsList.length;
    if (!seriesLen) {
        return '';
    }

    const $vars = paramsList[0].$vars || [];
    for (let i = 0; i < $vars.length; i++) {
        const alias = TPL_VAR_ALIAS[i];
        tpl = tpl.replace(wrapVar(alias), wrapVar(alias, 0));
    }
    for (let seriesIdx = 0; seriesIdx < seriesLen; seriesIdx++) {
        for (let k = 0; k < $vars.length; k++) {
            const val = paramsList[seriesIdx][$vars[k]];
            tpl = tpl.replace(
                wrapVar(TPL_VAR_ALIAS[k], seriesIdx),
                encode ? encodeHTML(val) : val
            );
        }
    }

    return tpl;
}

/**
 * simple Template formatter
 */
export function formatTplSimple(tpl: string, param: Dictionary<any>, encode?: boolean) {
    zrUtil.each(param, function (value, key) {
        tpl = tpl.replace(
            '{' + key + '}',
            encode ? encodeHTML(value) : value
        );
    });
    return tpl;
}

interface RichTextTooltipMarker {
    renderMode: TooltipRenderMode;
    content: string;
    style: Dictionary<unknown>;
}
export type TooltipMarker = string | RichTextTooltipMarker;
export type TooltipMarkerType = 'item' | 'subItem';
interface GetTooltipMarkerOpt {
    color?: ColorString;
    extraCssText?: string;
    // By default: 'item'
    type?: TooltipMarkerType;
    renderMode?: TooltipRenderMode;
    // id name for marker. If only one marker is in a rich text, this can be omitted.
    // By default: 'markerX'
    markerId?: string;
}
// Only support color string
export function getTooltipMarker(color: ColorString, extraCssText?: string): TooltipMarker;
export function getTooltipMarker(opt: GetTooltipMarkerOpt): TooltipMarker;
export function getTooltipMarker(inOpt: ColorString | GetTooltipMarkerOpt, extraCssText?: string): TooltipMarker {
    const opt = zrUtil.isString(inOpt) ? {
        color: inOpt,
        extraCssText: extraCssText
    } : (inOpt || {}) as GetTooltipMarkerOpt;
    const color = opt.color;
    const type = opt.type;
    extraCssText = opt.extraCssText;
    const renderMode = opt.renderMode || 'html';

    if (!color) {
        return '';
    }

    if (renderMode === 'html') {
        return type === 'subItem'
        ? '<span style="display:inline-block;vertical-align:middle;margin-right:8px;margin-left:3px;'
            + 'border-radius:4px;width:4px;height:4px;background-color:'
            // Only support string
            + encodeHTML(color) + ';' + (extraCssText || '') + '"></span>'
        : '<span style="display:inline-block;margin-right:4px;'
            + 'border-radius:10px;width:10px;height:10px;background-color:'
            + encodeHTML(color) + ';' + (extraCssText || '') + '"></span>';
    }
    else {
        // Should better not to auto generate style name by auto-increment number here.
        // Because this util is usually called in tooltip formatter, which is probably
        // called repeatly when mouse move and the auto-increment number increases fast.
        // Users can make their own style name by theirselves, make it unique and readable.
        const markerId = opt.markerId || 'markerX';
        return {
            renderMode: renderMode,
            content: '{' + markerId + '|}  ',
            style: type === 'subItem'
                ? {
                    width: 4,
                    height: 4,
                    borderRadius: 2,
                    backgroundColor: color
                }
                : {
                    width: 10,
                    height: 10,
                    borderRadius: 5,
                    backgroundColor: color
                }
        };
    }
}


/**
 * @deprecated Use `time/format` instead.
 * ISO Date format
 * @param {string} tpl
 * @param {number} value
 * @param {boolean} [isUTC=false] Default in local time.
 *           see `module:echarts/scale/Time`
 *           and `module:echarts/util/number#parseDate`.
 * @inner
 */
export function formatTime(tpl: string, value: unknown, isUTC?: boolean) {
    if (__DEV__) {
        deprecateReplaceLog('echarts.format.formatTime', 'echarts.time.format');
    }

    if (tpl === 'week'
        || tpl === 'month'
        || tpl === 'quarter'
        || tpl === 'half-year'
        || tpl === 'year'
    ) {
        tpl = 'MM-dd\nyyyy';
    }

    const date = parseDate(value);
    const utc = isUTC ? 'UTC' : '';
    const y = (date as any)['get' + utc + 'FullYear']();
    const M = (date as any)['get' + utc + 'Month']() + 1;
    const d = (date as any)['get' + utc + 'Date']();
    const h = (date as any)['get' + utc + 'Hours']();
    const m = (date as any)['get' + utc + 'Minutes']();
    const s = (date as any)['get' + utc + 'Seconds']();
    const S = (date as any)['get' + utc + 'Milliseconds']();

    tpl = tpl.replace('MM', pad(M, 2))
        .replace('M', M)
        .replace('yyyy', y)
        .replace('yy', y % 100 + '')
        .replace('dd', pad(d, 2))
        .replace('d', d)
        .replace('hh', pad(h, 2))
        .replace('h', h)
        .replace('mm', pad(m, 2))
        .replace('m', m)
        .replace('ss', pad(s, 2))
        .replace('s', s)
        .replace('SSS', pad(S, 3));

    return tpl;
}

/**
 * Capital first
 * @param {string} str
 * @return {string}
 */
export function capitalFirst(str: string): string {
    return str ? str.charAt(0).toUpperCase() + str.substr(1) : str;
}

/**
 * @return Never be null/undefined.
 */
export function convertToColorString(color: ZRColor, defaultColor?: ColorString): ColorString {
    defaultColor = defaultColor || 'transparent';
    return zrUtil.isString(color)
        ? color
        : zrUtil.isObject(color)
        ? (
            (color as GradientObject).colorStops
            && ((color as GradientObject).colorStops[0] || {}).color
            || defaultColor
        )
        : defaultColor;
}

export {truncateText} from 'zrender/src/graphic/helper/parseText';

/**
 * open new tab
 * @param link url
 * @param target blank or self
 */
export function windowOpen(link: string, target: string): void {
    /* global window */
    if (target === '_blank' || target === 'blank') {
        const blank = window.open();
        blank.opener = null;
        blank.location.href = link;
    }
    else {
        window.open(link, target);
    }
}


export {getTextRect} from '../legacy/getTextRect';
