import {
	TableType,
	WorkStepType,
} from "qrc:/js/lib/generated/enum";
import {
	changeArticleUserData,
	changeFlipSideUsage,
	changeMultipleMultiplicities,
	changeNodesProcessId,
	changeNodesUserData,
	changeNodeThickness,
} from "qrc:/js/lib/graph_manipulator";
import {
	computeOrderedProcesses,
} from "qrc:/js/lib/process";
import {
	assert,
	rootIdToVertex,
} from "qrc:/js/lib/utils";
import {
	createUpdatedNodeUserData,
	applyTubeToUserData,
} from "qrc:/js/lib/userdata_utils";
import {
	getTable,
} from "qrc:/js/lib/table_utils";
import {
	ChangeNodeProcessAndUserDataArg,
	changeNodesProcessAndUserData,
} from "qrc:/js/lib/graph_manipulator_utils";

import {
	createUpdatedArticleUserData,
} from "qrc:/js/lib/article_userdata_config";
import {
	RootIdWithGraphNodeWritableAttributes,
	TubeCuttingEditParams,
} from "./cli_shop_interface";

function applyBottomSideUsageChange(data: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	const d = data.filter(da => da.attributes.flipSide !== undefined);
	if (isFilterEmptyOrNodeMissing(d)) {
		return true;
	}
	const v = rootIdToVertex(d[0]!.rootId);
	const flipSide = d[0]!.attributes.flipSide;
	if (flipSide === undefined) {
		return wsi4.throwError("Wrong type");
	}
	if (!changeFlipSideUsage(v, flipSide)) {
		wsi4.util.error("Error changing bottom side usage");
		return false;
	}
	return true;
}

function applyTestReportChanges(data: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	const userDataData = data.filter(d => d.attributes.testReportRequired !== undefined)
		.map((d: RootIdWithGraphNodeWritableAttributes) => {
			if (d.attributes.testReportRequired === undefined) {
				return wsi4.throwError("Filter went wrong");
			}
			return {
				vertex: rootIdToVertex(d.rootId),
				userData: createUpdatedNodeUserData("testReportRequired", rootIdToVertex(d.rootId), d.attributes.testReportRequired),
			};
		});
	if (userDataData.length > 0 && !changeNodesUserData(userDataData)) {
		wsi4.util.error("Error changing userdata");
		return false;
	}
	return true;
}

function applyCuttingGasChanges(data: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	const userDataData = data.filter(d => d.attributes.cuttingGas !== undefined)
		.map((d: RootIdWithGraphNodeWritableAttributes) => {
			if (d.attributes.cuttingGas === undefined) {
				return wsi4.throwError("Filter went wrong");
			}
			return {
				vertex: rootIdToVertex(d.rootId),
				userData: createUpdatedNodeUserData("laserSheetCuttingGasId", rootIdToVertex(d.rootId), d.attributes.cuttingGas),
			};
		});
	if (userDataData.length > 0 && !changeNodesUserData(userDataData)) {
		wsi4.util.error("Error changing userdata");
		return false;
	}
	return true;
}

function applyFixedRotationChanges(data: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	const userDataData = data.filter(d => d.attributes.fixedRotations !== undefined)
		.map((d: RootIdWithGraphNodeWritableAttributes) => {
			if (d.attributes.fixedRotations === undefined) {
				return wsi4.throwError("Filter went wrong");
			}
			return {
				vertex: rootIdToVertex(d.rootId),
				userData: createUpdatedNodeUserData("fixedRotations", rootIdToVertex(d.rootId), d.attributes.fixedRotations),
			};
		});
	if (userDataData.length > 0 && !changeNodesUserData(userDataData)) {
		wsi4.util.error("Error changing userdata");
		return false;
	}
	return true;
}

function applyThicknessChanges(data: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	const thicknessData = data.filter(d => d.attributes.thickness !== undefined);
	return !thicknessData.reduce((res: boolean, t) => {
		const vertex = wsi4.node.vertexFromRootId(t.rootId);
		if (vertex !== undefined) {
			if (t.attributes.thickness === undefined) {
				return wsi4.throwError("Thickness undefined");
			}
			if (!changeNodeThickness(vertex, t.attributes.thickness)) {
				wsi4.util.error("Error changing thickness");
				return true;
			}
		}
		return res || false;
	}, false);
}

function isFilterEmptyOrNodeMissing(d: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	if (d.length > 1) {
		return wsi4.throwError("Attributes only changeable for one node at a time");
	}
	if (d.length === 0) {
		return true;
	}
	if (wsi4.node.vertexFromRootId(d[0]!.rootId) === undefined) {
		// vertex not in graph anymore
		return true;
	}
	return false;
}

function applyImportAttributesChange(data: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	const importAttributesData = data.reduce((u: Array<VertexWithMultiplicity>, d: RootIdWithGraphNodeWritableAttributes) => {
		if (d.attributes.importMultiplicity !== undefined) {
			u.push({
				vertex: rootIdToVertex(d.rootId),
				multiplicity: d.attributes.importMultiplicity,
			});
		}
		return u;
	}, []);
	if (importAttributesData.length > 0 && !changeMultipleMultiplicities(importAttributesData)) {
		wsi4.util.error("Error changing importAttributes");
		return false;
	}
	return true;
}

function applyArticleDenominationChanges(data: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	const userDataData = data.filter(d => d.attributes.articleName !== undefined
						|| d.attributes.articleExternalPartNumber !== undefined
						|| d.attributes.articleExternalDrawingNumber !== undefined
						|| d.attributes.articleExternalRevisionNumber !== undefined)
		.map((d: RootIdWithGraphNodeWritableAttributes) => {
			let articleUserData = wsi4.node.articleUserData(rootIdToVertex(d.rootId));
			if (d.attributes.articleName !== undefined) {
				articleUserData = createUpdatedArticleUserData("name", d.attributes.articleName, articleUserData);
			}
			if (d.attributes.articleExternalPartNumber !== undefined) {
				articleUserData = createUpdatedArticleUserData("externalPartNumber", d.attributes.articleExternalPartNumber, articleUserData);
			}
			if (d.attributes.articleExternalDrawingNumber !== undefined) {
				articleUserData = createUpdatedArticleUserData("externalDrawingNumber", d.attributes.articleExternalDrawingNumber, articleUserData);
			}
			if (d.attributes.articleExternalRevisionNumber !== undefined) {
				articleUserData = createUpdatedArticleUserData("externalRevisionNumber", d.attributes.articleExternalRevisionNumber, articleUserData);
			}
			return {
				vertex: rootIdToVertex(d.rootId),
				userData: articleUserData,
			};
		});

	if (userDataData.length === 0) {
		return true;
	}

	changeArticleUserData(userDataData);
	return true;
}

function applySheetMaterialChanges(data: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	const userDataData = data.reduce((u: Array<VertexWithUserData>, d: RootIdWithGraphNodeWritableAttributes) => {
		if (d.attributes.sheetMaterial !== undefined) {
			u.push({
				vertex: rootIdToVertex(d.rootId),
				userData: createUpdatedNodeUserData("sheetMaterialId", rootIdToVertex(d.rootId), d.attributes.sheetMaterial.identifier),
			});
		}
		return u;
	}, []);
	if (userDataData.length > 0 && !changeNodesUserData(userDataData)) {
		wsi4.util.error("Error changing userdata");
		return false;
	}
	return true;
}

function applyTubeCuttingChanges(allData: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	const relevantData = allData.filter(data => data.attributes.tubeCuttingEditParams !== undefined);
	if (relevantData.length === 0) {
		return true;
	}

	const tubes = getTable(TableType.tube);

	const args = relevantData.map((data): ChangeNodeProcessAndUserDataArg => {
		const vertex = rootIdToVertex(data.rootId);
		const attributes = data.attributes.tubeCuttingEditParams as TubeCuttingEditParams;
		const tube = tubes.find(row => row.identifier === attributes.tubeId);
		assert(tube !== undefined, "No matching tube found for ID " + attributes.tubeId);
		return {
			vertex: vertex,
			processId: attributes.processId,
			userData: applyTubeToUserData(
				vertex,
				tube,
			),
		};
	});
	return changeNodesProcessAndUserData(args);
}

function applyCommentChanges(data: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	const userDataData = data.reduce((u: Array<VertexWithUserData>, d: RootIdWithGraphNodeWritableAttributes) => {
		if (d.attributes.comment !== undefined) {
			u.push({
				vertex: rootIdToVertex(d.rootId),
				userData: createUpdatedNodeUserData("comment", rootIdToVertex(d.rootId), d.attributes.comment),
			});
		}
		return u;
	}, []);
	if (userDataData.length > 0 && !changeNodesUserData(userDataData)) {
		wsi4.util.error("Error changing userdata");
		return false;
	}
	return true;
}

function extractDataForVerticesInGraph(data: Array<RootIdWithGraphNodeWritableAttributes>): Array<RootIdWithGraphNodeWritableAttributes> {
	return data.filter(d => wsi4.node.vertexFromRootId(d.rootId) !== undefined);
}

function applyAttachmentChanges(data: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	const userDataData = data.reduce((u: Array<VertexWithUserData>, d: RootIdWithGraphNodeWritableAttributes) => {
		if (d.attributes.attachments !== undefined) {
			u.push({
				vertex: rootIdToVertex(d.rootId),
				userData: createUpdatedNodeUserData("attachments", rootIdToVertex(d.rootId), d.attributes.attachments),
			});
		}
		return u;
	}, []);
	if (userDataData.length > 0 && !changeNodesUserData(userDataData)) {
		wsi4.util.error("Error changing userdata");
		return false;
	}
	return true;
}

function applyForceSheetChange(data: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	const vwptds: VertexWithProcessTypeData[] = data.filter(d => d.attributes.forceSheetMetalPart !== undefined)
		.map((d): VertexWithProcessTypeData|undefined => {
			const vertex = rootIdToVertex(d.rootId);
			if (d.attributes.forceSheetMetalPart) {
				const availableWsts = wsi4.node.checkWorkStepAvailability(vertex, [
					WorkStepType.sheetBending,
					WorkStepType.sheetCutting,
				]);
				if (availableWsts.length === 0) {
					// Not expected but handled gracefully
					return undefined;
				}
				const processes = computeOrderedProcesses(availableWsts[0]!)
					.filter(p => p.active);
				if (processes.length === 0) {
					// Not expected but handled gracefully
					return undefined;
				}
				return {
					vertex: vertex,
					processId: processes[0]!.identifier,
					forced: true,
				};
			} else {
				return {
					vertex: vertex,
					processId: wsi4.node.processId(vertex),
					forced: false,
				};
			}
		})
		.filter((obj): obj is VertexWithProcessTypeData => obj !== undefined);
	if (vwptds.length > 0 && !changeNodesProcessId(vwptds)) {
		wsi4.util.error("Error changing userdata");
		return false;
	} else {
		return true;
	}
}

export function applyGraphNodeWritableAttributesToRootIds(data: Array<RootIdWithGraphNodeWritableAttributes>): boolean {
	// FIXME return which vertices didn't work instead just using one flag (see #1649)
	// NOTE: Changing the order of these functions could break the shop
	const changeFunctions = [
		applyCommentChanges,
		applyArticleDenominationChanges,
		applyAttachmentChanges,
		applyTestReportChanges,
		applyCuttingGasChanges,
		applyFixedRotationChanges,
		applyThicknessChanges,
		applySheetMaterialChanges,
		applyBottomSideUsageChange,
		applyTubeCuttingChanges,
		applyImportAttributesChange,
		applyForceSheetChange,
	];
	return changeFunctions.reduce((success: boolean, f) => {
		success = success && f(extractDataForVerticesInGraph(data));
		return success;
	}, true);
}
