package org.geogebra.common.geogebra3D.kernel3D.algos;

import org.geogebra.common.geogebra3D.kernel3D.geos.GeoPoint3D;
import org.geogebra.common.geogebra3D.kernel3D.geos.GeoPolygon3D;
import org.geogebra.common.geogebra3D.kernel3D.geos.GeoPolyhedron;
import org.geogebra.common.geogebra3D.kernel3D.geos.GeoPolyhedronNet;
import org.geogebra.common.geogebra3D.kernel3D.geos.GeoSegment3D;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.arithmetic.NumberValue;
import org.geogebra.common.kernel.geos.GeoPolygon;
import org.geogebra.common.kernel.kernelND.GeoPointND;
import org.geogebra.common.kernel.kernelND.GeoSegmentND;
import org.geogebra.common.kernel.matrix.Coords;

/**
 * Algo that compute the net for a polyhedron
 * 
 * @author Vincent
 *
 */
public class AlgoPolyhedronNetPyramid extends AlgoPolyhedronNet {
	private Coords p1;

	/**
	 * @param c
	 *            construction
	 * @param labels
	 *            labels
	 * @param p
	 *            polyhedron
	 * @param v
	 *            index
	 */
	public AlgoPolyhedronNetPyramid(Construction c, String[] labels,
			GeoPolyhedron p, NumberValue v) {
		super(c, labels, p, v);

	}

	@Override
	protected int getPointLengthFromLabelsLength(int length) {

		return (length - 2) / 6;
	}

	@Override
	protected void createNet(int n) {

		GeoPolyhedronNet net = getNet();

		// create bottom face
		outputPointsBottom.adjustOutputSize(n, false);
		outputPointsSide.adjustOutputSize(n, false);
		net.startNewFace();
		for (int i = 0; i < n; i++) {
			net.addPointToCurrentFace(outputPointsBottom.getElement(i));
		}
		net.endCurrentFace();

		// create side faces
		for (int i = 0; i < n; i++) {
			createSideFace(net, i, n);
		}
	}

	private void createSideFace(GeoPolyhedronNet net, int index,
			int newBottomPointsLength) {
		net.startNewFace();
		net.addPointToCurrentFace(outputPointsBottom.getElement(index));
		net.addPointToCurrentFace(outputPointsBottom
				.getElement((index + 1) % newBottomPointsLength));
		net.addPointToCurrentFace(outputPointsSide.getElement(index));
		net.endCurrentFace();
	}

	@Override
	protected void setOutputSideTop(int n, GeoPolygon3D polygon, int step,
			GeoSegmentND[] segments) {

		setOutputSide(polygon);
	}

	private void setOutputSide(GeoPolygon3D polygon) {

		outputPolygonsSide.addOutput(polygon, false);
		outputSegmentsSide.addOutput((GeoSegment3D) polygon.getSegments()[2],
				false);
		outputSegmentsSide.addOutput((GeoSegment3D) polygon.getSegments()[1],
				false);
	}

	@Override
	protected void adjustOutputSize(int newBottomPointsLength) {
		adjustOutputSize(newBottomPointsLength, true);
	}

	@Override
	protected void adjustOutputSize(int newBottomPointsLength,
			boolean setLabels) {

		super.adjustOutputSize(newBottomPointsLength);

		// current length
		int nOld = outputPointsSide.size();

		// update existing segments / sides
		if (newBottomPointsLength > bottomPointsLength) {
			for (int i = bottomPointsLength; i < newBottomPointsLength
					&& i <= nOld; i++) {
				// update bottom segments
				GeoSegmentND segmentBottom = outputSegmentsBottom
						.getElement(i - 1);
				segmentBottom.modifyInputPoints(
						outputPointsBottom.getElement(i - 1),
						outputPointsBottom.getElement(i));
			}

			for (int i = bottomPointsLength - 1; i < newBottomPointsLength
					&& i < nOld; i++) {
				// update last sides
				updateSide(i, newBottomPointsLength);
			}

		}

		// create sides if needed
		if (newBottomPointsLength > nOld) {

			// update side points
			outputPointsSide.adjustOutputSize(newBottomPointsLength, false);
			if (setLabels) {
				outputPointsSide.setLabels(null);
			}

			// create new sides
			GeoPolyhedronNet net = getNet();
			for (int i = nOld; i < newBottomPointsLength; i++) {
				createSideFace(net, i, newBottomPointsLength);
				GeoPolygon3D polygon = net.createPolygon(i + 1); // +1 shift
																	// since
																	// bottom is
																	// face #0
				setOutputSide(polygon);
				outputSegmentsBottom.addOutput(
						(GeoSegment3D) polygon.getSegments()[0], false); // add
																			// segment
																			// to
																			// bottom
																			// list
																			// now
			}
			if (setLabels) {
				outputSegmentsBottom.setLabels(null);
				outputSegmentsSide.setLabels(null);
				outputPolygonsSide.setLabels(null);
			}
			refreshOutput();

		}

		// updates
		if (newBottomPointsLength > bottomPointsLength) {

			// update bottom
			updateBottom(newBottomPointsLength);

		} else if (newBottomPointsLength < bottomPointsLength) {
			// update side points
			for (int i = newBottomPointsLength; i < bottomPointsLength; i++) {
				outputPointsSide.getElement(i).setUndefined();
				outputPointsBottom.getElement(i).setUndefined();
			}

			// update bottom segment
			GeoSegmentND segmentBottom = outputSegmentsBottom
					.getElement(newBottomPointsLength - 1);
			segmentBottom.modifyInputPoints(
					outputPointsBottom.getElement(newBottomPointsLength - 1),
					outputPointsBottom.getElement(0));

			// update bottom face
			updateBottom(newBottomPointsLength);

			// update last side
			updateSide(newBottomPointsLength - 1, newBottomPointsLength);

		}

		bottomPointsLength = newBottomPointsLength;
	}

	private void updateSide(int index, int newBottomPointsLength) {

		GeoPointND pointBottom1 = outputPointsBottom.getElement(index);
		GeoPointND pointBottom2 = outputPointsBottom
				.getElement((index + 1) % newBottomPointsLength);
		GeoPointND pointSide = outputPointsSide.getElement(index);

		// update segments
		GeoSegmentND segmentBottom = outputSegmentsBottom.getElement(index);
		GeoSegmentND segmentSide1 = outputSegmentsSide.getElement(2 * index);
		GeoSegmentND segmentSide2 = outputSegmentsSide
				.getElement((2 * index + 1) % (2 * newBottomPointsLength));
		segmentSide2.modifyInputPoints(pointBottom2, pointSide);
		segmentSide1.modifyInputPoints(pointSide, pointBottom1);

		// update side
		GeoPolygon polygon = outputPolygonsSide.getElement(index);
		GeoPointND[] points = new GeoPointND[3];
		points[0] = pointBottom1;
		points[1] = pointBottom2;
		points[2] = pointSide;
		polygon.modifyInputPoints(points);
		GeoSegmentND[] s = new GeoSegmentND[3];
		s[0] = segmentBottom;
		s[1] = segmentSide2;
		s[2] = segmentSide1;
		polygon.setSegments(s);
		polygon.calcArea();

	}

	@Override
	public void compute(double f0, GeoPolygon bottomPolygon, Coords[] points) {

		if (p1 == null) {
			p1 = new Coords(4);
		}

		Coords topCoords = p.getTopPoint();
		topCoords.projectPlane(
				bottomPolygon.getCoordSys().getMatrixOrthonormal(), p1);
		double d1 = p.getOrientedHeight();
		double f = f0;
		Coords faceDirection = bottomPolygon.getDirectionInD3();
		if (d1 < 0) { // top point below the bottom face : negative rotation
			f *= -1;
			d1 *= -1;
		}

		if (bottomPolygon.isConvexInverseDirection()) {
			f *= -1;
			faceDirection = faceDirection.mul(-1);
		}

		int n = points.length;
		Coords o2 = points[0];

		for (int i = 0; i < n; i++) {
			GeoPoint3D wpoint = outputPointsSide.getElement(i);
			wpoint.setCoords(topCoords, false);

			// angle between side face and bottom face
			Coords o = o2;
			o2 = points[(i + 1) % n];
			Coords vs = o2.sub(o).normalized();
			rotate(wpoint, topCoords, p1, o, vs, f, faceDirection, d1, false);
		}

		getNet().setArea(p.getArea());

	}

}
