import { query_vertex_is_boundary } from "../query/query_vertex_is_boundary.js";
import { collapseEdge, partialEdgeCollapse } from "./collapseEdge.js";
import { computeEdgeCollapseCost } from "./computeEdgeCollapseCost.js";

export class EdgeCollapseCandidate {
    constructor() {
        /**
         *
         * @type {TopoEdge|null}
         */
        this.edge = null;
        /**
         *
         * @type {TopoVertex|null}
         */
        this.victim_vertex = null;
        /**
         *
         * @type {TopoVertex|null}
         */
        this.target_vertex = null;

        /**
         *
         * @type {number}
         */
        this.cost = 0;

        /**
         * Debug value, used to track how many times the edge was updated
         * @type {number}
         */
        this.version = 0;

        /**
         * Collapsed victim point belonged to the border
         * @type {boolean}
         * @private
         */
        this.__border_collapse = false;
    }

    /**
     *
     * @returns {boolean}
     */
    isBorderCollapse() {
        return this.__border_collapse;
    }


    validate() {
        if (!this.edge.containsBothVertices(this.victim_vertex, this.target_vertex)) {
            return false;
        }

        return true;
    }

    /**
     *
     * @param {TopoMesh} mesh
     * @param {Map<number, Quadric3>} vertex_quadratics
     */
    collapse(mesh, vertex_quadratics) {
        // if (!this.validate()) {
        //     throw new Error("Invalid state");
        // }

        const v_from = this.victim_vertex;
        const v_to = this.target_vertex;

        if (v_from === v_to) {
            // degenerate
            partialEdgeCollapse(mesh, this.edge, v_from, v_to);
        } else {

            if (query_vertex_is_boundary(v_from)) {
                this.__border_collapse = true;
            }

            collapseEdge(mesh, this.edge, v_from, v_to);

            // accumulate error from removed vertex to the remaining one
            const q_from = vertex_quadratics.get(v_from.index);
            const q_to = vertex_quadratics.get(v_to.index);

            q_to.add(q_from);
        }
    }

    /**
     * Compute cost of collapse as well as which vertex should be the victim
     * @param {Map<number, Quadric3>} vertex_quadratics
     * @param {Set<number>} restricted_vertices
     * @returns {boolean}
     */
    update(vertex_quadratics, restricted_vertices) {
        this.version++;

        const edge = this.edge;

        const v0 = edge.v0;
        const v1 = edge.v1;

        const v0_restricted = restricted_vertices.has(v0.index);
        const v1_restricted = restricted_vertices.has(v1.index);

        if (v0_restricted) {
            if (v1_restricted) {
                return false;
            } else {

                this.victim_vertex = v1;
                this.target_vertex = v0;
                this.cost = computeEdgeCollapseCost(v1, v0, edge, vertex_quadratics);
            }
        } else {
            if (v1_restricted) {

                this.victim_vertex = v0;
                this.target_vertex = v1;
                this.cost = computeEdgeCollapseCost(v0, v1, edge, vertex_quadratics);
            } else {
                // unrestricted

                const cost_forward = computeEdgeCollapseCost(v0, v1, edge, vertex_quadratics);
                const cost_backward = computeEdgeCollapseCost(v1, v0, edge, vertex_quadratics);

                // pick minimum cost collapse
                if (cost_forward > cost_backward) {
                    this.victim_vertex = v1;
                    this.target_vertex = v0;
                    this.cost = cost_backward;
                } else {
                    this.victim_vertex = v0;
                    this.target_vertex = v1;
                    this.cost = cost_forward;
                }

            }
        }


        return true;
    }
}
