#ifndef FACE_3D_H
#define FACE_3D_H

#include "Vector3D.h"
#include "Line3D.h"
#include "Rectangle2D.h"
#include <vector>

namespace geometry{
	/**
	 * Class representing a 3D face / quad. The four corner points
	 * need not be orthogonal. The class stores the corner points and
	 * lines representing the edges, as well as a min / max vector for
	 * the axis aligned bounding box. 
	 *
	 *  p3 ---------- p2<br>
	 *  |             |<br>
	 *  |             |<br>
	 *  p0 ---------- p1<br>
	 * 
	 * @author Manuela Waldner
	 */
	template<class XNumber>
	class Face3D{
	private:
		/** Points of the face. */
		std::vector<Vector3D<XNumber> > points; 
		/** 3D lines holding the four coordinates. */
		std::vector<LineSegment3D<XNumber> > lines; 

		/** Minimum point of the axis aligned bounding box. */
		Vector3D<XNumber> min; 
		/** Maximum point of the axis aligned bounding box. */
		Vector3D<XNumber> max; 

		/**
		 * Updates the bounding box regarding to the incoming point. 
		 * If one of the dimensions of the point is smaller min or 
		 * greater max, the corresponding point is updated. 
		 * @param point The current point in the polygon. 
		 */
		void UpdateBoundingBox(Vector3D<XNumber> point); 
		/**
		 * Sets the min and max point to the first point in the face. 
		 * For all further points in the polygon, the UpdateBoundingBox method
		 * is called. 
		 */
		void CalcBoundingBox(); 
	public:
		/**
		 * Default constructor. 
		 */
		Face3D(); 
		/**
		 * Constructor. 
		 * Creates the list of points and lines with the given input parameters. 
		 * @param p0 The first corner point. 
		 * @param p1 The second corner point. 
		 * @param p2 The third corner point. 
		 * @param p3 The fourth corner point. 
		 */
		Face3D(Vector3D<XNumber> p0, Vector3D<XNumber> p1, Vector3D<XNumber> p2, Vector3D<XNumber> p3); 
		/**
		 * Copy Constructor. 
		 */
		Face3D(const Face3D& face); 

		/**
		 * Constructs the face by setting four corner points. 
		 * The existing corner points will be deleted. 
		 * @param p0 The first corner point. 
		 * @param p1 The second corner point. 
		 * @param p2 The third corner point. 
		 * @param p3 The fourth corner point. 
		 */
		void SetCornerPoints(Vector3D<XNumber> p0, Vector3D<XNumber> p1, Vector3D<XNumber> p2, Vector3D<XNumber> p3); 
		/**
		 * Adds a corner point to the face. 
		 * @param The point to be added. 
		 * @return Returns true if the point was added successfully, if 
		 * the face already existed of four corner points, the method 
		 * returns false. 
		 */
		bool AddCornerPoint(Vector3D<XNumber> point); 
		/**
		 * Sets a given corner point to a given value. 
		 * @param point The point to be set. 
		 * @param index The index of the point to be modified. 
		 * @return Returns true if the point was changed successfully, 
		 * if the given index was invalid, the method returns false. 
		 */
		bool SetCornerPoint(Vector3D<XNumber> point, int index); 

		/**
		 * Returns a point on the face, given by an index. 
		 * @param index The desired index. 
		 * @return Returns the point on the index. If the index 
		 * was invalid, the method returns 0. 
		 */
		Vector3D<XNumber> GetPoint(int index); 
		/**
		 * Returns an edge (line) of the face, given by an index. 
		 * NOTE: Index 0 indicates the edge between point 0 and 1
		 * and so on. 
		 * @param index The desired index. 
		 * @return Returns the line on the index. If the index was 
		 * invalid, the method returns 0. 
		 */
		LineSegment3D<XNumber> GetLine(int index); 
		/**
		 * Returns the min point of the axis aligned bounding box. 
		 * @return The min point. 
		 */
		Vector3D<XNumber> GetMin(); 
		/**
		 * Returns the max point of the axis aligned bounding box. 
		 * @return The max point. 
		 */
		Vector3D<XNumber> GetMax(); 

		Vector3D<XNumber> GetCenter(); 

		/**
		 * Multiplies all four corner points of the face with a given matrix. 
		 * @param m The matrix to be applied. 
		 */
		void MultMatrix(Matrix4x4 m); 

		/**
		 * Prints the face to the console. 
		 */
		void Print(); 

		/**
		 * Returns the corner points as 4x3 array. 
		 * NOTE: Don't forget to delete after use!
		 * @return The corner points as array. 
		 */
		XNumber** GetArray(); 

		Rectangle2D<XNumber> GetRectangle(); 
	};

	typedef Face3D<int> Face3Di; 
	typedef Face3D<short> Face3Ds; 
	typedef Face3D<long> Face3Dl; 
	typedef Face3D<float> Face3Df; 
	typedef Face3D<double> Face3Dd; 

	template <class XNumber> Face3D<XNumber>::Face3D(){
		this->min.SetValues(0, 0, 0); 
		this->max.SetValues(0, 0, 0); 
	}

	template <class XNumber> Face3D<XNumber>::Face3D(Vector3D<XNumber> p0, Vector3D<XNumber> p1, Vector3D<XNumber> p2, Vector3D<XNumber> p3){
		this->SetCornerPoints(p0, p1, p2, p3); 
	}

	template <class XNumber> Face3D<XNumber>::Face3D(const Face3D<XNumber> &face){
		this->lines = face.lines; 
		this->points = face.points; 
		this->min = face.min; 
		this->max = face.max; 
	}

	template <class XNumber> void Face3D<XNumber>::SetCornerPoints(Vector3D<XNumber> p0, Vector3D<XNumber> p1, Vector3D<XNumber> p2, Vector3D<XNumber> p3){
		this->points.clear(); 
		this->lines.clear(); 
		LineSegment3D<XNumber> l0, l1, l2, l3; 
		l0.SetValues(p0, p1); 
		l1.SetValues(p1, p2); 
		l2.SetValues(p2, p3); 
		l3.SetValues(p3, p0); 
		this->lines.push_back(l0); 
		this->lines.push_back(l1); 
		this->lines.push_back(l2); 
		this->lines.push_back(l3); 
		this->points.push_back(p0); 
		this->points.push_back(p1); 
		this->points.push_back(p2); 
		this->points.push_back(p3); 
		this->CalcBoundingBox(); 
	}

	template <class XNumber> bool Face3D<XNumber>::AddCornerPoint(Vector3D<XNumber> point){
		int s = this->points.size(); 
		if(s <= 3){
			LineSegment3D<XNumber> l; 
			if(s > 0){
				LineSegment3D<XNumber> tempL = this->lines.at(s - 1); 
				tempL.SetPoint1(point); 
				this->lines.at(s - 1) = tempL; 
				l.SetValues(point, this->points.at(0)); 
				this->UpdateBoundingBox(point); 
			}
			else{
				l.SetValues(point, point); 
				this->CalcBoundingBox(); 
			}
			this->points.push_back(point); 
			this->lines.push_back(l); 
			return true; 
		}
		return false; 
	}

	template <class XNumber> bool Face3D<XNumber>::SetCornerPoint(Vector3D<XNumber> point, int index){
		if(index >= 0 && index < this->points.size()){
			this->points.at(index) = point; 
			this->lines.at(index).SetPoint0(point); 
			index--; 
			if(index < 0) index = this->lines.size() - 1; 
			this->lines.at(index).SetPoint1(point); 
			this->UpdateBoundingBox(point); 
			return true; 
		}
		return false; 
	}

	template <class XNumber> Vector3D<XNumber> Face3D<XNumber>::GetPoint(int index){
		if(index >= 0 && index < this->points.size()){
			return this->points.at(index); 
		}
		return Vector3D<XNumber>(0, 0, 0); 
	}

	template <class XNumber> LineSegment3D<XNumber> Face3D<XNumber>::GetLine(int index){
		LineSegment3D<XNumber> l(Vector3D<XNumber>(0, 0, 0), Vector3D<XNumber>(0, 0, 0)); 
		if(index >= 0 && index < this->lines.size()){
			return this->lines.at(index); 
		}
		return l; 
	}

	template <class XNumber> Vector3D<XNumber> Face3D<XNumber>::GetMin(){
		return this->min; 
	}

	template <class XNumber> Vector3D<XNumber> Face3D<XNumber>::GetMax(){
		return this->max; 
	}

	template <class XNumber> Vector3D<XNumber> Face3D<XNumber>::GetCenter(){
		LineSegment3D<XNumber> l0(this->points.at(0), this->points.at(2)); 
		LineSegment3D<XNumber> l1(this->points.at(1), this->points.at(3)); 
		Vector3D<XNumber> intersect;
		if(l0.GetIntersectionPoint(intersect, l1) == 1){
			return intersect; 
		}
		else{
				//if lines are not intersecting, get shortest connecting line segment
				LineSegment3D<XNumber> connectLine = l0.GetShortestConnectingLineSegment(l1); 
				//if the result is valid, return the center of the connecting line
				if(connectLine.GetLength() > 0){
						return connectLine.GetPointOnLine(0.5f); 
				}
		}
		return this->points.at(0); 
	}

	template <class XNumber> void Face3D<XNumber>::CalcBoundingBox(){
		for(int i = 0; i < this->points.size(); i++){
			if(i == 0){
				this->min = this->points.at(i); 
				this->max = this->points.at(i); 
			}
			else{
				this->UpdateBoundingBox(this->points.at(i)); 
			}
		}
	}

	template <class XNumber> void Face3D<XNumber>::UpdateBoundingBox(Vector3D<XNumber> point){
		if(point[0] < this->min[0]) this->min[0] = point[0]; 
		if(point[1] < this->min[1]) this->min[1] = point[1]; 
		if(point[2] < this->min[2]) this->min[2] = point[2]; 
		if(point[0] > this->max[0]) this->max[0] = point[0]; 
		if(point[1] > this->max[1]) this->max[1] = point[1]; 
		if(point[2] > this->max[2]) this->max[2] = point[2]; 
	}

	template <class XNumber> void Face3D<XNumber>::MultMatrix(Matrix4x4 m){
		Vector3D<XNumber> p;
		for(int i = 0; i < this->points.size(); i++){
			 p = this->points.at(i); 
			 p.MultMatrix(m); 
			 this->SetCornerPoint(p, i); 
		}
		this->CalcBoundingBox(); 
	}

	template <class XNumber> void Face3D<XNumber>::Print(){
		printf("Face3D:\n"); 
		for(int i = 0; i < this->points.size(); i++){
			this->points.at(i).Print(); 
		}
	}

	template <class XNumber> XNumber** Face3D<XNumber>::GetArray(){
		XNumber** arr = new XNumber*[4]; 
		for(int i = 0; i < 4; i++){
			arr[i] = new XNumber[3]; 
			for(int j = 0; j < 3; j++){
				arr[i][j] = this->points.at(i)[j]; 
			}
		}
		return arr; 
	}

	template <class XNumber> Rectangle2D<XNumber> Face3D<XNumber>::GetRectangle(){
		Rectangle2D<XNumber> rect; 
		for(int i = 0; i < 4; i++){
			Vector2D<XNumber> p; 
			p.SetValues(this->points.at(i)[0], this->points.at(i)[1]); 
			rect.SetPoint(p, i); 
		}
		return rect; 
	}


};

#endif 
