import { Uint32Heap } from "../../../../collection/heap/Uint32Heap.js";
import { max2 } from "../../../../math/max2.js";
import {
    compute_edge_collapse_cost,
    decimate_edge_collapse_snap,
    edge_collapse_pick_target_vertex
} from "./decimate_edge_collapse_snap.js";
import { build_vertex_quadratics } from "./quadratic/build_vertex_quadratics.js";

/**
 *
 * @param {Iterable<TopoEdge>} edges
 * @param {Set<number>} restricted_vertices
 * @param {Uint32Heap} heap
 * @param {Map<number, Quadric3>} vertex_quadratics
 * @param {Map<number,TopoEdge>} edge_lookup
 */
export function edge_collapse_populate_heap(edges, restricted_vertices, heap, vertex_quadratics, edge_lookup) {

    const edge_array = Array.from(edges);
    const total_edge_count = edge_array.length;

    for (let i = 0; i < total_edge_count; i++) {
        const edge = edge_array[i];

        if (
            restricted_vertices.has(edge.v0.index)
            && restricted_vertices.has(edge.v1.index)
        ) {
            // both vertices are fixed, can't simplify the edge
            continue;
        }

        heap.insert(edge.index, compute_edge_collapse_cost(edge, vertex_quadratics, restricted_vertices));

        edge_lookup.set(edge.index, edge);
    }
}

/**
 *
 * @param {TopoMesh} mesh
 * @param {number} num_faces_to_remove
 * @param {Uint32Heap} heap
 * @param {Map<number,TopoEdge>} edge_lookup
 * @param {Set<number>} restricted_vertices
 * @param {Map<number, Quadric3>} vertex_quadratics
 */
export function edge_collapse_reduce_face_count(mesh, num_faces_to_remove, heap, edge_lookup, restricted_vertices, vertex_quadratics) {
    const mesh_faces = mesh.getFaces();

    const target_face_count = max2(0, mesh_faces.size - num_faces_to_remove);

    let cycle_count = 0;
    let cycle_limit = max2(heap.size, num_faces_to_remove * 10) + 10;

    while (
        !heap.is_empty()
        && mesh_faces.size > target_face_count
        && cycle_count < cycle_limit
        ) {
        const edge_index = heap.pop_min();

        const edge = edge_lookup.get(edge_index);

        let target;

        target = edge_collapse_pick_target_vertex(edge, restricted_vertices);

        if (target === undefined) {
            // invalid collapse
            continue;
        }

        decimate_edge_collapse_snap(mesh, edge, target, vertex_quadratics, heap, restricted_vertices);

        cycle_count++;
    }
}

/**
 * Simplifies a given topology mesh by reducing number of faces. Preserves original vertices
 * NOTE: preserves outline of the mesh, that is the open edge
 * NOTE: assumes that face normals are set
 * @param {TopoMesh} mesh
 * @param {number} num_faces_to_remove desired number of faces to remove
 * @param {Set<number>} [restricted_vertices] vertices that are not allowed to be removed
 */
export function simplifyTopoMesh2(mesh, num_faces_to_remove, restricted_vertices = new Set()) {

    const mesh_faces = mesh.getFaces();
    const face_count = mesh_faces.size;

    if (num_faces_to_remove <= 0 || face_count <= 0) {
        // we're already at the target
        return;
    }

    const heap = new Uint32Heap(mesh.getEdges().size);
    /**
     *
     * @type {Map<number, Quadric3>}
     */
    const vertex_quadratics = new Map();

    build_vertex_quadratics({ mesh: mesh, quadratics: vertex_quadratics });

    const edge_lookup = new Map();
    edge_collapse_populate_heap(mesh.getEdges(), restricted_vertices, heap, vertex_quadratics, edge_lookup);
    edge_collapse_reduce_face_count(mesh, num_faces_to_remove, heap, edge_lookup, restricted_vertices, vertex_quadratics);


    // debugValidateMesh(mesh);

    // get rid of degenerate edges
    // collapse_all_degenerate_edges(mesh.getEdges(), mesh);

    // debugValidateMesh(mesh);
}
