"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractProductId = exports.makeDeviceSerialNumberFromAddress = exports.extractDeviceComponentsFromStackInfo = exports.parseSerialNumber = exports.synapticonSerialNumberRegExp = exports.dummyHardwareDescriptionFileContentTemplate = exports.deviceComponentsMatch = exports.getImageIdFromHardwareDescription = exports.getAllComponentsFromHardwareDescription = exports.getSerialNumberFromHardwareDescription = exports.getNameFromHardwareDescription = exports.getApiIdentifierFromHardwareDescription = exports.getIdFromHardwareDescription = exports.isHardwareDescription = exports.isStackInfo = void 0;
const lodash_1 = require("lodash");
/**
 * Type guard to check if an object is of type `StackInfo`.
 *
 * This function checks if the provided object contains the necessary properties
 * (`mac_address`, `stack_serial_number`, and `boards`) and ensures `boards` is an array.
 *
 * @param obj - The object to check.
 * @returns `true` if the object conforms to the `StackInfo` type, otherwise `false`.
 */
function isStackInfo(obj) {
    return (obj && typeof obj === 'object' && 'mac_address' in obj && 'stack_serial_number' in obj && Array.isArray(obj.boards));
}
exports.isStackInfo = isStackInfo;
/**
 * Type guard to check if an object is of type `HardwareDescription`.
 *
 * This function validates if the provided object contains the necessary properties,
 * including `fileVersion`, a `device` object with `serialNumber`, and optionally an `assembly` property.
 *
 * @param obj - The object to check.
 * @returns `true` if the object conforms to the `HardwareDescription` type, otherwise `false`.
 */
function isHardwareDescription(obj) {
    return (obj &&
        typeof obj === 'object' &&
        'fileVersion' in obj &&
        typeof obj.fileVersion === 'string' &&
        ('assembly' in obj || 'device' in obj) &&
        'serialNumber' in obj.device &&
        typeof obj.device.serialNumber === 'string');
}
exports.isHardwareDescription = isHardwareDescription;
/**
 * Retrieves the ID from the assembly if available; otherwise, falls back to the device ID.
 *
 * @example
 * "9002"
 */
function getIdFromHardwareDescription(hw) {
    return hw.assembly ? hw.assembly.id : hw.device.id;
}
exports.getIdFromHardwareDescription = getIdFromHardwareDescription;
/**
 * Constructs the API ID from the hardware description file.
 * The API ID is a combination of the assembly or device ID, version, and optionally a Key ID, which is used for encrypting firmware binaries.
 * Note: In the "Hardware Description File Format" document, the API ID is now referred to as FWID for clarity.
 * @param hd The hardware description object.
 * @param skipKeyId If true, assembles the API ID without including the Key ID.
 * @returns An API identifier, such as "9600-02" or "9600-02-2423".
 */
function getApiIdentifierFromHardwareDescription(hd, skipKeyId = false) {
    let apiId = hd.assembly ? `${hd.assembly.id}-${hd.assembly.version}` : `${hd.device.id}-${hd.device.version}`;
    if (!skipKeyId && hd.device.keyId) {
        apiId += `-${hd.device.keyId}`;
    }
    return apiId;
}
exports.getApiIdentifierFromHardwareDescription = getApiIdentifierFromHardwareDescription;
function getNameFromHardwareDescription(hd) {
    return hd.assembly ? hd.assembly.name : hd.device.name;
}
exports.getNameFromHardwareDescription = getNameFromHardwareDescription;
function getSerialNumberFromHardwareDescription(hd) {
    return hd.assembly ? hd.assembly.serialNumber : hd.device.serialNumber;
}
exports.getSerialNumberFromHardwareDescription = getSerialNumberFromHardwareDescription;
function getAllComponentsFromHardwareDescription(hd) {
    return hd.assembly ? [...hd.assembly.components, ...hd.device.components] : hd.device.components;
}
exports.getAllComponentsFromHardwareDescription = getAllComponentsFromHardwareDescription;
function getImageIdFromHardwareDescription(hd) {
    return hd.assembly ? hd.assembly.imageId : hd.device.imageId;
}
exports.getImageIdFromHardwareDescription = getImageIdFromHardwareDescription;
/**
 * Verifies if two sets of device components are identical.
 * Both sets must contain the same number of components, and each component must match by name and version.
 */
function deviceComponentsMatch(a, b) {
    if (a.length === 0 && b.length === 0) {
        return false; // edge case: when both components are empty they don't match since there are no components to compare
    }
    const ac = a.map((c) => `${c.name}-${c.version[0]}`.replace(/\s/g, '').toLocaleLowerCase()).sort();
    const bc = b.map((c) => `${c.name}-${c.version[0]}`.replace(/\s/g, '').toLocaleLowerCase()).sort();
    return (0, lodash_1.isEqual)(ac, bc);
}
exports.deviceComponentsMatch = deviceComponentsMatch;
/**
 * This template is to be used in cases where hardware description file is not available on a device.
 * It is imperative to replace the serial number with a unique identifier.
 */
exports.dummyHardwareDescriptionFileContentTemplate = {
    fileVersion: '0.0.0',
    device: {
        macAddress: '',
        keyId: '0000',
        serialNumber: '0000-00-0000000-0000',
        name: 'SOMANET Device',
        id: '0000',
        version: '00',
        components: [],
    },
};
exports.synapticonSerialNumberRegExp = /^(\d{4})-(\d{2})-(\d+)-(\d{4})$/;
/**
 * Parses a serial number and returns an instance of {@link SerialNumber}.
 *
 * @throws {Error} Throws an error if the provided serial number does not match the expected pattern.
 */
function parseSerialNumber(serialNumber) {
    if (!exports.synapticonSerialNumberRegExp.test(serialNumber)) {
        throw new Error('The provided string is not a valid serial number.');
    }
    const [productId, revision, consecutiveNumber, productionWeek] = serialNumber.split('-');
    const versionedProductId = `${productId}-${revision}`;
    return { consecutiveNumber, productId, productionWeek, revision, versionedProductId };
}
exports.parseSerialNumber = parseSerialNumber;
/**
 * Extracts device components from the provided Stack Info.
 * The Stack Info typically includes three components: Com, Core, and Drive, each with a name, serial number, and version.
 */
function extractDeviceComponentsFromStackInfo(stackInfo) {
    return stackInfo.boards.map((board) => {
        var _a, _b, _c;
        return ({
            name: (_a = board.description) !== null && _a !== void 0 ? _a : '',
            serialNumber: (_b = board.serial_number) !== null && _b !== void 0 ? _b : '',
            version: (_c = (board.revision || board.version)) !== null && _c !== void 0 ? _c : '',
        });
    });
}
exports.extractDeviceComponentsFromStackInfo = extractDeviceComponentsFromStackInfo;
/**
 * Generates a device serial number from a given address.
 * The address is formatted into a string in the form `XXXX-XX-XXXX000-0000`.
 *
 * @param address - The device address as a number, which will be used to create the serial number.
 * @returns A string representing the formatted device serial number.
 *
 * @example
 * const address = 95010100014791843;
 * const serialNumber = makeDeviceSerialNumberFromAddress(address);
 * console.log(serialNumber); // "9501-01-00014790000-0000"
 */
function makeDeviceSerialNumberFromAddress(address) {
    const addr = address.toString();
    return addr.slice(0, 4) + '-' + addr.slice(4, 6) + '-' + addr.slice(6, 10) + '000-0000';
}
exports.makeDeviceSerialNumberFromAddress = makeDeviceSerialNumberFromAddress;
/**
 * Extracts product IDs and versioned product IDs from a given source.
 *
 * This function handles three types of input:
 * - If the source is a string, it parses the serial number to extract both `productId` and `versionedProductId`.
 * - If the source is a `HardwareDescription`, it extracts the `productId`, `versionedProductId`, `deviceProductId`,
 *   and `versionedDeviceProductId` from either the device or assembly information.
 * - If the source is a `StackInfo`, it parses the `stack_serial_number` to extract the `productId` and `versionedProductId`.
 *
 * @param source - The source from which to extract the product information. It can be a string,
 *                 `StackInfo`, or `HardwareDescription`.
 * @returns An object containing the following properties:
 *  - `deviceProductId`: The ID of the device.
 *  - `productId`: The general product ID.
 *  - `versionedDeviceProductId`: The device ID with version.
 *  - `versionedProductId`: The general product ID with version.
 *
 * @throws {Error} Throws an error if the `source` is of an invalid type.
 */
function extractProductId(source) {
    if (typeof source === 'string') {
        const { productId, versionedProductId } = parseSerialNumber(source);
        return { deviceProductId: productId, productId, versionedDeviceProductId: versionedProductId, versionedProductId };
    }
    if (isHardwareDescription(source)) {
        const productId = source.assembly ? source.assembly.id : source.device.id;
        const versionedProductId = source.assembly
            ? `${source.assembly.id}-${source.assembly.version}`
            : `${source.device.id}-${source.device.version}`;
        const deviceProductId = source.device.id;
        const versionedDeviceProductId = `${source.device.id}-${source.device.version}`;
        return { deviceProductId, productId, versionedDeviceProductId, versionedProductId };
    }
    if (isStackInfo(source)) {
        const { productId, versionedProductId } = parseSerialNumber(source.stack_serial_number);
        return { deviceProductId: productId, productId, versionedDeviceProductId: versionedProductId, versionedProductId };
    }
    throw new Error('Invalid source type. Expected string, StackInfo, or HardwareDescription.');
}
exports.extractProductId = extractProductId;
//# sourceMappingURL=hardware-description.js.map