package util;

import Jama.*;
import fragmenter.Fragmenter;
import geometric.Triangle;

import java.util.*;

/**
 * This class represents a mesh.
 * 
 * 
 */
public class Model {

	// the vertices and triangles
	private ArrayList<Matrix> vertices;
	private Triangle[] triangles;

	private Fragmenter frag;

	/**
	 * Constructs a new {@link Model}.
	 * 
	 * @param v
	 * @param t
	 */
	public Model(ArrayList<Matrix> v, Triangle[] t) {
		vertices = v;
		triangles = t;
	}

	/**
	 * Normalizes the mesh.
	 */
	public void normalize() {
		translateToOrigin();
		rotate();
		scale();
		flip();
	}

	/**
	 * Flips the mesh for normalization.
	 */
	public void flip() {
		double[] moment = { 0, 0, 0 };

		for (Triangle element : triangles) {
			for (int j = 0; j < moment.length; j++) {
				moment[j] += Math.signum(element.getCenter().get(j, 0)) * Math.pow(element.getCenter().get(j, 0), 2)
						* element.getSurface();
			}
		}

		Matrix F = new Matrix(3, 3);

		for (int i = 0; i < moment.length; i++) {
			if (moment[i] == 0) {
				moment[i] = 1;
			}
			F.set(i, i, Math.signum(moment[i]));
		}

		transform(F);
	}

	/**
	 * Scales the mesh to standard size.
	 */
	public void scale() {
		double max = 0;
		for (int i = 0; i < vertices.size(); i++) {
			if (Math.abs(vertices.get(i).get(0, 0)) > max) {
				max = Math.abs(vertices.get(i).get(0, 0));
			}
			if (Math.abs(vertices.get(i).get(1, 0)) > max) {
				max = Math.abs(vertices.get(i).get(1, 0));
			}
			if (Math.abs(vertices.get(i).get(2, 0)) > max) {
				max = Math.abs(vertices.get(i).get(2, 0));
			}
		}

		for (int i = 0; i < vertices.size(); i++) {
			vertices.set(i, vertices.get(i).times(1.0 / max));
		}
		for (Triangle element : triangles) {
			element.recalculate();
		}
	}

	/**
	 * Rotates the mesh according to wPCA.
	 */
	public void rotate() {
		// calculate weighted covariance matrix
		Matrix wcov = getWeightedCovMatrix();
		// get Eigenvectors and Eigenvalues
		EigenvalueDecomposition eigs = new EigenvalueDecomposition(wcov);

		Matrix values = eigs.getD();
		Matrix evectors = eigs.getV();

		// List of Eigenvectors
		List<EigenVector> eigenVectors = new ArrayList<EigenVector>();

		int[] rows = { 0, 1, 2 };
		int[] col = new int[1];
		for (int i = 0; i < values.getColumnDimension(); i++) {
			double value = values.get(i, i);
			col[0] = i;

			EigenVector v = new EigenVector(evectors.getMatrix(rows, col).getArray(), value);
			eigenVectors.add(v);
		}

		Collections.sort(eigenVectors);

		// build Rotationmatrix
		Matrix R = new Matrix(3, 3);
		int i = 0;
		for (EigenVector eigenVector : eigenVectors) {
			eigenVector.makeUnit();
			col[0] = i;
			R.setMatrix(rows, col, eigenVector);
			i++;
		}

		// do the rotation
		transform(R);
	}

	private Matrix getWeightedCovMatrix() {
		Matrix c = new Matrix(3, 3);
		for (Triangle element : triangles) {
			// c += surface*(t*t^T)
			Matrix center = element.getCenter();
			Matrix centerTransposed = center.transpose();
			Matrix product = center.times(centerTransposed);
			Matrix weightedProduct = product.times(element.getSurface());
			c.plusEquals(weightedProduct);
		}

		c.timesEquals(1.0 / triangles.length);

		return c;
	}

	public void translateToOrigin() {

		double surface = 0;
		double[] c = { 0.0, 0.0, 0.0 };
		Matrix center = new Matrix(c, 3);

		for (Triangle element : triangles) {
			double triangleSurface = element.getSurface();
			surface += triangleSurface;
			// center += tr_center[i] * tr_surface[i]
			center.plusEquals(element.getCenter().times(triangleSurface));
		}
		// center /= surface
		center.timesEquals(1.0 / surface);

		// translate: v[i] -= center
		translate(center.times(-1));

		for (Triangle element : triangles) {
			element.recalculate();
		}

	}

	/**
	 * Translates the mesh according to the given vector.
	 * 
	 * @param vektor
	 */
	public void translate(Matrix vektor) {
		for (int i = 0; i < vertices.size(); i++) {
			vertices.set(i, vertices.get(i).plus(vektor));
			// vertices.get(i).plusEquals(vektor);
		}
	}

	public ArrayList<Matrix> getVertices() {
		return vertices;
	}

	public Triangle[] getTriangles() {
		return triangles;
	}

	public void setFragmenter(Fragmenter f) {
		frag = f;
	}

	public void fragment(String path) {
		frag.doFragmentation(this);
		frag.writeFragments(path);
	}

	/**
	 * Insert the given {@link Matrix} as vertex and return its position in the array.
	 * 
	 * @param v
	 * @return
	 */
	public int addVertex(Matrix v) {
		vertices.add(v);
		return vertices.size() - 1;
	}

	/**
	 * Execute a transformation of the mesh with the given {@link Matrix}.
	 * 
	 * @param F
	 */
	public void transform(Matrix F) {
		for (int j = 0; j < vertices.size(); j++) {
			vertices.set(j, F.times(vertices.get(j)));
		}

		for (Triangle element : triangles) {
			element.recalculate();
		}
	}
}
