import {
	CamCommandType,
	DocumentAlignment,
	DocumentItemType,
	TableType,
	WorkStepType,
} from "qrc:/js/lib/generated/enum";
import {
	computeCameraDirection,
	rotate,
	scalarMult,
	sum,
} from "qrc:/js/lib/geometry_utils";
import {
	computeNodeMass,
	findVertexForAssemblyPath,
	getArticleName,
} from "qrc:/js/lib/graph_utils";
import {
	boundingBox3d,
	computeCommandsFaceArea,
} from "qrc:/js/lib/node_utils";
import {
	getTable,
} from "qrc:/js/lib/table_utils";
import {
	assert,
	bbDimensionX,
	bbDimensionY,
	bbDimensionZ,
	computeEntityFaceArea,
} from "qrc:/js/lib/utils";
import {
	accessAllUserData,
	collectNodeUserDataEntries,
} from "qrc:/js/lib/userdata_utils";

import {
	createCommentOnDemand,
	createHeading,
	createPageBreak,
	createPngImageItem,
	createSpacerItem,
	createTitle,
	kgToString,
	mmToString,
	squareMmToString,
} from "./export_utils";

function createPartInfoSection(componentVertex: Vertex, area: number): DocumentItem[][] {
	const box = boundingBox3d(componentVertex);
	const dimensions: [number, number, number] = [
		box !== undefined ? bbDimensionX(box) : 0,
		box !== undefined ? bbDimensionY(box) : 0,
		box !== undefined ? bbDimensionZ(box) : 0,
	];
	// Sorting in descending order
	dimensions.sort(function(lhs, rhs) {
		return lhs > rhs ? -1 : lhs < rhs ? 1 : 0;
	});

	// [kg]
	const mass = computeNodeMass(componentVertex);

	const materials = collectNodeUserDataEntries("sheetMaterial", componentVertex, accessAllUserData)
		.filter((lhs, index, self) => index === self.findIndex(rhs => lhs.identifier === rhs.identifier))
		.map(material => getTable(TableType.sheetMaterial)
			.find(row => row.identifier === material.identifier))
		.filter((material): material is SheetMaterial => material !== undefined)
		.map(material => material.name);

	const tableItem: DocumentItem = {
		type: DocumentItemType.table,
		content: {
			width: 12,
			alignment: DocumentAlignment.left,
			columnWidths: [
				22.5,
				22.5,
				10,
				22.5,
				22.5,
			],
			columnHeaders: [],
			rows: [
				[
					{
						text: wsi4.util.translate("area") + " / " + wsi4.util.translate("piece"),
						alignment: DocumentAlignment.left,
					},
					{
						text: squareMmToString(area, 3),
						alignment: DocumentAlignment.right,
					},
					{
						text: "",
						alignment: DocumentAlignment.left,
					},
					{
						text: wsi4.util.translate("Length"),
						alignment: DocumentAlignment.left,
					},
					{
						text: mmToString(dimensions[0], 2),
						alignment: DocumentAlignment.right,
					},
				],
				[
					{
						text: wsi4.util.translate("Material"),
						alignment: DocumentAlignment.left,
					},
					{
						text: materials.length === 0 ? wsi4.util.translate("Unknown") : materials.join(", "),
						alignment: DocumentAlignment.right,
					},
					{
						text: "",
						alignment: DocumentAlignment.left,
					},
					{
						text: wsi4.util.translate("Width"),
						alignment: DocumentAlignment.left,
					},
					{
						text: mmToString(dimensions[1], 2),
						alignment: DocumentAlignment.right,
					},
				],
				[
					{
						text: wsi4.util.translate("Mass"),
						alignment: DocumentAlignment.left,
					},
					{
						text: mass === undefined ? wsi4.util.translate("Unknown") : kgToString(mass),
						alignment: DocumentAlignment.right,
					},
					{
						text: "",
						alignment: DocumentAlignment.left,
					},
					{
						text: wsi4.util.translate("Height"),
						alignment: DocumentAlignment.left,
					},
					{
						text: mmToString(dimensions[2], 2),
						alignment: DocumentAlignment.right,
					},
				],
			],
		},
	};
	return [ [ tableItem ] ];
}

function createOverviewSection(vertex: Vertex): DocumentItem[][] {
	const box = boundingBox3d(vertex);
	const dimensions: [number, number, number] = [
		box !== undefined ? bbDimensionX(box) : 0,
		box !== undefined ? bbDimensionY(box) : 0,
		box !== undefined ? bbDimensionZ(box) : 0,
	];
	// Sorting in descending order
	dimensions.sort(function(lhs, rhs) {
		return lhs > rhs ? -1 : lhs < rhs ? 1 : 0;
	});

	const multiplicity = wsi4.node.multiplicity(vertex);
	// [kg]
	const mass = computeNodeMass(vertex);

	const materials = collectNodeUserDataEntries("sheetMaterial", vertex, accessAllUserData)
		.filter((lhs, index, self) => index === self.findIndex(rhs => lhs.identifier === rhs.identifier))
		.map(material => getTable(TableType.sheetMaterial)
			.find(row => row.identifier === material.identifier))
		.filter((material): material is SheetMaterial => material !== undefined)
		.map(material => material.name);

	// mm²
	const areaPerPiece = computeCommandsFaceArea(vertex);

	const tableItem: DocumentItem = {
		type: DocumentItemType.table,
		content: {
			width: 12,
			alignment: DocumentAlignment.left,
			columnWidths: [
				22.5,
				22.5,
				10,
				22.5,
				22.5,
			],
			columnHeaders: [],
			rows: [
				[
					{
						text: wsi4.util.translate("Multiplicity"),
						alignment: DocumentAlignment.left,
					},
					{
						text: String(multiplicity),
						alignment: DocumentAlignment.right,
					},
					{
						text: "",
						alignment: DocumentAlignment.left,
					},
					{
						text: wsi4.util.translate("Length"),
						alignment: DocumentAlignment.left,
					},
					{
						text: mmToString(dimensions[0], 2),
						alignment: DocumentAlignment.right,
					},
				],
				[
					{
						text: wsi4.util.translate("materials"),
						alignment: DocumentAlignment.left,
					},
					{
						text: materials.length === 0 ? wsi4.util.translate("Unknown") : materials.join(", "),
						alignment: DocumentAlignment.right,
					},
					{
						text: "",
						alignment: DocumentAlignment.left,
					},
					{
						text: wsi4.util.translate("Width"),
						alignment: DocumentAlignment.left,
					},
					{
						text: mmToString(dimensions[1], 2),
						alignment: DocumentAlignment.right,
					},
				],
				[
					{
						text: wsi4.util.translate("Mass"),
						alignment: DocumentAlignment.left,
					},
					{
						text: mass === undefined ? wsi4.util.translate("Unknown") : kgToString(mass),
						alignment: DocumentAlignment.right,
					},
					{
						text: "",
						alignment: DocumentAlignment.left,
					},
					{
						text: wsi4.util.translate("Height"),
						alignment: DocumentAlignment.left,
					},
					{
						text: mmToString(dimensions[2], 2),
						alignment: DocumentAlignment.right,
					},
				],
				[
					{
						text: wsi4.util.translate("area"),
						alignment: DocumentAlignment.left,
					},
					{
						text: squareMmToString(areaPerPiece * multiplicity, 3),
						alignment: DocumentAlignment.right,
					},
					{
						text: "",
						alignment: DocumentAlignment.left,
					},
					{
						text: wsi4.util.translate("area") + " / " + wsi4.util.translate("piece"),
						alignment: DocumentAlignment.left,
					},
					{
						text: squareMmToString(areaPerPiece, 3),
						alignment: DocumentAlignment.right,
					},
				],
			],
		},
	};

	return [
		[ tableItem ],
		...createCommentOnDemand(vertex),
	];
}

function createDetailSection(assembly: Assembly, vertex: Vertex, area: number, index: number): DocumentItem[][] {
	// Note: Number(Math.PI) required due to eslint
	type Point = {
		x: number,
		y: number,
		z: number,
	};
	const rotsCol0: [Point, Point, Point, Point] = [
		{
			x: 0,
			y: 0,
			z: 0.0,
		},
		{
			x: 0,
			y: 0,
			z: 0.5 * Number(Math.PI),
		},
		{
			x: 0,
			y: 0,
			z: 1.0 * Number(Math.PI),
		},
		{
			x: 0,
			y: 0,
			z: 1.5 * Number(Math.PI),
		},
	];
	const rotsCol1: [Point, Point, Point, Point] = [
		{
			x: 0,
			y: Math.PI,
			z: 0.0,
		},
		{
			x: 0,
			y: Math.PI,
			z: 1.5 * Number(Math.PI),
		},
		{
			x: 0,
			y: Math.PI,
			z: 1.0 * Number(Math.PI),
		},
		{
			x: 0,
			y: Math.PI,
			z: 0.5 * Number(Math.PI),
		},
	];
	type Rotations = {x : number, y : number, z : number};
	const defaultCamera = wsi4.geo.assembly.computeDefaultCamera(assembly);
	const computeCamera = (rots: Rotations): Camera3 => {
		const dir = rotate(computeCameraDirection(defaultCamera), rots.x, rots.y, rots.z);
		const up = rotate(defaultCamera.up, rots.x, rots.y, rots.z);
		return {
			eye: sum(defaultCamera.center, scalarMult(-1, dir)),
			center: defaultCamera.center,
			up: up,
		};
	};
	const createItem = (rot: Rotations, row: number, col: number) => {
		const uuid = "assembly_powder_coating_" + index.toString() + "_" + row.toString() + "_" + col.toString();
		return createPngImageItem(assembly, 4, DocumentAlignment.center, computeCamera(rot), uuid);
	};
	return [
		[ ...createHeading(getArticleName(vertex), 4) ],
		...createPartInfoSection(vertex, area),
		[
			createItem(rotsCol0[0], 0, 0),
			createSpacerItem(2),
			createItem(rotsCol1[0], 0, 1),
		],
		[
			createItem(rotsCol0[1], 1, 0),
			createSpacerItem(2),
			createItem(rotsCol1[1], 1, 1),
		],
		[
			createItem(rotsCol0[2], 2, 0),
			createSpacerItem(2),
			createItem(rotsCol1[2], 2, 1),
		],
		[
			createItem(rotsCol0[3], 3, 0),
			createSpacerItem(2),
			createItem(rotsCol1[3], 3, 1),
		],
	];
}

function findComponentVertex(inputVertex: Vertex, assemblyPath: AssemblyPath): Vertex {
	const vertex = (() => {
		const article = wsi4.graph.article(inputVertex);
		// Reverse as the *rightmost* vertex ist required when searching the respective component
		article.reverse();
		const v = article.find(v => wsi4.node.workStepType(v) === WorkStepType.joining || wsi4.node.workStepType(v) === WorkStepType.sheetBending || wsi4.node.workStepType(v) === WorkStepType.sheetCutting);
		return v === undefined ? inputVertex : v;
	})();
	const result = findVertexForAssemblyPath(vertex, assemblyPath);
	assert(result !== undefined, "Expecting valid component vertex");
	return result;
}

function extractSelectedAssemblies(inputVertex: Vertex): [ Assembly, Vertex, number ][] {
	const rootAssembly = wsi4.node.assembly(inputVertex);
	assert(rootAssembly !== undefined, "Expecting root assembly");
	assert(wsi4.node.workStepType(inputVertex) === WorkStepType.transform, "Expecting WST transform");
	return Array.from(wsi4.node.commands(inputVertex)
		.filter(command => command.type === CamCommandType.setColor)
		.reduce((acc: GeometryEntity[], command) => acc.concat(command.content.entities), [])
		.reduce(
			(acc, entity) => {
				const assembly = wsi4.geo.assembly.resolvePath(rootAssembly, entity.assemblyPath);
				assert(assembly !== undefined, "Expecting valid assembly path");
				const vertex = findComponentVertex(inputVertex, entity.assemblyPath);
				const key = wsi4.util.toKey(assembly);
				if (!acc.has(key)) {
					acc.set(key, [
						assembly,
						vertex,
						0,
					]);
				}
				acc.get(key)![2] += computeEntityFaceArea(rootAssembly, entity);
				return acc;
			},
			new Map<string, [ Assembly, Vertex, number ]>())
		.values());
}

function createDetailSections(vertex: Vertex): DocumentItem[][] {
	const assembly = wsi4.node.assembly(vertex);
	assert(assembly !== undefined, "Expecting valid assembly");
	return extractSelectedAssemblies(vertex)
		.reduce((acc, [
			asm,
			v,
			area,
		], index) => {
			acc.push([ ...createPageBreak() ]);
			acc.push(...createDetailSection(asm, v, area, index));
			return acc;
		}, createDetailSection(assembly, vertex, computeCommandsFaceArea(vertex), -1));
}

export function createCoatingDetails(vertex: Vertex): Array<Array<DocumentItem>> {
	return [
		...createTitle(wsi4.util.translate(wsi4.node.processType(vertex)), 3),
		[ createSpacerItem(12) ],
		...createOverviewSection(vertex),
		[ createSpacerItem(12) ],
		...createDetailSections(vertex),
	];
}
