import { settings } from "../../core/settings";
import { logger } from "../../core/logging";
import { div, px } from "../../core/dom";
import { OutputBackend } from "../../core/enums";
import { UIEventBus } from "../../core/ui_events";
import { load_module } from "../../core/util/modules";
import { CanvasLayer } from "../../core/util/canvas";
import { UIElement, UIElementView } from "../ui/ui_element";
import { InlineStyleSheet } from "../../core/dom";
import * as canvas_css from "../../styles/canvas.css";
import icons_css from "../../styles/icons.css";
async function init_webgl() {
    // We use a global invisible canvas and gl context. By having a global context,
    // we avoid the limitation of max 16 contexts that most browsers have.
    const canvas = document.createElement("canvas");
    const gl = canvas.getContext("webgl", { alpha: true, antialias: false, depth: false, premultipliedAlpha: true });
    // If WebGL is available, we store a reference to the ReGL wrapper on
    // the ctx object, because that's what gets passed everywhere.
    if (gl != null) {
        const webgl = await load_module(import("../glyphs/webgl"));
        if (webgl != null) {
            const regl_wrapper = webgl.get_regl(gl);
            if (regl_wrapper.has_webgl) {
                return { canvas, regl_wrapper };
            }
            else {
                logger.trace("WebGL is supported, but not the required extensions");
            }
        }
        else {
            logger.trace("WebGL is supported, but bokehjs(.min).js bundle is not available");
        }
    }
    else {
        logger.trace("WebGL is not supported");
    }
    return null;
}
const global_webgl = (() => {
    let _global_webgl;
    return async () => {
        if (_global_webgl !== undefined) {
            return _global_webgl;
        }
        else {
            return _global_webgl = await init_webgl();
        }
    };
})();
export class CanvasView extends UIElementView {
    static __name__ = "CanvasView";
    webgl = null;
    underlays_el;
    primary;
    overlays;
    overlays_el;
    events_el;
    ui_event_bus;
    _size = new InlineStyleSheet();
    initialize() {
        super.initialize();
        this.underlays_el = div({ class: canvas_css.layer });
        this.primary = this.create_layer();
        this.overlays = this.create_layer();
        this.overlays_el = div({ class: canvas_css.layer });
        this.events_el = div({ class: [canvas_css.layer, canvas_css.events] });
        this.ui_event_bus = new UIEventBus(this);
    }
    get layers() {
        return [
            this.underlays_el,
            this.primary,
            this.overlays,
            this.overlays_el,
            this.events_el,
        ];
    }
    async lazy_initialize() {
        await super.lazy_initialize();
        if (this.model.output_backend == "webgl") {
            this.webgl = await global_webgl();
            if (settings.force_webgl && this.webgl == null) {
                throw new Error("webgl is not available");
            }
        }
    }
    remove() {
        this.ui_event_bus.remove();
        super.remove();
    }
    stylesheets() {
        return [...super.stylesheets(), canvas_css.default, icons_css, this._size];
    }
    render() {
        super.render();
        const elements = [
            this.underlays_el,
            this.primary.el,
            this.overlays.el,
            this.overlays_el,
            this.events_el,
        ];
        this.shadow_el.append(...elements);
    }
    get pixel_ratio() {
        return this.primary.pixel_ratio; // XXX: primary
    }
    _update_bbox() {
        const changed = super._update_bbox();
        if (changed) {
            const { width, height } = this.bbox;
            this._size.replace(`.${canvas_css.layer}`, {
                width: px(width),
                height: px(height),
            });
            this.primary.resize(width, height);
            this.overlays.resize(width, height);
        }
        return changed;
    }
    after_resize() {
        if (this.plot_views.length != 0) {
            // Canvas is being managed by a plot, thus it should not attempt
            // self-resize, as it would result in inconsistent state and
            // possibly invalid layout and/or lack of repaint of a plot.
            this.finish();
        }
        else {
            super.after_resize();
        }
    }
    _after_resize() {
        super._after_resize();
        const { width, height } = this.bbox;
        this.primary.resize(width, height);
        this.overlays.resize(width, height);
    }
    resize() {
        this._update_bbox();
        this._after_resize();
    }
    prepare_webgl(frame_box) {
        // Prepare WebGL for a drawing pass
        const { webgl } = this;
        if (webgl != null) {
            // Sync canvas size
            const { width, height } = this.bbox;
            webgl.canvas.width = this.pixel_ratio * width;
            webgl.canvas.height = this.pixel_ratio * height;
            const [sx, sy, w, h] = frame_box;
            const { xview, yview } = this.bbox;
            const vx = xview.compute(sx);
            const vy = yview.compute(sy + h);
            const ratio = this.pixel_ratio;
            webgl.regl_wrapper.set_scissor(ratio * vx, ratio * vy, ratio * w, ratio * h);
            this._clear_webgl();
        }
    }
    blit_webgl(ctx) {
        // This should be called when the ctx has no state except the HIDPI transform
        const { webgl } = this;
        if (webgl != null && webgl.canvas.width * webgl.canvas.height > 0) {
            // Blit gl canvas into the 2D canvas. To do 1-on-1 blitting, we need
            // to remove the hidpi transform, then blit, then restore.
            // ctx.globalCompositeOperation = "source-over"  -> OK; is the default
            logger.debug("Blitting WebGL canvas");
            ctx.restore();
            ctx.drawImage(webgl.canvas, 0, 0);
            // Set back hidpi transform
            ctx.save();
            if (this.model.hidpi) {
                const ratio = this.pixel_ratio;
                ctx.scale(ratio, ratio);
                ctx.translate(0.5, 0.5);
            }
            this._clear_webgl();
        }
    }
    _clear_webgl() {
        const { webgl } = this;
        if (webgl != null) {
            // Prepare GL for drawing
            const { regl_wrapper, canvas } = webgl;
            regl_wrapper.clear(canvas.width, canvas.height);
        }
    }
    compose() {
        const composite = this.create_layer();
        const { width, height } = this.bbox;
        composite.resize(width, height);
        composite.ctx.drawImage(this.primary.canvas, 0, 0);
        composite.ctx.drawImage(this.overlays.canvas, 0, 0);
        return composite;
    }
    create_layer() {
        const { output_backend, hidpi } = this.model;
        return new CanvasLayer(output_backend, hidpi);
    }
    to_blob() {
        return this.compose().to_blob();
    }
    plot_views = [];
}
export class Canvas extends UIElement {
    static __name__ = "Canvas";
    constructor(attrs) {
        super(attrs);
    }
    static {
        this.prototype.default_view = CanvasView;
        this.define(({ Bool }) => ({
            hidpi: [Bool, true],
            output_backend: [OutputBackend, "canvas"],
        }));
    }
}
//# sourceMappingURL=canvas.js.map