import {
	Constraints,
} from "./constraints";
import {
	assert,
	getKeysOfObject,
} from "./utils";

export enum Level {
	fatalError = "fatalError",
	recoverableError = "recoverableError",
	warning = "warning",
	info = "info",
}

export type ManufacturingStateEntryType = "unknownError"
| "backendIssueInvalidBend"
| "backendIssueMultipleOuterContours"
| "backendIssueNestingFailed"
| "backendIssueUndetectedFeatures"
| "constraintBendAreasOverlap"
| "constraintBendFlangeTooShort"
| "constraintContourInBend"
| "constraintMaxBendLineNetLengthExceeded"
| "constraintMaxDimensionExceeded"
| "constraintMaxSheetThicknessExceeded"
| "constraintMinContourSizeUndercut"
| "constraintNoBendDeductionFound"
| "constraintNoBendDieAssigned"
| "constraintProfileNotSupported"
| "constraintSheetNotAvailable"
| "constraintTubeCuttingProcessCompatibility"
| "constraintTubeNestingNotAvailable"
| "constraintTubeNotAvailable"
| "constraintTubeDetectionNotLicensed"
;

export interface ManufacturingStateEntry {
	type: ManufacturingStateEntryType;
	level: Level;
	message: string;
}

/**
 * Node-specific information related to certain `WorkStepType`(s) and `Constraint`s
 *
 * For each node classification information is stored on a per-WorkStepType-basis.
 * ManufacturingState consists of node-specific information for a set of `WorkStepType`s and / or `Constraint`s (if any)
 */
export type ManufacturingState = ManufacturingStateEntry[];

const levelFlagMap: Readonly<{[index in Level] : Readonly<number>}> = {
	fatalError: 0x0, // 000
	recoverableError: 0x1, // 001
	warning: 0x3, // 011
	info: 0x7, // 111
};

export function computeResultingLevel(manufacturingState: Readonly<ManufacturingState>): Level {
	const resultingFlag = manufacturingState.reduce((acc: number, entry) => acc & levelFlagMap[entry.level], levelFlagMap[Level.info]);
	const result = getKeysOfObject(levelFlagMap)
		.find(key => levelFlagMap[key] === resultingFlag);
	assert(result !== undefined, "Expecting valid level");
	return result;
}

/**
 * Auxilary interface for generic handling of ReplyState
 *
 * Interface is not intended to be instantiated.
 * It can be considered a map of ReplyStateMap entry to the respective inner map's type.
 */
export interface ReplyStateTypeMap {
	undefined: {[index in keyof ReplyStateMapUndefined]-?: ReplyStateMapUndefined[index]};
	sheet: {[index in keyof ReplyStateMapSheet]-?: ReplyStateMapSheet[index]};
	sheetCutting: {[index in keyof ReplyStateMapSheetCutting]-?: ReplyStateMapSheetCutting[index]};
	joining: {[index in keyof ReplyStateMapJoining]-?: ReplyStateMapJoining[index]};
	tubeCutting: {[index in keyof ReplyStateMapTubeCutting]-?: ReplyStateMapTubeCutting[index]};
	sheetBending: {[index in keyof ReplyStateMapSheetBending]-?: ReplyStateMapSheetBending[index]};
	userDefined: {[index in keyof ReplyStateMapUserDefined]-?: ReplyStateMapUserDefined[index]};
	userDefinedBase: {[index in keyof ReplyStateMapUserDefinedBase]-?: ReplyStateMapUserDefinedBase[index]};
	packaging: {[index in keyof ReplyStateMapPackaging]-?: ReplyStateMapPackaging[index]};
	transform: {[index in keyof ReplyStateMapTransform]-?: ReplyStateMapTransform[index]};
	tube: {[index in keyof ReplyStateMapTube]-?: ReplyStateMapTube[index]};
}

type MapPropsTo<T, U> = Readonly<{[index in keyof T] : Readonly<U>}>;

type ReplyStateEntryTypeMap = Readonly<{[index in keyof ReplyStateTypeMap] : MapPropsTo<ReplyStateTypeMap[index], Readonly<ManufacturingStateEntryType>>}>;

type ReplyStateMessageMap = Readonly<{[index in keyof ReplyStateTypeMap] : MapPropsTo<ReplyStateTypeMap[index], Readonly<string>>}>;

export type ReplyStateLevelMap = Readonly<{[index in keyof ReplyStateTypeMap] : MapPropsTo<ReplyStateTypeMap[index], Readonly<Level>>}>;

function replyStateMapEntryToEntries<OuterKey extends keyof ReplyStateTypeMap>(outerKey: OuterKey, replyStateMap: ReplyStateIndicatorMap, replyStateLevelMap: ReplyStateLevelMap): ManufacturingState {
	const replyStateEntryTypeMap: ReplyStateEntryTypeMap = {
		undefined: {
			undefinedError: "unknownError",
		},
		userDefined: {
			undefinedError: "unknownError",
		},
		tubeCutting: {
			undefinedError: "unknownError",
			undetectedFeatures: "backendIssueUndetectedFeatures",
		},
		userDefinedBase: {
			undefinedError: "unknownError",
		},
		sheet: {
			undefinedError: "unknownError",
			nestingFailed: "backendIssueNestingFailed",
		},
		sheetBending: {
			undefinedError: "unknownError",
			undetectedFeatures: "backendIssueUndetectedFeatures",
			multipleOuterContours: "backendIssueMultipleOuterContours",
			// Error has been replaced (see respective constraint)
			contourInBend: "unknownError",
			invalidBend: "backendIssueInvalidBend",
		},
		packaging: {
			undefinedError: "unknownError",
			insufficientPackageVolume: "unknownError",
			overweightParts: "unknownError",
		},
		sheetCutting: {
			undefinedError: "unknownError",
			undetectedFeatures: "backendIssueUndetectedFeatures",
			multipleOuterContours: "backendIssueMultipleOuterContours",
			unused0: "unknownError",
		},
		joining: {
			undefinedError: "unknownError",
		},
		transform: {
			undefinedError: "unknownError",
		},
		tube: {
			undefinedError: "unknownError",
		},
	};

	const replyStateMessageMap: ReplyStateMessageMap = {
		undefined: {
			undefinedError: wsi4.util.translate("error_part"),
		},
		userDefined: {
			undefinedError: wsi4.util.translate("error_part"),
		},
		tubeCutting: {
			undefinedError: wsi4.util.translate("error_part"),
			undetectedFeatures: wsi4.util.translate("undetected_features"),
		},
		userDefinedBase: {
			undefinedError: wsi4.util.translate("error_part"),
		},
		sheet: {
			undefinedError: wsi4.util.translate("error_part"),
			nestingFailed: wsi4.util.translate("nesting_failed"),
		},
		sheetBending: {
			undefinedError: wsi4.util.translate("error_part"),
			undetectedFeatures: wsi4.util.translate("undetected_features"),
			multipleOuterContours: wsi4.util.translate("warning_multiple_outer_contours"),
			contourInBend: wsi4.util.translate("warning_innercontour_in_bend"),
			invalidBend: wsi4.util.translate("error_invalid_bends"),
		},
		packaging: {
			undefinedError: wsi4.util.translate("error_part"),
			insufficientPackageVolume: wsi4.util.translate("error_package_size_too_small"),
			overweightParts: wsi4.util.translate("error_assembly_too_heavy"),
		},
		sheetCutting: {
			undefinedError: wsi4.util.translate("error_part"),
			undetectedFeatures: wsi4.util.translate("undetected_features"),
			multipleOuterContours: wsi4.util.translate("warning_multiple_outer_contours"),
			unused0: "",
		},
		joining: {
			undefinedError: wsi4.util.translate("error_part"),
		},
		transform: {
			undefinedError: wsi4.util.translate("error_part"),
		},
		tube: {
			undefinedError: wsi4.util.translate("error_part"),
		},
	};

	const outerMapEntry = replyStateMap[outerKey];
	if (outerMapEntry === undefined) {
		return [];
	} else {
		const indicators = outerMapEntry.replyStateIndicators;
		const innerKeys = getKeysOfObject(indicators) as (keyof(ReplyStateTypeMap[OuterKey]))[];
		return innerKeys.map(innerKey => {
			const typeKey = innerKey as keyof ReplyStateEntryTypeMap[OuterKey];
			const type: ManufacturingStateEntryType = replyStateEntryTypeMap[outerKey][typeKey];

			const levelKey = innerKey as keyof ReplyStateLevelMap[OuterKey];
			const level: Level = replyStateLevelMap[outerKey][levelKey];

			const messageKey = innerKey as keyof ReplyStateMessageMap[OuterKey];
			const message: string = replyStateMessageMap[outerKey][messageKey];

			return {
				type: type,
				level: level,
				message: message,
			};
		});
	}
}

function replyStateMapToEntries(replyStateMap: ReplyStateIndicatorMap, targetWorkStepTypes: readonly Readonly<WorkStepType>[], replyStateLevelMap: ReplyStateLevelMap): ManufacturingState {
	return getKeysOfObject(replyStateMap)
		.filter(replyState => targetWorkStepTypes.some(wst => wst === replyState))
		.reduce((acc: ManufacturingState, replyState) => {
			acc.push(...replyStateMapEntryToEntries(replyState, replyStateMap, replyStateLevelMap));
			return acc;
		}, []);
}

export type ConstraintLevelMap = Readonly<{[index in keyof Constraints] : Readonly<Level>}>;

export type WstConstraintMap = {
	[index in WorkStepType]?: Constraints
};

function constraintsToEntries(wstConstraintMap: Readonly<WstConstraintMap>, targetWorkStepTypes: readonly Readonly<WorkStepType>[], constraintLevelMap: ConstraintLevelMap): ManufacturingState {
	const constraintTypeMap: {[index in keyof Constraints]: ManufacturingStateEntryType} = {
		bendAreasNotOverlapping: "constraintBendAreasOverlap",
		bendDie: "constraintNoBendDieAssigned",
		bendFlangeTooShort: "constraintBendFlangeTooShort",
		bendThickness: "constraintNoBendDeductionFound",
		contourInBend: "constraintContourInBend",
		dataCompleteness: "unknownError",
		dataConsistent: "unknownError",
		dataValidity: "unknownError",
		maxBendLineNetLength: "constraintMaxBendLineNetLengthExceeded",
		maxDimensions: "constraintMaxDimensionExceeded",
		maxSheetThickness: "constraintMaxSheetThicknessExceeded",
		minContourSize: "constraintMinContourSizeUndercut",
		sheetAvailability: "constraintSheetNotAvailable",
		transportSource: "unknownError",
		userDefinedProcessId: "unknownError",
		sheetMaterialAvailability: "unknownError",
		profileSupport: "constraintProfileNotSupported",
		tubeAvailability: "constraintTubeNotAvailable",
		tubeCuttingProcessCompatibility: "constraintTubeCuttingProcessCompatibility",
		tubeDetectionLicensed: "constraintTubeDetectionNotLicensed",
		tubeNestingAvailability: "constraintTubeNestingNotAvailable",
	};

	const constraintMessageMap: {[index in keyof Constraints]: string} = {
		maxDimensions: wsi4.util.translate("max_dimensions_constraint_violated"),
		sheetAvailability: wsi4.util.translate("sheet_availability_constraint_violated"),
		minContourSize: wsi4.util.translate("min_contour_size_constraint_violated"),
		dataConsistent: wsi4.util.translate("data_consistent_constraint_violated"),
		dataCompleteness: wsi4.util.translate("data_completeness_constraint_violated"),
		dataValidity: wsi4.util.translate("data_validity_constraint_violated"),
		bendDie: wsi4.util.translate("benddie_constraint_violated"),
		bendThickness: wsi4.util.translate("bendthickness_constraint_violated"),
		transportSource: wsi4.util.translate("transport_source_constraint_violated"),
		userDefinedProcessId: wsi4.util.translate("user_defined_process_id_constraint_violated"),
		maxBendLineNetLength: wsi4.util.translate("max_bend_line_net_length_constraint_violated"),
		maxSheetThickness: wsi4.util.translate("max_sheet_thickness_constraint_violated"),
		bendAreasNotOverlapping: wsi4.util.translate("bend_area_not_overlapping_constraint_violated"),
		sheetMaterialAvailability: wsi4.util.translate("sheet_material_not_available"),
		profileSupport: wsi4.util.translate("profile_not_supported"),
		tubeAvailability: wsi4.util.translate("tube_not_available"),
		tubeNestingAvailability: wsi4.util.translate("nesting_failed"),
		tubeCuttingProcessCompatibility: wsi4.util.translate("process_not_applicable"),
		tubeDetectionLicensed: wsi4.util.translate("tube_detection_not_licensed"),
		contourInBend: wsi4.util.translate("warning_innercontour_in_bend"),
		bendFlangeTooShort: wsi4.util.translate("bend_flange_too_short"),
	};

	return getKeysOfObject(wstConstraintMap)
		.filter(lhs => targetWorkStepTypes.some(rhs => lhs === rhs))
		.reduce((acc: ManufacturingState, wst) => {
			const constraints = wstConstraintMap[wst];
			const entries = (() => {
				if (constraints === undefined) {
					return [];
				} else {
					return getKeysOfObject(constraints)
						.filter(key => constraints[key])
						.map(key => ({
							type: constraintTypeMap[key],
							level: constraintLevelMap[key],
							message: constraintMessageMap[key],
						}));
				}
			})();
			acc.push(...entries);
			return acc;
		}, []);
}

/**
 * Compute manufacturing state for an associated vertex
 *
 * Note:  Consider using one of the vertex specific convenience functions provided by `node_utils.ts`.
 *
 * @param replyStateIndicatorMap ReplyStateIndicators for all `WorkStep`s
 * @param constraintMap Constraints for all `WorkStepType`s
 * @param replyStateLevelMap Maps reply state indicators to a [[ManufacturingState]] specific [[Level]]
 * @param constraintLevelMap Maps constraints to a [[ManufacturingState]] specific [[Level]]
 * @param targetWorkStepTypes `WorkStepType`s [[ManufacturingState]] should be computed for
 * @returns The resulting [[ManufacturingState]]
 */
export function computeManufacturingStateImpl(replyStateIndicatorMap: Readonly<ReplyStateIndicatorMap>,
	constraintMap: Readonly<WstConstraintMap>,
	replyStateLevelMap: ReplyStateLevelMap,
	constraintLevelMap: ConstraintLevelMap,
	targetWorkStepTypes: readonly Readonly<WorkStepType>[]): ManufacturingState {
	return [
		...replyStateMapToEntries(replyStateIndicatorMap, targetWorkStepTypes, replyStateLevelMap),
		...constraintsToEntries(constraintMap, targetWorkStepTypes, constraintLevelMap),
	];
}

/**
 * Compute export readiness based on manufacturingState
 *
 * Note:  Consider using the vertex-specific convenience function provided by `node_utils.ts`.
 */
export function isExportReadyImpl(manufacturingState: readonly Readonly<ManufacturingStateEntry>[]): boolean {
	switch (computeResultingLevel(manufacturingState)) {
		case Level.fatalError: return false;
		case Level.recoverableError: return false;
		case Level.warning: return true;
		case Level.info: return true;
	}
}
