import { Delaunator } from "./delaunator";
import { Voronoi } from './voronoi';

const tau = 2 * Math.PI, pow = Math.pow;



// A triangulation is collinear if all its triangles have a non-null area
function collinear(d: any) {
    const { triangles, coords } = d;
    for (let i = 0; i < triangles.length; i += 3) {
        const a = 2 * triangles[i],
            b = 2 * triangles[i + 1],
            c = 2 * triangles[i + 2],
            cross = (coords[c] - coords[a]) * (coords[b + 1] - coords[a + 1])
                - (coords[b] - coords[a]) * (coords[c + 1] - coords[a + 1]);
        if (cross > 1e-10) return false;
    }
    return true;
}

function jitter(x: number, y: number, r: number) {
    return [x + Math.sin(x + y) * r, y + Math.cos(x - y) * r];
}

export default class Delaunay {
    _delaunator: Delaunator;
    inedges: Int32Array;
    _hullIndex: Int32Array;
    points: Float64Array;
    collinear?: Int32Array;
    halfedges: any;
    hull!: Uint32Array;
    triangles!: Uint32Array;
    static from(points: number[]) {
        return new Delaunay(new Float64Array(points));
    }
    constructor(points: Float64Array) {
        this._delaunator = new Delaunator(points);
        this.inedges = new Int32Array(points.length / 2);
        this._hullIndex = new Int32Array(points.length / 2);
        this.points = this._delaunator.coords;
        this._init();
    }
    update() {
        this._delaunator.update();
        this._init();
        return this;
    }
    _init() {
        const d = this._delaunator, points = this.points;

        // check for collinear
        if (d.hull && d.hull.length > 2 && collinear(d)) {
            this.collinear = Int32Array.from({ length: points.length / 2 }, (_, i) => i)
                .sort((i, j) => points[2 * i] - points[2 * j] || points[2 * i + 1] - points[2 * j + 1]); // for exact neighbors
            const e = this.collinear[0], f = this.collinear[this.collinear.length - 1],
                bounds = [points[2 * e], points[2 * e + 1], points[2 * f], points[2 * f + 1]],
                r = 1e-8 * Math.hypot(bounds[3] - bounds[1], bounds[2] - bounds[0]);
            for (let i = 0, n = points.length / 2; i < n; ++i) {
                const p = jitter(points[2 * i], points[2 * i + 1], r);
                points[2 * i] = p[0];
                points[2 * i + 1] = p[1];
            }
            this._delaunator = new Delaunator(points);
        } else {
            delete this.collinear;
        }

        const halfedges = this.halfedges = this._delaunator.halfedges;
        const hull = this.hull = this._delaunator.hull;
        const triangles = this.triangles = this._delaunator.triangles;
        const inedges = this.inedges.fill(-1);
        const hullIndex = this._hullIndex.fill(-1);

        // Compute an index from each point to an (arbitrary) incoming halfedge
        // Used to give the first neighbor of each point; for this reason,
        // on the hull we give priority to exterior halfedges
        for (let e = 0, n = halfedges.length; e < n; ++e) {
            const p = triangles[e % 3 === 2 ? e - 2 : e + 1];
            if (halfedges[e] === -1 || inedges[p] === -1) inedges[p] = e;
        }
        for (let i = 0, n = hull.length; i < n; ++i) {
            hullIndex[hull[i]] = i;
        }

        // degenerate case: 1 or 2 (distinct) points
        if (hull.length <= 2 && hull.length > 0) {
            this.triangles = new Uint32Array(3).fill(-1);
            this.halfedges = new Uint32Array(3).fill(-1);
            this.triangles[0] = hull[0];
            this.triangles[1] = hull[1];
            this.triangles[2] = hull[1];
            inedges[hull[0]] = 1;
            if (hull.length === 2) inedges[hull[1]] = 0;
        }
    }
    voronoi(bounds: [number, number, number, number] | undefined) {
        return new Voronoi(this, bounds);
    }
    *neighbors(i: number) {
        const { inedges, hull, _hullIndex, halfedges, triangles, collinear } = this;

        // degenerate case with several collinear points
        if (collinear) {
            const l = collinear.indexOf(i);
            if (l > 0) yield collinear[l - 1];
            if (l < collinear.length - 1) yield collinear[l + 1];
            return;
        }

        const e0 = inedges[i];
        if (e0 === -1) return; // coincident point
        let e = e0, p0 = -1;
        do {
            yield p0 = triangles[e];
            e = e % 3 === 2 ? e - 2 : e + 1;
            if (triangles[e] !== i) return; // bad triangulation
            e = halfedges[e];
            if (e === -1) {
                const p = hull[(_hullIndex[i] + 1) % hull.length];
                if (p !== p0) yield p;
                return;
            }
        } while (e !== e0);
    }
    find(x:number, y:number, i = 0) {
        if ((x = +x, x !== x) || (y = +y, y !== y)) return -1;
        const i0 = i;
        let c;
        while ((c = this._step(i, x, y)) >= 0 && c !== i && c !== i0) i = c;
        return c;
    }
    _step(i:number, x:number, y:number) {
        const { inedges, hull, _hullIndex, halfedges, triangles, points } = this;
        if (inedges[i] === -1 || !points.length) return (i + 1) % (points.length >> 1);
        let c = i;
        let dc = pow(x - points[i * 2], 2) + pow(y - points[i * 2 + 1], 2);
        const e0 = inedges[i];
        let e = e0;
        do {
            let t = triangles[e];
            const dt = pow(x - points[t * 2], 2) + pow(y - points[t * 2 + 1], 2);
            if (dt < dc) dc = dt, c = t;
            e = e % 3 === 2 ? e - 2 : e + 1;
            if (triangles[e] !== i) break; // bad triangulation
            e = halfedges[e];
            if (e === -1) {
                e = hull[(_hullIndex[i] + 1) % hull.length];
                if (e !== t) {
                    if (pow(x - points[e * 2], 2) + pow(y - points[e * 2 + 1], 2) < dc) return e;
                }
                break;
            }
        } while (e !== e0);
        return c;
    }

    
   
}

