package extractors.hog;

import Jama.Matrix;
import util.Model;
import geometric.Triangle;

/**
 * This class represents a node (as axis aligned bounding box) in a hierarchical AABB-structure.
 * 
 * 
 */
public class OctNode {

	private OctNode parent;
	private OctNode[] children;
	// bounds of the box
	private Matrix lowerLeftBack, topRightFront;
	private Model mesh;

	/**
	 * Constructs a new {@link OctNode} with the given parent, the given bounds and the mesh inside the box.
	 * 
	 * @param parent
	 * @param lowerLeftBack
	 * @param topRightFront
	 * @param mesh
	 */
	public OctNode(OctNode parent, Matrix lowerLeftBack, Matrix topRightFront, Model mesh) {
		this.parent = parent;
		this.lowerLeftBack = lowerLeftBack;
		this.topRightFront = topRightFront;
		this.mesh = mesh;
	}

	/**
	 * Sets the i-th child.
	 * 
	 * @param i
	 * @param child
	 */
	public void setChild(int i, OctNode child) {
		if (children == null) {
			children = new OctNode[8];
		}
		children[i] = child;
	}

	/**
	 * Returns whether this node is a leaf.
	 * 
	 * @return whether this node is a leaf.
	 */
	public boolean isLeaf() {
		return children == null;
	}

	/**
	 * Returns whether this node is the root.
	 * 
	 * @return whether this node is the root.
	 */
	public boolean isRoot() {
		return parent == null;
	}

	/**
	 * Returns whether this node is empty, i.e. is a leaf and contains no triangles.
	 * 
	 * @return whether this node is empty.
	 */
	public boolean isEmpty() {
		return isLeaf() && mesh.getTriangles().length == 0;
	}

	/**
	 * Returns whether the given point is in the bounding box or not.
	 * 
	 * @param point
	 * @return
	 */
	public boolean isIn(Matrix point) {
		for (int i = 0; i < 3; i++) {
			if (lowerLeftBack.get(i, 0) - Triangle.EPS > point.get(i, 0)
					|| topRightFront.get(i, 0) + Triangle.EPS < point.get(i, 0)) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Computes the distance of a point to this bounding box.
	 * 
	 * @param point
	 * @return
	 */
	public double distanceToPoint(Matrix point) {
		if (isIn(point)) {
			return 0.0;
		}
		Matrix closestOnAABB = new Matrix(3, 1);
		for (int i = 0; i < 3; i++) {
			double tmp = point.get(i, 0) < lowerLeftBack.get(i, 0) ? lowerLeftBack.get(i, 0)
					: point.get(i, 0) > topRightFront.get(i, 0) ? topRightFront.get(i, 0) : point.get(i, 0);
			closestOnAABB.set(i, 0, tmp);
		}
		return closestOnAABB.minus(point).norm2();
	}

	/**
	 * Adjusts the bounds if necessary.
	 */
	public void recalculateBounds() {
		if (mesh != null) {
			for (Matrix vertex : mesh.getVertices()) {
				for (int i = 0; i < 3; i++) {
					double value = vertex.get(i, 0);
					if (value < lowerLeftBack.get(i, 0)) {
						lowerLeftBack.set(i, 0, value);
					} else if (value > topRightFront.get(i, 0)) {
						topRightFront.set(i, 0, value);
					}
				}
			}
		}
	}

	/**
	 * Returns the depth of this node.
	 * 
	 * @return
	 */
	public int getDepth() {
		int cnt = 0;
		OctNode current = this;
		while (current.getParent() != null) {
			cnt++;
			current = current.getParent();
		}
		return cnt;
	}

	/**
	 * Returns the volume of this bounding box.
	 * 
	 * @return
	 */
	public double getVolume() {
		double x = topRightFront.get(0, 0) - lowerLeftBack.get(0, 0);
		double y = topRightFront.get(1, 0) - lowerLeftBack.get(1, 0);
		double z = topRightFront.get(2, 0) - lowerLeftBack.get(2, 0);
		return Math.abs(x * y * z);
	}

	public static void main(String[] args) {
		double[][] llbArr = { { 0 }, { 0 }, { 0 } };
		double[][] trfArr = { { 1 }, { 1 }, { 1 } };
		double[][] pntArr = { { -1 }, { -1 }, { 0.5 } };

		OctNode oct = new OctNode(null, new Matrix(llbArr), new Matrix(trfArr), null);
		System.out.println(oct.distanceToPoint(new Matrix(pntArr)));
	}

	/**
	 * Clears the mesh, if it is not needed anymore. This reduces memory consumption significantly.
	 * 
	 * @return
	 */
	public boolean clearMesh() {
		if (!isLeaf()) {
			mesh = null;
			return true;
		}
		return false;
	}

	/**
	 * @return the mesh
	 */
	public Model getMesh() {
		return mesh;
	}

	/**
	 * @return the lowerLeftBack
	 */
	public Matrix getLowerLeftBack() {
		return lowerLeftBack;
	}

	/**
	 * @return the topRightFront
	 */
	public Matrix getTopRightFront() {
		return topRightFront;
	}

	/**
	 * @return the children
	 */
	public OctNode[] getChildren() {
		return children;
	}

	/**
	 * @return the center
	 */
	public Matrix getCenter() {
		return getLowerLeftBack().plus(getTopRightFront().minus(getLowerLeftBack()).times(0.5));
	}

	/**
	 * @return the parent
	 */
	public OctNode getParent() {
		return parent;
	}
}
