var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
var __read = (this && this.__read) || function (o, n) {
    var m = typeof Symbol === "function" && o[Symbol.iterator];
    if (!m) return o;
    var i = m.call(o), r, ar = [], e;
    try {
        while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
    }
    catch (error) { e = { error: error }; }
    finally {
        try {
            if (r && !r.done && (m = i["return"])) m.call(i);
        }
        finally { if (e) throw e.error; }
    }
    return ar;
};
var __spread = (this && this.__spread) || function () {
    for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
    return ar;
};
var __values = (this && this.__values) || function(o) {
    var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
    if (m) return m.call(o);
    if (o && typeof o.length === "number") return {
        next: function () {
            if (o && i >= o.length) o = void 0;
            return { value: o && o[i++], done: !o };
        }
    };
    throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
// tslint:disable: no-implicit-dependencies
import '@iotize/tap';
import '@iotize/tap/service/all';
import { sleep, deepCopy } from '@iotize/common/utility';
import { StepOperations } from '@iotize/common/task-manager';
import { AppPathType } from "@iotize/tap";
import { WRITE_CONFIGURATOR_CONFIG } from './generated/write-configurator-config';
import { enumKeyToEnumValue, getProfileIdFromConfig, isSpecialProfileId } from './util';
import { SpecialFeature } from '@iotize/tap';
import { ADMIN_USER, DEFAULT_HASH_ITERATION_COUNT, SPECIAL_USERS, SUPERVISOR_USER } from './default-config';
import { hexStringToBuffer } from '@iotize/common/byte-converter';
import { TapConfiguratorError } from './errors';
import { DEFAULT_SCRAM_SALT_GENERATOR, ScramAuth, hashLoginPassword } from '@iotize/tap/auth';
import { NumberConverter, TapStreamWriter } from '@iotize/tap/client/impl';
import { validateTapConfiguratorConfig } from './validate-tap-config';
import { VariableMetaConfigConverter } from '@iotize/tap/service/impl/variable';
import { FirmwareState, VariableType, converters } from '@iotize/tap/service/all';
import { TapRequestFrame } from '@iotize/tap/client/api';
import { getConverterFromVariableType, variableDataTypeToByteSize } from '@iotize/tap/data';
var variableMetaConverter = new VariableMetaConfigConverter();
var IGNORE_KEYS = [
    'version',
    'tap.security'
];
var USER_CREATE_KEY = 'data.profiles[]';
var CREATE_VARIABLE_CONFIG_KEY = 'data.variables[]';
function isHexString(input) {
    if (typeof input !== 'string') {
        return false;
    }
    return !!input.match(/^(0x)?([a-fA-F0-9])*$/);
}
export function tapConfigToServiceCalls(config, options) {
    var _a;
    function addCalls(calls, newCalls) {
        if (Array.isArray(newCalls)) {
            calls.push.apply(calls, __spread(newCalls));
        }
        else {
            calls.push(newCalls);
        }
    }
    function createWriteConfigCall(key, callContext) {
        if (!(key in WRITE_CONFIGURATOR_CONFIG)) {
            throw new Error("Invalid API configuration. Configuration key \"" + key + "\" does not exist");
        }
        var callFactory = WRITE_CONFIGURATOR_CONFIG[key];
        return callFactory(callContext);
    }
    function baseConfigObjectToCalls(entry, configKey, pathParameter) {
        var e_1, _a;
        var calls = [];
        if (entry === undefined) {
            return calls;
        }
        else if (Array.isArray(entry)) {
            var newConfigKey = configKey + '[]';
            try {
                for (var entry_1 = __values(entry), entry_1_1 = entry_1.next(); !entry_1_1.done; entry_1_1 = entry_1.next()) {
                    var itemValue = entry_1_1.value;
                    if (itemValue.id || itemValue.id === 0) {
                        pathParameter = itemValue.id;
                    }
                    if (pathParameter === undefined) {
                        throw new Error("Item must have an id for key \"" + newConfigKey);
                        // TODO we need to know parent profile ? 
                    }
                    // If there is a create instance function
                    if (newConfigKey in WRITE_CONFIGURATOR_CONFIG) {
                        if (newConfigKey === USER_CREATE_KEY && isSpecialProfileId(pathParameter)) {
                            // If it's a special user profile, we dont need the create call
                        }
                        else {
                            // throw new Error(`Missing create method for config key "${newConfigKey}"`);
                            var createBody = void 0;
                            if (newConfigKey === CREATE_VARIABLE_CONFIG_KEY) {
                                if (itemValue.rawMeta) {
                                    createBody = itemValue.rawMeta.length;
                                }
                            }
                            var createCallParam = {
                                firmwareVersion: options.firmwareVersion,
                                service: options.service,
                                value: createBody,
                                param: pathParameter
                            };
                            if (createBody === undefined) {
                                delete createCallParam.value;
                            }
                            try {
                                var newCalls = WRITE_CONFIGURATOR_CONFIG[newConfigKey](createCallParam);
                                addCalls(calls, newCalls);
                            }
                            catch (err) {
                                throw TapConfiguratorError.internalError("Cannot generate configuration requests from configuration key " + newConfigKey + ". Cause: " + err.message, err);
                            }
                        }
                    }
                    calls.push.apply(calls, __spread(baseConfigObjectToCalls(itemValue, newConfigKey, pathParameter)));
                }
            }
            catch (e_1_1) { e_1 = { error: e_1_1 }; }
            finally {
                try {
                    if (entry_1_1 && !entry_1_1.done && (_a = entry_1.return)) _a.call(entry_1);
                }
                finally { if (e_1) throw e_1.error; }
            }
        }
        else {
            if (configKey + ".$" in WRITE_CONFIGURATOR_CONFIG) {
                try {
                    addCalls(calls, createWriteConfigCall(configKey + ".$", {
                        firmwareVersion: options.firmwareVersion,
                        service: options.service,
                        value: entry,
                        param: pathParameter
                    }));
                }
                catch (err) {
                    throw TapConfiguratorError.internalError("Cannot generate create resource request for configuration key " + configKey + " for identifier \"" + pathParameter + "\". Cause: " + err.message, err);
                }
            }
            else {
                for (var subKey in entry) {
                    var newConfigKey = configKey ? configKey + "." + subKey : subKey;
                    var value = entry[subKey];
                    // We skip version key as it's a special key
                    if (value === undefined) {
                        continue;
                    }
                    else if (IGNORE_KEYS.indexOf(newConfigKey) >= 0) {
                        continue;
                    }
                    else if (pathParameter !== undefined && subKey === 'id' && value === pathParameter) {
                        // Skip id field
                        continue;
                    }
                    else if (newConfigKey in WRITE_CONFIGURATOR_CONFIG) {
                        var callContext = {
                            firmwareVersion: options.firmwareVersion,
                            service: options.service,
                            value: value,
                            param: pathParameter
                        };
                        addCalls(calls, createWriteConfigCall(newConfigKey, callContext));
                    }
                    else if (typeof value === 'object') {
                        calls.push.apply(calls, __spread(baseConfigObjectToCalls(value, newConfigKey, pathParameter)));
                    }
                    else {
                        throw new Error("Invalid config key \"" + newConfigKey + "\" with value " + value + ". " + (pathParameter ? "Current path parameter: " + pathParameter : ''));
                    }
                }
            }
        }
        return calls;
    }
    var flattenConfig = toFlatTapConfig(config, options);
    var service = options.service;
    var calls = [
        service.interface.putConfigVersionCall("255.255.65535")
    ];
    var security = ((_a = config.tap) === null || _a === void 0 ? void 0 : _a.security) || {};
    if (security.scramActivated) {
        // Usually security options are encoded on 2 bytes
        // However to enable scram during configuration, we must give 0x10 value 
        // Don't know why
        calls.push({
            method: TapRequestFrame.MethodType.PUT,
            path: '/1024//6',
            body: Uint8Array.from([0x10])
        });
    }
    calls.push.apply(calls, __spread(baseConfigObjectToCalls(flattenConfig, '', undefined)));
    if (security.scramActivated) {
        security.hashPassword = true;
    }
    calls.push(service.interface.putSecurityOptionsCall(security));
    calls.push(service.interface.putConfigVersionCall(config.version || '1.0.0'));
    return calls;
}
function arrayPadEnd(data, length, padValue) {
    if (padValue === void 0) { padValue = 0; }
    if (data.length >= length) {
        return data;
    }
    else {
        return TapStreamWriter.create(length)
            .writeBytes(data)
            .writeBytes(new Uint8Array(length - data.length).fill(padValue, 0, length - data.length))
            .toBytes;
    }
}
function arrayPadStart(data, length, padValue) {
    if (padValue === void 0) { padValue = 0; }
    if (data.length >= length) {
        return data;
    }
    else {
        return TapStreamWriter.create(length)
            .writeBytes(new Uint8Array(length - data.length).fill(padValue, 0, length - data.length))
            .writeBytes(data)
            .toBytes;
    }
}
function invalidVariableValueError(bundle, variable, msg) {
    return TapConfiguratorError.invalidConfiguration("data.bundle[" + bundle.id + "].variable[" + variable.id + "].value " + msg);
}
export function encodeInitialVariableValue(variable, bundle) {
    var variableLength = variable.length || 1;
    var variableDataType = variable.dataType === undefined ? VariableType.Data.UINT8 : VariableType.Data[variable.dataType];
    var itemByteSize = variableDataTypeToByteSize(variableDataType);
    function _encodeItem(value, index) {
        if (index === void 0) { index = 0; }
        var result;
        if (value instanceof Uint8Array) {
            result = value;
        }
        else if (isHexString(value)) {
            result = hexStringToBuffer(value);
        }
        else if (typeof value === 'number') {
            var converter = getConverterFromVariableType(variableDataType, 1);
            if (!converter) {
                throw invalidVariableValueError(bundle, variable, "cannot be converted to a buffer. No registered converter exist for this variable type.");
            }
            result = converter.encode(value);
        }
        else {
            throw invalidVariableValueError(bundle, variable, "has type " + typeof value + " which is not supported by the configurator");
        }
        if (result.length > itemByteSize) {
            throw invalidVariableValueError(bundle, variable, "at index " + index + " is on " + result.length + " bytes but expecting a maximum length of " + itemByteSize + " byte(s)");
        }
        else {
            result = arrayPadStart(result, itemByteSize);
        }
        return result;
    }
    var encodedValue;
    if (typeof variable.value === 'string' && variableDataType === VariableType.Data.ASCII) {
        encodedValue = converters.ascii.encode(variable.value);
    }
    else if (Array.isArray(variable.value)) {
        var buffers = variable.value
            .map(function (v, index) {
            return _encodeItem(v, index);
        });
        encodedValue = TapStreamWriter.mergeArrays.apply(TapStreamWriter, __spread(buffers));
    }
    else {
        encodedValue = _encodeItem(variable.value);
    }
    var byteLength = variableLength * itemByteSize;
    if (encodedValue.length > byteLength) {
        throw invalidVariableValueError(bundle, variable, "is encoded on " + encodedValue.length + " byte(s) but you have declared a maximum length of " + byteLength + " byte(s)");
    }
    return arrayPadEnd(encodedValue, byteLength);
}
export function toVariableFlatConfig(variable, bundle) {
    var variableLength = variable.length || 1;
    var variableDataType = variable.dataType === undefined ? VariableType.Data.UINT8 : VariableType.Data[variable.dataType];
    var variableType = {
        domain: variable.domain === undefined ? VariableType.Domain.TARGET : VariableType.Domain[variable.domain],
        data: variableDataType
    };
    var variableFlatConfig = {
        id: variable.id,
        length: variableLength,
        type: {
            domain: VariableType.Domain[variableType.domain],
            data: VariableType.Data[variableType.data]
        },
        bundleId: bundle.id
    };
    if (variable.address !== undefined) {
        variableFlatConfig.address = variable.address;
    }
    if (variable.rawMeta !== undefined) {
        variableFlatConfig.rawMeta = typeof variable.rawMeta === 'string' ? hexStringToBuffer(variable.rawMeta) : variable.rawMeta;
    }
    if (variable.modbusAddress !== undefined) {
        variableFlatConfig.modbusAddress = variable.modbusAddress;
    }
    if (variable.meta) {
        if (typeof variable.meta === 'object') {
            variableFlatConfig.rawMeta = variableMetaConverter.encode(variable.meta);
        }
        else {
            variableFlatConfig.rawMeta = hexStringToBuffer(variable.meta);
        }
        // delete variable.meta;
    }
    if (variable.value !== undefined && variable.value !== null) {
        variableFlatConfig.value = encodeInitialVariableValue(variable, bundle);
    }
    return variableFlatConfig;
}
export function toFlatTapConfig(config, options) {
    var e_2, _a, e_3, _b, e_4, _c, e_5, _d, e_6, _e, e_7, _f, e_8, _g;
    var _h, _j, _k, _l, _m, _o, _p;
    var configCopy = deepCopy(config);
    var flatProfiles = [];
    if (configCopy.target) {
        delete configCopy.target.type;
    }
    if (configCopy.data) {
        // Flaten profiles
        if (configCopy.data.profiles) {
            try {
                for (var _q = __values((_h = configCopy.data) === null || _h === void 0 ? void 0 : _h.profiles), _r = _q.next(); !_r.done; _r = _q.next()) {
                    var profile = _r.value;
                    var flatenProfile = profile;
                    if (flatenProfile.id === undefined) {
                        flatenProfile.id = getProfileIdFromConfig(flatenProfile);
                    }
                    if (isSpecialProfileId(flatenProfile.id)) {
                        delete flatenProfile.username;
                    }
                    var subProfiles = flatenProfile.users || [];
                    delete flatenProfile.users;
                    flatProfiles.push(flatenProfile);
                    try {
                        for (var subProfiles_1 = (e_3 = void 0, __values(subProfiles)), subProfiles_1_1 = subProfiles_1.next(); !subProfiles_1_1.done; subProfiles_1_1 = subProfiles_1.next()) {
                            var subProfile = subProfiles_1_1.value;
                            subProfile.alias = flatenProfile.id;
                            flatProfiles.push(subProfile);
                            if (isSpecialProfileId(subProfile.id)) {
                                delete subProfile.username;
                            }
                        }
                    }
                    catch (e_3_1) { e_3 = { error: e_3_1 }; }
                    finally {
                        try {
                            if (subProfiles_1_1 && !subProfiles_1_1.done && (_b = subProfiles_1.return)) _b.call(subProfiles_1);
                        }
                        finally { if (e_3) throw e_3.error; }
                    }
                }
            }
            catch (e_2_1) { e_2 = { error: e_2_1 }; }
            finally {
                try {
                    if (_r && !_r.done && (_a = _q.return)) _a.call(_q);
                }
                finally { if (e_2) throw e_2.error; }
            }
        }
        // Flaten acls and special features
        var flatSpecialFeatures = [];
        var flatVariables = [];
        if (configCopy.data.bundles) {
            try {
                for (var _s = __values(configCopy.data.bundles), _t = _s.next(); !_t.done; _t = _s.next()) {
                    var bundle = _t.value;
                    var acls = bundle.acls || {};
                    delete bundle.acls;
                    var flatAcls = [];
                    var _loop_1 = function (username) {
                        var acl = acls[username];
                        var profile = flatProfiles.find(function (p) { return p.username === username; });
                        if (!profile) {
                            profile = SPECIAL_USERS.find(function (p) { return p.username === username; });
                            if (!profile) {
                                throw new Error("Cannot find profile with username \"" + username + "\""); // TODO proper error
                            }
                        }
                        flatAcls.push({
                            groupId: profile.id,
                            read: acl.read,
                            write: acl.write
                        });
                    };
                    for (var username in acls) {
                        _loop_1(username);
                    }
                    if (flatAcls.length > 0) {
                        bundle.acl = flatAcls;
                    }
                    var variables = bundle.variables || [];
                    delete bundle.variables;
                    try {
                        for (var variables_1 = (e_5 = void 0, __values(variables)), variables_1_1 = variables_1.next(); !variables_1_1.done; variables_1_1 = variables_1.next()) {
                            var variable = variables_1_1.value;
                            flatVariables.push(toVariableFlatConfig(variable, bundle));
                        }
                    }
                    catch (e_5_1) { e_5 = { error: e_5_1 }; }
                    finally {
                        try {
                            if (variables_1_1 && !variables_1_1.done && (_d = variables_1.return)) _d.call(variables_1);
                        }
                        finally { if (e_5) throw e_5.error; }
                    }
                    var features = bundle.features || [];
                    delete bundle.features;
                    var _loop_2 = function (featureKey) {
                        var featureId = enumKeyToEnumValue(featureKey, SpecialFeature);
                        if (flatSpecialFeatures.find(function (f) { return f.id === featureId; })) {
                            throw new Error("Special feature \"" + featureId + "\" has already be given to another bundle");
                        }
                        flatSpecialFeatures.push({
                            id: featureId,
                            bundleId: bundle.id
                        });
                    };
                    try {
                        for (var features_1 = (e_6 = void 0, __values(features)), features_1_1 = features_1.next(); !features_1_1.done; features_1_1 = features_1.next()) {
                            var featureKey = features_1_1.value;
                            _loop_2(featureKey);
                        }
                    }
                    catch (e_6_1) { e_6 = { error: e_6_1 }; }
                    finally {
                        try {
                            if (features_1_1 && !features_1_1.done && (_e = features_1.return)) _e.call(features_1);
                        }
                        finally { if (e_6) throw e_6.error; }
                    }
                }
            }
            catch (e_4_1) { e_4 = { error: e_4_1 }; }
            finally {
                try {
                    if (_t && !_t.done && (_c = _s.return)) _c.call(_s);
                }
                finally { if (e_4) throw e_4.error; }
            }
        }
        if (flatSpecialFeatures.length > 0) {
            configCopy.data.features = flatSpecialFeatures;
        }
        if (flatVariables.length > 0) {
            configCopy.data.variables = flatVariables;
        }
    }
    if ((_k = (_j = config.tap) === null || _j === void 0 ? void 0 : _j.security) === null || _k === void 0 ? void 0 : _k.scramActivated) {
        if (!configCopy.tap) {
            configCopy.tap = {};
        }
        if (!configCopy.tap.scram) {
            configCopy.tap.scram = {};
        }
        if (!configCopy.tap.scram.hashIteration) {
            configCopy.tap.scram.hashIteration = DEFAULT_HASH_ITERATION_COUNT;
        }
    }
    var hashIteration = (_m = (_l = configCopy.tap) === null || _l === void 0 ? void 0 : _l.scram) === null || _m === void 0 ? void 0 : _m.hashIteration;
    var _loop_3 = function (specialProfile) {
        if (!flatProfiles.find(function (p) { return p.id === specialProfile.id; })) {
            flatProfiles.push({
                id: specialProfile.id,
                password: specialProfile.password || specialProfile.username,
                lifeTime: specialProfile.lifeTime || 30
            });
        }
    };
    try {
        for (var _u = __values([
            ADMIN_USER,
            SUPERVISOR_USER
        ]), _v = _u.next(); !_v.done; _v = _u.next()) {
            var specialProfile = _v.value;
            _loop_3(specialProfile);
        }
    }
    catch (e_7_1) { e_7 = { error: e_7_1 }; }
    finally {
        try {
            if (_v && !_v.done && (_f = _u.return)) _f.call(_u);
        }
        finally { if (e_7) throw e_7.error; }
    }
    try {
        for (var flatProfiles_1 = __values(flatProfiles), flatProfiles_1_1 = flatProfiles_1.next(); !flatProfiles_1_1.done; flatProfiles_1_1 = flatProfiles_1.next()) {
            var profile = flatProfiles_1_1.value;
            prepareUserPassword(profile, ((_o = configCopy === null || configCopy === void 0 ? void 0 : configCopy.tap) === null || _o === void 0 ? void 0 : _o.security) || {}, function (input) {
                if (!hashIteration) {
                    throw TapConfiguratorError.internalError("Hash iteration value must be set");
                }
                var salt = (options === null || options === void 0 ? void 0 : options.saltGenerator) ? options.saltGenerator() : DEFAULT_SCRAM_SALT_GENERATOR();
                return ScramAuth.createScramPasswordKey(input, {
                    salt: salt,
                    iterationNumber: hashIteration
                });
            });
        }
    }
    catch (e_8_1) { e_8 = { error: e_8_1 }; }
    finally {
        try {
            if (flatProfiles_1_1 && !flatProfiles_1_1.done && (_g = flatProfiles_1.return)) _g.call(flatProfiles_1);
        }
        finally { if (e_8) throw e_8.error; }
    }
    if (!configCopy.data) {
        configCopy.data = {};
    }
    configCopy.data.profiles = flatProfiles;
    if ((_p = configCopy.application) === null || _p === void 0 ? void 0 : _p.uri) {
        if (typeof configCopy.application.uri === 'object') {
            var appUri = configCopy.application.uri;
            configCopy.application.uri = "" + AppPathType[appUri.type] + appUri.value;
        }
    }
    return configCopy;
}
function prepareUserPassword(profile, securityConfig, scramPasswordGenerator) {
    if (profile.password) {
        if (profile.key) {
            throw TapConfiguratorError.invalidConfiguration("You cannot specify both password and passwordKey for profile configuration. Specify only one of those two options.");
        }
        if (securityConfig.scramActivated) {
            var passwordBytes = scramPasswordGenerator(profile.password);
            // debug(
            //     `Writing SCRAM password for user id ${userId} with hash: ${bufferToHexString(passwordBytes)}`
            // );
            delete profile.password;
            profile.key = passwordBytes;
        }
        else if (securityConfig.hashPassword) {
            var passwordBytes = hashLoginPassword(profile.password);
            // debug(
            //     `Writing password for user id ${userId} with hash: ${bufferToHexString(
            //         passwordBytes
            //     )}`
            // );
            delete profile.password;
            profile.key = passwordBytes;
        }
        else {
            // debug(
            //     `Writing password for user id ${userId} without hash: ${password}`
            // );
            // OK nothing to do
        }
    }
    // console.log('profile.passwordKey', typeof profile.passwordKey, profile.passwordKey?.constructor?.name);
}
function stepEvent(observer, step, context, extraData) {
    observer.next(__assign({ step: step, progress: {
            loaded: context.index + 1,
            total: context.total
        } }, (extraData || {})));
}
/**
 * Configure tap from a configuration file schema
 *
 */
var TapConfiguratorWriter = /** @class */ (function () {
    function TapConfiguratorWriter(options, host) {
        if (options === void 0) { options = {
            delayMs: 400
        }; }
        this.options = options;
        this.host = host;
    }
    TapConfiguratorWriter.prototype.createConfiguratorForTap = function (tap, config, options) {
        return __awaiter(this, void 0, void 0, function () {
            var firmwareVersion;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, tap.service.device.getFirmwareVersion()];
                    case 1:
                        firmwareVersion = (_a.sent()).body();
                        return [2 /*return*/, this.configure(tap, firmwareVersion, config, options)];
                }
            });
        });
    };
    TapConfiguratorWriter.prototype.configure = function (tap, firmwareVersion, config, options) {
        var _this = this;
        var _a, _b;
        var configureContext = {
            tap: tap,
            options: config.meta || {},
            tapConfig: config.config
        };
        var operations = [];
        if (!(options === null || options === void 0 ? void 0 : options.skipSchemaValidation)) {
            operations.push(function (observer, context) { return __awaiter(_this, void 0, void 0, function () {
                return __generator(this, function (_a) {
                    stepEvent(observer, TapConfiguratorWriter.Step.validatesConfig, context);
                    validateTapConfiguratorConfig(config);
                    return [2 /*return*/];
                });
            }); });
        }
        if (!configureContext.options.skipFactoryReset) {
            operations.push(function (observer, context) { return __awaiter(_this, void 0, void 0, function () {
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0:
                            stepEvent(observer, TapConfiguratorWriter.Step.factoryReset, context);
                            return [4 /*yield*/, tap.service.device.factoryReset()];
                        case 1:
                            (_a.sent()).successful();
                            return [2 /*return*/];
                    }
                });
            }); });
        }
        var useScram = !!((_b = (_a = configureContext.tapConfig.tap) === null || _a === void 0 ? void 0 : _a.security) === null || _b === void 0 ? void 0 : _b.scramActivated);
        if (useScram) {
            operations.push(function (observer, context) { return __awaiter(_this, void 0, void 0, function () {
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0:
                            stepEvent(observer, TapConfiguratorWriter.Step.enableSecureCom, context);
                            if (!useScram) return [3 /*break*/, 2];
                            return [4 /*yield*/, tap.encryption.start()];
                        case 1:
                            _a.sent();
                            _a.label = 2;
                        case 2: return [2 /*return*/];
                    }
                });
            }); });
        }
        var calls = tapConfigToServiceCalls(configureContext.tapConfig, {
            firmwareVersion: firmwareVersion,
            service: tap.service
        });
        // operations.push(async (observer, context) => {
        //     observer.next({
        //         step: TapConfigurator.Step.
        //     } as TapConfigurator.Event);
        //     const firmwareVersion = (await context.tap.service.device.getFirmwareVersion()).body();
        // });
        // operations.push(
        //     async (observer, context) => {
        //         stepEvent(observer, TapConfiguratorWriter.Step.setupConfigurationState, context);
        //         await this.setupConfigState(configureContext.tap);
        //     }
        // );
        // if (useScram) {
        //     operations.push(async (observer, context) => {
        //         // To write user password keys, we need to put scram configuration first
        //         stepEvent(observer, TapConfiguratorWriter.Step.writeSecurityOptions, context);
        //         tap.encryption.pause();
        //         (await tap.service.interface.putSecurityOptions({
        //             scramActivated: true
        //         } as SecurityOptions)).successful();
        //         tap.encryption.resume();
        //     });
        // }
        this.addConfigurCalls(configureContext, calls, operations);
        // operations.push(
        //     async (observer, context) => {
        //         stepEvent(observer, TapConfiguratorWriter.Step.applyConfig, context);
        //         await this.applyConfig(configureContext.tap, configureContext.tapConfig.version || '1.0.0');
        //     }
        // );
        operations.push(function (observer, context) { return __awaiter(_this, void 0, void 0, function () {
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        stepEvent(observer, TapConfiguratorWriter.Step.logout, context);
                        return [4 /*yield*/, configureContext.tap.auth.logout()];
                    case 1:
                        _a.sent();
                        tap.auth.clearCache();
                        return [2 /*return*/];
                }
            });
        }); });
        return StepOperations.sequential(operations);
    };
    TapConfiguratorWriter.prototype.setupConfigState = function (tap) {
        return __awaiter(this, void 0, void 0, function () {
            var state;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, tap.service.firmware.getState()];
                    case 1:
                        state = (_a.sent()).body();
                        if (state !== FirmwareState.NORMAL) {
                            throw TapConfiguratorError.unexpectedFirmwareState(state);
                        }
                        return [4 /*yield*/, tap.service.interface.putConfigVersion("255.255.65535")];
                    case 2:
                        (_a.sent()).successful();
                        return [2 /*return*/];
                }
            });
        });
    };
    // TODO move from here
    TapConfiguratorWriter.prototype.isConfigured = function (tap) {
        return __awaiter(this, void 0, void 0, function () {
            var configVersionCalled, intValue;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, tap.service.interface.getConfigVersion()];
                    case 1:
                        configVersionCalled = _a.sent();
                        configVersionCalled.successful();
                        intValue = configVersionCalled.body(NumberConverter.uint32());
                        return [2 /*return*/, intValue !== 0];
                }
            });
        });
    };
    /***
     *
     */
    TapConfiguratorWriter.prototype.applyConfig = function (tap, newVersion) {
        if (newVersion === void 0) { newVersion = "1.0.0"; }
        return __awaiter(this, void 0, void 0, function () {
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, this.sleep(this.options.delayMs)];
                    case 1:
                        _a.sent();
                        return [4 /*yield*/, tap.service.interface.putConfigVersion(newVersion)];
                    case 2:
                        (_a.sent()).successful();
                        return [4 /*yield*/, this.sleep(this.options.delayMs)];
                    case 3:
                        _a.sent();
                        return [4 /*yield*/, tap.service.interface.reloadConfig()];
                    case 4:
                        (_a.sent()).successful();
                        return [4 /*yield*/, this.sleep(this.options.delayMs)];
                    case 5:
                        _a.sent();
                        return [2 /*return*/];
                }
            });
        });
    };
    TapConfiguratorWriter.prototype.addConfigurCalls = function (configureContext, calls, operations) {
        var _this = this;
        calls.forEach(function (callOrError, index) {
            operations.push(function (observer, context) { return __awaiter(_this, void 0, void 0, function () {
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0:
                            if (!(callOrError instanceof Error)) return [3 /*break*/, 1];
                            // TODO 
                            stepEvent(observer, TapConfiguratorWriter.Step.executeCall, context, {
                                error: callOrError,
                                stepProgress: {
                                    total: calls.length,
                                    loaded: index + 1
                                }
                                // call: callOrError
                            });
                            return [3 /*break*/, 3];
                        case 1:
                            stepEvent(observer, TapConfiguratorWriter.Step.executeCall, context, {
                                stepProgress: {
                                    total: calls.length,
                                    loaded: index + 1
                                },
                                call: callOrError
                            });
                            return [4 /*yield*/, configureContext.tap.lwm2m.execute(callOrError)];
                        case 2:
                            (_a.sent()).successful();
                            _a.label = 3;
                        case 3: return [2 /*return*/];
                    }
                });
            }); });
        });
    };
    TapConfiguratorWriter.prototype.sleep = function (delayMs) {
        return sleep(delayMs);
    };
    return TapConfiguratorWriter;
}());
export { TapConfiguratorWriter };
(function (TapConfiguratorWriter) {
    ;
    var Step;
    (function (Step) {
        Step["validatesConfig"] = "TapConfiguratorStepValidatesConfig";
        Step["enableSecureCom"] = "TapConfiguratorStepenableSecureCommunication";
        Step["factoryReset"] = "TapConfiguratorStepFactoryReset";
        Step["setupConfigurationState"] = "TapConfiguratorStepSetupConfigurationState";
        Step["executeCall"] = "TapConfiguratorStepExecuteCall";
        Step["logout"] = "TapConfiguratorSteplogout";
        Step["applyConfig"] = "TapConfiguratorStepApplyConfig";
        Step["writeSecurityOptions"] = "TapConfiguratorStepWriteSecurityOptions";
    })(Step = TapConfiguratorWriter.Step || (TapConfiguratorWriter.Step = {}));
})(TapConfiguratorWriter || (TapConfiguratorWriter = {}));
//# sourceMappingURL=data:application/json;base64, 