import { ZWaveError, ZWaveErrorCodes, } from "@zwave-js/core";
import { Bytes } from "@zwave-js/shared";
import { assertNever } from "alcalzone-shared/helpers";
import { SUC_MAX_UPDATES } from "../../consts.js";
import { APPL_NODEPARM_MAX, NVM_SERIALAPI_HOST_SIZE, } from "./shared.js";
export class NVM500Adapter {
    constructor(nvm) {
        this._nvm = nvm;
    }
    _nvm;
    async get(property, required) {
        const info = this._nvm.info ?? await this._nvm.init();
        let ret;
        if (property.domain === "controller") {
            ret = await this.getControllerNVMProperty(info, property);
        }
        else if (property.domain === "lrnode") {
            throw new ZWaveError(`500 series NVM has no support for Long Range node information`, ZWaveErrorCodes.NVM_ObjectNotFound);
        }
        else {
            ret = await this.getNodeNVMProperty(property);
        }
        if (required && ret === undefined) {
            throw new ZWaveError(`NVM data for property ${JSON.stringify(property)} not found`, ZWaveErrorCodes.NVM_ObjectNotFound);
        }
        return ret;
    }
    async getOnly(property) {
        const data = await this._nvm.get(property);
        return data?.[0];
    }
    async getSingle(property, index) {
        const data = await this._nvm.getSingle(property, index);
        return data;
    }
    getAll(property) {
        return this._nvm.get(property);
    }
    async getControllerNVMProperty(info, property) {
        switch (property.type) {
            case "protocolVersion":
                return info.nvmDescriptor.protocolVersion;
            case "applicationVersion":
                return info.nvmDescriptor.firmwareVersion;
            case "protocolFileFormat":
            case "applicationFileFormat":
                // Not supported in 500 series, but we use 500 in JSON to designate a 500 series NVM
                return 500;
            case "applicationData":
                return this.getOnly("EEOFFSET_HOST_OFFSET_START_far");
            case "applicationName":
                // Not supported in 500 series
                return;
            case "homeId": {
                // 500 series stores the home ID as a number
                const homeId = await this.getOnly("EX_NVM_HOME_ID_far");
                if (homeId == undefined)
                    return;
                const ret = new Bytes(4).fill(0);
                // FIXME: BE? LE?
                ret.writeUInt32BE(homeId, 0);
                return ret;
            }
            case "learnedHomeId": {
                // 500 series stores the home ID as a number
                const homeId = await this.getOnly("NVM_HOMEID_far");
                if (homeId == undefined)
                    return;
                const ret = new Bytes(4).fill(0);
                // FIXME: BE? LE?
                ret.writeUInt32BE(homeId, 0);
                return ret;
            }
            case "nodeId":
                return this.getOnly("NVM_NODEID_far");
            case "lastNodeId":
                return this.getOnly("EX_NVM_LAST_USED_NODE_ID_START_far");
            case "staticControllerNodeId":
                return this.getOnly("EX_NVM_STATIC_CONTROLLER_NODE_ID_START_far");
            case "sucLastIndex":
                return this.getOnly("EX_NVM_SUC_LAST_INDEX_START_far");
            case "controllerConfiguration":
                return this.getOnly("EX_NVM_CONTROLLER_CONFIGURATION_far");
            case "maxNodeId":
                return this.getOnly("EX_NVM_MAX_NODE_ID_far");
            case "reservedId":
                return this.getOnly("EX_NVM_RESERVED_ID_far");
            case "systemState":
                return this.getOnly("NVM_SYSTEM_STATE");
            case "commandClasses": {
                const numCCs = await this.getOnly("EEOFFSET_CMDCLASS_LEN_far");
                const ret = await this.getAll("EEOFFSET_CMDCLASS_far");
                return ret?.slice(0, numCCs);
            }
            case "preferredRepeaters":
                return this.getOnly("NVM_PREFERRED_REPEATERS_far");
            case "appRouteLock": {
                return this.getOnly("EX_NVM_ROUTECACHE_APP_LOCK_far");
            }
            case "routeSlaveSUC": {
                return this.getOnly("EX_NVM_SUC_ROUTING_SLAVE_LIST_START_far");
            }
            case "sucPendingUpdate": {
                return this.getOnly("EX_NVM_PENDING_UPDATE_far");
            }
            case "pendingDiscovery": {
                return this.getOnly("NVM_PENDING_DISCOVERY_far");
            }
            case "nodeIds": {
                const nodeInfos = await this.getAll("EX_NVM_NODE_TABLE_START_far");
                return nodeInfos
                    ?.map((info, index) => info ? (index + 1) : undefined)
                    .filter((id) => id != undefined);
            }
            case "virtualNodeIds": {
                const ret = await this.getOnly("EX_NVM_BRIDGE_NODEPOOL_START_far");
                return ret ?? [];
            }
            case "sucUpdateEntries": {
                const ret = await this.getAll("EX_NVM_SUC_NODE_LIST_START_far");
                return ret?.filter(Boolean);
            }
            case "watchdogStarted":
                return this.getOnly("EEOFFSET_WATCHDOG_STARTED_far");
            case "powerLevelNormal":
                return this.getAll("EEOFFSET_POWERLEVEL_NORMAL_far");
            case "powerLevelLow":
                return this.getAll("EEOFFSET_POWERLEVEL_LOW_far");
            case "powerMode":
                return this.getOnly("EEOFFSET_MODULE_POWER_MODE_far");
            case "powerModeExtintEnable":
                return this.getOnly("EEOFFSET_MODULE_POWER_MODE_EXTINT_ENABLE_far");
            case "powerModeWutTimeout":
                return this.getOnly("EEOFFSET_MODULE_POWER_MODE_WUT_TIMEOUT_far");
            case "sucAwarenessPushNeeded":
            case "lastNodeIdLR":
            case "maxNodeIdLR":
            case "reservedIdLR":
            case "primaryLongRangeChannelId":
            case "dcdcConfig":
            case "lrNodeIds":
            case "includedInsecurely":
            case "includedSecurelyInsecureCCs":
            case "includedSecurelySecureCCs":
            case "rfRegion":
            case "txPower":
            case "measured0dBm":
            case "enablePTI":
            case "maxTXPower":
            case "nodeIdType":
            case "isListening":
            case "optionalFunctionality":
            case "genericDeviceClass":
            case "specificDeviceClass":
                // Not supported on 500 series, 700+ series only
                return;
            default:
                assertNever(property.type);
        }
    }
    async getNodeNVMProperty(property) {
        switch (property.type) {
            case "info": {
                const nodeId = property.nodeId;
                const nodeInfo = await this.getSingle("EX_NVM_NODE_TABLE_START_far", nodeId - 1);
                const sucUpdateIndex = await this.getSingle("EX_NVM_SUC_CONTROLLER_LIST_START_far", nodeId - 1) ?? 0xff;
                const neighbors = await this.getSingle("EX_NVM_ROUTING_TABLE_START_far", nodeId - 1) ?? [];
                if (!nodeInfo)
                    return;
                return {
                    nodeId,
                    ...nodeInfo,
                    neighbors,
                    sucUpdateIndex,
                };
            }
            case "routes": {
                const lwr = await this.getSingle("EX_NVM_ROUTECACHE_START_far", property.nodeId - 1);
                const nlwr = await this.getSingle("EX_NVM_ROUTECACHE_NLWR_SR_START_far", property.nodeId - 1);
                return { lwr, nlwr };
            }
        }
    }
    setOnly(property, value) {
        return this._nvm.set(property, [value]);
    }
    setSingle(property, index, value) {
        return this._nvm.setSingle(property, index, value);
    }
    setAll(property, value) {
        return this._nvm.set(property, value);
    }
    set(property, value) {
        if (property.domain === "controller") {
            return this.setControllerNVMProperty(property, value);
        }
        else if (property.domain === "lrnode") {
            throw new ZWaveError(`500 series NVM has no support for Long Range node information`, ZWaveErrorCodes.NVM_ObjectNotFound);
        }
        else {
            return this.setNodeNVMProperty(property, value);
        }
    }
    async setControllerNVMProperty(property, value) {
        switch (property.type) {
            case "protocolVersion":
            case "applicationVersion":
                // Only written during erase
                return;
            case "protocolFileFormat":
            case "applicationFileFormat":
                // Cannot be written
                return;
            case "applicationData":
                return this.setOnly("EEOFFSET_HOST_OFFSET_START_far", value ?? new Bytes(NVM_SERIALAPI_HOST_SIZE).fill(0xff));
            case "applicationName":
                // Not supported in 500 series
                return;
            case "homeId": {
                // 500 series stores the home ID as a number
                const homeId = value.readUInt32BE(0);
                return this.setOnly("EX_NVM_HOME_ID_far", homeId);
            }
            case "learnedHomeId": {
                // 500 series stores the home ID as a number
                const learnedHomeId = value?.readUInt32BE(0) ?? 0x00000000;
                return this.setOnly("NVM_HOMEID_far", learnedHomeId);
            }
            case "nodeId":
                return this.setOnly("NVM_NODEID_far", value);
            case "lastNodeId":
                return this.setOnly("EX_NVM_LAST_USED_NODE_ID_START_far", value);
            case "staticControllerNodeId":
                return this.setOnly("EX_NVM_STATIC_CONTROLLER_NODE_ID_START_far", value);
            case "sucLastIndex":
                return this.setOnly("EX_NVM_SUC_LAST_INDEX_START_far", value);
            case "controllerConfiguration":
                return this.setOnly("EX_NVM_CONTROLLER_CONFIGURATION_far", value);
            case "maxNodeId":
                return this.setOnly("EX_NVM_MAX_NODE_ID_far", value);
            case "reservedId":
                return this.setOnly("EX_NVM_RESERVED_ID_far", value);
            case "systemState":
                return this.setOnly("NVM_SYSTEM_STATE", value);
            case "commandClasses": {
                await this.setOnly("EEOFFSET_CMDCLASS_LEN_far", value.length);
                const CCs = Array.from({ length: APPL_NODEPARM_MAX })
                    .fill(0xff);
                for (let i = 0; i < value.length; i++) {
                    if (i < APPL_NODEPARM_MAX) {
                        CCs[i] = value[i];
                    }
                }
                await this.setAll("EEOFFSET_CMDCLASS_far", CCs);
                return;
            }
            case "preferredRepeaters":
                return this.setOnly("NVM_PREFERRED_REPEATERS_far", value);
            case "appRouteLock": {
                return this.setOnly("EX_NVM_ROUTECACHE_APP_LOCK_far", value);
            }
            case "routeSlaveSUC": {
                return this.setOnly("EX_NVM_SUC_ROUTING_SLAVE_LIST_START_far", value);
            }
            case "sucPendingUpdate": {
                return this.setOnly("EX_NVM_PENDING_UPDATE_far", value);
            }
            case "pendingDiscovery": {
                return this.setOnly("NVM_PENDING_DISCOVERY_far", value);
            }
            case "nodeIds":
                // Cannot be written. Is implied by the node info table
                return;
            case "virtualNodeIds": {
                return this.setOnly("EX_NVM_BRIDGE_NODEPOOL_START_far", value);
            }
            case "sucUpdateEntries": {
                const entries = value;
                const sucUpdateEntries = Array
                    .from({
                    length: SUC_MAX_UPDATES,
                })
                    .fill(undefined);
                for (let i = 0; i < entries.length; i++) {
                    if (i < SUC_MAX_UPDATES) {
                        sucUpdateEntries[i] = entries[i];
                    }
                }
                return this.setAll("EX_NVM_SUC_NODE_LIST_START_far", sucUpdateEntries);
            }
            case "watchdogStarted":
                return this.setOnly("EEOFFSET_WATCHDOG_STARTED_far", value);
            case "powerLevelNormal":
                return this.setAll("EEOFFSET_POWERLEVEL_NORMAL_far", value);
            case "powerLevelLow":
                return this.setAll("EEOFFSET_POWERLEVEL_LOW_far", value);
            case "powerMode":
                return this.setOnly("EEOFFSET_MODULE_POWER_MODE_far", value);
            case "powerModeExtintEnable":
                return this.setOnly("EEOFFSET_MODULE_POWER_MODE_EXTINT_ENABLE_far", value);
            case "powerModeWutTimeout":
                return this.setOnly("EEOFFSET_MODULE_POWER_MODE_WUT_TIMEOUT_far", value);
            case "sucAwarenessPushNeeded":
            case "lastNodeIdLR":
            case "maxNodeIdLR":
            case "reservedIdLR":
            case "primaryLongRangeChannelId":
            case "dcdcConfig":
            case "lrNodeIds":
            case "includedInsecurely":
            case "includedSecurelyInsecureCCs":
            case "includedSecurelySecureCCs":
            case "rfRegion":
            case "txPower":
            case "measured0dBm":
            case "enablePTI":
            case "maxTXPower":
            case "nodeIdType":
            case "isListening":
            case "optionalFunctionality":
            case "genericDeviceClass":
            case "specificDeviceClass":
                // Not supported on 500 series, 700+ series only
                return;
            default:
                assertNever(property.type);
        }
    }
    async setNodeNVMProperty(property, value) {
        switch (property.type) {
            case "info": {
                const nodeId = property.nodeId;
                const node = value;
                await this.setSingle("EX_NVM_NODE_TABLE_START_far", nodeId - 1, node
                    ? {
                        isListening: node.isListening,
                        isFrequentListening: node.isFrequentListening,
                        isRouting: node.isRouting,
                        supportedDataRates: node.supportedDataRates,
                        protocolVersion: node.protocolVersion,
                        optionalFunctionality: node.optionalFunctionality,
                        nodeType: node.nodeType,
                        supportsSecurity: node.supportsSecurity,
                        supportsBeaming: node.supportsBeaming,
                        genericDeviceClass: node.genericDeviceClass,
                        specificDeviceClass: node.specificDeviceClass
                            ?? null,
                    }
                    : undefined);
                await this.setSingle("EX_NVM_SUC_CONTROLLER_LIST_START_far", nodeId - 1, node?.sucUpdateIndex ?? 0xfe);
                await this.setSingle("EX_NVM_ROUTING_TABLE_START_far", nodeId - 1, node?.neighbors);
            }
            case "routes": {
                const nodeId = property.nodeId;
                const routes = value;
                await this.setSingle("EX_NVM_ROUTECACHE_START_far", nodeId - 1, routes.lwr);
                await this.setSingle("EX_NVM_ROUTECACHE_NLWR_SR_START_far", property.nodeId - 1, routes.nlwr);
            }
        }
    }
    async delete(_property) {
        throw new Error("Method not implemented.");
    }
    hasPendingChanges() {
        // We don't buffer changes
        return false;
    }
    async commit() {
        // We don't buffer changes at the moment
    }
}
//# sourceMappingURL=adapter.js.map