import { bitWiseAnd, bitWiseOr } from '@iotize/common/byte-stream';
import { TapStreamWriter, TapStreamReader } from '@iotize/tap/client/impl';

var CanSignal;
(function (CanSignal) {
    let Endianess;
    (function (Endianess) {
        Endianess["BIG_ENDIAN"] = "BIG_ENDIAN";
        Endianess["LITTLE_ENDIAN"] = "LITTLE_ENDIAN";
    })(Endianess = CanSignal.Endianess || (CanSignal.Endianess = {}));
})(CanSignal || (CanSignal = {}));

const DBC_DATA_FRAME_LENGTH = 8;
function encodeCanMessage(input, signals) {
    const result = TapStreamWriter.create(DBC_DATA_FRAME_LENGTH);
    for (const [attrName, signal] of Object.entries(signals)) {
        // TODO improve exception with try catch to know with field fail
        tapStreamWriteCanSignal(input[attrName], signal, result);
    }
    result.pos = DBC_DATA_FRAME_LENGTH;
    return result.toBytes;
}
function decodeCanMessage(stream, signals) {
    if (stream instanceof Uint8Array) {
        stream = new TapStreamReader(stream);
    }
    const result = {};
    for (const [attrName, signal] of Object.entries(signals)) {
        result[attrName] = tapStreamReadCanSignal(stream, signal);
    }
    return result;
}
function dbcBitPositionToDataFrameIndex(start) {
    const index = Math.abs((start % 8) - 7); // Equivalent de [-7, -5, -3, -1, 1, 3, 5, 7]
    return start + (-7 + 2 * index);
}
function tapStreamReadCanSignal(stream, signal) {
    const { bitLength } = signal;
    checkMaxLength(bitLength);
    if (stream instanceof Uint8Array) {
        stream = new TapStreamReader(stream);
    }
    const result = readBitsFromDbcSignalStream(stream, signal);
    return decodeSignalValueNumber(result, signal);
}
function readBitsFromDbcSignalStream(stream, { endianess, startBit, bitLength }) {
    const startIndex = dbcBitPositionToDataFrameIndex(startBit);
    let result;
    if (endianess === CanSignal.Endianess.LITTLE_ENDIAN) {
        result = 0 >>> 0;
        for (let dbcPosition = startBit + bitLength - 1; dbcPosition >= startBit; dbcPosition--) {
            let frameIndex = dbcBitPositionToDataFrameIndex(dbcPosition);
            stream.setBitPosition(frameIndex);
            result = result * 2 + stream.readBits(1);
        }
    }
    else {
        stream.pos = 0;
        stream.forwardBits(startIndex);
        result = stream.readBitsInt(bitLength);
    }
    return result;
}
function decodeSignalValueNumber(result, { offset, scaling, signed, bitLength: length }) {
    if (signed) {
        result = fromSignedOnNBits(result, length);
    }
    if (scaling !== undefined) {
        result = result * scaling;
    }
    if (offset !== undefined) {
        result = result + offset;
    }
    return result;
}
function encodeSignalValueNumber(value, { offset, scaling, signed, bitLength: length }) {
    if (offset !== undefined) {
        value = value - offset;
    }
    if (scaling !== 0 && scaling !== undefined) {
        value = Math.round(value / scaling);
    }
    if (signed) {
        value = toSignedOnNBits(value, length);
    }
    return value;
}
function tapStreamWriteCanSignal(value, signal, stream) {
    const { bitLength: length } = signal;
    checkMaxLength(length);
    value = encodeSignalValueNumber(value, signal);
    writeBitsToDbcMessageStream(stream, value, signal);
}
function writeBitsToDbcMessageStream(stream, value, { endianess, startBit, bitLength, }) {
    if (endianess === CanSignal.Endianess.LITTLE_ENDIAN) {
        let mask = bitShift1(bitLength - 1);
        for (let dbcPosition = startBit + bitLength - 1; dbcPosition >= startBit; dbcPosition--) {
            let frameIndex = dbcBitPositionToDataFrameIndex(dbcPosition);
            stream.setBitPosition(frameIndex);
            const bitValue = bitWiseAnd(value, mask);
            stream.writeBoolean(bitValue, 1);
            mask = mask / 2;
        }
    }
    else {
        const startIndex = dbcBitPositionToDataFrameIndex(startBit);
        stream.pos = 0;
        stream.forwardBits(startIndex);
        stream.writeBits(value, bitLength);
    }
}
function checkMaxLength(length) {
    if (length > 52) {
        // TODO proper error
        throw new Error(`CAN signal with bit length > 52 bits are not supported`);
    }
}
function fromSignedOnNBits(value, length) {
    checkMaxLength(length);
    const signedMask = bitShift1(length - 1);
    const negative = bitWiseAnd(value, signedMask) != 0;
    if (negative) {
        const valueMask = signedMask - 1;
        const cp2 = bitWiseAnd(valueMask, value);
        return cp2 - signedMask;
    }
    else {
        return value;
    }
}
function toSignedOnNBits(value, length) {
    checkMaxLength(length);
    if (value < 0) {
        if (value < -Math.pow(2, length)) {
            throw new Error(`Value ${value} is too small to be encoded as a signed on ${length} bits`);
        }
        const signedMask = bitShift1(length - 1);
        const valueMask = signedMask - 1;
        const cp2 = bitWiseAnd(valueMask, value >>> 0);
        const result = bitWiseOr(cp2, signedMask);
        return result;
    }
    else {
        if (value >= Math.pow(2, length)) {
            throw new Error(`Value ${value} is too big to be encoded as a signed on ${length} bits`);
        }
        return value;
    }
}
function bitShift1(size) {
    return Math.pow(2, size);
}

class CanSignalStreamConverter {
    constructor(options) {
        this.options = options;
    }
    encode(value, stream) {
        tapStreamWriteCanSignal(value, this.options.signal, stream);
    }
    decode(value) {
        return tapStreamReadCanSignal(value, this.options.signal);
    }
}

class CanMessageConverter {
    constructor(options) {
        this.options = options;
    }
    encode(msg) {
        return encodeCanMessage(msg, this.options.signals);
    }
    decode(data) {
        const stream = new TapStreamReader(data);
        return decodeCanMessage(stream, this.options.signals);
    }
}

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

export { CanMessageConverter, CanSignal, CanSignalStreamConverter, decodeCanMessage, encodeCanMessage };
//# sourceMappingURL=iotize-tap-target-can-dbc.js.map
