/**
 * Created by rockyl on 2018/11/6.
 */

import GraphicRenderer from "./GraphicRenderer";
import {TextStyle, EngineConfig, decorators, Texture, resource} from "scilla";

const {dirtyFieldDetector, dirtyFieldTrigger} = decorators;

export enum TextAlign{
	/**
	 * 左对齐
	 */
	LEFT = 'left',
	/**
	 * 居中对齐
	 */
	CENTER = 'center',
	/**
	 * 右对齐
	 */
	RIGHT = 'right',
}

export enum VerticalAlign{
	/**
	 * 顶部
	 */
	TOP = 'top',
	/**
	 * 中间
	 */
	MIDDLE = 'middle',
	/**
	 * 底部
	 */
	BOTTOM = 'bottom',
}

/**
 * 文本渲染组件
 */
export default class TextRenderer extends GraphicRenderer {
	/**
	 * 文本
	 */
	@dirtyFieldDetector
	text: string = '';

	/**
	 * 文本流
	 */
	@dirtyFieldTrigger
	textFlow: any = null;

	/**
	 * 文本对齐
	 */
	@dirtyFieldDetector
	textAlign: TextAlign = TextAlign.CENTER;

	/**
	 * 文本纵向对齐
	 */
	@dirtyFieldDetector
	verticalAlign: VerticalAlign = VerticalAlign.MIDDLE;

	/**
	 * 行间距
	 */
	@dirtyFieldDetector
	lineSpacing: number = 0;

	/**
	 * 字间距
	 */
	@dirtyFieldDetector
	letterSpacing: number = 0;

	/**
	 * 文图文本资源
	 */
	@dirtyFieldDetector
	fontRes: resource;

	protected _lineHeight: number = NaN;
	protected _bmpLineHeight: number = 0;
	protected _textWidth: number;
	protected _textHeight: number;

	/**
	 * 文本样式
	 */
	@dirtyFieldDetector
	textStyle: TextStyle = new TextStyle();

	@dirtyFieldDetector
	useCacheMode: boolean = true;

	protected _pureText: string;

	protected rows: any[];

	protected measureCache: any = {};

	protected onModify(value, key, oldValue) {
		super.onModify(value, key, oldValue);

		switch (key) {
			case 'textFlow':
				if(value){
					this.updateTextFlow();
				}
				break;
			case 'textStyle':
				value.onChange = this.makeDirty.bind(this);
				this.makeDirty();
				break;
		}
	}

	updateTextFlow(){
		let text = '';
		if (this.textFlow) {
			for (let item of this.textFlow) {
				text += item.text;
			}
		}

		this._pureText = text;
		this.makeDirty();
	}

	/**
	 * 行高
	 * 行高如果没设定就为fontSize的1.2倍，https://developer.mozilla.org/en-US/docs/Web/CSS/line-height#Values
	 */
	get lineHeight(): number {
		return isNaN(this._lineHeight) ? (
			this.isBmpMode ? this._bmpLineHeight : this.textStyle.fontSize * EngineConfig.lineHeightRatio
		) : this._lineHeight;
	}

	set lineHeight(value: number) {
		if (this._lineHeight != value) {
			this._lineHeight = value;
			this.dirty = true;
		}
	}

	/**
	 * 获取纯文本
	 */
	get pureText(): string {
		return this.textFlow ? this._pureText : this.text;
	}

	protected getRenderSize(): any {
		return {
			width: this._textWidth,
			height: this._textHeight,
		};
	}

	protected beforeDraw() {
		super.beforeDraw();

		if (!this.isBmpMode) {
			this.applyTextStyle();
		}
	}

	/**
	 * 应用文本样式
	 */
	protected applyTextStyle() {
		const {context, textStyle} = this;
		const {fontStyle, fontVariant, fontWeight, fontSize, fontFamily} = textStyle;

		context.font = `${fontStyle} ${fontVariant} ${fontWeight} ${fontSize}px ${fontFamily}`;
	}

	/**
	 * @inheritDoc
	 */
	protected fillAndStoke() {
		if (!this.pureText || this.pureText.length == 0) {
			return;
		}
		const {
			context, measureCache, lineHeight, lineSpacing, letterSpacing, rows, textAlign, verticalAlign, isBmpMode,
			transform: {width, height},
		} = this;

		//const expWidth = isNaN(width) ? boundsWidth : width;
		//const expHeight = isNaN(height) ? boundsHeight : height;

		const rowCount = rows.length;

		let x = 0;
		switch (textAlign) {
			case "left":
				x = 0;
				break;
			case "right":
				x = width;
				break;
			case "center":
			default:
				x = width / 2;
				break;
		}

		let y = 0;
		const drawHeight = rowCount * lineHeight + lineSpacing * (rowCount - 1);
		switch (verticalAlign) {
			case "top":
				y = 0;
				break;
			case "bottom":
				y = height - drawHeight;
				break;
			case "middle":
			default:
				y = (height - drawHeight) / 2;
				break;
		}

		y += lineHeight;

		context.textAlign = letterSpacing == 0 && !this.textFlow ? textAlign : 'left';
		context.textBaseline = 'bottom';

		let offY = 0;
		for (let i = 0; i < rowCount; i++) {
			const {text, lineWidth} = rows[i];

			if (letterSpacing == 0 && !isBmpMode && !this.textFlow) {
				this.drawText(i, text, x, y + offY);
			} else {
				let offX = 0;
				switch (textAlign) {
					case "left":
						offX = 0;
						break;
					case "right":
						offX = -lineWidth;
						break;
					case "center":
					default:
						offX = -lineWidth / 2;
						break;
				}

				for (let j = 0, lj = text.length; j < lj; j++) {
					const char = text[j];
					this.drawText(j, char, x + offX, y + offY);
					if (measureCache[char].width > 0) {
						offX += measureCache[char].width + letterSpacing;
					}
				}
			}
			offY += lineHeight + lineSpacing;
		}

		//console.log(this.cacheCanvas.toDataURL());
	}

	private getStyle(index){
		if(!this.textFlow){
			return null;
		}

		let targetItem;
		let count = 0;
		for(let item of this.textFlow){
			count += item.text.length;
			if(index < count){
				targetItem = item;
				break;
			}
		}

		return targetItem.style;
	}

	protected drawText(index, text, x, y) {
		const {context, borderWidth, isBmpMode, fontRes} = this;

		if (isBmpMode) {
			const texture: Texture = fontRes.getTexture(text);
			if (!texture) {
				return;
			}
			const {bounds: {x: textureX, y: textureY, width: textureWidth, height: textureHeight}} = texture;
			context.drawImage(texture.img, textureX, textureY, textureWidth, textureHeight, x, y, textureWidth, textureHeight);
		} else {
			let style = this.getStyle(index);
			if(style){
				if(style.hasOwnProperty('textColor')){
					context.fillStyle = style.textColor;
				}
			}else{
				context.fillStyle = this.fillColor;
			}
			context.fillText(text, x, y + 1);
			if (borderWidth > 0) {
				context.strokeText(text, x, y + 1);
			}
		}
	}

	protected get isBmpMode() {
		return !!this.fontRes;
	}

	protected measureText(text): any {
		let result;

		if (this.measureCache.hasOwnProperty(text)) {
			result = this.measureCache[text];
		} else {
			const {context, letterSpacing, isBmpMode, fontRes, lineHeight} = this;

			if (isBmpMode) {
				if (text.length == 1) {
					const texture: Texture = fontRes.getTexture(text);
					result = {
						width: texture ? texture.width : 0,
						height: texture ? texture.height : 0,
					}
				} else {
					let totalWidth = 0, totalHeight = 0;
					for (let char of text) {
						const measureResult = this.measureText(char);
						totalWidth += measureResult.width;
						if (measureResult.height > totalHeight) {
							totalHeight = measureResult.height;
						}
					}
					result = {
						width: totalWidth,
						height: totalHeight,
					}
				}
			} else {
				result = {
					width: context.measureText(text).width,
					height: lineHeight,
				};
			}

			result.width += letterSpacing * (text.length - 1);

			//如果是多个字且有字间距，就测量全部单个字母
			if (text.length == 1) {
				this.measureCache[text] = result;
			}
			if (letterSpacing != 0 && text.length > 1 || this.textFlow) {
				for (let char of text) {
					this.measureCache[char] = this.measureText(char);
				}
			}
		}

		return result;
	}

	protected splitText() {
		const {pureText: text, letterSpacing, lineSpacing, lineHeight, isBmpMode, transform: {explicitWidth, explicitHeight}} = this;

		this.measureCache = {};

		let textWidth = 0, textHeight = 0, maxHeight = 0;
		let rows = [], measureResult;

		if (text && text.length > 0) {
			if (isNaN(explicitWidth)) {
				const lines = text.split('\n');
				for (const line of lines) {
					measureResult = this.measureText(line);
					const mw = measureResult.width;
					if (mw > textWidth) {
						textWidth = mw;
					}
					if (isBmpMode) {
						const mh = measureResult.height;
						if (mh > maxHeight) {
							maxHeight = mh;
						}
					}
					rows.push({
						text: line,
						lineWidth: mw,
					});
				}
				if (!isBmpMode) {
					maxHeight = lineHeight;
				}
				this._bmpLineHeight = maxHeight;
			} else {
				const chars = text.split('');
				let lineWidth = 0, charWidth = 0, index = 0;
				let line = '';
				for (let i = 0, li = chars.length; i < li; i++) {
					const char = chars[i];
					if (char == '\n') {
						rows.push({
							text: line,
							lineWidth,
						});
						line = '';
						lineWidth = 0;
						index = 0;
					} else {
						measureResult = this.measureText(char);
						if (measureResult.width == 0) {
							continue;
						}
						charWidth = measureResult.width;
						const mh = measureResult.height;
						if (mh > maxHeight) {
							maxHeight = mh;
						}
						if (index > 0 && lineWidth + charWidth + (index == 0 ? 0 : letterSpacing) > explicitWidth) {
							rows.push({
								text: line,
								lineWidth,
							});
							line = '';
							lineWidth = 0;
							index = 0;
						}
						line += char;
						lineWidth += charWidth + (index == 0 ? 0 : letterSpacing);
						index++;
					}
				}
				this._bmpLineHeight = maxHeight;
				rows.push({
					text: line,
					lineWidth,
				});
				textWidth = explicitWidth;
			}

			textHeight = isNaN(explicitHeight) ? (
				maxHeight * rows.length + lineSpacing * (rows.length - 1)
			) : explicitHeight;
		} else {
			textWidth = isNaN(explicitWidth) ? 0 : explicitWidth;
			textHeight = isNaN(explicitHeight) ? 0 : explicitHeight;
		}

		this.rows = rows;
		this._textWidth = textWidth;
		this._textHeight = textHeight;

		return {
			rows,
			textWidth,
			textHeight,
		};
	}

	/**
	 * @inheritDoc
	 */
	measureBounds() {
		if (!this.dirty) {
			return;
		}

		if (!this.isBmpMode) {
			this.applyTextStyle();
		}

		this.splitText();

		super.measureBounds();
	}
}
