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());
    });
};
import { QueueComProtocol } from '@iotize/tap/protocol/core';
import { from, Observable } from 'rxjs';
import { first, map, share } from 'rxjs/operators';
import { BleConfig } from './ble-config';
import { BLEPacketBuilder } from './ble-packet-builder';
import { BLEPacketSplitter } from './ble-packet-splitter';
import { debug } from './debug';
import { BleComError } from './errors';
import { sanitizeUUID } from './util';
export const DEFAULT_BLE_OPTIONS = {
    mtu: BleConfig.maxPacketLengthWithoutOffset + 1,
    maximumBufferLength: 255,
    waitForWriteAcknowledge: true,
    preferedComServiceType: 'large-frame',
    sanitizeUUID: false,
};
/**
 * BLE communication
 *
 * With ble communication, data is split into sub packets.
 * This class handles creation of packet chunks.
 *
 * - You must only implement the function to send one packet chunk writeLwm2mPacketChunk()
 * -
 */
export class UniversalBleProtocolAdapter extends QueueComProtocol {
    constructor(peripheral, bleOptions = {}) {
        super();
        this.peripheral = peripheral;
        this._useSplitter = true;
        this.bleOptions = Object.assign(Object.assign({}, DEFAULT_BLE_OPTIONS), bleOptions);
        this.options.connect.timeout = 8000;
        this.options.send.timeout = 4000;
        this.options.disconnect.timeout = 8000;
        peripheral.stateChange.subscribe((newState) => {
            if (newState !== this.getConnectionState()) {
                this.setConnectionState(newState);
            }
        });
    }
    get lwm2mCharc() {
        if (!this._lwm2mCharc) {
            throw new Error(`Illegal state error, lwm2m characteristic is not set`);
        }
        return this._lwm2mCharc;
    }
    sanitizeUUID(uuid) {
        return this.bleOptions.sanitizeUUID ? sanitizeUUID(uuid) : uuid;
    }
    _connect() {
        return new Observable((emitter) => {
            (() => __awaiter(this, void 0, void 0, function* () {
                try {
                    yield this.peripheral.connect();
                    this._lwm2mCharc = yield this.setupLwm2mCharacteristic();
                    emitter.next(null);
                    emitter.complete();
                }
                catch (err) {
                    try {
                        yield this.peripheral.disconnect();
                    }
                    catch (err) {
                        debug('Failed to propertly disconnect after connection failed', err.message);
                    }
                    emitter.error(err);
                }
            }))();
        }).pipe(share());
    }
    _disconnect() {
        this._lwm2mCharc = undefined;
        if (this.peripheral) {
            return from(this.peripheral.disconnect());
        }
        return from(Promise.resolve());
    }
    setupLwm2mCharacteristic() {
        return __awaiter(this, void 0, void 0, function* () {
            const lwm2mServiceUUIDs = [
                this.sanitizeUUID(BleConfig.services.lwm2m.service),
            ];
            if (this.bleOptions.preferedComServiceType === 'large-frame') {
                lwm2mServiceUUIDs.push(this.sanitizeUUID(BleConfig.services.fastLwm2m.service));
            }
            const serviceMap = yield this.peripheral.discoverServices(lwm2mServiceUUIDs);
            debug('Found services ', Object.keys(serviceMap).join(', '), 'asked for services: ', lwm2mServiceUUIDs.join(', '));
            const charac = yield this._selectLwm2mCharacteristic(serviceMap);
            yield charac.enableNotifications(true);
            return charac;
        });
    }
    _selectLwm2mCharacteristic(serviceMap) {
        return __awaiter(this, void 0, void 0, function* () {
            const largeFrameServiceUUID = this.sanitizeUUID(BleConfig.services.fastLwm2m.service);
            const legacyLwm2mServiceUUID = this.sanitizeUUID(BleConfig.services.lwm2m.service);
            if (this.bleOptions.preferedComServiceType === 'legacy' &&
                serviceMap[legacyLwm2mServiceUUID]) {
                debug('Force usage of legacy lwm2m characteristic UUID: ' +
                    legacyLwm2mServiceUUID);
                return this._getLegacyLwm2mCharacteristic(serviceMap[legacyLwm2mServiceUUID]);
            }
            else if (serviceMap[largeFrameServiceUUID]) {
                debug('Found fast lwm2m characteristic UUID: ' + largeFrameServiceUUID);
                return this._getLargeFrameLwm2mCharacteristic(serviceMap[largeFrameServiceUUID]);
            }
            else if (serviceMap[legacyLwm2mServiceUUID]) {
                debug('Found legacy lwm2m characteristic UUID: ' + legacyLwm2mServiceUUID);
                return this._getLegacyLwm2mCharacteristic(serviceMap[legacyLwm2mServiceUUID]);
            }
            else {
                throw BleComError.serviceNotFound(legacyLwm2mServiceUUID);
            }
        });
    }
    _getLargeFrameLwm2mCharacteristic(service) {
        return __awaiter(this, void 0, void 0, function* () {
            this.bleOptions.mtu = 255; // TODO read from device
            this._useSplitter = false;
            return yield service.getCharacteristic(this.sanitizeUUID(BleConfig.services.fastLwm2m.charac));
        });
    }
    _getLegacyLwm2mCharacteristic(service) {
        return __awaiter(this, void 0, void 0, function* () {
            this.bleOptions.mtu = BleConfig.maxPacketLengthWithoutOffset + 1;
            this._useSplitter = true;
            return yield service.getCharacteristic(this.sanitizeUUID(BleConfig.services.lwm2m.charac));
        });
    }
    read() {
        return __awaiter(this, void 0, void 0, function* () {
            // debug('read()...');
            if (!this._readPromise) {
                this._readPromise = this._createReadPromise();
            }
            return this._readPromise;
        });
    }
    readUnit() {
        return __awaiter(this, void 0, void 0, function* () {
            return this.lwm2mCharc.data
                .pipe(first(), map((info) => info.data))
                .toPromise();
        });
    }
    write(data) {
        return __awaiter(this, void 0, void 0, function* () {
            // debug('write()...');
            this._readPromise = this._createReadPromise();
            if (this.useSplitter) {
                const chunks = BLEPacketSplitter.wrapWithChecksum(data, this.chunkSize).getPackets();
                for (const chunk of chunks) {
                    yield this.writeUnit(chunk);
                }
            }
            else {
                return this.writeUnit(data);
            }
        });
    }
    get useSplitter() {
        return this._useSplitter;
    }
    get chunkSize() {
        return this.bleOptions.mtu - 1;
    }
    writeUnit(data) {
        return __awaiter(this, void 0, void 0, function* () {
            if (data.length > this.bleOptions.mtu) {
                throw BleComError.writeSizeAboveMTU(data, this.bleOptions.mtu);
            }
            if (this.bleOptions.waitForWriteAcknowledge) {
                yield this.lwm2mCharc.write(data, true);
            }
            else {
                this.lwm2mCharc.write(data, true).catch((err) => {
                    console.warn(`Write error ignored`, err);
                });
            }
        });
    }
    _createReadPromise() {
        return __awaiter(this, void 0, void 0, function* () {
            // debug(`_createReadPromise()`);
            if (this.useSplitter) {
                const packetBuilder = new BLEPacketBuilder(this.bleOptions.maximumBufferLength);
                while (!packetBuilder.hasAllChunks()) {
                    const chunk = yield this.readUnit();
                    packetBuilder.append(chunk);
                }
                if (packetBuilder.isChecksumValid()) {
                    return packetBuilder.getData();
                }
                else {
                    throw BleComError.invalidBleChunkChecksum(packetBuilder);
                }
            }
            else {
                return this.readUnit();
            }
        });
    }
}
//# sourceMappingURL=data:application/json;base64, 