package cz.fidentis.analyst.visitors.mesh;

import cz.fidentis.analyst.mesh.MeshVisitor;
import cz.fidentis.analyst.mesh.core.MeshFacet;
import cz.fidentis.analyst.mesh.core.MeshPoint;
import java.io.Serializable;
import java.util.List;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

/**
 * Visitor that computes a 3D bounding box (cube).
 * <p>
 * This visitor is thread-safe. 
 * </p>
 * 
 * @author Radek Oslejsek
 */
public class BoundingBox extends MeshVisitor implements Serializable {

    private BBox bbox;
    
    @Override
    public synchronized void visitMeshFacet(MeshFacet facet) {
        if (bbox == null) {
            bbox = new BBox(facet.getVertices());
        } else {
            bbox.compute(facet.getVertices());
        }
    }
    
    /**
     * Returns computed bounding box.
     * 
     * @return Bounding box or {@code null}
     */
    public BBox getBoundingBox() {
        return bbox;
    }
    
    /**
     * 3D bounding box (cube) of {@link MeshPoint}s.
     * 
     * @author Natalia Bebjakova
    */
    public class BBox implements Serializable {
    
        private Point3d maxPoint;
        private Point3d minPoint;

        /** 
         * Creates bounding box from given mesh points.
         * 
         * @param points List of mesh points, must not be null or empty
         * @throws IllegalArgumentException if the @code{points} param is null or empty
         */
        public BBox(List<MeshPoint> points) {
            if (points == null || points.isEmpty()) {
                throw new IllegalArgumentException("points");
            }

            minPoint = new Point3d(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
            maxPoint = new Point3d(Double.NEGATIVE_INFINITY,Double.NEGATIVE_INFINITY,Double.NEGATIVE_INFINITY);

            compute(points);
        }

        /** 
         * Creates bounding box from given mesh points, possibly extends the existing BB.
         * 
         * @param points List of mesh points, must not be null or empty
         */
        public void compute(List<MeshPoint> points) {
            if (points == null || points.isEmpty()) {
                return;
            }

            for (int i = 0; i < points.size(); i++) {
                MeshPoint point = points.get(i);

                minPoint.x = Math.min(minPoint.x, point.getPosition().x);
                minPoint.y = Math.min(minPoint.y, point.getPosition().y);
                minPoint.z = Math.min(minPoint.z, point.getPosition().z);

                maxPoint.x = Math.max(maxPoint.x, point.getPosition().x);
                maxPoint.y = Math.max(maxPoint.y, point.getPosition().y);
                maxPoint.z = Math.max(maxPoint.z, point.getPosition().z);                    
            }
        }        

        /**
         * Return the upper-bound corner of the bounding cube
         * @return max point of the bounding box
         */
        public Point3d getMaxPoint() {
            return maxPoint;
        }

        /**
         * Return centroid of the bounding cube.
         * @return middle point of the bounding box
         */
        public Point3d getMidPoint() {
            Point3d p = new Point3d(minPoint);
            p.add(maxPoint);
            p.scale(0.5);
            return p;
        }

        /**
         * Return the lower-bound corner of the bounding cube
         * @return min point of the bounding box
         */
        public Point3d getMinPoint() {
            return minPoint;
        }

        /**
         * Return volume diagonal of the bounding box.
         * @return maximal diagonal of bounding box
         */
        public double getDiagonalLength() {
            Vector3d v = new Vector3d(maxPoint);
            v.sub(minPoint);
            return v.length();
        }

        /**
         * Returns description of BoundignBox.
         * 
         * @return String representation of the bounding box
         */
        @Override
        public String toString() {
            String str = "BoundingBox: ";
            str += System.lineSeparator();
            str += "\t" + "- min point : " + this.minPoint + System.lineSeparator();
            str += "\t" + "- max point : " + this.maxPoint + System.lineSeparator();
            return str;
        }      

    }
}
