/*
This replaces THREE's font, so that we can have one shape per character
*/

import {Shape, ShapePath} from 'three';
import {PolyDictionary} from '../../../types/GlobalTypes';

interface Glyph {
	_cachedOutline: any[];
	ha: number;
	o: string;
}

export interface FontData {
	resolution: number;
	boundingBox: {
		yMax: number;
		yMin: number;
	};
	underlineThickness: number;
	glyphs: PolyDictionary<Glyph>;
	familyName: string;
}

export interface FontGenerateShapesOptions {
	size?: number;
	isCCW?: boolean;
	// noHoles?: boolean;
}

export class Font {
	public readonly isFont = true;
	public readonly type = 'font';
	constructor(private data: FontData) {}

	generateShapes(text: string, options: FontGenerateShapesOptions = {}) {
		if (options.size == null) {
			options.size = 100;
		}
		if (options.isCCW == null) {
			options.isCCW = false;
		}
		// if (options.noHoles == null) {
		// 	options.noHoles = false;
		// }

		const allShapes: Array<Shape[]> = [];
		const allPaths = createPaths(text, options.size, this.data);

		for (const pathsForChar of allPaths) {
			const shapesForChar: Shape[] = [];
			for (const path of pathsForChar) {
				const shapes = path.toShapes(options.isCCW);
				shapesForChar.push(...shapes);
			}
			allShapes.push(shapesForChar);
		}

		return allShapes;
	}
}

function createPaths(text: string, size: number, data: FontData) {
	const chars = Array.from(text);
	const scale = size / data.resolution;
	const lineHeight = (data.boundingBox.yMax - data.boundingBox.yMin + data.underlineThickness) * scale;

	const allPaths: Array<ShapePath[]> = [];

	let offsetX = 0,
		offsetY = 0;

	for (let i = 0; i < chars.length; i++) {
		const char = chars[i];

		const pathsForChar: ShapePath[] = [];
		if (char === '\n') {
			offsetX = 0;
			offsetY -= lineHeight;
		} else {
			const ret = createPath(char, scale, offsetX, offsetY, data);
			if (ret) {
				offsetX += ret.offsetX;
				pathsForChar.push(ret.path);
			}
		}
		allPaths.push(pathsForChar);
	}

	return allPaths;
}

function createPath(char: string, scale: number, offsetX: number, offsetY: number, data: FontData) {
	const glyph = data.glyphs[char] || data.glyphs['?'];

	if (!glyph) {
		console.error('THREE.Font: character "' + char + '" does not exists in font family ' + data.familyName + '.');

		return;
	}

	const path = new ShapePath();

	let x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2;

	if (glyph.o) {
		const outline = glyph._cachedOutline || (glyph._cachedOutline = glyph.o.split(' '));

		for (let i = 0, l = outline.length; i < l; ) {
			const action = outline[i++];
			switch (action) {
				case 'm': // moveTo
					x = outline[i++] * scale + offsetX;
					y = outline[i++] * scale + offsetY;

					path.moveTo(x, y);

					break;

				case 'l': // lineTo
					x = outline[i++] * scale + offsetX;
					y = outline[i++] * scale + offsetY;

					path.lineTo(x, y);

					break;

				case 'q': // quadraticCurveTo
					cpx = outline[i++] * scale + offsetX;
					cpy = outline[i++] * scale + offsetY;
					cpx1 = outline[i++] * scale + offsetX;
					cpy1 = outline[i++] * scale + offsetY;

					path.quadraticCurveTo(cpx1, cpy1, cpx, cpy);

					break;

				case 'b': // bezierCurveTo
					cpx = outline[i++] * scale + offsetX;
					cpy = outline[i++] * scale + offsetY;
					cpx1 = outline[i++] * scale + offsetX;
					cpy1 = outline[i++] * scale + offsetY;
					cpx2 = outline[i++] * scale + offsetX;
					cpy2 = outline[i++] * scale + offsetY;

					path.bezierCurveTo(cpx1, cpy1, cpx2, cpy2, cpx, cpy);

					break;
			}
		}
	}
	return {offsetX: glyph.ha * scale, path: path};
}
