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 { ArrayBufferFromString } from "./array-from-string";
import { getSharedTracer } from "./tracing/sharedtracer";
import { BridgeArrayBufferHeader_u32, hasBridgeArrayBufferHeader, replyIdFromBridgeArrayBuffer } from "./bridged-message-parser";
// Some no-op tracing helpers for concise code and good dead code removal in bundlers
let tracer = null;
let traceStart = (name) => { };
let traceEnd = () => { };
if (!(typeof Z_TRACING)) {
    tracer = getSharedTracer();
    traceStart = (name) => { tracer.start(name); };
    traceEnd = () => { tracer.end(); };
}
// This class exposes the android native bridge through an interface that 
// looks like the iOS webkit.messageHandlers one - postMessage returns a
// promise that is resolved with the reply from the app side
// There are two supported formats for the reply messages - a string-based format
// or a binary format (which can either be sent as a string or an ArrayBuffer).
// The binary format header is handled by the functions from bridged-message-parser
// The string format supports these reply message types:
// zprreply:messageId:0:[optional error string]
// zprreply:messageId:1:
// zprreply:messageId:1:stringDataLen:stringData[optional extra binary data]
// regexp for matching the formats above, allowing 1-12 digits for message ID
// and string data length
// Captured groups are messageId, success flag, success string data length
// Maximum matching prefix is 9 + 12 + 3 + 12 + 1 = 37 characters
const msgHeaderRegexp = /^zprreply:(\d{1,12}):(?:0:|(1):(?:(\d{1,12}):|$))/;
export class AndroidBridgeMessageHandler {
    constructor() {
        this._messageId = 0;
        this._replyResolvers = new Map();
        this._queuedMessages = [];
        this._processMessage = (msg) => {
            if (typeof msg.data === "string") {
                traceStart("ABMH: processStringMessage");
                this._processStringMessage(msg.data);
                traceEnd();
                return;
            }
            if (typeof msg.data === "object" && (msg.data instanceof ArrayBuffer)) {
                traceStart("ABMH: processArrayBufferMessage");
                this._processArrayBufferMessage(msg.data);
                traceEnd();
            }
        };
        this._initRequested = false;
    }
    static IsSupported() {
        let hasAndroidInterface = !!(globalThis.zprCameraBridge);
        return hasAndroidInterface;
    }
    static SharedInstance() {
        if (AndroidBridgeMessageHandler._instance == null) {
            AndroidBridgeMessageHandler._instance = new AndroidBridgeMessageHandler();
        }
        return AndroidBridgeMessageHandler._instance;
    }
    postMessage(message) {
        return __awaiter(this, void 0, void 0, function* () {
            traceStart("ABMH: postMessage");
            let fullMsg = `zpr:${this._messageId}:${message}`;
            let reply = new Promise((resolve, reject) => {
                this._replyResolvers.set(this._messageId, { resolve, reject });
            });
            if (!this._nativePort) {
                this._queuedMessages.push(fullMsg);
                this._initPort();
            }
            else {
                this._nativePort.postMessage(fullMsg);
            }
            this._messageId++;
            traceEnd();
            return reply;
        });
    }
    _processStringMessage(msgData) {
        // Check for the string-encoding of the ZPRM array buffer format
        if (hasBridgeArrayBufferHeader(msgData)) {
            let messageId = replyIdFromBridgeArrayBuffer(msgData);
            let replyResolvers = this._replyResolvers.get(messageId);
            if (!replyResolvers)
                return;
            // Can remove the resolvers from the map
            this._replyResolvers.delete(messageId);
            // Resolve the promise with the full string
            replyResolvers.resolve(msgData);
            return;
        }
        let match = msgData.match(msgHeaderRegexp);
        if (!match)
            return;
        let matchLength = match[0].length;
        let messageId = Number.parseInt(match[1]);
        let replyResolvers = this._replyResolvers.get(messageId);
        if (!replyResolvers)
            return;
        // Regardless of how we resolve the promise, we've had the response
        // for this messageId now, so can remove from the Map
        this._replyResolvers.delete(messageId);
        if (match[2] !== "1") {
            replyResolvers.reject(new Error(msgData.substring(matchLength)));
            return;
        }
        if (match[3] === undefined) {
            // Success without any data to pass back
            replyResolvers.resolve();
            return;
        }
        let stringDataLength = Number.parseInt(match[3]);
        let stringDataEnd = matchLength + stringDataLength;
        if (stringDataEnd == msgData.length) {
            // Resolve with a string
            replyResolvers.resolve(msgData.substring(matchLength));
            return;
        }
        let stringData = msgData.substring(matchLength, stringDataEnd);
        // Convert the full string to an array buffer
        // Even though we don't need the first bit of the string for the byte data,
        // this way avoids a large substring or more complex indexing code in
        // ArrayBufferFromString
        traceStart("ABMH: ArrayBufferFromString");
        let arrayBuffer = new ArrayBuffer(msgData.length * 2);
        ArrayBufferFromString(arrayBuffer, msgData);
        traceEnd();
        // Determine the offset of the byte data - rounded up to the nearest multiple
        // of 16 to meet any alignment restrictions of the underlying data types (as
        // the string header may have just been prepended to correctly-aligned data)
        let byteDataOffset = stringDataEnd * 2;
        byteDataOffset = ((byteDataOffset + 15) >> 4) << 4;
        // Resolve the promise with the combined data
        let reply = { stringData, arrayBuffer, byteDataOffset };
        replyResolvers.resolve(reply);
    }
    _processArrayBufferMessage(msgData) {
        if (msgData.byteLength < 32)
            return;
        // Check the ZPRM header
        let header = new Uint32Array(msgData, 0, 2);
        if (header[0] !== BridgeArrayBufferHeader_u32)
            return;
        let messageId = header[1];
        let replyResolvers = this._replyResolvers.get(messageId);
        if (!replyResolvers)
            return;
        // Remove the resolvers from the map
        this._replyResolvers.delete(messageId);
        // Resolve with the full ArrayBuffer
        replyResolvers.resolve(msgData);
    }
    _initPort() {
        // Ensure we only call the request once
        if (this._initRequested)
            return;
        this._initRequested = true;
        // The one-time listener to receive the port from native ZapparCameraBridge
        const listener = (msg) => {
            if (msg.data === "zprCameraBridgePort" && msg.ports.length > 0) {
                this._nativePort = msg.ports[0];
                // Post initial messages in the order they were queued
                for (const msg of this._queuedMessages) {
                    this._nativePort.postMessage(msg);
                }
                this._queuedMessages = [];
                // Remove this listener, add the main processMessage one
                window.removeEventListener("message", listener);
                window.addEventListener("message", this._processMessage);
            }
        };
        // Use the JSInterface from ZapparCameraBridge to request a message channel
        window.addEventListener("message", listener);
        let origin = window.origin || document.location.origin;
        window.zprCameraBridge.requestMessageChannel(origin);
    }
}
AndroidBridgeMessageHandler._instance = null;
