<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>ImJoy Plugin Template</title>
    <meta name="description" content="Template for ImJoy plugin">
    <meta name="author" content="ImJoy-Team">
</head>

<body>
<ul id="console">

</ul>
<script id="worker" type="javascript/worker">
const window = self;
const src = `
{{ script | safe }}
`
const startupScript = `
import js
import micropip
import sys
import traceback
import asyncio

# patch ssl module for fastapi etc.
from types import ModuleType
m = ModuleType("ssl")
m.SSLObject = None
m.MemoryBIO = None
m.SSLContext = None
sys.modules["ssl"] = m
del m

async def run():
    try:
        await micropip.install(["imjoy-rpc>=0.3.34", {% for req in requirements %}"{{req}}", {% endfor %}])
        # map imjoy_rpc to imjoy
        import imjoy_rpc
        sys.modules
        sys.modules["imjoy"] = imjoy_rpc
        js.__resolve()
    except Exception as e:
        js.__reject(traceback.format_exc())

asyncio.get_event_loop().run_until_complete(run())
`

function installPackage(){
    return new Promise((resolve, reject)=>{
        self.__resolve = resolve
        self.__reject = reject
        self.pyodide.runPython(startupScript)
    })
}

const toObject = (x) => {
    if(x===undefined || x===null) return x;
    if(self.pyodide.isPyProxy(x)){
        return x.toJs({dict_converter : Object.fromEntries})
    }
    else if (x instanceof Array) {
        return x.map(toObject);
    } else {
        return x;
    }
}

importScripts("https://cdn.jsdelivr.net/npm/socket.io-client@4.0.1/dist/socket.io.min.js")

async function setupPyodide() {
    if(self.pyodide) return;
    importScripts('https://cdn.jsdelivr.net/pyodide/v0.18.1/full/pyodide.js');
    self.pyodide = await loadPyodide({
        indexURL : 'https://cdn.jsdelivr.net/pyodide/v0.18.1/full/',
        stdout: (text) => {
            self.postMessage({"type": "stdout", "content": text})
        },
        stderr: (text) => {
            self.postMessage({"type": "stderr", "content": text})
        },
    });
    await self.pyodide.loadPackage(['micropip']);
    await installPackage()
}

self.onmessage = function(e) {
    const config = e.data
    config.server_token=config.token
    self.config = config
    const url = config.server_url.replace(
        "http://localhost",
        "http://127.0.0.1"
    );
    const extraHeaders = {};
    if (config.token) {
        extraHeaders.Authorization = "Bearer " + config.token;
    }
    // const basePath = new URL(url).pathname;
    // Note: extraHeaders only works for polling transport (the default)
    // If we switch to websocket only, the headers won't be respected
    const socket = io(url, {
        withCredentials: true,
        extraHeaders,
    });
    socket.on("connect", () => {
        self.sendMessage = function(data){
            data = toObject(data)
            socket.emit("plugin_message", data)
        }
        setupPyodide().then(()=>{
            socket.emit("register_plugin", config, async (result) => {
                if (!result.success) {
                    console.error(result.detail);
                    reject(result.detail);
                    self.postMessage({type: "stderr", "content": result.detail})
                    return;
                }

                self.postMessage({type: "stdout", "content": "Plugin registered: " + config.name})
                self.postMessage({type: "title", "content": config.name})
                self.setMessageCallback = (cb)=>{
                    socket.on("plugin_message", cb);
                }
                // const startup_script = await (await fetch("./start_pyodide.py")).text()
                // run pyodide in a new context
                const dict = pyodide.pyimport("dict");
                self.pyodide.runPython(src, dict());
        
            })
        })
        socket.on("connect_error", (error) => {
            console.error("connection error", error);
        });
        socket.on("disconnect", () => {
            console.error("disconnected");
        });
    })
    
}
    
</script>
<script>
const consoleElem = document.getElementById('console');
const blob = new Blob([
    document.querySelector('#worker').textContent
], { type: "text/javascript" })
const worker = new Worker(window.URL.createObjectURL(blob));
worker.onerror = console.error
worker.onmessage = (e)=>{
    if(e.data.type === 'title'){
        document.title = e.data.content;
        return
    }
    if(e.data.type === 'stdout'){
        const li = document.createElement('li')
        li.innerHTML = e.data.content;
        li.style.color ="blue";
        consoleElem.appendChild(li)
    }
    else if(e.data.type === 'stderr'){
        const li = document.createElement('li')
        li.innerHTML = e.data.content;
        li.style.color ="red";
        consoleElem.appendChild(li)
    }
    // make sure we don't have too many elements in the dom
    if(consoleElem.children.length>1024){
        consoleElem.removeChild(consoleElem.firstChild)
    }
}
const cfg = {{ config | tojson(indent=2) }}
const config = Object.assign(cfg, Object.fromEntries(new URLSearchParams(window.location.search)));
if(!config.server_url) config.server_url = window.location.origin;
worker.postMessage(config); 

</script>
</body>
</html>