function isFlags(enumObj: object): boolean {
	const values = Object.values(enumObj).filter(v => typeof v === "number");
	return values.some(v => v !== 0 && (v & (v - 1)) === 0);
}

function isValidEnumValue(value: any, enumType: object): boolean {
	if (isFlags(enumType)) {
		const allFlags = Object.values(enumType).reduce((acc, val) => typeof val === "number" ? acc | val : acc, 0);
		return typeof value === "number" && (value & allFlags) === value;
	} else {
		return Object.values(enumType).includes(value);
	}
}

function getEnumValues(enumObj: object): string[] | number[] {
	if (!isFlags(enumObj))
		return Object.values(enumObj);
	return Object.values(enumObj).filter(value => typeof value === "number") as number[];
}

function getEnumString<T extends { [key: string]: string | number }>(enumObj: T, flags: number): string {
    if (flags === 0) 
        return Object.keys(enumObj).find(key => enumObj[key] === 0) || 'None';

    const attributes = Object.entries(enumObj).filter(([key, value]) => 
            typeof value === 'number' && value !== 0 && (flags & value) === value)
        	.map(([key]) => key);

    return attributes.length === 1 ? attributes[0] : attributes.join(', ');
}

function validateEnumProperty(json: any, property: string, enumType: object): number | string | null {
	if (json.hasOwnProperty(property) && isValidEnumValue(json[property], enumType))
		return json[property];
	return null;
}

function hasFlag(value: number | string, flag: number | string, enumType: object): boolean {
	if (isFlags(enumType))
		return typeof value === "number" && typeof flag === "number" && (value & flag) === flag;
	else
		return value === flag;
}

function addFlag(value: number, flag: number, enumType: object): number {
	if (isFlags(enumType))
		return value | flag;
	else
		throw new Error("Attempted to add flag on non-flags enum");
}

function removeFlag(value: number, flag: number, enumType: object): number {
	if (isFlags(enumType))
		return value & ~flag;
	else
		throw new Error("Attempted to remove flag on non-flags enum");
}

function toggleFlag(value: number, flag: number, enumType: object): number {
	if (isFlags(enumType))
		return value ^ flag;
	else
		throw new Error("Attempted to toggle flag on non-flags enum");
}

const EnumUtils = {
	hasFlag,
	addFlag,
	removeFlag,
	toggleFlag,
	validateEnumProperty,
	getEnumValues,
	getEnumString
};

export default EnumUtils;