"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isAnalogDataType = isAnalogDataType;
exports.getCluster = getCluster;
exports.getClusterAttribute = getClusterAttribute;
exports.getClusterCommand = getClusterCommand;
exports.getClusterCommandResponse = getClusterCommandResponse;
exports.getGlobalCommand = getGlobalCommand;
exports.isClusterName = isClusterName;
exports.getFoundationCommand = getFoundationCommand;
exports.getFoundationCommandByName = getFoundationCommandByName;
exports.processAttributeWrite = processAttributeWrite;
exports.processAttributePreRead = processAttributePreRead;
exports.processAttributePostRead = processAttributePostRead;
exports.processParameterWrite = processParameterWrite;
exports.processParameterRead = processParameterRead;
const cluster_1 = require("./definition/cluster");
const datatypes_1 = require("./definition/datatypes");
const enums_1 = require("./definition/enums");
const foundation_1 = require("./definition/foundation");
const status_1 = require("./definition/status");
const zclStatusError_1 = require("./zclStatusError");
/** Runtime fast lookup */
const ZCL_CLUSTERS_ID_TO_NAMES = (() => {
    const map = new Map();
    for (const clusterName in cluster_1.Clusters) {
        const cluster = cluster_1.Clusters[clusterName];
        map.set(cluster.ID, clusterName);
    }
    return map;
})();
function isAnalogDataType(dataType) {
    return (dataType === enums_1.DataType.UINT8 ||
        dataType === enums_1.DataType.UINT16 ||
        dataType === enums_1.DataType.UINT24 ||
        dataType === enums_1.DataType.UINT32 ||
        dataType === enums_1.DataType.UINT40 ||
        dataType === enums_1.DataType.UINT48 ||
        dataType === enums_1.DataType.UINT56 ||
        dataType === enums_1.DataType.INT8 ||
        dataType === enums_1.DataType.INT16 ||
        dataType === enums_1.DataType.INT24 ||
        dataType === enums_1.DataType.INT32 ||
        dataType === enums_1.DataType.INT40 ||
        dataType === enums_1.DataType.INT48 ||
        dataType === enums_1.DataType.INT56 ||
        dataType === enums_1.DataType.SEMI_PREC ||
        dataType === enums_1.DataType.SINGLE_PREC ||
        dataType === enums_1.DataType.DOUBLE_PREC ||
        dataType === enums_1.DataType.TOD ||
        dataType === enums_1.DataType.DATE ||
        dataType === enums_1.DataType.UTC);
}
function getCluster(key, manufacturerCode = undefined, customClusters = {}) {
    let cluster;
    if (typeof key === "number") {
        // custom clusters have priority over Zcl clusters, except in case of better match (see below)
        for (const clusterName in customClusters) {
            const foundCluster = customClusters[clusterName];
            if (foundCluster.ID === key) {
                // priority on first match when matching only ID
                if (cluster === undefined) {
                    cluster = foundCluster;
                }
                if (manufacturerCode && foundCluster.manufacturerCode === manufacturerCode) {
                    cluster = foundCluster;
                    break;
                }
                if (!foundCluster.manufacturerCode) {
                    cluster = foundCluster;
                    break;
                }
            }
        }
        if (!cluster) {
            const zclName = ZCL_CLUSTERS_ID_TO_NAMES.get(key);
            if (zclName) {
                const foundCluster = cluster_1.Clusters[zclName];
                // TODO: can remove all below once all manuf-specific moved to ZHC
                // priority on first match when matching only ID
                if (cluster === undefined) {
                    cluster = foundCluster;
                }
                if (manufacturerCode && foundCluster.manufacturerCode === manufacturerCode) {
                    cluster = foundCluster;
                }
                else if (foundCluster.manufacturerCode === undefined) {
                    cluster = foundCluster;
                }
            }
        }
        // TODO: cluster.ID can't be undefined?
        if (!cluster || cluster.ID === undefined) {
            cluster = { name: `${key}`, ID: key, attributes: {}, commands: {}, commandsResponse: {} };
            // XXX: align behavior with string key?
            // throw new ZclStatusError(Status.UNSUPPORTED_CLUSTER, `${key}`);
        }
    }
    else {
        cluster = key in customClusters ? customClusters[key] : cluster_1.Clusters[key];
        // TODO: cluster.ID can't be undefined?
        if (!cluster || cluster.ID === undefined) {
            throw new zclStatusError_1.ZclStatusError(status_1.Status.UNSUPPORTED_CLUSTER, key);
        }
    }
    return cluster;
}
function getClusterAttribute(cluster, key, manufacturerCode) {
    const attributes = cluster.attributes;
    if (typeof key === "number") {
        let partialMatchAttr;
        for (const attrKey in attributes) {
            const attr = attributes[attrKey];
            if (attr.ID === key) {
                if (manufacturerCode !== undefined && attr.manufacturerCode === manufacturerCode) {
                    return attr;
                }
                if (attr.manufacturerCode === undefined) {
                    partialMatchAttr = attr;
                }
            }
        }
        return partialMatchAttr;
    }
    return attributes[key];
    // XXX: align behavior with cmds?
    // throw new ZclStatusError(Status.UNSUPPORTED_ATTRIBUTE, `${cluster.name}:${key}`);
}
function getClusterCommand(cluster, key) {
    const commands = cluster.commands;
    if (typeof key === "number") {
        for (const cmdKey in commands) {
            const cmd = commands[cmdKey];
            if (cmd.ID === key) {
                return cmd;
            }
        }
    }
    else {
        const cmd = commands[key];
        if (cmd) {
            return cmd;
        }
    }
    throw new zclStatusError_1.ZclStatusError(status_1.Status.UNSUP_COMMAND, `${cluster.name}:${key}`);
}
function getClusterCommandResponse(cluster, key) {
    const commandResponses = cluster.commandsResponse;
    if (typeof key === "number") {
        for (const cmdKey in commandResponses) {
            const cmd = commandResponses[cmdKey];
            if (cmd.ID === key) {
                return cmd;
            }
        }
    }
    else {
        const cmd = commandResponses[key];
        if (cmd) {
            return cmd;
        }
    }
    throw new zclStatusError_1.ZclStatusError(status_1.Status.UNSUP_COMMAND, `response ${cluster.name}:${key}`);
}
function getGlobalCommandNameById(id) {
    for (const commandName in foundation_1.Foundation) {
        if (foundation_1.Foundation[commandName].ID === id) {
            return commandName;
        }
    }
    throw new zclStatusError_1.ZclStatusError(status_1.Status.UNSUP_COMMAND, `foundation:${id}`);
}
function getGlobalCommand(key) {
    const name = typeof key === "number" ? getGlobalCommandNameById(key) : key;
    const command = foundation_1.Foundation[name];
    if (!command) {
        throw new zclStatusError_1.ZclStatusError(status_1.Status.UNSUP_COMMAND, `foundation:${key}`);
    }
    return command;
}
function isClusterName(name) {
    return name in cluster_1.Clusters;
}
function getFoundationCommand(id) {
    for (const commandName in foundation_1.Foundation) {
        const command = foundation_1.Foundation[commandName];
        if (command.ID === id) {
            return command;
        }
    }
    throw new zclStatusError_1.ZclStatusError(status_1.Status.UNSUP_COMMAND, `foundation:${id}`);
}
function getFoundationCommandByName(name) {
    const command = foundation_1.Foundation[name];
    if (command === undefined) {
        throw new zclStatusError_1.ZclStatusError(status_1.Status.UNSUP_COMMAND, `foundation:${name}`);
    }
    return command;
}
/** Check if value is equal to either min, max, minRef or maxRef */
function isMinOrMax(entry, value) {
    if (value === entry.max || value === entry.min) {
        return true;
    }
    return false;
}
function processRestrictions(entry, value) {
    if (entry.min !== undefined && value < entry.min) {
        throw new zclStatusError_1.ZclStatusError(status_1.Status.INVALID_VALUE, `${entry.name} requires min of ${entry.min}`);
    }
    if (entry.minExcl !== undefined && value <= entry.minExcl) {
        throw new zclStatusError_1.ZclStatusError(status_1.Status.INVALID_VALUE, `${entry.name} requires min exclusive of ${entry.minExcl}`);
    }
    if (entry.max !== undefined && value > entry.max) {
        throw new zclStatusError_1.ZclStatusError(status_1.Status.INVALID_VALUE, `${entry.name} requires max of ${entry.max}`);
    }
    if (entry.maxExcl !== undefined && value >= entry.maxExcl) {
        throw new zclStatusError_1.ZclStatusError(status_1.Status.INVALID_VALUE, `${entry.name} requires max exclusive of ${entry.maxExcl}`);
    }
    if (entry.length !== undefined && value.length !== entry.length) {
        throw new zclStatusError_1.ZclStatusError(status_1.Status.INVALID_VALUE, `${entry.name} requires length of ${entry.length}`);
    }
    if (entry.minLen !== undefined && value.length < entry.minLen) {
        throw new zclStatusError_1.ZclStatusError(status_1.Status.INVALID_VALUE, `${entry.name} requires min length of ${entry.minLen}`);
    }
    if (entry.maxLen !== undefined && value.length > entry.maxLen) {
        throw new zclStatusError_1.ZclStatusError(status_1.Status.INVALID_VALUE, `${entry.name} requires max length of ${entry.maxLen}`);
    }
}
function processAttributeWrite(attribute, value) {
    if (attribute.write !== true) {
        throw new zclStatusError_1.ZclStatusError(status_1.Status.NOT_AUTHORIZED, `${attribute.name} (${attribute.ID}) is not writable`);
    }
    if (value == null) {
        return attribute.default !== undefined ? attribute.default : value /* XXX: dangerous fallback */;
    }
    // if default, always valid
    if (attribute.default === value) {
        return value;
    }
    if (Number.isNaN(value)) {
        if (attribute.default === undefined) {
            const nonValue = datatypes_1.ZCL_TYPE_INVALID_BY_TYPE[attribute.type];
            if (nonValue === undefined) {
                throw new zclStatusError_1.ZclStatusError(status_1.Status.INVALID_FIELD, `${attribute.name} (${attribute.ID}) does not have a default nor a non-value`);
            }
            return nonValue;
        }
        return attribute.default;
    }
    processRestrictions(attribute, value);
    return value;
}
function processAttributePreRead(attribute) {
    if (attribute.read === false) {
        throw new zclStatusError_1.ZclStatusError(status_1.Status.NOT_AUTHORIZED, `${attribute.name} (${attribute.ID}) is not readable`);
    }
}
function processAttributePostRead(attribute, value) {
    // should never happen?
    if (value == null) {
        return value;
    }
    // if default, always valid
    if (attribute.default === value) {
        return value;
    }
    // if type does not have an `invalid` (undefined) it won't match since value is checked above
    if (value === datatypes_1.ZCL_TYPE_INVALID_BY_TYPE[attribute.type]) {
        // if value is same as max or min, ignore invalid sentinel
        if (isMinOrMax(attribute, value)) {
            return value;
        }
        // return NaN for both number & bigint to keep logic consistent
        return Number.NaN;
    }
    processRestrictions(attribute, value);
    return value;
}
function processParameterWrite(parameter, value) {
    // should never happen?
    if (value == null) {
        return value;
    }
    if (Number.isNaN(value)) {
        const nonValue = datatypes_1.ZCL_TYPE_INVALID_BY_TYPE[parameter.type];
        if (nonValue === undefined) {
            throw new zclStatusError_1.ZclStatusError(status_1.Status.INVALID_FIELD, `${parameter.name} does not have a non-value`);
        }
        return nonValue;
    }
    processRestrictions(parameter, value);
    return value;
}
function processParameterRead(parameter, value) {
    // should never happen?
    if (value == null) {
        return value;
    }
    // if type does not have an `invalid` (undefined) it won't match since value is checked above
    if (value === datatypes_1.ZCL_TYPE_INVALID_BY_TYPE[parameter.type]) {
        // if value is same as max or min, ignore invalid sentinel
        if (isMinOrMax(parameter, value)) {
            return value;
        }
        // return NaN for both number & bigint to keep logic consistent
        return Number.NaN;
    }
    processRestrictions(parameter, value);
    return value;
}
//# sourceMappingURL=utils.js.map