"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.IIPHandler = void 0;
const ImageRenderer_1 = require("./ImageRenderer");
const ImageStorage_1 = require("./ImageStorage");
const Base64Decoder_wasm_1 = require("xterm-wasm-parts/lib/base64/Base64Decoder.wasm");
const IIPHeaderParser_1 = require("./IIPHeaderParser");
const IIPMetrics_1 = require("./IIPMetrics");
// limit hold memory in base64 decoder
const KEEP_DATA = 4194304;
// default IIP header values
const DEFAULT_HEADER = {
    name: 'Unnamed file',
    size: 0,
    width: 'auto',
    height: 'auto',
    preserveAspectRatio: 1,
    inline: 0
};
class IIPHandler {
    constructor(_opts, _renderer, _storage, _coreTerminal) {
        this._opts = _opts;
        this._renderer = _renderer;
        this._storage = _storage;
        this._coreTerminal = _coreTerminal;
        this._aborted = false;
        this._hp = new IIPHeaderParser_1.HeaderParser();
        this._header = DEFAULT_HEADER;
        this._dec = new Base64Decoder_wasm_1.default(KEEP_DATA);
        this._metrics = IIPMetrics_1.UNSUPPORTED_TYPE;
    }
    reset() { }
    start() {
        this._aborted = false;
        this._header = DEFAULT_HEADER;
        this._metrics = IIPMetrics_1.UNSUPPORTED_TYPE;
        this._hp.reset();
    }
    put(data, start, end) {
        if (this._aborted)
            return;
        if (this._hp.state === 4 /* HeaderState.END */) {
            if (this._dec.put(data, start, end)) {
                this._dec.release();
                this._aborted = true;
            }
        }
        else {
            const dataPos = this._hp.parse(data, start, end);
            if (dataPos === -1) {
                this._aborted = true;
                return;
            }
            if (dataPos > 0) {
                this._header = Object.assign({}, DEFAULT_HEADER, this._hp.fields);
                if (!this._header.inline || !this._header.size || this._header.size > this._opts.iipSizeLimit) {
                    this._aborted = true;
                    return;
                }
                this._dec.init(this._header.size);
                if (this._dec.put(data, dataPos, end)) {
                    this._dec.release();
                    this._aborted = true;
                }
            }
        }
    }
    end(success) {
        if (this._aborted)
            return true;
        let w = 0;
        let h = 0;
        // early exit condition chain
        let cond = true;
        if (cond = success) {
            if (cond = !this._dec.end()) {
                this._metrics = (0, IIPMetrics_1.imageType)(this._dec.data8);
                if (cond = this._metrics.mime !== 'unsupported') {
                    w = this._metrics.width;
                    h = this._metrics.height;
                    if (cond = w && h && w * h < this._opts.pixelLimit) {
                        [w, h] = this._resize(w, h).map(Math.floor);
                        cond = w && h && w * h < this._opts.pixelLimit;
                    }
                }
            }
        }
        if (!cond) {
            this._dec.release();
            return true;
        }
        const blob = new Blob([this._dec.data8], { type: this._metrics.mime });
        this._dec.release();
        if (!window.createImageBitmap) {
            const url = URL.createObjectURL(blob);
            const img = new Image();
            return new Promise(r => {
                img.addEventListener('load', () => {
                    var _a;
                    URL.revokeObjectURL(url);
                    const canvas = ImageRenderer_1.ImageRenderer.createCanvas(window.document, w, h);
                    (_a = canvas.getContext('2d')) === null || _a === void 0 ? void 0 : _a.drawImage(img, 0, 0, w, h);
                    this._storage.addImage(canvas);
                    r(true);
                });
                img.src = url;
                // sanity measure to avoid terminal blocking from dangling promise
                // happens from corrupt data (onload never gets fired)
                setTimeout(() => r(true), 1000);
            });
        }
        return createImageBitmap(blob, { resizeWidth: w, resizeHeight: h })
            .then(bm => {
            this._storage.addImage(bm);
            return true;
        });
    }
    _resize(w, h) {
        var _a, _b, _c, _d;
        const cw = ((_a = this._renderer.dimensions) === null || _a === void 0 ? void 0 : _a.css.cell.width) || ImageStorage_1.CELL_SIZE_DEFAULT.width;
        const ch = ((_b = this._renderer.dimensions) === null || _b === void 0 ? void 0 : _b.css.cell.height) || ImageStorage_1.CELL_SIZE_DEFAULT.height;
        const width = ((_c = this._renderer.dimensions) === null || _c === void 0 ? void 0 : _c.css.canvas.width) || cw * this._coreTerminal.cols;
        const height = ((_d = this._renderer.dimensions) === null || _d === void 0 ? void 0 : _d.css.canvas.height) || ch * this._coreTerminal.rows;
        const rw = this._dim(this._header.width, width, cw);
        const rh = this._dim(this._header.height, height, ch);
        if (!rw && !rh) {
            const wf = width / w; // TODO: should this respect initial cursor offset?
            const hf = (height - ch) / h; // TODO: fix offset issues from float cell height
            const f = Math.min(wf, hf);
            return f < 1 ? [w * f, h * f] : [w, h];
        }
        return !rw
            ? [w * rh / h, rh]
            : this._header.preserveAspectRatio || !rw || !rh
                ? [rw, h * rw / w] : [rw, rh];
    }
    _dim(s, total, cdim) {
        if (s === 'auto')
            return 0;
        if (s.endsWith('%'))
            return parseInt(s.slice(0, -1)) * total / 100;
        if (s.endsWith('px'))
            return parseInt(s.slice(0, -2));
        return parseInt(s) * cdim;
    }
}
exports.IIPHandler = IIPHandler;