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 {
	assert,
	getKeysOfObject,
	isBoolean,
	isNumber,
	parseJson,
} from "./utils";

/**
 * 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 {
	getKeysOfObject(defaultGraphRepResourcesConfig)
		.forEach(key => {
			if (partialConfig[key] === undefined) {
				partialConfig[key] = defaultGraphRepResourcesConfig[key] as any;
			}
		});
	assert(isGraphRepResourcesConfig(partialConfig));
	return partialConfig;
}

/**
 * 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;
}

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,
};

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 defaultSettings: Readonly<Settings> = {
	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,
};

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

/*
 * List of keys for all settings
 */
export function settingsKeys() : Array<keyof Settings & string> {
	return getKeysOfObject(defaultSettings);
}

/*
 * 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),
	);
}
