import { BehaviorSubject, never, timer, empty, combineLatest, Subject, Observable } from 'rxjs';
import { flatMap, share, switchMap, map, startWith, first } from 'rxjs/operators';
import { CodeError, isCodeError } from '@iotize/common/error';
import { VariableFormat, TapError } from '@iotize/tap';
import { StringConverter, ArrayConverter, FloatConverter, NumberConverter, BooleanConverter, TlvBundleConverter, TapStreamWriter, TapStreamReader } from '@iotize/tap/client/impl';
import { VariableType } from '@iotize/tap/service/impl/variable';
import { ResultCode } from '@iotize/tap/client/api';
import '@iotize/tap/service/impl/target';
import '@iotize/tap/service/impl/interface';

var MonitorEngine;
(function (MonitorEngine) {
    let State;
    (function (State) {
        State[State["START"] = 0] = "START";
        State[State["PAUSE"] = 1] = "PAUSE";
        State[State["STOP"] = 2] = "STOP";
    })(State = MonitorEngine.State || (MonitorEngine.State = {}));
})(MonitorEngine || (MonitorEngine = {}));

var __awaiter$a = (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());
    });
};
const TAG$1 = 'TapMonitorEngine';
class MonitoringController {
    constructor(initalState = INITAL_MONITORING_CONFIG) {
        this.initalState = initalState;
        this.subject = new BehaviorSubject(this.initalState);
    }
    get state() {
        return this.subject.value.state;
    }
    start(options) {
        this.subject.next(Object.assign({
            state: MonitorEngine.State.START,
        }, options));
        return this;
    }
    stop() {
        this.subject.next({
            state: MonitorEngine.State.STOP,
        });
        return this;
    }
    pause() {
        this.subject.next({
            state: MonitorEngine.State.PAUSE,
        });
        return this;
    }
    setPeriod(period) {
        this.subject.next({
            period,
        });
    }
    asSubject() {
        return this.subject;
    }
}
function createDataStreamMonitor(stream, ticker, eventSubject) {
    return ticker.pipe(flatMap((tickerState) => __awaiter$a(this, void 0, void 0, function* () {
        // debug(TAG, `Running call at time ${time}`);
        return stream
            .readRaw()
            .then((value) => {
            // TODO event ?
            return value;
        })
            .catch((error) => {
            eventSubject.next({
                type: 'error',
                error,
                stream,
                timerState: tickerState,
            });
        });
    })), 
    // filter<DataType | undefined, DataType>((value): value is DataType => {
    //     return value !== undefined;
    // }),
    share());
}
const INITAL_MONITORING_CONFIG = {
    dueTime: 0,
    period: 1000,
    refresh: false,
    state: MonitorEngine.State.STOP,
};
function createMonitoringTicker(
// call: (time: number) => Promise<DataType>,
controller) {
    const controllerSubject = controller instanceof MonitoringController
        ? controller.asSubject()
        : controller;
    let lastState = Object.assign(Object.assign({}, INITAL_MONITORING_CONFIG), controllerSubject.value);
    return controllerSubject.pipe(switchMap((partialState) => {
        const newState = Object.assign(Object.assign({}, lastState), partialState);
        lastState = newState;
        // console.log(TAG, 'Apply new monitoring state: ', newState);
        switch (newState.state) {
            case MonitorEngine.State.STOP:
                return empty();
            case MonitorEngine.State.START:
                return timer(newState.dueTime, newState.period).pipe(map((time) => ({
                    time,
                    state: newState,
                })));
            case MonitorEngine.State.PAUSE:
                return never();
            default:
                throw new Error(`InternalErorr: Not implemented monitoring state: ${newState.state}`);
        }
    }));
}

class TapDataError extends CodeError {
    constructor(msg, code, cause) {
        super(msg, code);
        this.cause = cause;
    }
    static unexpectedEncodedValueError(variable, rawData, err) {
        return new TapDataError(`An error occured while encoding value for variable ${variable.id}. ${err.message}`, TapDataError.Code.UnexpectedEncodedValueError);
    }
    static unexpectedVariableDataRead(variable, rawData, err) {
        return new TapDataError(`An error occured while decoding value for variable ${variable.id}. ${err.message}`, TapDataError.Code.UnexpectedVariableData);
    }
    static bundleNotFound(key) {
        return new TapDataError(`Bundle with identifier "${key.toString()}" is not registered`, TapDataError.Code.BundleNotFound);
    }
    static variableNotFound(key) {
        return new TapDataError(`Variable with identifier "${key.toString()}" is not registered`, TapDataError.Code.VariableNotFound);
    }
    static bundleNotConfigured(config, err) {
        return new TapDataError(`Bundle with id "${config.id}" does not exist on this Tap. You should reconfigure your Tap to add this bundle.`, TapDataError.Code.BundleNotConfigured, err);
    }
    static variableNotConfigured(config, err) {
        return new TapDataError(`Variable with id "${config.id}" does not exist on this Tap. You should reconfigure your Tap to add this variable.`, TapDataError.Code.VariableNotConfigured, err);
    }
}
(function (TapDataError) {
    let Code;
    (function (Code) {
        Code["UnexpectedVariableData"] = "TapDataErrorUnexpectedVariableData";
        Code["UnexpectedEncodedValueError"] = "TapDataErrorUnexpectedEncodedValueError";
        Code["BundleNotFound"] = "TapDataErrorBundleNotFound";
        Code["VariableNotFound"] = "TapDataErrorVariableNotFound";
        Code["BundleNotConfigured"] = "TapDataErrorBundleNotConfigured";
        Code["VariableNotConfigured"] = "TapDataErrorVariableNotConfigured";
    })(Code = TapDataError.Code || (TapDataError.Code = {}));
})(TapDataError || (TapDataError = {}));

const ɵ0 = (v) => v, ɵ1 = (v) => v;
const BLANK_CONVERTER = {
    encode: ɵ0,
    decode: ɵ1,
};

const ASCII_WITH_END_OF_STRING_CHAR_CONVERTER = new StringConverter('ascii', {
    addEndOfStringCharacter: true,
    replaceEmptyStringWithEndOfLineChar: true,
    stripEndOfStringCharacter: true,
});
function computeByteLengthFromFormat(info) {
    return Math.ceil(computeBitLengthFromFormat(info) / 8);
}
function computeBitLengthFromFormat(info) {
    return info.length * variableFormatToBitSize(info.format);
}
function variableFormatToByteSize(format) {
    return Math.ceil(variableFormatToBitSize(format) / 8);
}
function variableFormatToBitSize(format) {
    switch (format) {
        case VariableFormat._1_BIT:
            return 1;
        case VariableFormat._8_BITS:
            return 8;
        case VariableFormat._16_BITS:
            return 16;
        case VariableFormat._32_BITS:
            return 32;
        default:
            // TODO better error
            throw new Error(`InternalError: missing bit length for format value value "${format}" (name: ${VariableFormat[format]})`);
    }
}
function variableDataTypeToByteSize(dataType) {
    return typeConfig[dataType].size;
}
/**
 * Map Variable Type to a byte size when encoded as a Tap frame.
 */
const typeConfig = {
    [VariableType.Data.BOOLEAN]: {
        size: 1,
    },
    [VariableType.Data.UINT8]: {
        size: 1,
    },
    [VariableType.Data.UINT16]: {
        size: 2,
    },
    [VariableType.Data.UINT32]: {
        size: 4,
    },
    [VariableType.Data.INT8]: {
        size: 1,
    },
    [VariableType.Data.INT16]: {
        size: 2,
    },
    [VariableType.Data.INT32]: {
        size: 4,
    },
    [VariableType.Data.FLOAT32]: {
        size: 4,
    },
    [VariableType.Data.ASCII]: {
        size: 1,
    },
};
function getConverterFromVariableConfig(variableConfig) {
    const typeKey = variableConfig.dataType;
    if (!(typeKey in VariableType.Data)) {
        throw new Error(`Unknown variable type key "${typeKey}"`); // TODO better error
    }
    return getConverterFromVariableType(VariableType.Data[typeKey], variableConfig.length || 0);
}
/**
 * Get converter from variable type and length (number of element)
 * @param dataType
 * @param length > 1 if it's an array.
 *
 * @return undefined if converter does not exist for the given type
 */
function getConverterFromVariableType(dataType, length = 1) {
    if (dataType === VariableType.Data.ASCII) {
        return ASCII_WITH_END_OF_STRING_CHAR_CONVERTER;
    }
    const converter = getRawConverterFromVariableDataType(dataType);
    if (converter && length > 1) {
        return new ArrayConverter(converter, {
            sizeOfItem: variableDataTypeToByteSize(dataType),
        });
    }
    else {
        return converter;
    }
}
// type TypeMapping = {
//     [VariableType.Data.ASCII]: string;
//     [VariableType.Data.FLOAT32]: number;
//     [VariableType.Data.INT32]: number;
//     [VariableType.Data.INT16]: number;
//     [VariableType.Data.INT8]: number;
//     [VariableType.Data.UINT8]: number;
//     [VariableType.Data.UINT16]: number;
//     [VariableType.Data.UINT32]: number;
// }
/**
 * Get raw converter instance from variable type
 * TODO improve output typing with mapping
 * @param format
 * @param length
 */
function getRawConverterFromVariableDataType(type) {
    switch (type) {
        // TODO
        // case VariableType.Data.RAW:
        //     return undefined;
        case VariableType.Data.BOOLEAN:
            return BooleanConverter.instanceBit0();
        case VariableType.Data.UINT8:
            return NumberConverter.uint8();
        case VariableType.Data.UINT16:
            return NumberConverter.uint16();
        case VariableType.Data.UINT32:
            return NumberConverter.uint32();
        case VariableType.Data.INT8:
            return NumberConverter.int8();
        case VariableType.Data.INT16:
            return NumberConverter.int16();
        case VariableType.Data.INT32:
            return NumberConverter.int32();
        case VariableType.Data.FLOAT32:
            return FloatConverter.instance32();
        case VariableType.Data.ASCII:
            return StringConverter.ascii();
        default:
            return undefined;
    }
}

var __awaiter$9 = (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());
    });
};
class AbstractBundleDataStream {
    constructor(_variables // private _variables: EditableDataStreamInterface<any, Uint8Array>[] // converter: EncoderDecoder<DataType, Uint8Array>
    ) {
        this._variables = _variables;
    }
    get variables() {
        return Object.values(this._variables);
    }
    readRaw() {
        return __awaiter$9(this, void 0, void 0, function* () {
            const raw = yield this.readRawInner();
            this.notifyRawValue(raw);
            return raw;
        });
    }
    // abstract read(): Promise<DataType>;
    read() {
        return __awaiter$9(this, void 0, void 0, function* () {
            yield this.readRaw();
            return this.valueSnapshot;
        });
    }
    write(value) {
        return __awaiter$9(this, void 0, void 0, function* () {
            // if (typeof value !== 'object') {
            //     throw TapDataError.illegalParameter();
            // }
            for (const key in value) {
                yield this.variable(key).writeRaw(value[key]);
            }
        });
    }
    variable(id) {
        if (!(id in this._variables)) {
            throw new Error(`Variable "${id}" not found in bundle "${this.id.toString()}"`);
        }
        return this._variables[id];
    }
    get rawValues() {
        const variables = Object.values(this._variables);
        return combineLatest(variables.map((v) => v.rawValues.pipe(startWith(undefined)))).pipe(map((values) => {
            return values.reduce((accumulator, v, index) => {
                accumulator[variables[index].id] = v; // TODO
                return accumulator;
            }, {});
        }));
    }
    get rawValueSnaphot() {
        const variables = Object.values(this._variables);
        return variables.reduce((accumulator, v, index) => {
            accumulator[variables[index].id] = v.rawValueSnaphot;
            return accumulator;
        }, {});
    }
    get valueSnapshot() {
        const variables = Object.values(this._variables);
        return variables.reduce((accumulator, v, index) => {
            accumulator[variables[index].id] = v.valueSnapshot;
            return accumulator;
        }, {});
    }
    get values() {
        const variables = Object.values(this._variables);
        return combineLatest(variables.map((v) => v.values.pipe(startWith(undefined)))).pipe(map((values) => {
            return values.reduce((accumulator, v, index) => {
                accumulator[variables[index].id] = v;
                return accumulator;
            }, {});
        }));
    }
    notifyRawValue(v) {
        var _a;
        return __awaiter$9(this, void 0, void 0, function* () {
            for (const id in v) {
                const value = v[id];
                if (value !== undefined) {
                    (_a = this._variables[id]) === null || _a === void 0 ? void 0 : _a.notifyRawValue(value);
                }
            }
        });
    }
    findVariable(id) {
        return id in this._variables ? this._variables[id] : undefined;
    }
    hasVariable(id) {
        return !!this.findVariable(id);
    }
    writeRaw(values) {
        return __awaiter$9(this, void 0, void 0, function* () {
            for (const key in values) {
                const value = values[key];
                if (value !== undefined) {
                    yield this.variable(key).writeRaw(value);
                }
            }
        });
    }
}
class AbstractEditableDataStream {
    constructor() {
        // static fromCustom(arg0: CustomVariableConfig) {
        //     return new Variable<T>(config, );
        // }
        // public static fromConfig<T>(config: VariableConfig) {
        //     return new Variable<T>(config, );
        // }
        // public static fromMemory(config: MemoryInfoConfig) {
        //     return new Variable<T>(config, );
        // }
        this.rawDataStream = new Subject(); // = new Subject<RawType>();
        // private selfReadStream = new Subject<RawType>();
        this.values = this.rawDataStream.pipe(map((v) => this.decodeValue(v)));
        // if (dataStream) {
        //     this.dataStream = merge(
        //         dataStream,
        //         this.selfReadStream
        //     );
        // }
        // else {
        //     this.dataStream = this.selfReadStream;
        // }
        // this.dataStream = this.dataStream
        //     .pipe(
        //         tap(v => this.lastRawValue = v)
        //     );
        // // Do we need to subscribe ?
        // this.dataStream.subscribe();
    }
    get lastRawValue() {
        return this._lastRawValue;
    }
    get rawValueSnaphot() {
        return this.lastRawValue;
    }
    get valueSnapshot() {
        if (this.lastRawValue === undefined) {
            return undefined;
        }
        return this.decodeValue(this.lastRawValue);
    }
    get rawValues() {
        return this.rawDataStream;
    }
    //  {
    //     const value = await this.rawReader(this.context);
    //     this.notifyRawValue(value);
    //     return value;
    // }
    notifyRawValue(newValue) {
        return __awaiter$9(this, void 0, void 0, function* () {
            this._lastRawValue = newValue;
            this.rawDataStream.next(newValue);
        });
    }
    readRaw() {
        return __awaiter$9(this, void 0, void 0, function* () {
            const value = yield this.readRawInner();
            this.notifyRawValue(value);
            return value;
        });
    }
    read() {
        return __awaiter$9(this, void 0, void 0, function* () {
            const p = this.values.pipe(first()).toPromise();
            yield this.readRaw();
            return p;
        });
    }
    write(value) {
        return __awaiter$9(this, void 0, void 0, function* () {
            // if (!this.rawWritter) {
            //     throw new Error(`Cannot write value for ${this.id}`);
            // }
            const encoded = this.encodeValue(value);
            return this.writeRaw(encoded);
        });
    }
    encodeValue(value) {
        try {
            return this.converter.encode(value);
        }
        catch (err) {
            throw TapDataError.unexpectedEncodedValueError(this, value, err);
        }
    }
    decodeValue(data) {
        try {
            return this.converter.decode(data);
        }
        catch (err) {
            throw TapDataError.unexpectedVariableDataRead(this, data, err);
        }
    }
}

var __awaiter$8 = (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());
    });
};
/**
 * You should use ConverterVariableView instead
 */
class VariableView {
    constructor(id, context) {
        this.id = id;
        this.context = context;
    }
    get converter() {
        return this.context.converter;
    }
    get indexes() {
        return this.context.indexes;
    }
    get rawValues() {
        return this.context.variable.rawValues.pipe(map((rawData) => {
            return this.subDataView(rawData);
        }));
    }
    get valueSnapshot() {
        const lastRawValue = this.rawValueSnaphot;
        return lastRawValue !== undefined
            ? this.decodeValue(this.subDataView(lastRawValue))
            : undefined;
    }
    get values() {
        return this.rawValues.pipe(map((v) => this.decodeValue(v)));
    }
    get rawValueSnaphot() {
        const parentValueSnapshot = this.context.variable.rawValueSnaphot;
        return parentValueSnapshot !== undefined
            ? this.subDataView(parentValueSnapshot)
            : parentValueSnapshot;
    }
    notifyRawValue(newValue) { }
    read() {
        return __awaiter$8(this, void 0, void 0, function* () {
            return this.decodeValue(yield this.readRaw());
        });
    }
    write(value) {
        return this.writeRaw(this.encodeValue(value));
    }
    readRaw() {
        return __awaiter$8(this, void 0, void 0, function* () {
            const rawData = yield this.context.variable.readRaw();
            return this.subDataView(rawData);
        });
    }
    writeRaw(value) {
        return __awaiter$8(this, void 0, void 0, function* () {
            let existingData;
            if (this.context.useLastKnownValueBeforeWrite) {
                existingData =
                    this.context.variable.rawValueSnaphot ||
                        (yield this.context.variable.readRaw());
            }
            else {
                existingData = yield this.context.variable.readRaw();
            }
            this.indexes.forEach((subIndex, index) => {
                existingData[subIndex] = value[index];
            });
            yield this.context.variable.writeRaw(existingData);
        });
    }
    subDataView(rawData) {
        return this.indexes.reduce((acc, subIndex, index) => {
            acc[index] = rawData[subIndex];
            return acc;
        }, new Uint8Array(this.indexes.length));
    }
    encodeValue(value) {
        try {
            return this.converter.encode(value);
        }
        catch (err) {
            throw TapDataError.unexpectedEncodedValueError(this, value, err);
        }
    }
    decodeValue(data) {
        try {
            return this.converter.decode(data);
        }
        catch (err) {
            throw TapDataError.unexpectedVariableDataRead(this, data, err);
        }
    }
}

class AbstractVariable extends AbstractEditableDataStream {
    /**
     * Create a subvariable instance from the given variable index
     * @param key the uniq variable identifier to use for data manager
     * @param rawIndexes list of array index (starting from 0) to extract. Each index represents 1 byte of data
     *
     * @deprecated manually create your instance
     */
    variableView(key, rawIndexes, options) {
        return new VariableView(key, {
            converter: (options === null || options === void 0 ? void 0 : options.converter) || BLANK_CONVERTER,
            indexes: rawIndexes,
            variable: this,
            useLastKnownValueBeforeWrite: options === null || options === void 0 ? void 0 : options.useLastKnownValueBeforeWrite,
        });
    }
}

var __awaiter$7 = (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());
    });
};
class ModbusAccessVariable extends AbstractVariable {
    constructor(id, context) {
        super();
        this.id = id;
        this.context = context;
    }
    get converter() {
        return this.context.converter || BLANK_CONVERTER;
    }
    get config() {
        return this.context.config;
    }
    readRawInner() {
        return __awaiter$7(this, void 0, void 0, function* () {
            return (yield this.context.targetService.modbusRead(this.context.config)).body();
        });
    }
    writeRaw(value) {
        return __awaiter$7(this, void 0, void 0, function* () {
            if (!value || !(value instanceof Uint8Array)) {
                throw new Error(`Bad parameter for ModbusAccessVariable.writeRaw(). Expecting an Uint8Array.`);
            }
            const expectedByteSize = this.config.length * variableFormatToByteSize(this.config.format);
            if (value.length !== expectedByteSize) {
                throw new Error(`Encoded value does not have the expected byte size. Expecting ${expectedByteSize} byte(s) but given ${value.length} byte(s)`);
            }
            return (yield this.context.targetService.modbusWrite({
                data: value,
                options: this.context.config,
            })).body();
        });
    }
}

var __awaiter$6 = (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());
    });
};
function createTapVariableFromConfig(variableService, key, value) {
    return new TapVariable(key, {
        variableService: variableService,
        config: {
            id: value.id,
            dataType: value.dataType,
            length: value.length,
        },
        converter: value.converter ||
            (value.dataType !== undefined
                ? getConverterFromVariableType(value.dataType, value.length || 1)
                : BLANK_CONVERTER),
    });
}
class TapVariable extends AbstractVariable {
    constructor(
    /**
     * Variable name identifier.
     */
    id, context) {
        super();
        this.id = id;
        this.context = context;
    }
    get variableId() {
        return this.context.config.id;
    }
    get converter() {
        return this.context.converter || BLANK_CONVERTER;
    }
    get config() {
        return this.context.config;
    }
    set converter(converter) {
        this.context.converter = converter;
    }
    readRawInner() {
        return __awaiter$6(this, void 0, void 0, function* () {
            try {
                let rawData = (yield this.context.variableService.getValue(this.context.config.id)).body();
                if (this.context.subIndex) {
                    rawData = this.context.subIndex.reduce((acc, subIndex, index) => {
                        acc[index] = rawData[subIndex];
                        return acc;
                    }, new Uint8Array(this.context.subIndex.length));
                }
                return rawData;
            }
            catch (err) {
                if (isCodeError(TapError.Code.ResponseStatusError, err)) {
                    switch (err.response.status) {
                        case ResultCode.NOT_FOUND:
                            throw TapDataError.variableNotConfigured(this.context.config, err);
                    }
                }
                throw err;
            }
        });
    }
    writeRaw(value) {
        return __awaiter$6(this, void 0, void 0, function* () {
            try {
                if (this.context.subIndex) {
                    const existingData = (yield this.context.variableService.getValue(this.context.config.id)).body();
                    this.context.subIndex.forEach((subIndex, index) => {
                        existingData[subIndex] = value[index];
                    });
                    value = existingData;
                }
                (yield this.context.variableService.setValue(this.context.config.id, value)).body();
            }
            catch (err) {
                if (isCodeError(TapError.Code.ResponseStatusError, err)) {
                    switch (err.response.status) {
                        case ResultCode.NOT_FOUND:
                            throw TapDataError.variableNotConfigured(this.context.config, err);
                    }
                }
                throw err;
            }
        });
    }
}
// TODO add Sub variable data stream (IE read/write index )

var __awaiter$5 = (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());
    });
};
const TLV_BUNDLE_CONVERTER = new TlvBundleConverter();
function createTapBundleFromConfig(tap, name, config) {
    const variables = Object.keys(config.variables).reduce((acc, key) => {
        const value = config.variables[key];
        acc[key] = createTapVariableFromConfig(tap.service.variable, key, value);
        return acc;
    }, {});
    return new TapBundle(name, {
        tap,
        config: {
            id: config.id,
        },
    }, variables);
}
class TapBundle extends AbstractBundleDataStream {
    constructor(_id, context, _variables
    // converter: EncoderDecoder<DataType, Uint8Array>
    ) {
        super(_variables);
        this._id = _id;
        this.context = context;
        // this.converter = TLV_BUNDLE_CONVERTER;
        // super(
        //     context,
        //     undefined,
        // );
    }
    get id() {
        return this._id;
    }
    get bundleId() {
        return this.context.config.id;
    }
    // addConfiguredVariable<T>(name: string, options: {
    //     id: number;
    //     dataType: VariableType.Data,
    //     length?: number
    // }) {
    //     if (this.hasVariable(options.id)) {
    //         throw new Error(`Alredy has variable ${name}`);
    //     }
    //     // this.context.config.variables.push({
    //     //     name,
    //     //     id: options.id
    //     // });
    //     const variable = new ConfiguredVariableDataStream<DataType>(
    //         // config as VariableConfigWithName,
    //         {
    //             tap: this.context.tap,
    //             config: {
    //                 id: options.id,
    //                 name
    //             }
    //         },
    //         getConverterFromVariableType(
    //             options.dataType,
    //             options.length || 1
    //         ),
    //         // dataStream,
    //     );
    //     this._variables.push(variable);
    // }
    readRawInner() {
        return __awaiter$5(this, void 0, void 0, function* () {
            try {
                const variableIdMapping = (yield this.context.tap.service.bundle.getValues(this.bundleId)).body(TLV_BUNDLE_CONVERTER);
                const streamIdMapping = {}; // Partial<RawBundleType<DataType>>
                // for (const tapVariableId of vara)
                for (const variable of this.variables) {
                    if (variable instanceof TapVariable) {
                        if (variable.variableId in variableIdMapping) {
                            streamIdMapping[variable.id] =
                                variableIdMapping[variable.variableId];
                        }
                        else {
                            streamIdMapping[variable.id] = undefined;
                            // if (!(variable.id in streamIdMapping)) {
                            //     streamIdMapping[variable.id] = variableIdMapping[]
                            // }
                            // (streamIdMapping as any)[variable.id]
                            // TODO un mapped ?
                            // throw new Error(`${variable.id} is not mapped`)
                        }
                    }
                }
                return streamIdMapping;
            }
            catch (err) {
                if (isCodeError(TapError.Code.ResponseStatusError, err)) {
                    switch (err.response.status) {
                        case ResultCode.NOT_FOUND:
                            throw TapDataError.bundleNotConfigured(this.context.config, err);
                    }
                }
                throw err;
            }
        });
    }
}

var __awaiter$4 = (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());
    });
};
class TargetMemoryVariable extends AbstractVariable {
    constructor(id, context) {
        super();
        this.id = id;
        this.context = context;
    }
    get converter() {
        return this.context.converter || BLANK_CONVERTER;
    }
    get config() {
        return this.context.config;
    }
    readRawInner() {
        return __awaiter$4(this, void 0, void 0, function* () {
            return (yield this.context.targetService.readAddress(this.context.config)).body();
        });
    }
    writeRaw(value) {
        return __awaiter$4(this, void 0, void 0, function* () {
            if (!value || !(value instanceof Uint8Array)) {
                throw new Error(`Bad parameter for TargetMemoryVariable.writeRaw(). Expecting an Uint8Array.`);
            }
            const expectedByteSize = this.config.length * variableFormatToByteSize(this.config.format);
            if (value.length !== expectedByteSize) {
                // TODO better error
                throw new Error(`Encoded value does not have the expected byte size. Expecting ${expectedByteSize} byte(s) but given ${value.length} byte(s)`);
            }
            return (yield this.context.targetService.writeAddress({
                data: value,
                info: this.context.config,
            })).body();
        });
    }
}

function createDataManagerConfigFromTapDataConfig(config) {
    var _a;
    const bundles = (_a = config === null || config === void 0 ? void 0 : config.bundles) === null || _a === void 0 ? void 0 : _a.reduce((acc, bundle) => {
        var _a;
        const bundleName = (bundle.name || bundle.id);
        const variablesConfig = (_a = bundle.variables) === null || _a === void 0 ? void 0 : _a.reduce((acc, v) => {
            const variableName = (typeof v.meta === 'object' ? v.meta.name : v.id) || v.id;
            acc[variableName] = {
                id: v.id,
                dataType: VariableType.Data[v.dataType],
                length: v.length || 1,
                converter: getConverterFromVariableConfig(v),
            };
            return acc;
        }, {});
        const bundleConfig = {
            id: bundle.id,
            variables: variablesConfig || {},
        };
        acc[bundleName] = bundleConfig;
        return acc;
    }, {});
    return {
        bundles,
    };
}

var __awaiter$3 = (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());
    });
};
function createTapBundlesFromConfig(tap, bundles) {
    const bundleDataStreams = {};
    for (const name in bundles) {
        bundleDataStreams[name] = createTapBundleFromConfig(tap, name, bundles[name]);
    }
    return bundleDataStreams;
}
function createDataManagerFromTapConfig(tap, config) {
    const { bundles } = config;
    return new TypedDataManager(tap, createTapBundlesFromConfig(tap, bundles));
}
function readDataManagerTapConfigFromTap(tap, config = {
    bundleIdNameMap: {},
    variableIdNameMap: {},
}) {
    return __awaiter$3(this, void 0, void 0, function* () {
        const { bundleIdNameMap, variableIdNameMap, observer } = config;
        (yield tap.service.target.connect()).successful();
        const readProfileResponse = yield tap.service.variable.readProfile();
        // if (!readProfileResponse.isSuccessful()) {
        //     switch (readProfileResponse.status) {
        //         case ResultCode.SERVICE_UNAVAILABLE:
        //             // If service is unavailable, it probably means that target is not connected yet
        //             (await tap.service.target.connect()).successful();
        //             readProfileResponse = await tap.service.variable.readProfile();
        //             break;
        //         default:
        //             // Throw error otherwise
        //             readProfileResponse.successful();
        //     }
        // }
        readProfileResponse.successful();
        const rawBundleData = readProfileResponse.rawBody();
        const profileData = TLV_BUNDLE_CONVERTER.decode(rawBundleData);
        const bundles = {};
        const variableCount = Object.keys(profileData).length;
        let index = 0;
        observer === null || observer === void 0 ? void 0 : observer.next({
            step: 'start-read-variable-config',
            profileData,
            progress: {
                loaded: index,
                total: variableCount,
            },
        });
        for (const variableId in profileData) {
            const variableIdInt = parseInt(variableId, 10);
            if (isNaN(variableIdInt)) {
                throw new Error(`Invalid variable integer identifier: ${variableId}`);
                continue;
            }
            observer === null || observer === void 0 ? void 0 : observer.next({
                step: 'read-variable',
                variableId: variableIdInt,
                progress: {
                    loaded: index + 1,
                    total: variableCount,
                },
            });
            const { bundleId, variableName, config: variableInfo, } = yield readTapVariableConfigFromTap(tap, variableIdInt, variableIdNameMap ? variableIdNameMap[variableIdInt] : undefined);
            let bundleName;
            if (bundleIdNameMap && bundleId in bundleIdNameMap) {
                bundleName = bundleIdNameMap[bundleId];
            }
            else {
                let readBundleName = (yield tap.service.bundle.getName(bundleId)).body();
                // Fix bug on firmware versions that returns fixed size for bundle name (at least until firmware v1.106)
                const endOfStringIndex = readBundleName.indexOf('\u0000');
                if (endOfStringIndex >= 0) {
                    readBundleName = readBundleName.substr(0, endOfStringIndex);
                }
                if (!readBundleName) {
                    bundleName = bundleId;
                }
                else {
                    bundleName = readBundleName;
                }
            }
            if (!(bundleName in bundles)) {
                bundles[bundleName] = {
                    id: bundleId,
                    variables: {},
                };
            }
            else {
                // TODO what to do when duplicated bundle name ?
            }
            bundles[bundleName].variables[variableName] = variableInfo;
            index++;
        }
        observer === null || observer === void 0 ? void 0 : observer.next({
            step: 'end-read-variable-config',
            progress: {
                loaded: variableCount,
                total: variableCount,
            },
        });
        return bundles;
    });
}
function readTapVariableConfigFromTap(tap, variableId, variableName) {
    return __awaiter$3(this, void 0, void 0, function* () {
        const [typeResponse, bundleIdResponse, lengthResponse, metaResponse] = yield tap.service.interface.executeMultipleCalls([
            tap.service.variable.getTypeCall(variableId),
            tap.service.variable.getBundleIdCall(variableId),
            tap.service.variable.getNumberOfElementsCall(variableId),
            tap.service.variable.getMetaCall(variableId),
        ]);
        const bundleId = bundleIdResponse.body();
        const variableType = typeResponse.body();
        const length = lengthResponse.body();
        const dataType = variableType.data;
        if (!variableName) {
            const meta = metaResponse.body();
            variableName = meta.name || variableId.toString();
        }
        return {
            bundleId,
            variableName,
            config: {
                id: variableId,
                dataType,
                length,
            },
        };
    });
}

var __awaiter$2 = (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());
    });
};
const TAG = 'DataManager';
/**
 * Typed data manager is usually build from Tap configuration
 *
 * It allow you to have compile time check for bundle/variable types.
 * However, you cannot add/remove/edit variables (as it would change types)
 * Use `DataManager` if you want this features
 */
class TypedDataManager {
    constructor(tap, bundles, 
    // variables: Record<VariableKey, EditableDataStream<VariableByType[VariableKey]>>
    monitoring = new MonitoringController()) {
        // const variablesCache: Record<string, EditableDataStream<VariableByType[VariableKey], unknown>> = {};
        // for (const bundleKey in this.bundles) {
        //     const bundle = this.bundles[bundleKey];
        //     for (const variableKey in bundle.variables) {
        //         const variable = bundle.variables[variableKey];
        //         variablesCache[variable.config.name] = variable;
        //     }
        // }
        // this.variablesCache = variablesCache as Record<VariableKey, EditableDataStream<VariableByType[VariableKey], unknown>>;
        // this.setupStreams();
        this.tap = tap;
        this.bundles = bundles;
        this.monitoring = monitoring;
        // switchableStream!: SwitchableStream;
        // dataStream: Observable<Record<string, any>>;
        // tlvConverter: TlvBundleConverter<Record<string, Uint8Array>>;
        // sourceControllers: Record<string, DataSourceController> = {};
        // protected variablesCache: Record<VariableKey, EditableDataStream<VariableByType[VariableKey], unknown>>;
        // private tickers: Record<string, {
        //     controller: MonitoringControllerSubject,
        //     ticker: Observable<MonitoringTickerState>
        // }> = {};
        // private ticker = createMonitoringTicker(this.monitoring);
        this._events = new Subject();
        // this.variablesCache = {} as any;
        const ticker = createMonitoringTicker(this.monitoring);
        ticker.subscribe((newTick) => __awaiter$2(this, void 0, void 0, function* () {
            // console.log('New tick', newTick);
            yield this.refreshValues();
        }));
    }
    get values() {
        const bundles = this.listBundles();
        return combineLatest(bundles.map((v) => v.values.pipe(startWith(undefined)))).pipe(map((values) => {
            return values.reduce((accumulator, v, index) => {
                const bundleStreamId = bundles[index].id;
                accumulator[bundleStreamId] = v;
                return accumulator;
            }, {});
        }));
    }
    get events() {
        return this._events.asObservable();
    }
    listVariables() {
        return this.listBundles().reduce((acc, bundle) => {
            acc.push(...bundle.variables);
            return acc;
        }, []);
    }
    bundle(key) {
        if (!(key in this.bundles)) {
            throw TapDataError.bundleNotFound(key);
        }
        return this.bundles[key];
    }
    // TODO fix typing
    variable(key) {
        const variable = this.listVariables().find((v) => v.id === key);
        if (!variable) {
            throw TapDataError.variableNotFound(key);
        }
        return variable;
    }
    // public addBundleFromConfig(key: BundleKey, bundleConfig: BundleConfig) {
    //     // const key = bundleConfig.name || bundleConfig.id.toString();
    //     // this.bundles[key] = createTapBundleDataFromConfig(bundleConfig);
    //     throw new Error(`Not implemented yet`);
    //     return this;
    // }
    destroy() {
        this.monitoring.stop();
    }
    refreshValues() {
        return __awaiter$2(this, void 0, void 0, function* () {
            const values = {};
            for (const bundle of this.listBundles()) {
                values[bundle.id] = yield bundle
                    .read()
                    .catch((error) => {
                    this._events.next({
                        error,
                        type: DataManager.EventType.error,
                    });
                    return error;
                });
            }
            return values;
        });
    }
    listBundles() {
        return Object.values(this.bundles);
    }
    // public setupStreams() {
    //     // this.bundle('x').monitor.start();
    //     // this.variable('y').monitor.
    //     const variableStream = this.ticker
    //         .pipe(
    //             switchMap(() => {
    //                 return merge(
    //                     this.listVariables().map(v => defer(() => v.read()))
    //                 );
    //             })
    //         );
    //     const bundleStream = this.ticker
    //         .pipe(
    //             switchMap(() => {
    //                 return merge(
    //                     this.listBundles().map(v => defer(() => v.read()))
    //                 );
    //             })
    //         );
    //     // const profileStream = this.ticker
    //     //     .pipe(
    //     //         switchMap(async () => {
    //     //             // debug(TAG, `New call ${time} to read profile`);
    //     //             const profileData = (await this.tap.service.variable.readProfile()).body();
    //     //             const decoded = this.tlvConverter.decode(profileData);
    //     //             this.rawDataStream.next(decoded);
    //     //         })
    //     //     );
    //     this.switchableStream
    //         .addStream(
    //             'variable',
    //             variableStream
    //         )
    //         .addStream(
    //             'bundle',
    //             bundleStream
    //         )
    //     // .addStream(
    //     //     'profile',
    //     //     profileStream
    //     // );
    // }
    hasBundle(bundleKey) {
        return bundleKey in this.bundles;
    }
    createConfiguredBundle(key, config) {
        return createTapBundleFromConfig(this.tap, key, config);
    }
    createConfiguredVariable(key, config) {
        return createTapVariableFromConfig(this.tap.service.variable, key, config);
    }
    /**
     * Create a variable with direct modbus access
     * May/May not be available according to the configured target protocol on your device
     *
     * @param id
     * @param options
     */
    createModbusAccessVariable(id, options) {
        return new ModbusAccessVariable(id, {
            config: options.config,
            targetService: this.tap.service.target,
            converter: options.converter,
        });
    }
    /**
     * Create direct target access to variable
     * May/May not be available according to configured target protocol on your device
     * @param id
     * @param options
     */
    createTargetMemoryVariable(id, options) {
        return new TargetMemoryVariable(id, {
            config: options.config,
            targetService: this.tap.service.target,
            converter: options.converter,
        });
    }
}
class DataManager extends TypedDataManager {
    // tapStreamBuilder = new TapDataStreamBuilder();
    constructor(tap, bundles = {}, 
    // variables: Record<VariableKey, EditableDataStream<VariableByType[VariableKey]>>
    monitoring = new MonitoringController()) {
        super(tap, bundles, monitoring);
    }
    // registerConfiguredBundle(config: BundleConfig) {
    //     throw new Error('Not implemented yet');
    // }
    // registerConfiguredVariable(config: VariableConfig) {
    //     // this.variables.push();
    //     throw new Error('Not implemented yet');
    // }
    // createTapBundle<T>(name: string, bundleId: number, variables: T): BundleDataStreamInterface<T> {
    //     return createTapBundleFromConfig(
    //         this.tap,
    //         name,
    //         {
    //             id: bundleId,
    //             variables
    //         }
    //     );
    // }
    setBundles(bundles) {
        this.bundles = bundles;
    }
    addBundle(bundle) {
        this.bundles[bundle.id] = bundle;
    }
    removeBundle(bundleKey) {
        delete this.bundles[bundleKey];
    }
    addVariable(bundleKey, variable) {
        const bundle = this.bundle(bundleKey);
        bundle.variables.push(variable); // TODO fix typing
    }
    /**
     * Register bundles configured in the Tap
     */
    registerBundles(bundlesConfig) {
        const tapBundles = createTapBundlesFromConfig(this.tap, bundlesConfig);
        this.setBundles(tapBundles);
        return tapBundles;
    }
    registerBundle(key, config) {
        const tapBundle = createTapBundleFromConfig(this.tap, key, config);
        this.addBundle(tapBundle);
    }
    clear() {
        this.bundles = {};
    }
    /**
     * Synchronize DataManager bundles and variables with the connected Tap device
     * It will the current profile accessible variables/bundles
     */
    synchronizeTapConfig() {
        return new Observable((observer) => {
            try {
                readDataManagerTapConfigFromTap(this.tap, {
                    observer,
                })
                    .then((config) => {
                    const newBundles = this.registerBundles(config);
                    observer.next({
                        step: 'done',
                        bundles: newBundles,
                    });
                    observer.complete();
                })
                    .catch((err) => {
                    observer.error(err);
                });
            }
            catch (err) {
                observer.error(err);
            }
        });
    }
    /**
     * Configure DataManager bundles and variables from a DataConfig object (found in Tap configuration files)
     * Data decoder will be dedecuced from dataType/length field.
     */
    configureWithDataConfig(config) {
        this.clear();
        this.registerBundles(createDataManagerConfigFromTapDataConfig(config).bundles);
    }
}
(function (DataManager) {
    let EventType;
    (function (EventType) {
        EventType["error"] = "error";
    })(EventType = DataManager.EventType || (DataManager.EventType = {}));
})(DataManager || (DataManager = {}));

var __awaiter$1 = (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());
    });
};
class VirtualBundle extends AbstractBundleDataStream {
    constructor(id, variables) {
        super(variables);
        this.id = id;
    }
    readRawInner() {
        return __awaiter$1(this, void 0, void 0, function* () {
            const output = {};
            for (const v of this.variables) {
                output[v.id] = yield v.readRaw();
            }
            return output;
        });
    }
}

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());
    });
};
class ConverterVariableView {
    constructor(id, context) {
        this.id = id;
        this.context = context;
    }
    get converter() {
        return this.context.converter;
    }
    get valueSnapshot() {
        const lastRawValue = this.context.variable.rawValueSnaphot;
        return lastRawValue !== undefined
            ? this.decodeValue(lastRawValue)
            : undefined;
    }
    get values() {
        return this.context.variable.rawValues.pipe(map((v) => this.decodeValue(v)));
    }
    read() {
        return __awaiter(this, void 0, void 0, function* () {
            const result = yield this.context.variable.readRaw();
            return this.decodeValue(result);
        });
    }
    write(value) {
        return __awaiter(this, void 0, void 0, function* () {
            const stream = yield this.getCurrentValueStream();
            this.encodeValue(value, stream);
            stream.pos = stream.getStreamSize();
            return this.context.variable.writeRaw(stream.toBytes);
        });
    }
    getCurrentValueStream() {
        return __awaiter(this, void 0, void 0, function* () {
            let currentValue;
            if (this.context.useLastKnownValueBeforeWrite) {
                currentValue = this.context.variable.rawValueSnaphot;
            }
            else {
                currentValue = yield this.context.variable.readRaw();
            }
            if (!currentValue) {
                currentValue = new Uint8Array();
            }
            return new TapStreamWriter(currentValue);
        });
    }
    encodeValue(value, stream) {
        try {
            this.converter.encode(value, stream);
        }
        catch (err) {
            throw TapDataError.unexpectedEncodedValueError(this, value, err);
        }
    }
    decodeValue(data) {
        try {
            const stream = TapStreamReader.create(data);
            return this.converter.decode(stream);
        }
        catch (err) {
            throw TapDataError.unexpectedVariableDataRead(this, data, err);
        }
    }
}

/**
 * Generated bundle index. Do not edit.
 */

export { AbstractEditableDataStream, AbstractVariable, ConverterVariableView, DataManager, ModbusAccessVariable, MonitorEngine, MonitoringController, TapBundle, TapDataError, TapVariable, TargetMemoryVariable, TypedDataManager, VariableView, VirtualBundle, computeBitLengthFromFormat, computeByteLengthFromFormat, createDataManagerConfigFromTapDataConfig, createDataManagerFromTapConfig, createMonitoringTicker, createTapBundleFromConfig, createTapVariableFromConfig, getConverterFromVariableConfig, getConverterFromVariableType, readDataManagerTapConfigFromTap, readTapVariableConfigFromTap, variableDataTypeToByteSize, variableFormatToBitSize as ɵa, AbstractBundleDataStream as ɵc, BLANK_CONVERTER as ɵd };
//# sourceMappingURL=iotize-tap-data.js.map
