import {
	toPx,
	isNumber,
	getImageInfo
} from './utils'
import {
	GD
} from './gradient'
import QR from './qrcode'


export class Draw {
	constructor(context, canvas, use2dCanvas = false, isH5PathToBase64 = false, sleep) {
		this.ctx = context
		this.canvas = canvas || null
		this.use2dCanvas = use2dCanvas
		this.isH5PathToBase64 = isH5PathToBase64
		this.sleep = sleep
		this.count = 0
		this.progress = 0
	}
	roundRect(x, y, w, h, r, fill = false, stroke = false, ) {
		if (r < 0) return
		const {
			ctx
		} = this
		ctx.beginPath()
		if (!r) {
			ctx.rect(x, y, w, h)
		} else {
			let {
				borderTopLeftRadius: tl = r || 0,
				borderTopRightRadius: tr = r || 0,
				borderBottomRightRadius: br = r || 0,
				borderBottomLeftRadius: bl = r || 0
			} = r || {}
			// 右下角
			ctx.arc(x + w - br, y + h - br, br, 0, Math.PI * 0.5)
			ctx.lineTo(x + bl, y + h)
			// 左下角
			ctx.arc(x + bl, y + h - bl, bl, Math.PI * 0.5, Math.PI)
			ctx.lineTo(x, y + tl)
			// 左上角
			ctx.arc(x + tl, y + tl, tl, Math.PI, Math.PI * 1.5)
			ctx.lineTo(x + w - tr, y)
			// 右上角
			ctx.arc(x + w - tr, y + tr, tr, Math.PI * 1.5, Math.PI * 2)
			ctx.lineTo(x + w, y + h - br)
		}
		ctx.closePath()
		if (stroke) ctx.stroke()
		if (fill) ctx.fill()
	}
	setTransform(box, {
		transform,
		transformOrigin: o = 'center center'
	}) {
		const {
			ctx
		} = this
		let {
			scaleX = 1,
				scaleY = 1,
				translateX = 0,
				translateY = 0,
				rotate = 0,
				skewX = 0,
				skewY = 0
		} = transform || {}
		let {
			left: x,
			top: y,
			width: w,
			height: h
		} = box
		translateX = toPx(translateX, w) || 0
		translateY = toPx(translateY, h) || 0

		const yMaps = {
			"top": toPx('0%', 1),
			"center": toPx('50%', 1, true),
			"bottom": toPx('100%', 1),
		}
		const xMaps = {
			"left": toPx('0%', 1),
			"center": toPx('50%', 1, true),
			"right": toPx('100%', 1)
		}
		o = o.split(' ').filter((v, i) => i < 2).reduce((c, v) => {
			if (/\d+/.test(v)) {
				let n = toPx(v, 1, true) / (/px|rpx$/.test(v) ? (isNumber(c.x) ? h : w) : 1)
				return isNumber(c.x) ? Object.assign(c, {
					y: n
				}) : Object.assign(c, {
					x: n
				})
			} else {
				return isNumber(xMaps[v]) && !isNumber(c.x) ? Object.assign(c, {
					x: xMaps[v]
				}) : Object.assign(c, {
					y: yMaps[v] || 0.5
				})
			}
		}, {})
		ctx.scale(scaleX, scaleY)
		const offset = {
			x: w * (scaleX > 0 ? 1 : -1) * o.x + (x + translateX) / scaleX,
			y: h * (scaleY > 0 ? 1 : -1) * o.y + (y + translateY) / scaleY
		}
		ctx.translate(offset.x, offset.y)
		if (rotate) {
			ctx.rotate(rotate * Math.PI / 180)
		}
		if (skewX || skewY) {
			ctx.transform(1, Math.tan(skewY * Math.PI / 180), Math.tan(skewX * Math.PI / 180), 1, 0, 0)
		}
		return {
			x: -w * o.x,
			y: -h * o.y,
			w,
			h
		}
	}
	setBackground(bg, w, h) {
		const {
			ctx
		} = this
		if (!bg) {
			// #ifndef MP-TOUTIAO || MP-BAIDU
			ctx.setFillStyle('transparent')
			// #endif
			// #ifdef MP-TOUTIAO || MP-BAIDU
			ctx.setFillStyle('rgba(0,0,0,0)')
			// #endif
		} else if (GD.isGradient(bg)) {
			GD.doGradient(bg, w, h, ctx);
		} else {
			ctx.setFillStyle(bg)
		}
	}
	setShadow({
		boxShadow: bs = []
	}) {
		const {
			ctx
		} = this
		if (bs.length) {
			const [x, y, b, c] = bs
			ctx.setShadow(x, y, b, c)
		}
	}
	setBorder(box, style) {
		const {
			ctx
		} = this
		let {
			width: w,
			height: h
		} = box
		const {
			border,
			borderBottom,
			borderTop,
			borderRight,
			borderLeft,
			borderRadius: r
		} = style;
		const {
			borderWidth: bw = 0,
			borderStyle: bs,
			borderColor: bc,
		} = border || {}
		const {
			borderBottomWidth: bbw = bw,
			borderBottomStyle: bbs = bs,
			borderBottomColor: bbc = bc,
		} = borderBottom || {}
		const {
			borderTopWidth: btw = bw,
			borderTopStyle: bts = bs,
			borderTopColor: btc = bc,
		} = borderTop || {}
		const {
			borderRightWidth: brw = bw,
			borderRightStyle: brs = bs,
			borderRightColor: brc = bc,
		} = borderRight || {}
		const {
			borderLeftWidth: blw = bw,
			borderLeftStyle: bls = bs,
			borderLeftColor: blc = bc,
		} = borderLeft || {}

		let {
			borderTopLeftRadius: tl = r || 0,
			borderTopRightRadius: tr = r || 0,
			borderBottomRightRadius: br = r || 0,
			borderBottomLeftRadius: bl = r || 0
		} = r || {
			r,
			r,
			r,
			r
		}
		if (!borderBottom && !borderLeft && !borderTop && !borderRight && !border) return;
		const _borderType = (w, s, c) => {
			if (s == 'dashed') {
				// #ifdef MP
				ctx.setLineDash([Math.ceil(w * 4 / 3), Math.ceil(w * 4 / 3)])
				// #endif
				// #ifndef MP
				ctx.setLineDash([Math.ceil(w * 6), Math.ceil(w * 6)])
				// #endif
			} else if (s == 'dotted') {
				ctx.setLineDash([w, w])
			}
			ctx.setStrokeStyle(c)
		}
		const _setBorder = (x1, y1, x2, y2, x3, y3, r1, r2, p1, p2, p3, bw, bs, bc) => {
			ctx.save()
			// this.setOpacity(style)
			// this.setTransform(box, style)
			ctx.setLineWidth(bw)
			_borderType(bw, bs, bc)
			ctx.beginPath()
			ctx.arc(x1, y1, r1, Math.PI * p1, Math.PI * p2)
			ctx.lineTo(x2, y2)
			ctx.arc(x3, y3, r2, Math.PI * p2, Math.PI * p3)
			ctx.stroke()
			ctx.restore()
		}
		ctx.save()
		this.setOpacity(style)
		let {
			x,
			y
		} = this.setTransform(box, style)
		if (border) {
			ctx.setLineWidth(bw)
			_borderType(bw, bs, bc)
			this.roundRect(x, y, w, h, r, false, bc ? true : false)
			ctx.restore()
		}

		if (borderBottom) {
			_setBorder(x + w - br, y + h - br, x + bl, y + h, x + bl, y + h - bl, br, bl, 0.25, 0.5, 0.75, bbw, bbs,
				bbc)
		}
		if (borderLeft) {
			// 左下角
			_setBorder(x + bl, y + h - bl, x, y + tl, x + tl, y + tl, bl, tl, 0.75, 1, 1.25, blw, bls, blc)
		}
		if (borderTop) {
			// 左上角
			_setBorder(x + tl, y + tl, x + w - tr, y, x + w - tr, y + tr, tl, tr, 1.25, 1.5, 1.75, btw, bts, btc)
		}
		if (borderRight) {
			// 右上角
			_setBorder(x + w - tr, y + tr, x + w, y + h - br, x + w - br, y + h - br, tr, br, 1.75, 2, 0.25, btw,
				bts, btc)
		}
	}
	setOpacity({
		opacity = 1
	}) {
		this.ctx.setGlobalAlpha(opacity)
	}
	drawView(box, style) {
		const {
			ctx
		} = this
		const {
			width: w,
			height: h
		} = box
		let {
			borderRadius: br = 0,
			backgroundColor: bg,
		} = style || {}
		ctx.save()
		this.setOpacity(style)
		let {
			x,
			y
		} = this.setTransform(box, style)
		this.setShadow(style)
		this.setBackground(bg, w, h)
		this.roundRect(x, y, w, h, br, true, false)
		ctx.restore()
		this.setBorder(box, style)
	}
	async drawImage(img, box = {}, style = {}, custom = true) {
		await new Promise(async (resolve, reject) => {
			const {
				ctx,
				sleep
			} = this
			const canvas = this.canvas
			let {
				borderRadius = 0,
					mode,
					padding = {},
					backgroundColor: bg,
			} = style
			const {
				paddingTop: pt = 0,
				paddingLeft: pl = 0,
				paddingRight: pr = 0,
				paddingBottom: pb = 0
			} = padding;
			let {
				left: x,
				top: y,
				width: w,
				height: h
			} = box
			ctx.save()
			if (!custom) {
				this.setOpacity(style)
				let {
					x: x1,
					y: y1
				} = this.setTransform(box, style)
				if (bg) {
					this.setBackground(bg, w, h)
				}
				x = x1
				y = y1
				this.setShadow(style)
				this.roundRect(x, y, w, h, borderRadius, borderRadius ? true : false, false)
			}
			ctx.clip()
			const _modeImage = (img) => {
				x += pl
				y += pt
				w = w - pl - pr
				h = h - pt - pb
				// 获得图片原始大小
				let {
					width: rw,
					height: rh,
					src
				} = img
				let sX = 0
				let sY = 0
				// 绘画区域比例
				const cp = w / h
				// 原图比例
				const op = rw / rh
				if (!rw) {
					mode = 'scaleToFill'
				}
				switch (mode) {
					case 'aspectFit':
						if (cp >= op) {
							rw = h * op;
							rh = h
							sX = x + Math.round(w - rw) / 2
							sY = y
						} else {
							rw = w
							rh = w / op;
							sX = x
							sY = y + Math.round(h - rh) / 2
						}
						ctx.drawImage(src, sX, sY, rw, rh);
						break;
					case 'aspectFill':
						if (cp >= op) {
							rh = rw / cp;
							// sY = Math.round((h - rh) / 2)
						} else {
							rw = rh * cp;
							sX = Math.round(((img.width || w) - rw) / 2)
						}
						// 百度小程序
						// #ifdef MP-BAIDU
						ctx.drawImage(src, x, y, w, h, sX, sY, rw, rh)
						// #endif
						// #ifndef MP-BAIDU
						ctx.drawImage(src, sX, sY, rw, rh, x, y, w, h)
						// #endif
						break;
					default:
						// scaleToFill
						ctx.drawImage(src, x, y, w, h);
				}
			}
			const _restore = () => {
				ctx.restore()
				this.setBorder(box, style)
				setTimeout(resolve, sleep)
			}
			const _drawImage = (img, isReset = false) => {
				if (this.use2dCanvas) {
					const Image = canvas.createImage()
					Image.onload = () => {
						img.src = Image
						_modeImage(img)
						_restore()
					}
					Image.onerror = async () => {
						if (isReset) {
							console.error(`createImage fail: ${JSON.stringify(img)}`)
						}
						resolve(true)
					}
					Image.src = img.src
				} else {
					_modeImage(img)
					_restore()
				}
			}
			if (typeof img === 'string') {
				const {
					path: src,
					width,
					height
				} = await getImageInfo(img, this.isH5PathToBase64)
				_drawImage({
					src,
					width,
					height
				})
			} else {
				try {
					_drawImage(img)
				} catch (e) {
					const {
						path: src,
						width,
						height
					} = await getImageInfo(img.originSrc, this.isH5PathToBase64, true)
					_drawImage({
						src,
						width,
						height
					}, true)
				}

			}
		})
	}
	drawText(text, box, style, rules) {
		const {
			ctx
		} = this
		let {
			width: w,
			height: h,
			offsetLeft: ol = 0,
			offsetTop: ot = 0,
		} = box
		let {
			color = '#000000',
				lineHeight = '1.4em',
				fontSize = 14,
				fontWeight,
				fontFamily,
				textStyle,
				textAlign = 'left',
				verticalAlign: va = 'top',
				backgroundColor: bg,
				maxLines,
				display,
				padding = {},
				borderRadius = 0,
				textDecoration: td
		} = style
		lineHeight = toPx(lineHeight, fontSize)
		if (!text) return
		ctx.save()
		this.setOpacity(style)
		let {
			x,
			y
		} = this.setTransform(box, style)
		ctx.setTextBaseline('middle')
		ctx.setFonts({
			fontFamily,
			fontSize,
			fontWeight,
			textStyle
		})
		ctx.setTextAlign(textAlign)
		ctx.translate(0, -(fontSize / 2));
		// 垂直布局
		y += fontSize
		if (bg) {
			this.setBackground(bg, w, h)
			this.roundRect(x, y - fontSize / 2, w, h, borderRadius, 1, 0)
		}
		y += ot
		this.setShadow(style)
		ctx.setFillStyle(color)
		let rulesObj = {};
		if (rules) {
			if (rules.word.length > 0) {
				for (let i = 0; i < rules.word.length; i++) {
					// let reg = new RegExp(rules.word[i], "g")
					// let result;
					// while(result=reg.exec(text)) {
					// 		rulesObj[result.index] = {
					// 			reset: true,
					// 			char: result[0]
					// 		};
					// }
					let startIndex = 0,
						index;
					while ((index = text.indexOf(rules.word[i], startIndex)) > -1) {
						rulesObj[index] = {
							reset: true
						};
						for (let j = 0; j < rules.word[i].length; j++) {
							rulesObj[index + j] = {
								reset: true
							};
						}
						startIndex = index + 1;
					}
				}
			}
		}
		// 水平布局
		switch (textAlign) {
			case 'left':
				break
			case 'center':
				x += 0.5 * w
				break
			case 'right':
				x += w
				break
			default:
				break
		}
		const textWidth = ctx.measureText(text).width
		const actualHeight = Math.ceil(textWidth / w) * lineHeight
		let paddingTop = Math.ceil((h - actualHeight) / 2)
		if (paddingTop < 0) paddingTop = 0

		// 绘线
		const _drawLine = (x, y, textWidth) => {
			let to = x
			switch (textAlign) {
				case 'left':
					x = x
					to += textWidth
					break
				case 'center':
					x = x - textWidth / 2
					to = x + textWidth
					break
				case 'right':
					to = x
					x = x - textWidth
					break
				default:
					break
			}

			if (td) {
				ctx.setLineWidth(fontSize / 13);
				ctx.beginPath();

				if (/\bunderline\b/.test(td)) {
					ctx.moveTo(x, y - fontSize * 0.5);
					ctx.lineTo(to, y - fontSize * 0.5);
				}

				if (/\boverline\b/.test(td)) {
					ctx.moveTo(x, y - fontSize * 1.5);
					ctx.lineTo(to, y - fontSize * 1.5);
				}
				if (/\bline-through\b/.test(td)) {
					ctx.moveTo(x, y - fontSize);
					ctx.lineTo(to, y - fontSize);
				}
				ctx.closePath();
				ctx.setStrokeStyle(color);
				ctx.stroke();
			}
		}
		const _reset = (text, x, y) => {
			const rs = Object.keys(rulesObj)
			for (let i = 0; i < rs.length; i++) {
				const item = rulesObj[rs[i]]
				ctx.save();
				ctx.setFillStyle(rules.color);
				if (item.char) {
					ctx.fillText(item.char, item.x, item.y)
				}
				ctx.restore();
			}
		}
		const _setText = (isReset, char) => {
			if (isReset) {
				const t1 = Math.round(ctx.measureText('\u0020').width)
				const t2 = Math.round(ctx.measureText('\u3000').width)
				const width = Math.round(ctx.measureText(char).width)
				let _char = ''
				let _num = 1
				if (width == t2) {
					_char = '\u3000'
					_num = 1
				} else {
					_char = '\u0020'
					_num = Math.ceil(width / t1)
				}
				return {
					char: new Array(_num).fill(_char).join(''),
					width
				}
			} else {
				return {
					char
				}
			}
		}
		const _setRulesObj = (text, index, x, y) => {
			rulesObj[index].x = x
			rulesObj[index].y = y
			rulesObj[index].char = text
		}
		const _setRules = (x, rs, text, textWidth, {
			startIndex = 0,
			endIndex
		}) => {
			let clonetext = text
			if (/·/.test(text)) {
				clonetext = clonetext.replace(/·/g, '.')
				textWidth = ctx.measureText(clonetext).width
			}
			let _text = text.split('')
			let _x = x
			for (let i = 0; i < rs.length; i++) {
				const index = rs[i]
				const key = index - startIndex
				const t = _text[key]
				if (t) {
					let {
						char,
						width
					} = _setText(rulesObj[index], t)
					_text[key] = char
					if (textAlign == 'center') {
						_x = x - 0.5 * (textWidth - width)
					}
					if (textAlign == 'right') {
						_x = x - textWidth + width
					}
					_setRulesObj(t, index, _x + ctx.measureText(clonetext.substring(0, key)).width, y +
						inlinePaddingTop)
				} else {
					continue
				}

			}
			return _text
		}
		const inlinePaddingTop = Math.ceil((lineHeight - fontSize) / 2) + 1
		// 不超过一行
		if (textWidth + ol <= w && !text.includes('\n')) {
			x = x + ol
			const rs = Object.keys(rulesObj)
			let _text = ''
			if (rs) {
				_text = _setRules(x, rs, text, textWidth, {})
				_reset()
			}
			ctx.fillText(_text.join(''), x, y + inlinePaddingTop)
			y += lineHeight
			_drawLine(x, y, textWidth)
			ctx.restore()
			this.setBorder(box, style)
			return
		}
		// 多行文本
		const chars = text.split('')
		const _y = y
		let _x = x
		// 逐行绘制
		let line = ''
		let lineIndex = 0
		let startIndex = 0
		for (let index = 0; index <= chars.length; index++) {
			let ch = chars[index] || ''
			const isLine = ch === '\n'
			const isRight = ch == '' // index == chars.length
			ch = isLine ? '' : ch;
			let textline = line + ch
			let textWidth = ctx.measureText(textline).width
			// 绘制行数大于最大行数，则直接跳出循环
			if (lineIndex >= maxLines) {
				break;
			}
			if (lineIndex == 0) {
				textWidth = textWidth + ol
				_x = x + ol
			} else {
				textWidth = textWidth
				_x = x
			}

			if (textWidth > w || isLine || isRight) {
				let endIndex = index
				lineIndex++
				line = isRight && textWidth <= w ? textline : line
				if (lineIndex === maxLines && textWidth > w) {
					while (ctx.measureText(`${line}...`).width > w) {
						if (line.length <= 1) {
							// 如果只有一个字符时，直接跳出循环
							break;
						}
						line = line.substring(0, line.length - 1);
					}
					line += '...'
				}
				const rs = Object.keys(rulesObj)
				let _text = ''
				if (rs) {
					_text = _setRules(x, rs, line, textWidth, {
						startIndex,
						endIndex
					})
					_reset()
				}
				ctx.fillText(_text.join(''), _x, y + inlinePaddingTop)
				y += lineHeight
				_drawLine(_x, y, textWidth)
				line = ch
				startIndex = endIndex + (isLine ? 1 : 0)
				if ((y + lineHeight) > (_y + h)) break
			} else {
				line = textline
			}
		}
		ctx.restore()
		this.setBorder(box, style)
	}
	async drawNode(element) {
		const {
			layoutBox: box,
			computedStyle: style,
			attributes: attr,
			name,
			rules,
			children,
			parent
		} = element
		if (!parent.hasOwnProperty('id')) {
			this.count = Math.max(element.count || 1, this.count)
		}
		const {
			src,
			text
		} = element.attributes
		if (name === 'view') {
			this.drawView(box, style)
		} else if (name === 'image' && src) {
			await this.drawImage(attr, box, style, false)
		} else if (name === 'text') {
			this.drawText(text, box, style, rules)
		} else if (name === 'qrcode') {
			if (QR?.api) {
				QR.api.draw(text, this, box, style)
			}
		}
		this.progress = (this.progress || 0) + 1
		if (!children) return
		const childs = Object.values ? Object.values(children) : Object.keys(children).map((key) => children[key]);
		for (const child of childs) {
			await this.drawNode(child)
		}
	}
	listen(type, callBack) {
		if(type == 'progressChange') {
			Object.defineProperty(this, 'progress', {
				configurable: true,
				set: (v) => {
					this._progress = v;
					callBack(v / this.count);
				},
				get: () => {
					return this._progress;
				}
			})
		}
	}
}
