import {useToastService} from "@/components/ui/services/toast.js";
import {useConfirmService} from "@/components/ui/services/confirm.js";
import {useDialogService} from "@/components/ui/services/dialog.js";
import axios from "axios";
import EventBus from "@/lib/events.js";
import PopupStates from "@/utils/popup_states.js";
import PopTipComponent from "@/components/common/PopTipComponent.vue";
import reloadPage from "@/lib/reload_page.js";
import router from "@/router/router.js"
import {$dt} from "@primevue/themes";

export default function useUtils() {
	const {newToast} = useToastService()
	const {newConfirm} = useConfirmService()
	const {newDialog} = useDialogService()
	const eventBus = new EventBus()
	const popupStates = new PopupStates()

	const utils = {
		set(key, value) {
			localStorage.setItem(key, JSON.stringify(value));
		},
		get(key) {
			var item = localStorage.getItem(key);
			if (item == null || item.length == 0) {
				return null;
			}

			return JSON.parse(item);
		},
		getString(key) {
			var value = this.get(key);
			if (typeof (value) == "string") {
				return value;
			}
			return "";
		},
		getBool(key) {
			return Boolean(this.get(key));
		},
		remove(key) {
			localStorage.removeItem(key)
		},
		match(source, keyword) {
			if (source == null) {
				return false;
			}
			if (keyword == null) {
				return true;
			}
			source = source.trim();
			keyword = keyword.trim();
			if (keyword.length == 0) {
				return true;
			}
			if (source.length == 0) {
				return false;
			}
			var pieces = keyword.split(/\s+/);
			for (var i = 0; i < pieces.length; i++) {
				var pattern = pieces[i];
				pattern = pattern.replace(/(\+|\*|\?|[|]|{|}|\||\\|\(|\)|\.)/g, "\\$1");
				var reg = new RegExp(pattern, "i");
				if (!reg.test(source)) {
					return false;
				}
			}
			return true;
		},
		clone(source) {
			let s = JSON.stringify(source)
			return JSON.parse(s)
		},
		formatBytes(bytes) {
			bytes = Math.ceil(bytes);
			if (bytes < Math.pow(1024, 1)) {
				return bytes + "B";
			}
			if (bytes < Math.pow(1024, 2)) {
				return (Math.round(bytes * 100 / Math.pow(1024, 1)) / 100) + "KiB";
			}
			if (bytes < Math.pow(1024, 3)) {
				return (Math.round(bytes * 100 / Math.pow(1024, 2)) / 100) + "MiB";
			}
			if (bytes < Math.pow(1024, 4)) {
				return (Math.round(bytes * 100 / Math.pow(1024, 3)) / 100) + "GiB";
			}
			if (bytes < Math.pow(1024, 5)) {
				return (Math.round(bytes * 100 / Math.pow(1024, 4)) / 100) + "TiB";
			}
			if (bytes < Math.pow(1024, 6)) {
				return (Math.round(bytes * 100 / Math.pow(1024, 5)) / 100) + "PiB";
			}
			return (Math.round(bytes * 100 / Math.pow(1024, 6)) / 100) + "EiB";
		},
		formatBits(bits, decimal) {
			bits = Math.ceil(bits);
			let div = 10000
			switch (decimal) {
				case 1:
					div = 10
					break
				case 2:
					div = 100
					break
				case 3:
					div = 1000
					break
				case 4:
					div = 10000
					break
			}
			if (bits < Math.pow(1024, 1)) {
				return bits + "bps";
			}
			if (bits < Math.pow(1024, 2)) {
				return (Math.round(bits * div / Math.pow(1024, 1)) / div) + "Kbps";
			}
			if (bits < Math.pow(1024, 3)) {
				return (Math.round(bits * div / Math.pow(1024, 2)) / div) + "Mbps";
			}
			if (bits < Math.pow(1024, 4)) {
				return (Math.round(bits * div / Math.pow(1024, 3)) / div) + "Gbps";
			}
			if (bits < Math.pow(1024, 5)) {
				return (Math.round(bits * div / Math.pow(1024, 4)) / div) + "Tbps";
			}
			if (bits < Math.pow(1024, 6)) {
				return (Math.round(bits * div / Math.pow(1024, 5)) / div) + "Pbps";
			}
			return (Math.round(bits * div / Math.pow(1024, 6)) / div) + "Ebps";
		},
		formatNumber(x) {
			if (x == null) {
				return "null"
			}
			let s = x.toString()
			let dotIndex = s.indexOf(".")
			if (dotIndex >= 0) {
				return this.formatNumber(s.substring(0, dotIndex)) + "." + s.substring(dotIndex + 1)
			}

			if (s.length <= 3) {
				return s;
			}
			let result = []
			for (let i = 0; i < Math.floor(s.length / 3); i++) {
				let start = s.length - (i + 1) * 3
				result.push(s.substring(start, start + 3))
			}
			if (s.length % 3 != 0) {
				result.push(s.substring(0, s.length % 3))
			}
			return result.reverse().join(", ")
		},
		formatCount(x) {
			let unit = ""
			let divider = ""
			if (x >= 1000 * 1000 * 1000) {
				unit = "B"
				divider = 1000 * 1000 * 1000
			} else if (x >= 1000 * 1000) {
				unit = "M"
				divider = 1000 * 1000
			} else if (x >= 1000) {
				unit = "K"
				divider = 1000
			}
			if (unit.length == 0) {
				return x.toString()
			}
			return (Math.round(x * 100 / divider) / 100) + unit
		},
		bytesAxis(stats, countFunc) {
			let max = Math.max.apply(this, stats.map(countFunc))
			let divider = 1
			let unit = "B"
			if (max >= Math.pow(1024, 6)) {
				unit = "E"
				divider = Math.pow(1024, 6)
			} else if (max >= Math.pow(1024, 5)) {
				unit = "P"
				divider = Math.pow(1024, 5)
			} else if (max >= Math.pow(1024, 4)) {
				unit = "T"
				divider = Math.pow(1024, 4)
			} else if (max >= Math.pow(1024, 3)) {
				unit = "G"
				divider = Math.pow(1024, 3)
			} else if (max >= Math.pow(1024, 2)) {
				unit = "M"
				divider = Math.pow(1024, 2)
			} else if (max >= Math.pow(1024, 1)) {
				unit = "K"
				divider = Math.pow(1024, 1)
			}
			return {
				unit: unit,
				divider: divider
			}
		},
		bitsAxis(stats, countFunc) {
			let axis = this.bytesAxis(stats, countFunc)
			let unit = axis.unit
			if (unit == "B") {
				unit = "bps"
			} else {
				unit += "bps"
			}
			return {
				unit: unit,
				divider: axis.divider
			}
		},
		countAxis(stats, countFunc) {
			let max = Math.max.apply(this, stats.map(countFunc))
			let divider = 1
			let unit = ""
			if (max >= 1000 * 1000 * 1000) {
				unit = "B"
				divider = 1000 * 1000 * 1000
			} else if (max >= 1000 * 1000) {
				unit = "M"
				divider = 1000 * 1000
			} else if (max >= 1000) {
				unit = "K"
				divider = 1000
			}
			return {
				unit: unit,
				divider: divider,
				max: max
			}
		},
		splitFormat(s) {
			let matchResult = s.match(/^([0-9.]+)([a-zA-Z]+)$/)
			let size = parseFloat(matchResult[1])
			let unit = matchResult[2]
			return [size, unit]
		},

		_fixDialogHeader(count) {
			if (count > 10) {
				return
			}

			setTimeout(() => {
				const dialogs = document.querySelectorAll(".p-dialog")
				if (dialogs.length == 0) {
					return
				}

				const dialog = dialogs[dialogs.length - 1]
				const headerElement = dialog.querySelector(".p-dialog-header .p-dialog-title")
				const tags = ["header"]
				let headerContentElement
				for (const tag of tags) {
					const el = document.querySelector(".p-dialog-content " + tag)
					if (el != null) {
						headerContentElement = el
						break
					}
				}

				if (headerElement != null && headerContentElement != null) {
					headerElement.innerHTML = headerContentElement.innerHTML
					headerContentElement.parentNode.removeChild(headerContentElement)
				} else {
					count += 1
					setTimeout(() => {
						this._fixDialogHeader(count)
					})
				}
			}, 10)
		},

		// popup(component, url, options)
		// popup(url, options)
		// TODO 实现 showCloseButton 参考老的代码实现
		popup(component, url, options) {
			if (typeof component === "string") {
				let ctx = window.X_VIEW_CTX
				if (popupStates.length > 0) {
					ctx = popupStates.last().ctx
				}
				const routerInfo = router.resolve(ctx.$Tea.url(component))
				if (routerInfo != null && typeof routerInfo === "object" && routerInfo.matched.length > 0) {
					options = url
					url = component
					const loader = routerInfo.matched[0].components.default
					component = loader().then((v) => {
						utils.popup(v.default, url, options)
					}).catch(e => {
						throw e
					})
					return
				} else {
					console.error("'utils.popup(url, options)' says: no router config found for '" + component + "'")
					return
				}
			}

			if (typeof component !== "object" && typeof component !== "function") {
				console.error("'utils.popup(component, url, options)' says: require component argument")
				return
			}

			if (url == null) {
				const dialog = newDialog(component)
				dialog.header(" ")
				dialog.onClose((e) => {
					if (typeof (options["onClose"]) === "function") {
						options["onClose"].call(utils.CURRENT_CTX, e)
					}
				})
				dialog.onShow(() => {
					this._fixDialogHeader(0)
				})

				let width = "45rem"
				let height = "30rem"
				if (options["width"] != null) {
					width = options["width"]
				}
				if (options["height"] != null) {
					height = options["height"]
				}

				dialog.style("width", width)
				dialog.style("height", height)
				dialog.data({
					content: options.content
				})
				dialog.show()
				return
			}

			// read new popup data
			let ctx = window.X_VIEW_CTX
			if (popupStates.length > 0) {
				ctx = popupStates.last().ctx
			}
			url = ctx.$Tea.url(url)

			this._readPopup(url, (resp, headers) => {
				const popupState = {
					action: {parent: "", path: ""},
					data: {},
					callback: null,
					dialogInstance: null,
					url: url
				}

				// process data and headers
				if (resp.data !== null && typeof resp.data === "object") {
					popupState.data = resp.data
				}

				for (const k in headers) {
					switch (k.toLowerCase()) {
						case "x-view-action-parent":
							popupState.action.parent = headers[k]
							break
						case "x-view-action-path":
							popupState.action.path = headers[k]
							break

					}
				}
				popupStates.push(popupState)

				// show popup component
				if (options == null) {
					options = {}
				}

				const dialog = newDialog(component)
				dialog.header(" ")
				dialog.onClose((e) => {
					// async to wait popup unmounted
					setTimeout(() => {
						if (typeof (options["onClose"]) === "function") {
							options["onClose"].call(utils.CURRENT_CTX, e)
						}
					}, 10)
				})
				dialog.onShow(() => {
					this._fixDialogHeader(0)
				})

				let width = "45em"
				let height = null
				if (options["width"] != null) {
					width = options["width"]
				}
				if (options["height"] != null) {
					height = options["height"]
				}

				dialog.style("width", width)
				dialog.style("height", height)
				dialog.style("position", "absolute")
				dialog.style("top", "130px");
				dialog.style("border-radius", "8px");
				const dialogInstance = dialog.show()
				popupState.dialogInstance = dialogInstance

				popupState.callback = () => {
					dialogInstance.close()
				}

				if (typeof (options["callback"]) == "function") {
					popupState.callback = (...allArgs) => {
						dialogInstance.close()
						setTimeout(() => {
							options["callback"].apply(utils.CURRENT_CTX, allArgs)
						}, 100)
					}
				}
			})
		},

		POPUP_HEIGHT_LG: "40em",

		popupSuccess(url, width, height) {
			let options = {}
			if (width != null) {
				options["width"] = width
			}
			if (height != null) {
				options["height"] = height
			}
			options["callback"] = () => {
				this.success("保存成功", () => {
					utils.reload()
				})
			}
			this.popup(url, options)
		},

		_readPopup(url, successFn) {
			eventBus.emit("showLoadingBar")
			axios.get(url, {
				withCredentials: true,
				headers: {
					"X-From-Front": "1"
				}
			})
				.then(function (resp) {
					eventBus.emit("hideLoadingBar")

					if (resp.status != 200) {
						console.error("retrieve popup url '" + url + "' failed: ", resp)
						return
					}

					const data = resp.data
					if (data === null || typeof data !== "object" || data.code >= 400) {
						console.error("invalid response from popup url '" + url + "': ", data)
						return
					}

					if (typeof successFn === "function") {
						successFn.call(utils.CURRENT_CTX, data, resp.headers)
					}
				}).catch(() => {
				eventBus.emit("hideLoadingBar")
			})
		},

		popupFinish(...allArgs) {
			const popupState = popupStates.last()
			if (popupState != null && typeof popupState.callback === "function") {
				popupState.callback.call(popupState.ctx, ...allArgs)
			}
		},
		popupTip(html) {
			this.popup(PopTipComponent, null, {
				width: "34em",
				height: (html.length > 100) ? "22em" : "14em",
				content: html
			})
		},
		closePopup() {
			const popupState = popupStates.last()
			if (popupState != null) {
				popupState.dialogInstance.close()
			}
		},
		hasPopup() {
			return popupStates.length > 0
		},
		success(message, callback) {
			newConfirm(message)
				.header(" ")
				.acceptLabel("确定")
				.rejectProps({
					style: {display: "none"}
				})
				.icon('pi pi-check-circle')
				.onAccept(() => {
					if (typeof (callback) == "function") {
						setTimeout(function () {
							callback.call(utils.CURRENT_CTX)
						});
					} else if (typeof (callback) == "string") {
						window.location = callback
					}
				})
				.onClose(() => {
					if (typeof (callback) == "function") {
						setTimeout(function () {
							callback();
						});
					} else if (typeof (callback) == "string") {
						window.location = callback
					}
				})
				.show()
		},
		toast(message, timeout, callback) {
			if (timeout == null) {
				timeout = 2000
			}

			newToast(message)
				.life(timeout)
				.info()
				.onClose(() => {
					if (typeof callback == "function") {
						callback()
					}
				})
				.show()
		},
		successToast(message, timeout, callback) {
			if (timeout == null) {
				timeout = 1500
			}

			newToast(message)
				.life(timeout)
				.success()
				.onClose(() => {
					if (typeof callback == "function") {
						callback()
					}
				})
				.show()
		},
		successRefresh(message) {
			this.success(message, () => {
				this.reload()
			})
		},
		warn(message, callback) {
			newConfirm(message)
				.header(" ")
				.acceptLabel("确定")
				.rejectProps({
					style: {display: "none"}
				})
				.icon('pi pi-exclamation-triangle')
				.onAccept(() => {
					if (typeof (callback) == "function") {
						setTimeout(function () {
							callback();
						});
					} else if (typeof (callback) == "string") {
						window.location = callback
					}
				})
				.onClose(() => {
					if (typeof (callback) == "function") {
						setTimeout(function () {
							callback();
						});
					} else if (typeof (callback) == "string") {
						window.location = callback
					}
				})
				.show()
		},
		confirm(message, callback) {
			newConfirm(message)
				.header(" ")
				.acceptLabel("确认")
				.rejectLabel("取消")
				.onAccept(() => {
					if (typeof (callback) == "function") {
						callback.call(utils.CURRENT_CTX)
					}
				})
				.show()
		},
		reload() {
			// reload in popup
			if (popupStates.length > 0) {
				let ctx = popupStates.last().ctx
				ctx.$Tea.reloadViewData(ctx.url)
				return
			}

			// reload in main pages
			reloadPage()
		},
		reloadWindow() {
			window.location.reload()
		},
		renderBarChart(options) {
			let chartId = options.id
			if (chartId == null || chartId.length == 0) {
				throw new Error("'options.id' should not be empty")
			}

			let name = options.name
			let values = options.values
			if (values == null || !(values instanceof Array)) {
				throw new Error("'options.values' should be array")
			}

			let xFunc = options.x
			if (typeof (xFunc) != "function") {
				throw new Error("'options.x' should be a function")
			}

			let tooltipFunc = options.tooltip
			if (typeof (tooltipFunc) != "function") {
				throw new Error("'options.tooltip' should be a function")
			}

			let axis = options.axis
			if (axis == null) {
				axis = {unit: "", count: 1}
			}
			let valueFunc = options.value
			if (typeof (valueFunc) != "function") {
				throw new Error("'options.value' should be a function")
			}
			let click = options.click

			let bottom = 24
			let rotate = 0
			let chartBox = document.getElementById(chartId)
			if (chartBox == null) {
				return
			}
			this.initChart(chartBox, undefined, (chart) => {
				let result = this.xRotation(chart, values.map(xFunc))
				if (result != null) {
					bottom = result[0]
					rotate = result[1]
				}
				let option = {
					xAxis: {
						data: values.map(xFunc),
						axisLabel: {
							interval: 0,
							rotate: rotate,
							color: utils.chartLabelColor
						}
					},
					yAxis: {
						axisLabel: {
							formatter: function (value) {
								return value + axis.unit
							},
							color: utils.chartLabelColor
						}
					},
					tooltip: {
						show: true,
						trigger: "item",
						formatter: function (args) {
							return tooltipFunc.apply(this, [args, values])
						}
					},
					grid: {
						left: 40,
						top: 10,
						right: 20,
						bottom: bottom
					},
					series: [
						{
							name: name,
							type: "bar",
							data: values.map(valueFunc),
							itemStyle: {
								color: this.DefaultChartColor
							},
							barWidth: "10em",
							areaStyle: {}
						}
					],
					animation: true,
				}
				chart.setOption(option)
				if (click != null) {
					chart.on("click", function (args) {
						click.call(this, args, values)
					})
				}
				chart.resize()
			})
		},
		renderLineChart(options) {
			let chartId = options.id
			let name = options.name
			let values = options.values
			let xFunc = options.x
			let xColorFunc = options.xColor
			let tooltipFunc = options.tooltip
			let axis = options.axis
			let valueFunc = options.value
			let max = options.max
			let interval = options.interval
			let left = options.left
			if (typeof left != "number") {
				left = 0
			}

			if (xColorFunc == null) {
				xColorFunc = this.chartLabelColor
			}

			let right = options.right
			if (typeof right != "number") {
				right = 0
			}

			let chartBox = document.getElementById(chartId)
			if (chartBox == null) {
				console.error("chart id '" + chartId + "' not found")
				return
			}
			this.initChart(chartBox, options.cache, (chart) => {
				let option = {
					xAxis: {
						data: values.map(xFunc),
						axisLabel: {
							interval: interval,
							color: xColorFunc
						}
					},
					yAxis: {
						axisLabel: {
							formatter: function (value) {
								return value + axis.unit
							},
							color: utils.chartLabelColor
						},
						max: max
					},
					tooltip: {
						show: true,
						trigger: "item",
						formatter: function (args) {
							if (tooltipFunc != null) {
								return tooltipFunc.apply(this, [args, values])
							}
							return null
						}
					},
					grid: {
						left: 40 + left,
						top: 10,
						right: 20 + right,
						bottom: 20
					},
					series: [
						{
							name: name,
							type: "line",
							data: values.map(valueFunc),
							itemStyle: {
								color: this.DefaultChartColor
							},
							areaStyle: {},
							smooth: true,
							markLine: options.markLine
						}
					],
					animation: true,
					smooth: true
				}
				chart.setOption(option)
				chart.resize()
			})
		},
		renderGaugeChart(options) {
			let chartId = options.id
			let name = options.name // 标题
			let min = options.min // 最小值
			let max = options.max // 最大值
			let value = options.value // 当前值
			let unit = options.unit // 单位
			let detail = options.detail // 说明文字
			let color = options.color // 颜色
			let startAngle = options.startAngle
			if (startAngle == null) {
				startAngle = 225
			}
			let endAngle = options.endAngle
			if (endAngle == null) {
				endAngle = -45
			}

			color = this.chartColor(color)

			let chartBox = document.getElementById(chartId)
			if (chartBox == null) {
				return
			}
			this.initChart(chartBox, undefined, (chart) => {
				let option = {
					fontFamily: "Lato,'Helvetica Neue',Arial,Helvetica,sans-serif",
					color: color,
					title: (name != null && name.length > 0) ? {
						text: name,
						top: 1,
						bottom: 0,
						x: "center",
						fontSize: 12,
						fontWeight: "bold",
						fontFamily: "Lato,'Helvetica Neue',Arial,Helvetica,sans-serif"
					} : null,
					legend: {
						data: [""],
						textStyle: {
							color: utils.chartLabelColor()
						}
					},
					xAxis: {
						data: [],
						axisLabel: {
							color: utils.chartLabelColor
						}
					},
					yAxis: {
						axisLabel: {
							color: utils.chartLabelColor
						}
					},
					series: [{
						name: "",
						type: "gauge",
						min: min,
						max: max,
						startAngle: startAngle,
						endAngle: endAngle,

						data: [
							{
								"name": "",// 不显示名称
								"value": Math.round(value * 100) / 100
							}
						],
						radius: "100%",
						center: ["50%", (name != null && name.length > 0) ? "60%" : "50%"],

						splitNumber: 5,
						splitLine: {
							length: 4
						},

						axisLine: {
							lineStyle: {
								width: 4
							}
						},
						axisTick: {
							show: true,
							length: 2
						},
						axisLabel: {
							formatter: function (v) {
								return v;
							},
							fontSize: 8
						},
						progress: {
							show: true,
							width: 4
						},
						detail: {
							formatter: function (v) {
								return unit;
							},
							fontSize: 12,
							fontWeight: "normal",
							fontFamily: "Arial,Helvetica,sans-serif",
							color: "grey",
							//lineHeight: 16
							valueAnimation: true
						},

						pointer: {
							width: 2
						}
					}],

					grid: {
						left: -2,
						right: 0,
						bottom: 0,
						top: 0
					},
					axisPointer: {
						show: false
					},
					tooltip: {
						formatter: 'X:{b0} Y:{c0}',
						show: false
					},
					animation: true
				};

				chart.setOption(option)
			})
		},

		renderPercentChart(options) {
			let chartId = options.id
			let color = this.chartColor(options.color)
			let value = options.value
			let name = options.name
			let total = options.total
			if (total == null) {
				total = 100
			}
			let unit = options.unit
			if (unit == null) {
				unit = ""
			}

			let max = options.max
			if (max != null && max <= value) {
				max = null
			}
			let maxColor = this.chartColor(options.maxColor)
			let maxName = options.maxName

			let chartBox = document.getElementById(chartId)
			if (chartBox == null) {
				return
			}
			this.initChart(chartBox, undefined, (chart) => {
				let option = {
					tooltip: {
						formatter: "{a} <br/>{b} : {c}" + unit
					},
					series: [
						{
							name: name,
							max: total,
							type: "gauge",
							radius: "100%",
							detail: {
								formatter: "{value}",
								show: false,
								valueAnimation: true
							},
							data: [
								{
									value: value,
									name: name
								}
							],
							pointer: {
								show: false
							},
							splitLine: {
								show: false
							},
							axisTick: {
								show: false
							},
							axisLine: {
								show: true,
								lineStyle: {
									width: 4
								}
							},
							progress: {
								show: true,
								width: 4,
								itemStyle: {
									color: color
								}
							},
							splitNumber: {
								show: false
							},
							title: {
								show: false
							},
							startAngle: 270,
							endAngle: -90
						}
					]
				}

				if (max != null) {
					option.series.push({
						name: maxName,
						max: total,
						type: "gauge",
						radius: "100%",
						detail: {
							formatter: "{value}",
							show: false,
							valueAnimation: true
						},
						data: [
							{
								value: max,
								name: maxName
							}
						],
						pointer: {
							show: false
						},
						splitLine: {
							show: false
						},
						axisTick: {
							show: false
						},
						axisLine: {
							show: true,
							lineStyle: {
								width: 4
							}
						},
						progress: {
							show: true,
							width: 4,
							itemStyle: {
								color: maxColor,
								opacity: 0.3
							}
						},
						splitNumber: {
							show: false
						},
						title: {
							show: false
						},
						startAngle: 270,
						endAngle: -90
					})
				}

				chart.setOption(option)
			})
		},

		xRotation(chart, names) {
			if (names.length == 0) {
				return null
			}
			let chartWidth = chart.getWidth()
			let avgItemWidth = chartWidth / names.length
			let shouldRotate = false
			names.forEach(function (name) {
				if (name.length * 14 > avgItemWidth) {
					shouldRotate = true
				}
			})
			if (shouldRotate) {
				return [40, -20]
			}
			return null
		},
		chartMap: {}, // dom id => chart
		echarts: null,
		setEcharts(echarts) {
			this.echarts = echarts
		},
		initChart(dom, cache, callback) {
			if (dom.clientHeight == 0) {
				setTimeout(() => {
					this.initChart(dom, cache, callback)
				}, 50)
				return
			}

			if (typeof (cache) != "boolean") {
				cache = true
			}

			let domId = dom.getAttribute("id")
			if (domId != null && domId.length > 0 && typeof (this.chartMap[domId]) == "object") {
				callback(this.chartMap[domId])
				return this.chartMap[domId]
			}

			let instance = this.echarts.init(dom)
			window.addEventListener("resize", function () {
				instance.resize()
			})
			if (cache) {
				this.chartMap[domId] = instance
			}
			callback(instance)
		},
		encodeHTML(s) {
			s = s.replace(/&/g, "&amp;")
			s = s.replace(/</g, "&lt;")
			s = s.replace(/>/g, "&gt;")
			s = s.replace(/"/g, "&quot;")
			return s
		},
		chartColor(color) {
			if (color == null || color.length == 0) {
				color = utils.DefaultChartColor
				if (color.length == 9) {
					color = color.substring(0, color.length - 2) // remove alpha
				}

			}

			if (color == "red") {
				color = "#ee6666"
			}
			if (color == "yellow") {
				color = "#fac858"
			}
			if (color == "blue") {
				color = "#5470c6"
			}
			if (color == "green") {
				color = "#3ba272"
			}
			return color
		},
		colorScheme() {
			const className = document.querySelector("html").className
			if (typeof className === "string" && className.indexOf("dark") >= 0) {
				return "dark"
			}
			return "light"
		},
		chartLabelColor() {
			return (utils.colorScheme() == "light") ? "#334155" : "#d4d4d8"
		},
		DefaultChartColor: (() => {
			if (window.X_VIEW_THEME) {
				const color = $dt(window.X_VIEW_THEME + ".500")
				if (color && color.value) {
					return color.value + "aa"
				}
			}
			return "#3b82f6aa"
		})(),
		validateIP(ip) {
			if (typeof ip != "string") {
				return false
			}

			if (ip.length == 0) {
				return false
			}

			// IPv6
			if (ip.indexOf(":") >= 0) {
				let pieces = ip.split(":")
				if (pieces.length > 8) {
					return false
				}
				let isOk = true
				pieces.forEach(function (piece) {
					if (!/^[\da-fA-F]{0,4}$/.test(piece)) {
						isOk = false
					}
				})

				return isOk
			}

			if (!ip.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)) {
				return false
			}
			let pieces = ip.split(".")
			let isOk = true
			pieces.forEach(function (v) {
				let v1 = parseInt(v)
				if (v1 > 255) {
					isOk = false
				}
			})
			return isOk
		},

		convertSizeCapacityToBytes(c) {
			if (c == null) {
				return 0
			}
			switch (c.unit) {
				case "byte":
					return c.count
				case "kb":
					return c.count * 1024
				case "mb":
					return c.count * Math.pow(1024, 2)
				case "gb":
					return c.count * Math.pow(1024, 3)
				case "tb":
					return c.count * Math.pow(1024, 4)
				case "pb":
					return c.count * Math.pow(1024, 5)
				case "eb":
					return c.count * Math.pow(1024, 6)
			}
			return 0
		},
		compareSizeCapacity(c1, c2) {
			let b1 = this.convertSizeCapacityToBytes(c1)
			let b2 = this.convertSizeCapacityToBytes(c2)
			if (b1 == b2) {
				return 0
			}
			if (b1 > b2) {
				return 1
			}
			return -1
		},
		convertSizeCapacityToString(c) {
			if (c == null || c.count == null || c.unit == null || c.unit.length == 0) {
				return ""
			}
			if (c.unit == "byte") {
				return c.count + "B"
			}
			return c.count + c.unit[0].toUpperCase() + "i" + c.unit.substring(1).toUpperCase()
		},
		get CURRENT_CTX() {
			let ctx = window.X_VIEW_CTX
			if (popupStates.length > 0) {
				ctx = popupStates.last().ctx
			}
			return ctx
		},
		moveElement(arr, fromIndex, toIndex) {
			if (!arr || arr.length == 0 || fromIndex == toIndex) {
				return
			}

			const fromElement = arr[fromIndex]
			if (fromIndex < toIndex) {
				for (let i = fromIndex; i < toIndex; i++) {
					arr[i] = arr[i + 1]
				}
			} else if (fromIndex > toIndex) {
				for (let i = fromIndex; i > toIndex; i--) {
					arr[i] = arr[i - 1]
				}
			}
			arr[toIndex] = fromElement
		},
		closeInstantMessage() {
			newToast("").removeGroup("instantMessage")
		},
		showInstantMessage(message) {
			this.closeInstantMessage()

			newToast(message)
				.group("instantMessage")
				.life(3600 * 1000)
				.info()
				.closable(false)
				.show()
		}
	}

	return utils
}

String.prototype.quoteIP = function () {
	let ip = this.toString()
	if (ip.length == 0) {
		return ""
	}
	if (ip.indexOf(":") < 0) {
		return ip
	}
	if (ip.substring(0, 1) == "[") {
		return ip
	}
	return "[" + ip + "]"
}