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 * as ZNM from "./zappar-cv";
import { getRuntimeObject } from "./gen/zappar-cwrap";
import { zappar_server } from "./gen/zappar-server";
import { MsgManager } from "./messages";
import { mat4 } from "gl-matrix";
import { camera_profile_t, frame_pixel_format_t } from "./gen/zappar-native";
import { handleImageBitmap } from "./worker-imagebitmap";
import { getWorkerMessageAPI } from "./worker-messages";
import { getDataDownloadAPI } from "./data-download";
export let messageManager = new MsgManager();
let workerMessageChannel0;
let workerMessageChannel1;
let latestCameraToScreenRotation = 0;
let rawDataAccessEnabled = false;
const rawCameraFrameBuffersByPipeline = new Map();
const cameraProfileBySource = new Map();
export function launchWorkerServer(wasmUrl, module, shouldRecordData) {
    return __awaiter(this, void 0, void 0, function* () {
        let mod = ZNM.default({
            locateFile: (path, prefix) => {
                if (path.endsWith("zappar-cv.wasm")) {
                    return wasmUrl;
                }
                return prefix + path;
            },
            instantiateWasm: (imports, successCallback) => {
                const instance = new WebAssembly.Instance(module, imports);
                successCallback(instance);
                return instance.exports;
            },
            onRuntimeInitialized: () => {
                let r = getRuntimeObject(mod);
                const workerMessageAPI = getWorkerMessageAPI(mod);
                const dataDownloadAPI = (shouldRecordData > 0) ? getDataDownloadAPI(mod) : undefined;
                dataDownloadAPI === null || dataDownloadAPI === void 0 ? void 0 : dataDownloadAPI.data_should_record_set(shouldRecordData); // Only happens if dataDownloadAPI is defined
                let server = new zappar_server(r, (pipelineId, ab) => {
                    messageManager.postOutgoingMessage({
                        p: pipelineId,
                        t: "zappar",
                        d: ab
                    }, [ab]);
                });
                messageManager.postOutgoingMessage("loaded", []);
                messageManager.onIncomingMessage.bind((msg) => {
                    var _a, _b, _c;
                    switch (msg.t) {
                        case "zappar":
                            server.processBuffer(msg.d);
                            messageManager.postOutgoingMessage({ t: "buf", d: msg.d }, [msg.d]);
                            break;
                        case "buf":
                            (_a = server.serializersByPipelineId.get(msg.p)) === null || _a === void 0 ? void 0 : _a.bufferReturn(msg.d);
                            break;
                        case "cameraFrameC2S": {
                            let msgt = msg;
                            let pipeline = server._pipeline_by_instance.get(msgt.p);
                            let att;
                            if (pipeline) {
                                r.pipeline_camera_frame_submit(pipeline, msgt.d, msgt.width, msgt.height, msgt.token, msgt.c2d, msgt.cm, msgt.userFacing, msgt.captureTime);
                                r.pipeline_frame_update(pipeline);
                                att = r.pipeline_camera_frame_device_attitude(pipeline);
                                server.exploreState();
                                handleMessages(mod, workerMessageAPI);
                                if (dataDownloadAPI)
                                    handleDataDownload(mod, dataDownloadAPI);
                            }
                            let ret = {
                                token: msgt.token,
                                d: msgt.d,
                                p: msgt.p,
                                t: "cameraFrameRecycleS2C",
                                att
                            };
                            messageManager.postOutgoingMessage(ret, [msgt.d]);
                            break;
                        }
                        case "rawenabled": {
                            const msgt = msg;
                            rawDataAccessEnabled = msgt.v;
                            break;
                        }
                        case "rawrequest": {
                            const msgt = msg;
                            const buffers = rawCameraFrameBuffersByPipeline.get(msgt.p);
                            const ret = {
                                t: "raw",
                                token: msgt.token,
                                p: msgt.p,
                                data: buffers ? (_c = (_b = buffers.ready.find(entry => entry.token === msgt.token)) === null || _b === void 0 ? void 0 : _b.data) !== null && _c !== void 0 ? _c : null : null,
                            };
                            messageManager.postOutgoingMessage(ret, []);
                            break;
                        }
                        case "cameraProfileC2S": {
                            let msgt = msg;
                            cameraProfileBySource.set(msgt.source, msgt.p);
                            break;
                        }
                        case "streamC2S": {
                            let msgt = msg;
                            consumeStream(mod, r, msgt.s, msgt.p, msgt.userFacing, server, msgt.source, workerMessageAPI, dataDownloadAPI).then(() => {
                                let m = { t: "streamEndedS2C", p: msgt.p, source: msgt.source };
                                // console.log('Stream ended, requesting new stream');
                                messageManager.postOutgoingMessage(m, []);
                            }).catch(err => {
                                // console.log('Stream ended with error', err);
                            });
                            break;
                        }
                        case "cameraToScreenC2S": {
                            let msgt = msg;
                            latestCameraToScreenRotation = msgt.r;
                            break;
                        }
                        case "imageBitmapC2S": {
                            let msgt = msg;
                            handleImageBitmap(msgt, r, server, messageManager);
                            break;
                        }
                        case "sensorDataC2S": {
                            const msgt = msg;
                            const pipeline = server._pipeline_by_instance.get(msgt.p);
                            if (!pipeline)
                                break;
                            switch (msgt.sensor) {
                                case "accel":
                                    r.pipeline_motion_accelerometer_submit(pipeline, msgt.timestamp, msgt.x, msgt.y, msgt.z);
                                    break;
                                case "accel_w_gravity_int":
                                    r.pipeline_motion_accelerometer_with_gravity_submit_int(pipeline, msgt.timestamp, msgt.interval, msgt.x, msgt.y, msgt.z);
                                    break;
                                case "accel_wo_gravity_int":
                                    r.pipeline_motion_accelerometer_without_gravity_submit_int(pipeline, msgt.timestamp, msgt.interval, msgt.x, msgt.y, msgt.z);
                                    break;
                                case "attitude_int":
                                    r.pipeline_motion_attitude_submit_int(pipeline, msgt.timestamp, msgt.interval, msgt.x, msgt.y, msgt.z);
                                    break;
                                case "attitude":
                                    r.pipeline_motion_attitude_submit(pipeline, msgt.timestamp, msgt.x, msgt.y, msgt.z);
                                    break;
                                case "rotation_rate_int":
                                    r.pipeline_motion_rotation_rate_submit_int(pipeline, msgt.timestamp, msgt.interval, msgt.x, msgt.y, msgt.z);
                                    break;
                                case "rotation_rate":
                                    r.pipeline_motion_rotation_rate_submit(pipeline, msgt.timestamp, msgt.x, msgt.y, msgt.z);
                                    break;
                                case "relative_orientation":
                                    r.pipeline_motion_relative_orientation_submit_int(pipeline, msgt.timestamp, msgt.interval, msgt.x, msgt.y, msgt.z, msgt.w);
                                    break;
                            }
                            break;
                        }
                        case "attitudeMatrixC2S": {
                            const msgt = msg;
                            const pipeline = server._pipeline_by_instance.get(msgt.p);
                            if (!pipeline)
                                break;
                            r.pipeline_motion_attitude_matrix_submit(pipeline, msgt.m);
                            break;
                        }
                    }
                });
            }
        });
    });
}
;
function consumeStream(mod, r, stream, p, userFacing, server, source, workerMessageAPI, dataDownloadAPI) {
    return __awaiter(this, void 0, void 0, function* () {
        while (true) {
            let reader;
            try {
                reader = yield stream.getReader();
            }
            catch (err) {
                // console.log("Error getting reader", err);
                yield delay(1000);
                continue;
            }
            try {
                yield consumeReader(mod, r, reader, p, userFacing, server, source, workerMessageAPI, dataDownloadAPI);
                return;
            }
            catch (err) {
                // console.log("Consuming reader error 2", err);
            }
            yield delay(1000);
            return;
        }
    });
}
let streamDataBufferPointer = 0;
let streamDataBufferLength = 0;
let tokenId = 1;
function getFrameOrTimeout(reader) {
    return new Promise((resolve, reject) => {
        const token = setTimeout(() => {
            // console.log('Frame timeout');
            reject('Frame timeout');
        }, 2000);
        reader.read().then(result => {
            clearTimeout(token);
            resolve(result);
        });
    });
}
const cameraToDeviceTransform = mat4.create();
const cameraModel = new Float32Array([300, 300, 160, 120, 0, 0]);
function consumeReader(mod, r, reader, p, userFacing, server, source, workerMessageAPI, dataDownloadAPI) {
    var _a, _b;
    return __awaiter(this, void 0, void 0, function* () {
        while (true) {
            let result = yield getFrameOrTimeout(reader);
            if (result.done) {
                // console.log("Stream done", userFacing);
                (_a = result.value) === null || _a === void 0 ? void 0 : _a.close();
                return;
            }
            let frame = result.value;
            let size = frame.allocationSize();
            if (size > streamDataBufferLength) {
                if (streamDataBufferPointer > 0)
                    mod._free(streamDataBufferPointer);
                streamDataBufferPointer = mod._malloc(size);
                streamDataBufferLength = size;
            }
            yield frame.copyTo(mod.HEAPU8.subarray(streamDataBufferPointer, streamDataBufferPointer + streamDataBufferLength));
            let token = tokenId;
            tokenId++;
            const width = frame.visibleRect.width;
            const height = frame.visibleRect.height;
            let uvTransform;
            let dataWidth = width;
            let dataHeight = height;
            switch (latestCameraToScreenRotation) {
                case 270:
                    uvTransform = new Float32Array([0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1]);
                    dataWidth = height;
                    dataHeight = width;
                    break;
                case 180:
                    uvTransform = new Float32Array([-1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1]);
                    break;
                case 90:
                    uvTransform = new Float32Array([0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1]);
                    dataWidth = height;
                    dataHeight = width;
                    break;
                default:
                    uvTransform = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
                    break;
            }
            let downSample = false;
            if (cameraProfileBySource.get(source) !== camera_profile_t.HIGH) {
                dataWidth /= 2;
                dataHeight /= 2;
                downSample = true;
            }
            let clone = frame.clone();
            if (userFacing)
                mat4.fromScaling(cameraToDeviceTransform, [-1, 1, -1]);
            else
                mat4.identity(cameraToDeviceTransform);
            let focalLength = (userFacing ? 300.0 : 240.0) * dataWidth / 320.0;
            cameraModel[0] = focalLength;
            cameraModel[1] = focalLength;
            cameraModel[2] = dataWidth * 0.5;
            cameraModel[3] = dataHeight * 0.5;
            const ret = {
                token: token,
                d: clone,
                p: p,
                t: "videoFrameS2C",
                userFacing,
                uvTransform,
                w: dataWidth,
                h: dataHeight,
                cameraToDevice: cameraToDeviceTransform,
                cameraModel,
                source
            };
            messageManager.postOutgoingMessage(ret, [ret.d, ret.uvTransform.buffer]);
            const pipeline = server._pipeline_by_instance.get(p);
            if (pipeline) {
                try {
                    r.pipeline_camera_frame_submit_raw_pointer(pipeline, streamDataBufferPointer, size, framePixelFormatFromFormat(frame.format), width, height, token, cameraToDeviceTransform, latestCameraToScreenRotation, cameraModel, userFacing, (_b = frame.timestamp) !== null && _b !== void 0 ? _b : -1, downSample);
                    handleMessages(mod, workerMessageAPI);
                    if (dataDownloadAPI)
                        handleDataDownload(mod, dataDownloadAPI);
                }
                catch (err) {
                    console.log('Exception during camera processing', err);
                    // const func = mod.cwrap('what_to_stderr', 'number', ['number']);
                    // func(err);
                    // (mod as any).ccall('what_to_stderr', 'number', ['number'], [err]);
                }
                r.pipeline_frame_update(pipeline);
                if (rawDataAccessEnabled) {
                    let buffers = rawCameraFrameBuffersByPipeline.get(p);
                    if (!buffers) {
                        buffers = { available: [], ready: [] };
                        rawCameraFrameBuffersByPipeline.set(p, buffers);
                    }
                    if (buffers.ready.length > 4) {
                        const removed = buffers.ready.splice(0, 1);
                        for (const entry of removed)
                            buffers.available.push(new Uint8Array(entry.data.data));
                    }
                    const size = r.pipeline_camera_frame_data_raw_size(pipeline);
                    let buffer;
                    while (!buffer || buffer.byteLength < size) {
                        if (buffers.available.length < 1)
                            buffers.available.push(new Uint8Array(size));
                        buffer = buffers.available.pop();
                    }
                    const dataPointer = r.pipeline_camera_frame_data_raw(pipeline);
                    buffer.set(mod.HEAPU8.subarray(dataPointer, dataPointer + size));
                    buffers.ready.push({ token, data: {
                            data: buffer,
                            width: r.pipeline_camera_data_width(p),
                            height: r.pipeline_camera_data_height(p),
                        } });
                }
                server.exploreState();
            }
            frame.close();
        }
    });
}
function delay(ms) {
    return new Promise(resolve => {
        setTimeout(resolve, ms);
    });
}
;
function framePixelFormatFromFormat(f) {
    switch (f) {
        case "I420": return frame_pixel_format_t.FRAME_PIXEL_FORMAT_I420;
        case "I420A": return frame_pixel_format_t.FRAME_PIXEL_FORMAT_I420A;
        case "I422": return frame_pixel_format_t.FRAME_PIXEL_FORMAT_I422;
        case "I444": return frame_pixel_format_t.FRAME_PIXEL_FORMAT_I444;
        case "NV12": return frame_pixel_format_t.FRAME_PIXEL_FORMAT_NV12;
        case "RGBA":
        case "RGBX":
            return frame_pixel_format_t.FRAME_PIXEL_FORMAT_RGBA;
        case "BGRA":
        case "BGRX":
            return frame_pixel_format_t.FRAME_PIXEL_FORMAT_BGRA;
    }
    return frame_pixel_format_t.FRAME_PIXEL_FORMAT_Y;
}
function handleMessages(mod, m) {
    const toSend = m.worker_message_send_count();
    if (toSend === 0)
        return;
    if (!workerMessageChannel0 || !workerMessageChannel1) {
        workerMessageChannel0 = new MessageChannel();
        workerMessageChannel0.port1.start();
        workerMessageChannel0.port1.addEventListener('message', evt => {
            if (evt.data.t !== 'msgrec')
                return;
            const data = evt.data.data;
            const ptr = mod._malloc(data.byteLength);
            mod.HEAPU8.set(data, ptr);
            m.worker_message_receive(evt.data.reference, data.byteLength, ptr, 0);
            mod._free(ptr);
        });
        workerMessageChannel1 = new MessageChannel();
        workerMessageChannel1.port1.start();
        workerMessageChannel1.port1.addEventListener('message', evt => {
            if (evt.data.t !== 'msgrec')
                return;
            const data = evt.data.data;
            const ptr = mod._malloc(data.byteLength);
            mod.HEAPU8.set(data, ptr);
            m.worker_message_receive(evt.data.reference, data.byteLength, ptr, 1);
            mod._free(ptr);
        });
        messageManager.postOutgoingMessage({ t: 'setupCeresWorker', port0: workerMessageChannel0.port2, port1: workerMessageChannel1.port2 }, [workerMessageChannel0.port2, workerMessageChannel1.port2]);
    }
    for (let i = 0; i < toSend; i++) {
        const reference = m.worker_message_send_reference(i);
        const dataSize = m.worker_message_send_data_size(i);
        const instance = m.worker_message_send_instance(i);
        const dataPtr = m.worker_message_send_data(i);
        const data = mod.HEAPU8.slice(dataPtr, dataPtr + dataSize);
        (instance === 0 ? workerMessageChannel0 : workerMessageChannel1).port1.postMessage({
            t: 'msgsend',
            data,
            reference,
            instance
        }, [data.buffer]);
    }
    m.worker_message_send_clear();
}
function handleDataDownload(mod, d) {
    const size = d.data_download_size();
    if (size === 0)
        return;
    const dataPtr = d.data_download();
    const data = mod.HEAPU8.slice(dataPtr, dataPtr + size);
    messageManager.postOutgoingMessage({ t: '_z_datadownload', data }, [data.buffer]);
    d.data_download_clear();
}
