import { vec3 } from "gl-matrix";
import { assert } from "../../../../../assert.js";
import { v3_dot } from "../../../../vec3/v3_dot.js";
import { query_edge_is_boundary } from "../../query/query_edge_is_boundary.js";
import { tm_face_area } from "../../tm_face_area.js";
import { tm_face_normal } from "../../tm_face_normal.js";
import { Quadric3 } from "./Quadric3.js";

/**
 *
 * @param {Map<number, Quadric3>} destination
 * @param {Map<number, Quadric3>} source
 * @param {TopoMesh} mesh
 */
export function deep_copy_vertex_quadratics(destination, source, mesh) {
    const mesh_vertices = mesh.vertices;
    const mesh_vertex_count = mesh_vertices.length;

    for (let i = 0; i < mesh_vertex_count; i++) {
        const mesh_vertex = mesh_vertices[i];

        const vertex_index = mesh_vertex.index;

        const vertex_quadratic = source.get(vertex_index);

        destination.set(vertex_index, vertex_quadratic.clone());
    }
}


/**
 * @see "Boundary and Texture Preserving Mesh Simplification Algorithm for Virtual Reality" by Kanchan Bahirat et.al. University of Texas/Dallas
 * @param {TopoEdge} edge
 * @param {number} weight
 * @param {Map<number, Quadric3>} vertex_quadratics
 */
function apply_border_penalty_to_vertex_quadrics(edge, weight, vertex_quadratics) {
    const v0 = edge.v0;
    const v1 = edge.v1;

    const edge_vector = [];
    vec3.sub(edge_vector, v0, v1);

    // Edge must have exactly 1 triangle attached
    const edge_faces = edge.faces;

    assert.equal(edge_faces.length, 1, 'must have exactly 1 face');

    const tri = edge_faces[0];

    const normal = [];
    tm_face_normal(normal, tri);

    const abc = [];

    vec3.cross(abc, edge_vector, normal);
    vec3.normalize(abc, abc);

    const d = -vec3.dot(abc, v0);

    const constraint = new Quadric3();
    constraint.setFromVector4(abc[0], abc[1], abc[2], d);
    constraint.multiplyScalar(vec3.length(edge_vector) * weight);
    // constraint.multiplyScalar(weight);

    // apply constraint
    const v0_quadratic = vertex_quadratics.get(v0.index);
    const v1_quadratic = vertex_quadratics.get(v1.index);

    v0_quadratic.add(constraint);
    v1_quadratic.add(constraint);
}

/**
 *
 * @param {TopoMesh} mesh
 * @param {TopoEdge[]} border_set
 * @param {Map<number, Quadric3>} vertex_quadratics
 * @param {number} [weight]
 */
export function apply_border_penalties_to_vertex_quadrics(mesh, border_set, vertex_quadratics, weight = 10) {
    const edge_count = border_set.length;
    for (let i = 0; i < edge_count; i++) {
        const edge = border_set[i];
        apply_border_penalty_to_vertex_quadrics(edge, weight, vertex_quadratics);
    }
}

/**
 *
 * @param {Quadric3} quadric
 * @param {TopoTriangle} face
 */
function quadric_set_from_face(quadric, face){
    const v0 = face.vertices[0];

    // compute triangle's plane
    face.computeNormal();

    // compute plane offset
    const normal_x = face.normal[0];
    const normal_y = face.normal[1];
    const normal_z = face.normal[2];

    const plane_constant = -v3_dot(
        normal_x, normal_y, normal_z,

        v0.x, v0.y, v0.z
    );

    quadric.setFromVector4(normal_x, normal_y, normal_z, plane_constant);
}

/**
 *
 * @param {TopoMesh} mesh
 * @param {Map<number, Quadric3>} quadratics VertexID -> Quadratic map
 * @param {boolean} [use_triangle_area]
 * @param {boolean} [use_border_constraints]
 */
export function build_vertex_quadratics({
                                            mesh,
                                            quadratics,
                                            use_triangle_area = false,
                                            use_border_constraints = true
                                        }) {
    // initialize vertex quadratics
    const mesh_vertices = mesh.vertices;
    const mesh_vertex_count = mesh_vertices.length;


    let i, face;

    for (i = 0; i < mesh_vertex_count; i++) {
        const mesh_vertex = mesh_vertices[i];

        const vertex_quadratic = new Quadric3();

        quadratics.set(mesh_vertex.index, vertex_quadratic);
    }

    // compute values for quadratics
    const q = new Quadric3();

    const faces = mesh.getFaces();

    for (face of faces) {

        quadric_set_from_face(q,face);

        if (use_triangle_area) {
            const triangle_area = tm_face_area(face);

            q.multiplyScalar(triangle_area);
        }

        const face_vertices = face.vertices;
        const face_vertex_count = face_vertices.length;

        for (i = 0; i < face_vertex_count; i++) {
            const face_vertex = face_vertices[i];

            // accumulate to adjacent vertex
            const face_vertex_quadratic = quadratics.get(face_vertex.index);

            face_vertex_quadratic.add(q);
        }
    }

    if (use_border_constraints) {
        for (let edge of mesh.getEdges()) {
            if (query_edge_is_boundary(edge)) {
                apply_border_penalty_to_vertex_quadrics(edge, 10, quadratics);
            }
        }
    }
}
