import {
	TableType,
} from "qrc:/js/lib/generated/enum";
import {
	isStringIndexedInterface,
} from "qrc:/js/lib/generated/typeguard";
import {
	ErpInterfaceVersion,
	isErpInterfaceVersion,
} from "./erp_interface";

import {
	getTable,
} from "./table_utils";
import {
	exhaustiveStringTuple,
	getKeysOfObject,
	isBoolean,
	isNumber,
	isString,
	parseJson,
} from "./utils";

/**
 * Configures the computation of a project's best case completion time
 *
 * "maximalPath":
 * For each maximal path the manufacturing times and idle times are accumulated.
 * The most expensive path defines the resulting time.
 *
 * "nodeInclManufacturingTime":
 * The node where the sum of manufacturing time and idle time is maximal defines the resulting time.
 *
 * "Best case": This time can be reached if all possible processes are handled in parallel.
 */
export type CompletionTimeMode = "maximalPath" | "maximalNodeInclManufacturingTime" | "maximalNodeExclManufacturingTime";

export function isCompletionTimeMode(arg: unknown): arg is CompletionTimeMode {
	return isString(arg) && exhaustiveStringTuple<CompletionTimeMode>()(
		"maximalPath",
		"maximalNodeInclManufacturingTime",
		"maximalNodeExclManufacturingTime",
	).some(value => value === arg);
}

/**
 * Note:  When changing this interface the compatibility with older and / or newer versions *must* be maintained.
 *
 *        If the respective type-guard returns false for an existing table entry, the user-defined config will be
 *        overwritten with the then current default config.
 *
 *        This potentially breaks existing ERP interfaces (e.g. compressed vs. uncompressed data).
 *
 *        To prevent this (and for ease of maintenance) the Partial<> version of this interface is stored to the
 *        settings table.
 *
 *        Rationale: As the settings table cannot be migrated as of now (table is required for the migration process)
 *        the migration of outdated versions of this interface is not an option.  Reading the Partial<> interface
 *        makes reading the settings table more robust wrt changes in the interface.
 */
export interface GraphRepResourcesConfig {
	bendDrawingHtmls: boolean,
	dxfs: boolean;
	dxfsCompressed: boolean;
	geos: boolean;
	geosCompressed: boolean;
	svgs: boolean;
	svgsCompressed: boolean;
	inputSteps: boolean;
	inputStepsCompressed: boolean;
	outputSteps: boolean;
	outputStepsCompressed: boolean;
	attachments: boolean;
	pngs: boolean;
	subGraphs: boolean;
}

export function isGraphRepResourcesConfig(arg: unknown): arg is GraphRepResourcesConfig {
	const map: {[index in keyof GraphRepResourcesConfig]: (prop: unknown) => prop is GraphRepResourcesConfig[index]} = {
		bendDrawingHtmls: isBoolean,
		dxfs: isBoolean,
		dxfsCompressed: isBoolean,
		geos: isBoolean,
		geosCompressed: isBoolean,
		svgs: isBoolean,
		svgsCompressed: isBoolean,
		inputSteps: isBoolean,
		inputStepsCompressed: isBoolean,
		outputSteps: isBoolean,
		outputStepsCompressed: isBoolean,
		attachments: isBoolean,
		pngs: isBoolean,
		subGraphs: isBoolean,
	};
	return isStringIndexedInterface(arg) && getKeysOfObject(map)
		.every(key => map[key](arg[key]));
}

function isOptionalBoolean(arg: unknown): arg is boolean|undefined {
	return arg === undefined || isBoolean(arg);
}

export function isPartialGraphRepResourcesConfig(arg: unknown): arg is GraphRepResourcesConfig {
	const map: {[index in keyof GraphRepResourcesConfig]: (prop: unknown) => prop is GraphRepResourcesConfig[index] | undefined} = {
		bendDrawingHtmls: isOptionalBoolean,
		dxfs: isOptionalBoolean,
		dxfsCompressed: isOptionalBoolean,
		geos: isOptionalBoolean,
		geosCompressed: isOptionalBoolean,
		svgs: isOptionalBoolean,
		svgsCompressed: isOptionalBoolean,
		inputSteps: isOptionalBoolean,
		inputStepsCompressed: isOptionalBoolean,
		outputSteps: isOptionalBoolean,
		outputStepsCompressed: isOptionalBoolean,
		attachments: isOptionalBoolean,
		pngs: isOptionalBoolean,
		subGraphs: isOptionalBoolean,
	};
	return isStringIndexedInterface(arg) && getKeysOfObject(map)
		.every(key => map[key](arg[key]));
}

const defaultGraphRepResourcesConfig: Readonly<GraphRepResourcesConfig> = {
	bendDrawingHtmls: false,
	dxfs: true,
	dxfsCompressed: false,
	geos: true,
	geosCompressed: false,
	svgs: true,
	svgsCompressed: false,
	inputSteps: true,
	inputStepsCompressed: false,
	outputSteps: true,
	outputStepsCompressed: false,
	attachments: true,
	pngs: true,
	subGraphs: false,
};

export function complementGraphRepResourcesConfig(partialConfig: Partial<GraphRepResourcesConfig>): GraphRepResourcesConfig {
	return {
		bendDrawingHtmls: partialConfig.bendDrawingHtmls ?? false,
		dxfs: partialConfig.dxfs ?? true,
		dxfsCompressed: partialConfig.dxfsCompressed ?? false,
		geos: partialConfig.geos ?? true,
		geosCompressed: partialConfig.geosCompressed ?? false,
		svgs: partialConfig.svgs ?? true,
		svgsCompressed: partialConfig.svgsCompressed ?? false,
		inputSteps: partialConfig.inputSteps ?? true,
		inputStepsCompressed: partialConfig.inputStepsCompressed ?? false,
		outputSteps: partialConfig.outputSteps ?? true,
		outputStepsCompressed: partialConfig.outputStepsCompressed ?? false,
		attachments: partialConfig.attachments ?? true,
		pngs: partialConfig.pngs ?? true,
		subGraphs: partialConfig.subGraphs ?? false,
	};
}

/**
 * By default each entry is considered optional.
 * The table is typically querried via a function that returns the default in case the value is unset.
 *
 * Not to be confused with table-interface `Setting` which is the row-type of the associated table.
 */
export interface Settings {
	sheetTestReportEnabled: boolean;
	sheetTestReportCosts: number;
	sheetCuttingFixedRotationsEnabled: boolean;
	manualMechanicalDeburringSpeed: number;
	tableMigrationVersion: number;
	laserSheetCuttingVMax: number;
	laserSheetCuttingAMax: number;
	graphRepResourcesConfig: Partial<GraphRepResourcesConfig>;
	// `null` indicates that the version is unspecified (which in turn should lead to a fallback to the latest version)
	// Note: Using `null` instead of `undefined` as the value is serialized as JSON (which does not work well for `undefined`)
	erpInterfaceVersion: ErpInterfaceVersion|null;
	sheetNestingDistance: number;
	// An inner contour of a nested part will *not* be considered scrap if its area is below this threshold
	sheetScrapAreaThreshold: number;
	// Additional distance that is added to the geometrical minimum bend flange length [mm]
	bendFlangeSafetyDistance: number;
	// Unusable length of a tube that is required to clamp the semi-manufactured part in the machine [mm]
	tubeClampingLength: number;
	// [mm]
	tubeNestingDistance: number;
	csvLocale: string;
	// Toggle distribution of setup time among compatible die bending nodes
	dieBendingSetupTimeDistributionEnabled: boolean;
	// Generic place to store configurations of third party ERP interfaces
	thirdPartyErpInterfaceConfig: string;
	completionTimeMode: CompletionTimeMode;
}

const typeGuardMap: {[index in keyof Settings]: (arg: unknown) => arg is Settings[index]} = {
	sheetTestReportEnabled: isBoolean,
	sheetTestReportCosts: isNumber,
	sheetCuttingFixedRotationsEnabled: isBoolean,
	manualMechanicalDeburringSpeed: isNumber,
	tableMigrationVersion: isNumber,
	laserSheetCuttingVMax: isNumber,
	laserSheetCuttingAMax: isNumber,
	graphRepResourcesConfig: isPartialGraphRepResourcesConfig,
	erpInterfaceVersion: (arg: unknown): arg is ErpInterfaceVersion|null => arg === null || isErpInterfaceVersion(arg),
	sheetNestingDistance: isNumber,
	sheetScrapAreaThreshold: isNumber,
	bendFlangeSafetyDistance: isNumber,
	tubeClampingLength: isNumber,
	tubeNestingDistance: isNumber,
	csvLocale: isString,
	dieBendingSetupTimeDistributionEnabled: isBoolean,
	thirdPartyErpInterfaceConfig: isString,
	completionTimeMode: isCompletionTimeMode,
};

function isMatchingType<Key extends keyof Settings>(key: Key, arg: unknown): arg is Settings[Key] {
	return typeGuardMap[key](arg);
}

function parseValue<Key extends keyof Settings>(key: Key, value: string): Settings[Key]|undefined {
	return parseJson(value, (arg: unknown): arg is Settings[Key] => isMatchingType(key, arg));
}

/**
 * Get settings entry for key
 */
export function getSetting<Key extends keyof Settings>(key: Key, table: readonly Readonly<Setting>[]|undefined = undefined): Settings[Key]|undefined {
	table = table === undefined ? getTable(TableType.setting) : table;
	const entry = table.find(setting => setting.key === key);
	return entry === undefined ? undefined : parseValue(key, entry.value);
}

/**
 * Create new version of table where the submitted entry is updated
 */
export function createUpdatedTable<Key extends keyof Settings>(key: Key, value: Readonly<Settings[Key]>, table: Readonly<Setting>[]|undefined = undefined): Setting[] {
	table = table === undefined ? Array.from(getTable(TableType.setting)) : table;
	const index = table.findIndex(setting => setting.key === key);
	if (index === -1) {
		table.push({
			key: key,
			value: JSON.stringify(value),
		});
	} else {
		table[index] = {
			key: key,
			value: JSON.stringify(value),
		};
	}
	return table;
}

const defaultSettingsFuncMap: {[index in keyof Settings]: () => Settings[index]} = {
	sheetTestReportEnabled: () => true,
	// [Currency]
	sheetTestReportCosts: () => 20,
	sheetCuttingFixedRotationsEnabled: () => true,
	// [m/min]
	manualMechanicalDeburringSpeed: () => 2,
	tableMigrationVersion: () => 0,
	// [m/min]
	laserSheetCuttingVMax: () => 100,
	// [m/s²]
	laserSheetCuttingAMax: () => 10,
	graphRepResourcesConfig: () => defaultGraphRepResourcesConfig,
	erpInterfaceVersion: () => null,
	// [mm]
	sheetNestingDistance: () => 10,
	// [mm²]
	sheetScrapAreaThreshold: () => 0,
	// [mm]
	bendFlangeSafetyDistance: () => 2,
	// [mm]
	tubeClampingLength: () => 0,
	// [mm]
	tubeNestingDistance: () => 10,
	csvLocale: () => wsi4.locale.system(),
	dieBendingSetupTimeDistributionEnabled: () => true,
	thirdPartyErpInterfaceConfig: () => "",
	completionTimeMode: () => "maximalPath",
};

/*
 * Default setting for key
 */
export function defaultSetting<Key extends keyof Settings>(key: Key): Settings[Key] {
	return defaultSettingsFuncMap[key]();
}

/*
 * List of keys for all settings
 */
export function settingsKeys() : Array<keyof Settings & string> {
	return exhaustiveStringTuple<keyof Settings>()(
		"sheetTestReportEnabled",
		"sheetTestReportCosts",
		"sheetCuttingFixedRotationsEnabled",
		"manualMechanicalDeburringSpeed",
		"tableMigrationVersion",
		"laserSheetCuttingVMax",
		"laserSheetCuttingAMax",
		"graphRepResourcesConfig",
		"erpInterfaceVersion",
		"sheetNestingDistance",
		"sheetScrapAreaThreshold",
		"bendFlangeSafetyDistance",
		"tubeClampingLength",
		"tubeNestingDistance",
		"csvLocale",
		"dieBendingSetupTimeDistributionEnabled",
		"thirdPartyErpInterfaceConfig",
		"completionTimeMode",
	);
}

/*
 * Check if a setting is available
 */
export function isSettingAvailable<Key extends keyof Settings>(key: Key, table: readonly Readonly<Setting>[]|undefined = undefined): boolean {
	return getSetting(key, table) !== undefined;
}

export function getSettingOrDefault<Key extends keyof Settings>(key: Key, table: readonly Readonly<Setting>[]|undefined = undefined): Settings[Key] {
	return getSetting(key, table) ?? defaultSetting(key);
}

export function getSettingGraphRepResourcesConfig(table?: readonly Readonly<Setting>[]): GraphRepResourcesConfig {
	return complementGraphRepResourcesConfig(
		getSettingOrDefault("graphRepResourcesConfig", table),
	);
}
