import { SpatialIndex } from "../../core/util/spatial";
import { Glyph, GlyphView } from "./glyph";
import { generic_area_vector_legend } from "./utils";
import { minmax2 } from "../../core/util/arrayable";
import { sum } from "../../core/util/arrayable";
import { LineVector, FillVector, HatchVector } from "../../core/property_mixins";
import * as hittest from "../../core/hittest";
import * as p from "../../core/properties";
import { Selection } from "../selections/selection";
import { unreachable } from "../../core/util/assert";
export class MultiPolygonsView extends GlyphView {
    static __name__ = "MultiPolygonsView";
    _hole_index;
    _project_data() {
        // TODO
    }
    _index_data(index) {
        const { min, max } = Math;
        const { data_size } = this;
        for (let i = 0; i < data_size; i++) {
            const xsi = this.xs[i];
            const ysi = this.ys[i];
            if (xsi.length == 0 || ysi.length == 0) {
                index.add_empty();
                continue;
            }
            let xi0 = +Infinity;
            let xi1 = -Infinity;
            let yi0 = +Infinity;
            let yi1 = -Infinity;
            for (let j = 0, endj = xsi.length; j < endj; j++) {
                const xsij = xsi[j][0]; // do not use holes
                const ysij = ysi[j][0]; // do not use holes
                if (xsij.length != 0 && ysij.length != 0) {
                    const [xij0, xij1, yij0, yij1] = minmax2(xsij, ysij);
                    xi0 = min(xi0, xij0);
                    xi1 = max(xi1, xij1);
                    yi0 = min(yi0, yij0);
                    yi1 = max(yi1, yij1);
                }
            }
            index.add_rect(xi0, yi0, xi1, yi1);
        }
        this._hole_index = this._index_hole_data();
    }
    _index_hole_data() {
        const { min, max } = Math;
        const { data_size } = this;
        const index = new SpatialIndex(data_size);
        for (let i = 0; i < data_size; i++) {
            const xsi = this.xs[i];
            const ysi = this.ys[i];
            if (xsi.length == 0 || ysi.length == 0) {
                index.add_empty();
                continue;
            }
            let xi0 = +Infinity;
            let xi1 = -Infinity;
            let yi0 = +Infinity;
            let yi1 = -Infinity;
            for (let j = 0, endj = xsi.length; j < endj; j++) {
                const xsij = xsi[j];
                const ysij = ysi[j];
                if (xsij.length > 1 && ysij.length > 1) {
                    for (let k = 1, endk = xsij.length; k < endk; k++) {
                        const [xij0, xij1, yij0, yij1] = minmax2(xsij[k], ysij[k]);
                        xi0 = min(xi0, xij0);
                        xi1 = max(xi1, xij1);
                        yi0 = min(yi0, yij0);
                        yi1 = max(yi1, yij1);
                    }
                }
            }
            index.add_rect(xi0, yi0, xi1, yi1);
        }
        index.finish();
        return index;
    }
    _mask_data() {
        const { x_source, y_source } = this.renderer.coordinates;
        return this.index.indices({
            x0: x_source.min, x1: x_source.max,
            y0: y_source.min, y1: y_source.max,
        });
    }
    _paint(ctx, indices, data) {
        if (!this.visuals.fill.doit && !this.visuals.line.doit) {
            return;
        }
        const { sxs, sys } = { ...this, ...data };
        for (const i of indices) {
            ctx.beginPath();
            const sx_i = sxs[i];
            const sy_i = sys[i];
            const nj = Math.min(sx_i.length, sy_i.length);
            for (let j = 0; j < nj; j++) {
                const sx_ij = sx_i[j];
                const sy_ij = sy_i[j];
                const nk = Math.min(sx_ij.length, sy_ij.length);
                for (let k = 0; k < nk; k++) {
                    const sx_ijk = sx_ij[k];
                    const sy_ijk = sy_ij[k];
                    const nl = Math.min(sx_ijk.length, sy_ijk.length);
                    for (let l = 0; l < nl; l++) {
                        const sx_ijkl = sx_ijk[l];
                        const sy_ijkl = sy_ijk[l];
                        if (l == 0) {
                            ctx.moveTo(sx_ijkl, sy_ijkl);
                        }
                        else {
                            ctx.lineTo(sx_ijkl, sy_ijkl);
                        }
                    }
                    ctx.closePath();
                }
            }
            this.visuals.fill.apply(ctx, i, "evenodd");
            this.visuals.hatch.apply(ctx, i, "evenodd");
            this.visuals.line.apply(ctx, i);
        }
    }
    _hit_poly(geometry) {
        const { sx: sxs, sy: sys, greedy = false } = geometry;
        const candidates = (() => {
            const xs = this.renderer.xscale.v_invert(sxs);
            const ys = this.renderer.yscale.v_invert(sys);
            const [x0, x1, y0, y1] = minmax2(xs, ys);
            return this.index.indices({ x0, x1, y0, y1 });
        })();
        const indices = [];
        for (const i of candidates) {
            const sxs_i = this.sxs[i];
            const sys_i = this.sys[i];
            let hit = !greedy;
            const nj = sxs_i.length;
            for (let j = 0; j < nj; j++) {
                const sxs_ij0 = sxs_i[j][0];
                const sys_ij0 = sys_i[j][0];
                const nk = sxs_ij0.length;
                for (let k = 0; k < nk; k++) {
                    const sxs_ij0k = sxs_ij0[k];
                    const sys_ij0k = sys_ij0[k];
                    if (!hittest.point_in_poly(sxs_ij0k, sys_ij0k, sxs, sys)) {
                        if (!greedy) {
                            hit = false;
                            break;
                        }
                    }
                    else {
                        if (greedy) {
                            hit = true;
                            break;
                        }
                    }
                }
                if (!greedy) {
                    if (!hit) {
                        break;
                    }
                }
                else {
                    if (hit) {
                        break;
                    }
                }
            }
            if (hit) {
                indices.push(i);
            }
        }
        return new Selection({ indices });
    }
    _hit_rect(geometry) {
        const { sx0, sx1, sy0, sy1, greedy } = geometry;
        const sxs = [sx0, sx1, sx1, sx0];
        const sys = [sy0, sy0, sy1, sy1];
        return this._hit_poly({ type: "poly", sx: sxs, sy: sys, greedy });
    }
    _hit_point(geometry) {
        const { sx, sy } = geometry;
        const x = this.renderer.xscale.invert(sx);
        const y = this.renderer.yscale.invert(sy);
        const candidates = this.index.indices({ x0: x, y0: y, x1: x, y1: y });
        const hole_candidates = this._hole_index.indices({ x0: x, y0: y, x1: x, y1: y });
        const indices = [];
        for (const index of candidates) {
            const sxs = this.sxs[index];
            const sys = this.sys[index];
            for (let j = 0, endj = sxs.length; j < endj; j++) {
                const nk = sxs[j].length;
                if (hittest.point_in_poly(sx, sy, sxs[j][0], sys[j][0])) {
                    if (nk == 1) {
                        indices.push(index);
                    }
                    else if (!hole_candidates.get(index)) {
                        indices.push(index);
                    }
                    else if (nk > 1) {
                        let in_a_hole = false;
                        for (let k = 1; k < nk; k++) {
                            const sxs_k = sxs[j][k];
                            const sys_k = sys[j][k];
                            if (hittest.point_in_poly(sx, sy, sxs_k, sys_k)) {
                                in_a_hole = true;
                                break;
                            }
                            else {
                                continue;
                            }
                        }
                        if (!in_a_hole) {
                            indices.push(index);
                        }
                    }
                }
            }
        }
        return new Selection({ indices });
    }
    _get_snap_coord(array) {
        return sum(array) / array.length;
    }
    scenterxy(i, sx, sy) {
        if (this.sxs[i].length == 1) {
            // We don't have discontinuous objects so we're ok
            const scx = this._get_snap_coord(this.sxs[i][0][0]);
            const scy = this._get_snap_coord(this.sys[i][0][0]);
            return [scx, scy];
        }
        else {
            // We have discontinuous objects, so we need to find which
            // one we're in, we can use point_in_poly again
            const sxs = this.sxs[i];
            const sys = this.sys[i];
            for (let j = 0, end = sxs.length; j < end; j++) {
                if (hittest.point_in_poly(sx, sy, sxs[j][0], sys[j][0])) {
                    const scx = this._get_snap_coord(sxs[j][0]);
                    const scy = this._get_snap_coord(sys[j][0]);
                    return [scx, scy];
                }
            }
        }
        unreachable();
    }
    map_data() {
        if (this.inherited_xs && this.inherited_ys) {
            this._inherit_attr("sxs");
            this._inherit_attr("sys");
        }
        else {
            const { xs, ys } = this;
            const n_i = xs.length;
            const sxs = new Array(n_i);
            const sys = new Array(n_i);
            for (let i = 0; i < n_i; i++) {
                const n_j = xs[i].length;
                sxs[i] = new Array(n_j);
                sys[i] = new Array(n_j);
                for (let j = 0; j < n_j; j++) {
                    const n_k = xs[i][j].length;
                    sxs[i][j] = new Array(n_k);
                    sys[i][j] = new Array(n_k);
                    for (let k = 0; k < n_k; k++) {
                        const [sx, sy] = this.renderer.coordinates.map_to_screen(xs[i][j][k], ys[i][j][k]);
                        sxs[i][j][k] = sx;
                        sys[i][j][k] = sy;
                    }
                }
            }
            this._define_attr("sxs", sxs);
            this._define_attr("sys", sys);
        }
    }
    draw_legend_for_index(ctx, bbox, index) {
        generic_area_vector_legend(this.visuals, ctx, bbox, index);
    }
}
export class MultiPolygons extends Glyph {
    static __name__ = "MultiPolygons";
    constructor(attrs) {
        super(attrs);
    }
    static {
        this.prototype.default_view = MultiPolygonsView;
        this.define(({}) => ({
            xs: [p.XCoordinateSeqSeqSeqSpec, { field: "xs" }],
            ys: [p.YCoordinateSeqSeqSeqSpec, { field: "ys" }],
        }));
        this.mixins([LineVector, FillVector, HatchVector]);
    }
}
//# sourceMappingURL=multi_polygons.js.map