package cz.fidentis.analyst.symmetry;

import cz.fidentis.analyst.mesh.core.MeshFacet;
import cz.fidentis.analyst.mesh.core.MeshPoint;
import javax.vecmath.Vector3d;

/**
 * Symmetry plane with votes used for the decision about symmetry estimate of 3D models.
 *
 * @author Natalia Bebjakova
 * 
 */
public class ApproxSymmetryPlane extends Plane implements Comparable<ApproxSymmetryPlane> {
    
    private int votes;

    /**
     * Constructor.
     * 
     * @param sigPoints Mesh vertices with the most significant curvature
     * @param config Symmetry plane configuration
     * @param i index of the first significant point used for the plane construction
     * @param j index of the second significant point used for the plane construction
     * @param maxDist Distance limit
     * @throws UnsupportedOperationException if the symmetry plane cannot be created
     */
    public ApproxSymmetryPlane(SignificantPoints sigPoints, SymmetryConfig config, int i, int j, double maxDist) throws UnsupportedOperationException {
        if (i == j) {
            throw new UnsupportedOperationException();
        }
        setNormAndDist(sigPoints, config, i, j);
        computeVotes(sigPoints, config, maxDist);
    }

    /**
     * returns number of votes that were given to plane while computing the symmetry 
     * 
     * @return Number of votes 
     */
    public int getVotes() {
        return votes;
    }
    
    private void setNormAndDist(SignificantPoints sigPoints, SymmetryConfig config, int i, int j) {
        MeshFacet facetI = sigPoints.getMeshFacet(i);
        MeshFacet facetJ = sigPoints.getMeshFacet(j);
        int sigPointI = sigPoints.getVertexIndex(i);
        int sigPointJ = sigPoints.getVertexIndex(j);
        MeshPoint meshPointI = facetI.getVertex(sigPointI);
        MeshPoint meshPointJ = facetJ.getVertex(sigPointJ);
                
        // accpet only point pairs with significantly different curvatures (WTF?)
        double minRatio = config.getMinCurvRatio();
        double maxRatio = 1.0 / minRatio;
        double ratioIJ = sigPoints.getCachedCurRatio(i, j);
        if (ratioIJ < minRatio || ratioIJ > maxRatio) {
            throw new UnsupportedOperationException();
        }
                
        Vector3d p1 = new Vector3d(meshPointI.getPosition());
        Vector3d p2 = new Vector3d(meshPointJ.getPosition());
        Vector3d normal = new Vector3d(p1);
        normal.sub(p2);
        normal.normalize(); 
        
        // accpect only point pair with oposite normals along with the plane normal:
        double normCos = sigPoints.getCachedNormCosVec(i, j).dot(normal);
        if (Math.abs(normCos) < config.getMinNormAngleCos()) {
            throw new UnsupportedOperationException();
        }
        
        setDistance(-normal.dot(sigPoints.getCachedAvgPos(i, j))); 
        setNormal(normal);
    }

    /**
     * Computes votes for given plane 
     *
     * @param sigPoints Mesh vertices with the most significant curvature
     * @param config Symmetry plane configuration
     * @param maxDist Distance limit
     */
    private void computeVotes(SignificantPoints sigPoints, SymmetryConfig config, double maxDist) {
        normalize();
        Vector3d normal = getNormal();
        double d = getDistance();
        double maxCurvRatio = 1.0 / config.getMinCurvRatio();
        
        for (int i = 0; i < sigPoints.size(); i++) {
            for (int j = 0; j < sigPoints.size(); j++) {
                if (i == j) {
                    continue;
                }
                
                MeshFacet facetI = sigPoints.getMeshFacet(i);
                MeshFacet facetJ = sigPoints.getMeshFacet(j);
                
                double ratioIJ = sigPoints.getCachedCurRatio(i, j);
                if (ratioIJ < config.getMinCurvRatio() || ratioIJ > maxCurvRatio) {
                    continue;
                }
                
                double normCos = sigPoints.getCachedNormCosVec(i, j).dot(normal);
                if (Math.abs(normCos) < config.getMinNormAngleCos()) {
                    continue;
                }
                
                // Caching this part doesn't improve efficiency anymore:
                Vector3d vec = new Vector3d(facetI.getVertex(sigPoints.getVertexIndex(i)).getPosition());
                vec.sub(facetJ.getVertex(sigPoints.getVertexIndex(j)).getPosition());
                vec.normalize();
                double cos = vec.dot(normal);
                if (Math.abs(cos) < config.getMinAngleCos()) {
                    continue;
                }
                
                Vector3d avg = sigPoints.getCachedAvgPos(i, j);
                double dist = Math.abs(normal.dot(avg) + d);
                if (dist <= maxDist) {                 
                    votes++;  
                }   
            }
        }
    }
    
    /**
     * Enables to compare two approximate planes due to number of votes 
     * 
     * @param other plane to be compared 
     * @return number that decides which plane has more votes 
     */
    @Override
    public int compareTo(ApproxSymmetryPlane other) {
        return Integer.compare(votes, other.votes);
    }
    
    @Override
    public String toString() {
        return this.getNormal() + " " + getDistance() + " " + votes;
    }
}
