import * as p from "../../core/properties";
import * as bbox from "../../core/util/bbox";
import * as visuals from "../../core/visuals";
import * as uniforms from "../../core/uniforms";
import { settings } from "../../core/settings";
import { DOMComponentView } from "../../core/dom_view";
import { Model } from "../../model";
import { build_views } from "../../core/build_views";
import { logger } from "../../core/logging";
import { ScreenArray, Indices } from "../../core/types";
import { isString } from "../../core/util/types";
import { RaggedArray } from "../../core/util/ragged_array";
import { inplace_map } from "../../core/util/arrayable";
import { inplace, project_xy } from "../../core/util/projections";
import { is_equal, EqNotImplemented } from "../../core/util/eq";
import { SpatialIndex } from "../../core/util/spatial";
import { assert } from "../../core/util/assert";
import { BBox } from "../../core/util/bbox";
import { FactorRange } from "../ranges/factor_range";
import { Selection } from "../selections/selection";
import { Decoration } from "../graphics/decoration";
const { abs, ceil } = Math;
export const inherit = Symbol("inherit");
export class GlyphView extends DOMComponentView {
    static __name__ = "GlyphView";
    visuals;
    get renderer() {
        return this.parent;
    }
    /** @internal */
    glglyph;
    get has_webgl() {
        return this.glglyph != null;
    }
    _index = null;
    _data_size = null;
    _nohit_warned = new Set();
    get index() {
        const { _index } = this;
        if (_index != null) {
            return _index;
        }
        else {
            throw new Error(`${this}.index_data() wasn't called`);
        }
    }
    get data_size() {
        const { base } = this;
        if (base != null) {
            return base.data_size;
        }
        else {
            const { _data_size } = this;
            if (_data_size != null) {
                return _data_size;
            }
            else {
                throw new Error(`${this}.set_data() wasn't called`);
            }
        }
    }
    initialize() {
        super.initialize();
        this.visuals = new visuals.Visuals(this);
    }
    decorations = new Map();
    *children() {
        yield* super.children();
        yield* this.decorations.values();
    }
    async lazy_initialize() {
        await super.lazy_initialize();
        await build_views(this.decorations, this.model.decorations, { parent: this.parent });
        const { webgl } = this.canvas;
        if (webgl != null && this.load_glglyph != null) {
            const cls = await this.load_glglyph();
            this.glglyph = new cls(webgl.regl_wrapper, this);
        }
    }
    request_paint() {
        this.parent.request_paint();
    }
    get canvas() {
        return this.renderer.parent.canvas_view;
    }
    paint(ctx, indices, data) {
        if (this.glglyph != null) {
            this.glglyph.render(ctx, indices, this.base ?? this);
        }
        else if (this.canvas.webgl != null && settings.force_webgl) {
            throw new Error(`${this} doesn't support webgl rendering`);
        }
        else {
            this._paint(ctx, indices, data);
        }
    }
    has_finished() {
        return true;
    }
    notify_finished() {
        this.renderer.notify_finished();
    }
    _bounds(bounds) {
        return bounds;
    }
    bounds() {
        return this._bounds(this.index.bbox);
    }
    log_bounds() {
        const { x0, x1 } = this.index.bounds(bbox.positive_x());
        const { y0, y1 } = this.index.bounds(bbox.positive_y());
        return this._bounds({ x0, y0, x1, y1 });
    }
    get_anchor_point(anchor, i, [sx, sy]) {
        switch (anchor) {
            case "center":
            case "center_center": {
                const [x, y] = this.scenterxy(i, sx, sy);
                return { x, y };
            }
            default:
                return null;
        }
    }
    sdist(scale, pts, spans, pts_location = "edge", dilate = false) {
        const n = pts.length;
        const sdist = new ScreenArray(n);
        const compute = scale.s_compute;
        if (pts_location == "center") {
            for (let i = 0; i < n; i++) {
                const pts_i = pts[i];
                const halfspan_i = spans.get(i) / 2;
                const spt0 = compute(pts_i - halfspan_i);
                const spt1 = compute(pts_i + halfspan_i);
                sdist[i] = abs(spt1 - spt0);
            }
        }
        else {
            for (let i = 0; i < n; i++) {
                const pts_i = pts[i];
                const spt0 = compute(pts_i);
                const spt1 = compute(pts_i + spans.get(i));
                sdist[i] = abs(spt1 - spt0);
            }
        }
        if (dilate) {
            inplace_map(sdist, (sd) => ceil(sd));
        }
        return sdist;
    }
    draw_legend_for_index(_ctx, _bbox, _index) { }
    hit_test(geometry) {
        const hit = (() => {
            switch (geometry.type) {
                case "point": return this._hit_point?.(geometry);
                case "span": return this._hit_span?.(geometry);
                case "rect": return this._hit_rect?.(geometry);
                case "poly": return this._hit_poly?.(geometry);
            }
        })();
        if (hit != null) {
            return hit;
        }
        if (!this._nohit_warned.has(geometry.type)) {
            logger.debug(`'${geometry.type}' selection not available for ${this.model.type}`);
            this._nohit_warned.add(geometry.type);
        }
        return null;
    }
    _hit_rect_against_index(geometry) {
        const { sx0, sx1, sy0, sy1 } = geometry;
        const [x0, x1] = this.renderer.coordinates.x_scale.r_invert(sx0, sx1);
        const [y0, y1] = this.renderer.coordinates.y_scale.r_invert(sy0, sy1);
        const indices = [...this.index.indices({ x0, x1, y0, y1 })];
        return new Selection({ indices });
    }
    _project_xy(x, xs, y, ys) {
        const inherited_x = this._is_inherited(x);
        const inherited_y = this._is_inherited(y);
        if (!inherited_x && !inherited_y) {
            inplace.project_xy(xs, ys);
        }
        else if (!inherited_x || !inherited_y) {
            const [proj_x, proj_y] = project_xy(xs, ys);
            this._define_attr(x, proj_x);
            this._define_attr(y, proj_y);
        }
    }
    _project_data() { }
    *_iter_visuals() {
        for (const visual of this.visuals) {
            for (const prop of visual) {
                if (prop instanceof p.VectorSpec || prop instanceof p.ScalarSpec) {
                    yield prop;
                }
            }
        }
    }
    _base = null;
    get base() {
        return this._base;
    }
    set_base(base) {
        if (base != this && base instanceof this.constructor) {
            this._base = base;
        }
        else {
            this._base = null;
        }
    }
    _define_or_inherit_attr(attr, fn) {
        const value = fn();
        if (value === inherit) {
            this._inherit_attr(attr);
        }
        else {
            this._define_attr(attr, value);
        }
    }
    _define_attr(attr, value) {
        Object.defineProperty(this, attr, {
            configurable: true,
            enumerable: true,
            value,
        });
        this._define_inherited(attr, false);
    }
    _inherit_attr(attr) {
        const { base } = this;
        assert(base != null);
        this._inherit_from(attr, base);
    }
    _inherit_from(attr, base) {
        Object.defineProperty(this, attr, {
            configurable: true,
            enumerable: true,
            get() {
                return base[attr];
            },
        });
        this._define_inherited(attr, true);
    }
    _define_inherited(attr, value) {
        Object.defineProperty(this, `inherited_${attr}`, {
            configurable: true,
            enumerable: true,
            value,
        });
    }
    _can_inherit_from(prop, base) {
        if (base == null) {
            return false;
        }
        const base_prop = base.model.property(prop.attr);
        const value = prop.get_value();
        const base_value = base_prop.get_value();
        try {
            return is_equal(value, base_value);
        }
        catch (error) {
            if (error instanceof EqNotImplemented) {
                return false;
            }
            else {
                throw error;
            }
        }
    }
    _is_inherited(prop) {
        const name = isString(prop) ? prop : prop.attr;
        return this[`inherited_${name}`];
    }
    set_visuals(source, indices) {
        for (const prop of this._iter_visuals()) {
            const { base } = this;
            if (base != null && this._can_inherit_from(prop, base)) {
                this._inherit_from(prop.attr, base);
            }
            else {
                const uniform = prop.uniform(source).select(indices);
                this._define_attr(prop.attr, uniform);
            }
        }
        for (const visual of this.visuals) {
            visual.update();
        }
        this.glglyph?.set_visuals_changed();
    }
    _transform_array(prop, array) {
        const { x_source, y_source } = this.renderer.coordinates;
        const range = prop.dimension == "x" ? x_source : y_source;
        if (range instanceof FactorRange) {
            if (prop instanceof p.CoordinateSpec) {
                array = range.v_synthetic(array);
            }
            else if (prop instanceof p.CoordinateSeqSpec) {
                for (let i = 0; i < array.length; i++) {
                    array[i] = range.v_synthetic(array[i]);
                }
            }
            else if (prop instanceof p.CoordinateSeqSeqSeqSpec) {
                // TODO
            }
        }
        let final_array;
        if (prop instanceof p.CoordinateSeqSpec) {
            // TODO: infer precision
            final_array = RaggedArray.from(array, Float64Array);
        }
        else if (prop instanceof p.CoordinateSeqSeqSeqSpec) {
            // TODO RaggedArrayN
            final_array = array;
        }
        else {
            final_array = array;
        }
        return final_array;
    }
    async set_data(source, indices, indices_to_update) {
        const visuals = new Set(this._iter_visuals());
        const { base } = this;
        this._data_size = indices.count;
        for (const prop of this.model) {
            if (!(prop instanceof p.VectorSpec || prop instanceof p.ScalarSpec)) {
                continue;
            }
            if (visuals.has(prop)) { // let set_visuals() do the work, at least for now
                continue;
            }
            if (base != null && this._can_inherit_from(prop, base)) {
                this._inherit_from(prop.attr, base);
                if (prop instanceof p.DistanceSpec || prop instanceof p.ScreenSizeSpec) {
                    this._inherit_from(`max_${prop.attr}`, base);
                }
            }
            else {
                if (prop instanceof p.BaseCoordinateSpec) {
                    const array = this._transform_array(prop, indices.select(prop.array(source)));
                    this._define_attr(prop.attr, array);
                }
                else {
                    const uniform = prop.uniform(source).select(indices);
                    this._define_attr(prop.attr, uniform);
                    if (prop instanceof p.DistanceSpec || prop instanceof p.ScreenSizeSpec) {
                        const max_value = uniforms.max(uniform);
                        this._define_attr(`max_${prop.attr}`, max_value);
                    }
                }
            }
        }
        if (this.renderer.plot_view.model.use_map) {
            this._project_data();
        }
        this._set_data(indices_to_update ?? null); // TODO doesn't take subset indices into account
        await this._set_lazy_data(indices_to_update ?? null); // TODO doesn't take subset indices into account
        for (const decoration of this.decorations.values()) {
            decoration.marking.set_data(source, indices);
        }
        this.glglyph?.set_data_changed();
        if (base == null) {
            this.index_data();
        }
    }
    _set_data(_indices) { }
    async _set_lazy_data(_indices) { }
    /**
     * Any data transformations that require visuals.
     */
    after_visuals() { }
    async after_lazy_visuals() { }
    get _index_size() {
        return this.data_size;
    }
    index_data() {
        const index = new SpatialIndex(this._index_size);
        this._index_data(index);
        index.finish();
        this._index = index;
    }
    mask_data() {
        /** Returns subset indices in the viewport. */
        if (this._mask_data == null) {
            return Indices.all_set(this.data_size);
        }
        else {
            return this._mask_data();
        }
    }
    map_data() {
        const { x_scale, y_scale } = this.renderer.coordinates;
        const { base } = this;
        const v_compute = (prop) => {
            const scale = prop.dimension == "x" ? x_scale : y_scale;
            const array = this[prop.attr];
            if (array instanceof RaggedArray) {
                return new RaggedArray(array.offsets, scale.v_compute(array.data));
            }
            else {
                return scale.v_compute(array);
            }
        };
        for (const prop of this.model) {
            if (prop instanceof p.BaseCoordinateSpec) {
                if (base != null && this._is_inherited(prop)) {
                    this._inherit_from(`s${prop.attr}`, base);
                }
                else {
                    const array = v_compute(prop);
                    this._define_attr(`s${prop.attr}`, array);
                }
            }
        }
        this._map_data();
        this.glglyph?.set_data_mapped();
    }
    // This is where specs not included in coords are computed, e.g. radius.
    _map_data() { }
    get bbox() {
        if (this.base == null) {
            const { x0, y0, x1, y1 } = this.index.bbox;
            const { x_scale, y_scale } = this.renderer.coordinates;
            const [sx0, sx1] = x_scale.r_compute(x0, x1);
            const [sy0, sy1] = y_scale.r_compute(y0, y1);
            return BBox.from_rect({ x0: sx0, y0: sy0, x1: sx1, y1: sy1 });
        }
        else {
            return undefined;
        }
    }
}
export class Glyph extends Model {
    static __name__ = "Glyph";
    constructor(attrs) {
        super(attrs);
    }
    static {
        this.define(({ List, Ref }) => ({
            decorations: [List(Ref(Decoration)), []],
        }));
    }
}
//# sourceMappingURL=glyph.js.map