import {
	DocXImageType,
	DocXTableCellType,
	FileType,
	TableType,
	WorkStepType,
} from "qrc:/js/lib/generated/enum";
import {
	computeCameraDirection,
	computeWcsDimensions,
	cross,
	multiply3,
	normalize,
	scalarMult,
	sum,
} from "qrc:/js/lib/geometry_utils";
import {
	computeNodeMass,
	getArticleName,
} from "qrc:/js/lib/graph_utils";
import {
	assert,
	bbDimensionX,
	bbDimensionY,
	defaultPngResolution,
	getKeysOfObject,
	renderDefaultPng,
} from "qrc:/js/lib/utils";
import {
	boxFromResolution,
	defaultSvgResolution,
	sceneForVertex,
} from "qrc:/js/lib/scene_utils";
import {
	getTable,
} from "qrc:/js/lib/table_utils";
import {
	accessAllUserData,
	collectNodeUserDataEntries,
	isCompatibleToNodeUserDataEntry,
	nodeUserDatum,
} from "qrc:/js/lib/userdata_utils";

import {
	docXImageCell,
	DocXImageContent,
	kgToString,
	mmToString,
} from "./export_utils";

interface General {
	articleName: string;
	materials: string;
	mass: string,
	length: string;
	width: string;
	height: string;
	multiplicity: string;
	comment: string;
	png: DocXImageContent | undefined;
}

interface Bend {
	number: string;
	angle: string;
	upperDieGroup: string;
	lowerDieGroup: string;
	constructedRadius: string;
	resultingRadius: string;
	deduction: string;
}

interface Unfolding {
	svg: DocXImageContent;
	measurements: string;
	bends: Bend[];
}

interface Drawing {
	svg: DocXImageContent;
	png: DocXImageContent | undefined;
}

interface BendDrawingsData {
	general: General;
	unfolding: Unfolding;
	drawings: Drawing[];
}

function createGeneral(vertex: Vertex): General {
	const dimensions = (() => {
		const assembly = wsi4.node.assembly(vertex) ?? wsi4.node.inputAssembly(vertex);
		if (assembly === undefined) {
			return undefined;
		} else {
			return computeWcsDimensions(assembly);
		}
	})();

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

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

	const assembly = wsi4.node.assembly(vertex);

	return {
		articleName: getArticleName(vertex),
		materials: materials,
		mass: mass === undefined ? "N/A" : kgToString(mass),
		length: dimensions === undefined ? "N/A" : mmToString(dimensions.x, 2),
		width: dimensions === undefined ? "N/A" : mmToString(dimensions.y, 2),
		height: dimensions === undefined ? "N/A" : mmToString(dimensions.z, 2),
		multiplicity: multiplicity.toFixed(0),
		comment: (() => {
			if (!isCompatibleToNodeUserDataEntry("comment", vertex)) {
				return "";
			}

			const comment = nodeUserDatum("comment", vertex);
			if (comment === undefined || comment.length === 0) {
				return "";
			}
			return comment;
		})(),
		png: assembly === undefined ? undefined : {
			imageType: DocXImageType.png,
			content: renderDefaultPng(assembly, defaultPngResolution()),
		},
	};
}

function radToDeg(angle: number): number {
	return angle * 180 / Math.PI;
}

function createBends(vertex: Vertex): Bend[] {
	const bendLineData = wsi4.node.computeBendLineData(vertex);
	if (bendLineData === undefined) {
		return [];
	}
	const dieChoiceMap = wsi4.node.dieChoiceMap(vertex);
	if (dieChoiceMap === undefined) {
		return [];
	}
	const upperDieGroupTable = getTable(TableType.upperDieGroup);
	const lookUpUpperDieGroup = (bendDescriptor: number) => {
		const entry = dieChoiceMap.find(entry => entry.bendDescriptor === bendDescriptor);
		assert(entry !== undefined, "Expecting valid die choice map entry");
		const id = entry.bendDieChoice.upperDieGroupId;
		if (id === undefined) {
			return "N/A";
		}
		const row = upperDieGroupTable.find(r => r.identifier === id);
		return row === undefined ? "N/A" : row.name;
	};
	const lowerDieGroupTable = getTable(TableType.lowerDieGroup);
	const lookUpLowerDieGroup = (bendDescriptor: number) => {
		const entry = dieChoiceMap.find(entry => entry.bendDescriptor === bendDescriptor);
		assert(entry !== undefined, "Expecting valid die choice map entry");
		const id = entry.bendDieChoice.lowerDieGroupId;
		if (id === undefined) {
			return "N/A";
		}
		const row = lowerDieGroupTable.find(r => r.identifier === id);
		return row === undefined ? "N/A" : row.name;
	};
	return bendLineData
		.sort((lhs, rhs) => lhs.bendDescriptor - rhs.bendDescriptor)
		.map(bendLineData => {
			const index = bendLineData.bendDescriptor;
			const mapEntry = dieChoiceMap.find(entry => entry.bendDescriptor === bendLineData.bendDescriptor);
			assert(mapEntry !== undefined, "Expecting valid die choice map entry");
			return {
				number: index.toFixed(0),
				upperDieGroup: lookUpUpperDieGroup(index),
				lowerDieGroup: lookUpLowerDieGroup(index),
				angle: radToDeg(bendLineData.bendAngle).toFixed(1),
				constructedRadius: bendLineData.constructedInnerRadius.toFixed(1),
				resultingRadius: mapEntry.bendDieChoice.baseClass.innerRadius.toFixed(1),
				deduction: mapEntry.bendDieChoice.sharpDeduction.toFixed(3),
			};
		});
}

function createUnfolding(vertex: Vertex): Unfolding {

	const twoDimRep = wsi4.node.twoDimRep(vertex);
	assert(twoDimRep !== undefined, "export_bend_drawing_docx::createUnfolding(): Expecting twoDimRep");

	const bbox = wsi4.cam.util.boundingBox2(twoDimRep);
	const x = bbDimensionX(bbox);
	const y = bbDimensionY(bbox);
	const z = wsi4.node.sheetThickness(vertex);
	assert(z !== undefined, "Expecting valid sheet thickness");

	const svg = wsi4.geo.util.renderScene(sceneForVertex(
		vertex,
		{
			cuttingContours: true,
			engravings: true,
			bendLineShowLabels: true,
			bendLineShowAffectedSegments: true,
			tubeCuttingShowTubeContours: false,
			fontSize: 42,
		},
	), FileType.png, {resolution: defaultSvgResolution()});

	return {
		svg: {
			content: svg,
			imageType: DocXImageType.png,
		},
		measurements: `${wsi4.util.translate("Dimensions")} [mm]: ${x.toFixed(1)} x ${y.toFixed(1)} x ${z.toFixed(1)}`,
		bends: createBends(vertex),
	};
}

function createDrawing(vertex: Vertex, measurementScene: MeasurementScene, resolution: Resolution): Drawing {
	const svg = wsi4.geo.util.renderScene(measurementScene.scene, FileType.png, {
		resolution: resolution,
		viewPort: boxFromResolution(resolution),
	});

	const camera = measurementScene.camera;
	// Update camera to look at assembly from upper left corner
	{
		const dir = computeCameraDirection(camera);
		const cr = normalize(cross(dir, camera.up));
		const target = normalize(sum(cr, sum(scalarMult(0.1, normalize(dir)), scalarMult(0.4, normalize(camera.up)))));

		const rotationMatrix = wsi4.geo.util.rotationMatrix(cr, target);
		const newDir = multiply3(rotationMatrix, dir);
		camera.eye = sum(camera.center, scalarMult(-1, newDir));
	}

	const assembly = wsi4.node.assembly(vertex);
	return {
		svg: {
			content: svg,
			imageType: DocXImageType.png,
		},
		png: assembly === undefined ? undefined : {
			imageType: DocXImageType.png,
			content: wsi4.geo.assembly.renderIntoPng(assembly, camera, defaultPngResolution()),
		},
	};
}

function createDrawings(vertex: Vertex): Drawing[] {
	const resolution = defaultSvgResolution();
	return wsi4.node.bendMeasurementScenes(vertex, 48, resolution)
		.reduce((acc: Drawing[], measurementScene) => [
			...acc,
			createDrawing(vertex, measurementScene, resolution),
		], []);
}

export function createBendDrawingsData(vertex: Vertex): BendDrawingsData {
	assert(wsi4.node.workStepType(vertex) === WorkStepType.sheetBending);
	return {
		general: createGeneral(vertex),
		unfolding: createUnfolding(vertex),
		drawings: createDrawings(vertex),
	};
}

const generalPlaceholder: {[index in keyof General]: string} = {
	articleName: "articlename",
	materials: "materials",
	mass: "mass",
	length: "length",
	height: "height",
	width: "width",
	multiplicity: "quantity",
	comment: "comment",
	png: "articlepng",
};

const generalTranslationKey: {[index in keyof General]: string} = {
	articleName: "Name",
	materials: "materials",
	mass: "Mass",
	length: "Length",
	width: "Width",
	height: "Height",
	multiplicity: "Multiplicity",
	comment: "Comment",
	png: "", // not used
};

function createGeneralTables(general: General): DocXTableCell[][][] {
	const tables: DocXTableCell[][][] = [];
	for (const key of getKeysOfObject(general)) {
		if (key === "png") {
			tables.push([ [ docXImageCell("$" + generalPlaceholder[key] + "$", general[key]) ] ]);
			continue;
		}
		tables.push([
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$" + generalPlaceholder[key] + "text$",
						text: wsi4.util.translate(generalTranslationKey[key]),
					},
				},
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$" + generalPlaceholder[key] + "$",
						text: general[key],
					},
				},
			],
		]);
	}
	return tables;
}

function tolerancesTexts(): DocXTableCell[][][] {
	const translationPlaceholderMap = {
		tolerancesheader: "tolerances_in_mm_for_nominal_size_range_in_mm",
		toleranceclasstext: "tolerance_class",
		toleranceclassfine: "fine",
		toleranceclassmedium: "medium",
		toleranceclassrough: "rough",
		toleranceclassveryrough: "very_rough",
	};
	const tables: DocXTableCell[][][] = [];
	for (const key of getKeysOfObject(translationPlaceholderMap)) {
		tables.push([
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$" + key + "$",
						text: wsi4.util.translate(translationPlaceholderMap[key]),
					},
				},
			],
		]);
	}
	return tables;
}

function unfoldingTexts(): DocXTableCell[][][] {
	const translationPlaceholderMap = {
		upperdiegroup: "upper_die_group",
		lowerdiegroup: "lower_die_group",
		constructedradius: "radius_constructed",
		resultingradius: "radius_resulting",
		benddeduction: "bend_deduction",
	};
	const tables: DocXTableCell[][][] = [];
	for (const key of getKeysOfObject(translationPlaceholderMap)) {
		tables.push([
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$" + key + "text$",
						text: wsi4.util.translate(translationPlaceholderMap[key]),
					},
				},
			],
		]);
	}
	return tables;
}

function bendsTable(bends: Bend[]): DocXTableCell[][] {
	const placeholder: {[index in keyof Bend]: string} = {
		number: "bendnumber",
		angle: "bendangle",
		upperDieGroup: "upperdiegroup",
		lowerDieGroup: "lowerdiegroup",
		constructedRadius: "constructedradius",
		resultingRadius: "resultingradius",
		deduction: "benddeduction",
	};
	return bends.map(bend => getKeysOfObject(bend).map(key => ({
		type: DocXTableCellType.text,
		content: {
			placeholder: "$" + placeholder[key] + "$",
			text: bend[key],
		},
	})));
}

function createUnfoldingTables(unfolding: Unfolding): DocXTableCell[][][] {
	return [
		[
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$unfolding$",
						text: wsi4.util.translate("unfolding"),
					},
				},
			],
		],
		[ [ docXImageCell("$unfoldingsvg$", unfolding.svg) ] ],
		[
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$measurements$",
						text: unfolding.measurements,
					},
				},
			],
		],
		...unfoldingTexts(),
		bendsTable(unfolding.bends),
	];
}

function createDrawingTables(drawing: Drawing, index: number): DocXTableCell[][][] {
	return [
		[
			[
				{
					type: DocXTableCellType.text,
					content: {
						placeholder: "$drawingnumber$",
						text: (index + 1).toFixed(0),
					},
				},
			],
		],
		[ [ docXImageCell("$drawingsvg$", drawing.svg) ] ],
		[ [ docXImageCell("$drawingpng$", drawing.png) ] ],
	];
}

function createDrawingsTables(drawings: Drawing[]): DocXTableCell[][] {
	return drawings.map((drawing, index) => [
		{
			type: DocXTableCellType.tables,
			content: {
				tables: createDrawingTables(drawing, index),
			},
		},
	]);
}

export function createBendDrawingsDocx(templ: ArrayBuffer, bendDrawingsData: BendDrawingsData): ArrayBuffer | undefined {
	const d = wsi4.documentCreator.generateDocX(templ,
		[
			[
				[
					{
						type: DocXTableCellType.text,
						content: {
							placeholder: "$benddrawing$",
							text: wsi4.util.translate("bend_drawing"),
						},
					},
				],
			],
			...createGeneralTables(bendDrawingsData.general),
			...tolerancesTexts(),
			...createUnfoldingTables(bendDrawingsData.unfolding),
			createDrawingsTables(bendDrawingsData.drawings),
		]);
	return d.byteLength === 0 ? undefined : d;
}
