import { NULL_POINTER } from "../BinaryTopology.js";
import { bt_edge_get_or_create } from "./edge/bt_edge_get_or_create.js";

/**
 * Populates supplied topology with data from supplied indexed geometry (triangles)
 * @see https://github.com/blender/blender/blob/9cb061f4f0119e647173e7d354e1457e97632333/source/blender/blenkernel/intern/mesh_calc_edges.cc
 * @see https://github.com/blender/blender/blob/9cb061f4f0119e647173e7d354e1457e97632333/source/blender/io/stl/importer/stl_import_mesh.cc#L63
 * @param {BinaryTopology} out
 * @param {number[]|Uint32Array|Uint16Array|Uint8Array} indices
 * @param {number[]|Float32Array} vertex_positions
 * @param {number[]|Float32Array} [vertex_normals] Optional vertex normals. Can be skipped.
 */
export function bt_mesh_from_indexed_geometry(
    out,
    indices,
    vertex_positions,
    vertex_normals
) {

    // remove any existing data
    out.clear();

    /*
    NOTES:
        - we use {@link BinaryElementPool#allocate_continuous} to avoid having to allocate additional mapping structure
     */

    // ingest all vertices
    const vertex_count = vertex_positions.length / 3;

    // NOTE: we ignore vertex offset, because after clear - it is guaranteed to be 0
    out.vertices.allocate_continuous(vertex_count);

    for (let i = 0; i < vertex_count; i++) {
        const i3 = i * 3;

        const vertex_id = i;

        out.vertex_write_coordinate(vertex_id, vertex_positions, i3);

        if (vertex_normals !== undefined) {
            out.vertex_write_normal(vertex_id, vertex_normals, i3);
        }

        // initialize edge to NULL
        out.vertex_write_edge(vertex_id, NULL_POINTER);
    }

    const face_count = indices.length / 3;

    // create faces
    // NOTE: we ignore vertex offset, because after clear - it is guaranteed to be 0
    out.faces.allocate_continuous(face_count);

    // reserve some memory to avoid extra allocations
    out.loops.ensure_capacity(face_count * 3);
    out.edges.ensure_capacity(face_count * 1.6);

    for (let i = 0; i < face_count; i++) {
        const i3 = i * 3;

        const a = indices[i3];
        const b = indices[i3 + 1];
        const c = indices[i3 + 2];

        const vertex_a = a;
        const vertex_b = b;
        const vertex_c = c;

        const face_id = i;

        // create edges
        const edge_ab = bt_edge_get_or_create(out, vertex_a, vertex_b);
        const edge_bc = bt_edge_get_or_create(out, vertex_b, vertex_c);
        const edge_ca = bt_edge_get_or_create(out, vertex_c, vertex_a);

        // allocate loops
        const loop_a = out.loop_create();
        const loop_b = out.loop_create();
        const loop_c = out.loop_create();

        // write loops
        out.loop_write_vertex(loop_a, vertex_a);
        out.loop_write_face(loop_a, face_id);

        out.loop_write_vertex(loop_b, vertex_b);
        out.loop_write_face(loop_b, face_id);

        out.loop_write_vertex(loop_c, vertex_c);
        out.loop_write_face(loop_c, face_id);

        // link loops to edges
        link_loop_to_edge(out, loop_a, edge_ab);
        link_loop_to_edge(out, loop_b, edge_bc);
        link_loop_to_edge(out, loop_c, edge_ca);


        /*
        NOTE: Vertices (#BMLoop.v & #BMLoop.next.v) always contain vertices from (#BMEdge.v1 & #BMEdge.v2).
         */
        // link up loops around the face
        out.loop_write_next(loop_a, loop_b);
        out.loop_write_prev(loop_a, loop_c);

        out.loop_write_next(loop_b, loop_c);
        out.loop_write_prev(loop_b, loop_a);

        out.loop_write_next(loop_c, loop_a);
        out.loop_write_prev(loop_c, loop_b);

        // update face
        out.face_write_loop(face_id, loop_a);

    }
}

/**
 *
 * @param {BinaryTopology} mesh
 * @param {number} loop_id
 * @param {number} edge_id
 */
function link_loop_to_edge(mesh, loop_id, edge_id) {
    mesh.loop_write_edge(loop_id, edge_id);

    /**
     * First loop attached to the edge
     * @type {number}
     */
    const existing_loop = mesh.edge_read_loop(edge_id);

    if (existing_loop === NULL_POINTER) {

        // no loops connected yet
        mesh.edge_write_loop(edge_id, loop_id);

    } else {

        // insert loop between existing loop and the next one
        const next = mesh.loop_read_radial_next(existing_loop);

        mesh.loop_write_radial_next(existing_loop, loop_id);
        mesh.loop_write_radial_prev(next, loop_id);

        mesh.loop_write_radial_next(loop_id, next);
        mesh.loop_write_radial_prev(loop_id, existing_loop);

    }

}


