/**
 * 
 * Written by Márton Münz and Philip C Biggin
 * Copyright (c) University of Oxford, United Kingdom
 * Visit http://sbcb.bioch.ox.ac.uk/jgromacs/
 * 
 * This source code file is part of JGromacs v1.0.
 * 
 * JGromacs v1.0 is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * JGromacs v1.0. is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with JGromacs v1.0. If not, see <http://www.gnu.org/licenses/>.
 * 
 */

package jgromacs.data;
import java.util.ArrayList;


import jama.Matrix;

/**
 * Objects of this class represent a list of 3D points
 *
 */
public class PointList implements Cloneable {

	private ArrayList<Point3D> points = new ArrayList<Point3D>();

	// Constructor
	
	/**
	 * Constructs a new PointList object
	 *
	 * 
	 */
	public PointList() {
		
	}
	
	// Getters, setters and modifications
	
	/**
	 * Returns points in an ArrayList
	 * @return Points in an ArrayList
	 */
	public ArrayList<Point3D> getPointsAsArrayList() {
		return points;
	}

	/**
	 * Loads points from an ArrayList
	 * @param points ArrayList of points
	 */
	public void setPointFromArrayList(ArrayList<Point3D> points) {
		this.points = points;
	}
	
	/**
	 * Adds a new point to the point list
	 * @param point new point
	 */
	public void addPoint(Point3D point){
		points.add(point);
	}
	
	/**
	 * Returns the point of index i
	 * @return Point of index i
	 * @param i index
	 */
	public Point3D getPoint(int i){
		return (Point3D)points.get(i);
	}
	
	/**
	 * Replaces the point of index i with a given point
	 * @param i index
	 * @param point new point
	 */
	public void setPoint(int i, Point3D point){
		points.set(i, point);
	}
	
	/**
	 * Removes the point of index i from the point list
	 * @param i index
	 */
	public void removePoint(int i){
		points.remove(i);
	}
	
	/**
	 * Removes the given point from the point list
	 * @param point point to remove
	 */
	public void removePoint(Point3D point){
		points.remove(point);
	}
	
	/**
	 * Returns the number of points in the list
	 * @return Number of points
	 */
	public int getNumberOfPoints(){
		return points.size();
	}
	
	/**
	 * Returns a subset of points defined by an ArrayList of indices
	 * @param indices ArrayList of indices
	 * @return subset of points
	 */
	public PointList getSubList(ArrayList<Integer> indices){
		PointList ret = new PointList();
		for (int i = 0; i < indices.size(); i++) 
			ret.addPoint(getPoint(indices.get(i)));
		return ret;
	}
	
	/**
	 * Returns point coordinates in a 3xN matrix
	 * @return coordinate matrix
	 */
	public Matrix getAsMatrix(){
		Matrix ret = new Matrix(3,getNumberOfPoints());
		for (int i = 0; i < getNumberOfPoints(); i++) {
			ret.set(0, i, getPoint(i).getX());
			ret.set(1, i, getPoint(i).getY());
			ret.set(2, i, getPoint(i).getZ());
		}
		return ret;
	}
	
	/**
	 * Loads point coordinates from a 3xN matrix
	 * @param M coordinate matrix
	 */
	public void loadFromMatrix(Matrix M){
		points.clear();
		for (int i = 0; i < M.getColumnDimension(); i++)
		addPoint(new Point3D(M.get(0, i),M.get(1, i),M.get(2, i)));
	}
	
	// Transformations
	
	/**
	 * Centers the points (around the origin)
	 * 
	 */
	public void centerPoints(){
		Point3D centroid = new Point3D(0,0,0);
		for (int i = 0; i < getNumberOfPoints(); i++){
			centroid.setX(centroid.getX()+getPoint(i).getX());
			centroid.setY(centroid.getY()+getPoint(i).getY());
			centroid.setZ(centroid.getZ()+getPoint(i).getZ());
		}
			centroid.setX(centroid.getX()/getNumberOfPoints());
			centroid.setY(centroid.getY()/getNumberOfPoints());
			centroid.setZ(centroid.getZ()/getNumberOfPoints());
		for (int i = 0; i < getNumberOfPoints(); i++){
				getPoint(i).setX(getPoint(i).getX()-centroid.getX());
				getPoint(i).setY(getPoint(i).getY()-centroid.getY());
				getPoint(i).setZ(getPoint(i).getZ()-centroid.getZ());
		}	
	}
	
	/**
	 * Returns the centroid of points
	 * @return centroid point
	 */
	public Point3D getCentroid(){
		Point3D centroid = new Point3D(0,0,0);
		for (int i = 0; i < getNumberOfPoints(); i++){
			centroid.setX(centroid.getX()+getPoint(i).getX());
			centroid.setY(centroid.getY()+getPoint(i).getY());
			centroid.setZ(centroid.getZ()+getPoint(i).getZ());
		}
			centroid.setX(centroid.getX()/getNumberOfPoints());
			centroid.setY(centroid.getY()/getNumberOfPoints());
			centroid.setZ(centroid.getZ()/getNumberOfPoints());
		return centroid;
	}
	
	/**
	 * Rotates the points by a given 3x3 rotation matrix
	 * @param rotationMatrix rotation matrix
	 */
	public void rotate(Matrix rotationMatrix){
		Matrix vector = new Matrix(3,1,0);
		for (int i = 0; i < getNumberOfPoints(); i++) {
			vector.set(0, 0, getPoint(i).getX());
			vector.set(1, 0, getPoint(i).getY());
			vector.set(2, 0, getPoint(i).getZ());
			Matrix result = rotationMatrix.times(vector);
			getPoint(i).setX(result.get(0, 0));
			getPoint(i).setY(result.get(1, 0));
			getPoint(i).setZ(result.get(2, 0));
		}
	}
	
	/**
	 * Translates the points by a given vector
	 * @param vector translation vector
	 */
	public void translate(Point3D vector){
		for (int i = 0; i < getNumberOfPoints(); i++) {
			getPoint(i).setX(getPoint(i).getX()+vector.getX());
			getPoint(i).setY(getPoint(i).getY()+vector.getY());
			getPoint(i).setZ(getPoint(i).getZ()+vector.getZ());
		}
	}
	
	// toString, equals, clone
	
	/**
	 * Returns the String representation of point list
	 *
	 * @return String representation
	 */
	public String toString(){
		StringBuffer buf = new StringBuffer();
		for (int i = 0; i < points.size(); i++) 
		buf.append(((Point3D)points.get(i)).toString()+"\n");
		return buf.toString();
	}
	
	/**
	 * Returns summary information about the point list
	 *
	 * @return summary information
	 */
	public String toStringInfo(){
		String ret = "("+getNumberOfPoints()+" points)";
		return ret;
	}
	
	/**
	 * Returns an identical PointList object 
	 *
	 * @return clone of the point list
	 */
	public Object clone(){
		try {
			PointList ret = (PointList)super.clone();
			ret.points = new ArrayList<Point3D>();
			for (int i = 0; i < getNumberOfPoints(); i++)
			ret.addPoint((Point3D)getPoint(i).clone());
			return ret;
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
			return null;
		}
		
	}
	
	/**
	 * Returns true if the two point lists are identical
	 * @param other the other point list
	 * 
	 */
	public boolean equals(Object other){
		if (other == null) return false;
		if (other == this) return true;
		if (this.getClass() != other.getClass()) return false;
		PointList pl = (PointList)other;
		if (getNumberOfPoints()!=pl.getNumberOfPoints()) return false;
		for (int i = 0; i < getNumberOfPoints(); i++) 
		if (!getPoint(i).equals(pl.getPoint(i))) return false;
		return true;
	}
	
	/**
	 * Returns hash code
	 * 
	 */
	public int hashCode() {
		return points.size();
	}
	
	
	
}
