import { noop } from "../../../function/noop.js";
import { compute_tetrahedron_volume } from "./compute_tetrahedron_volume.js";
import { is_tetrahedron_degenerate } from "./is_tetrahedron_degenerate.js";
import { INVALID_NEIGHBOUR } from "./TetrahedralMesh.js";

/**
 *
 * @param {TetrahedralMesh} mesh
 * @param {number} tet
 * @param {number} neighbour_id
 * @param {function(string):*} consumer
 * @return {boolean}
 */
export function validate_neighbour(mesh, tet, neighbour_id, consumer) {
    let valid = true;

    const neighbour = mesh.getNeighbour(tet, neighbour_id);

    if (neighbour === INVALID_NEIGHBOUR) {
        return true;
    }

    // decode neighbour
    const neighbour_tet = neighbour >> 2;
    const neighbour_vertex_id = neighbour & 3;

    if (!mesh.exists(neighbour_tet)) {


        consumer(`Tetrahedron ${tet} Neighbour[${neighbour_id}] ${neighbour_tet} does not exist in the mesh`);

        return false;
    }

    const back_link = mesh.getNeighbour(neighbour_tet, neighbour_vertex_id);

    if (back_link === INVALID_NEIGHBOUR) {

        consumer(`Tetrahedron ${tet} Neighbour[${neighbour_id}] ${neighbour_tet} links back to invalid neighbour (no neighbour), expected ${tet} instead`);
        return false;
    }

    const back_link_tet = back_link >> 2;
    const back_link_vertex_index = back_link & 3;

    if (back_link_tet !== tet) {
        valid = false;

        consumer(`Tetrahedron ${tet} Neighbour[${neighbour_id}] ${neighbour_tet} links back to a different tet, expected ${tet}, got ${back_link_tet}`)
    } else if (back_link_vertex_index !== neighbour_id) {
        valid = false;

        consumer(`Tetrahedron ${tet} Neighbour[${neighbour_id}] ${neighbour_tet} links back to a different vertex, expected ${neighbour_id}, got ${back_link_vertex_index}`);

    }

    for (let j = 0; j < 4; j++) {
        const v = mesh.getVertexIndex(tet, j);

        if (j === neighbour_id) {
            continue;
        }

        if (!mesh.tetContainsVertex(neighbour_tet, v)) {
            valid = false;

            consumer(`Tetrahedron ${tet} Neighbour[${neighbour_id}] ${neighbour_tet} does not share vertex ${v} with the tet`);

        }
    }

    return valid;
}


/**
 *
 * @param {TetrahedralMesh} mesh
 * @param {number} tet
 * @param {function(problem:string):*} consumer
 */
export function validate_tetrahedron_neighbourhood(mesh, tet, consumer = noop) {
    let valid = true;

    for (let i = 0; i < 4; i++) {

        if (!validate_neighbour(mesh, tet, i, consumer)) {
            valid = false;
        }

    }

    // validate all neighbours together
    for (let i = 0; i < 3; i++) {
        const neighbour_a = mesh.getNeighbour(tet, i);

        if (neighbour_a === INVALID_NEIGHBOUR) {
            continue;
        }

        const tet_a = neighbour_a >>> 2;

        for (let j = i+1; j < 4; j++) {
            const neighbour_b = mesh.getNeighbour(tet, j);

            if (neighbour_b === INVALID_NEIGHBOUR) {
                continue;
            }

            const tet_b = neighbour_b >>> 2;

            if (tet_a === tet_b) {
                consumer(`Tetrahedron ${tet} points to the same neighbour tetrahedron ${tet_a} for neighbour index ${i} and ${j}. Each neighbour tet must be unique`)

                valid = false;
            }
        }
    }

    return valid;
}

/**
 *
 * @param {TetrahedralMesh} mesh
 * @param {number} tet
 * @param {number} max_index
 * @param {function(problem:string):*} consumer
 */
function validate_tetrahedron_point_index_range(mesh, tet, max_index, consumer) {
    let valid = true;

    for (let i = 0; i < 4; i++) {

        const vertex_index = mesh.getVertexIndex(tet, i);

        if (vertex_index > max_index) {
            consumer(`Tetrahedron ${tet} points to non-existing vertex ${vertex_index} as it's ordinal vertex ${i}. Maximum allowed vertex index is ${max_index}`);
            valid = false;
        }

    }

    return valid;
}

/**
 *
 * @param {TetrahedralMesh} mesh
 * @param {ArrayLike<number>|number[]|Float32Array} points
 * @param {function(problem:string):*} [consumer]
 * @returns {boolean}
 */
export function validate_tetrahedral_mesh(mesh, points, consumer = noop) {
    let is_valid = true;

    const max_point_index = (points.length / 3) - 1;

    mesh.forEach((tet, mesh) => {

        if (!validate_tetrahedron_point_index_range(mesh, tet, max_point_index, consumer)) {
            is_valid = false;
        }

        const volume = compute_tetrahedron_volume(mesh, points, tet);
        if (volume < 0) {
            is_valid = false;

            consumer(`Tetrahedron ${tet} has negative volume ${volume}`);
        }

        // check for degeneracy
        if (is_tetrahedron_degenerate(mesh, tet)) {
            is_valid = false;
            consumer(`Tetrahedron ${tet} is degenerate (multiple vertices have the same index)`);
        }

        if (!validate_tetrahedron_neighbourhood(mesh, tet, consumer)) {
            is_valid = false;
        }

    });


    return is_valid;
}
