/**
 * 
 * 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 jama.Matrix;

/**
 * Objects of this class represent a single 3D point
 *
 */
public class Point3D implements Cloneable{
	
	private double x = 0;
	private double y = 0;
	private double z = 0;
	
	// Constructors
	
	/**
	 * Constructs a new Point3D object
	 *
	 * 
	 */
	public Point3D() {
		
	}
	
	/**
	 * Constructs a new Point3D object with the given coordinates
	 * @param x X coordinate
	 * @param y Y coordinate
	 * @param z Z coordinate
	 */
	public Point3D(double x, double y, double z) {
		super();
		this.x = x;
		this.y = y;
		this.z = z;
	}

	// Getters and setters

	/**
	 * Returns the X coordinate of point
	 *
	 * @return X coordinate
	 */
	public double getX() {
		return x;
	}

	/**
	 * Sets the X coordinate of point
	 * @param x X coordinate
	 * 
	 */
	public void setX(double x) {
		this.x = x;
	}

	/**
	 * Returns the Y coordinate of point
	 *
	 * @return Y coordinate
	 */
	public double getY() {
		return y;
	}

	/**
	 * Sets the Y coordinate of point
	 *  @param y Y coordinate
	 * 
	 */
	public void setY(double y) {
		this.y = y;
	}

	/**
	 * Returns the Z coordinate of point
	 *
	 * @return Z coordinate
	 */
	public double getZ() {
		return z;
	}

	/**
	 * Sets the Z coordinate of point
	 *  @param z Z coordinate
	 * 
	 */
	public void setZ(double z) {
		this.z = z;
	}
	
	// Distance between between points
	
	/**
	 * Returns the Euclidean distance between this point and another point
	 *  @param other another point
	 * @return distance between points
	 */
	public double distance(Point3D other){
		return Math.sqrt(Math.pow(x-other.x, 2)+Math.pow(y-other.y, 2)+Math.pow(z-other.z, 2));
	}
	
	// Operations
	
	/** 
	  * Adds another vector to this vector
	  * @param other another vector
	  * @return result vector
	 */
	public Point3D plus(Point3D other){
		return new Point3D(x+other.x,y+other.y,z+other.z);
	}
	
	/** 
	  * Subtracts another vector from this vector
	  * @param other another vector
	  * @return result vector
	 */
	public Point3D minus(Point3D other){
		return new Point3D(x-other.x,y-other.y,z-other.z);
	}
	
	/**
	 * Returns the inner product of this vector and another vector 
	 * @param other another vector
	 * @return inner product
	 */
	public double innerProduct(Point3D other){
		return x*other.x+y*other.y+z*other.z; 
	}
	
	/**
	 * Returns the cross product of this vector and another vector 
	 * @param other another vector
	 * @return cross product
	 */
	public Point3D crossProduct(Point3D other){
		Point3D ret = new Point3D();
		ret.setX(y*other.z-z*other.y);
		ret.setY(z*other.x-x*other.z);
		ret.setZ(x*other.y-y*other.x);
		return ret; 
	}
	
	/**
	 * Returns this vector multiplied by a scalar
	 * @param scalar scalar value
	 * @return result vector
	 */
	public Point3D multiplyByScalar(double scalar){
		return new Point3D(x*scalar,y*scalar,z*scalar);
	}
	
	/**
	 * Returns the length of the vector
	 * @return length of vector
	 */
	public double length(){
		return Math.sqrt(innerProduct(this));
	}
	
	/**
	 * Returns the resulting vector of a matrix transformation
	 * @param matrix 3x3 transformation matrix
	 * @return transformed vector
	 */
	public Point3D transformByMatrix(Matrix matrix){
		Point3D ret = new Point3D();
		ret.setX(matrix.get(0, 0)*x+matrix.get(0, 1)*y+matrix.get(0, 2)*z);
		ret.setY(matrix.get(1, 0)*x+matrix.get(1, 1)*y+matrix.get(1, 2)*z);
		ret.setZ(matrix.get(2, 0)*x+matrix.get(2, 1)*y+matrix.get(2, 2)*z);
		return ret;
	}
	
	
// toString, clone and equals
	
	/**
	 * Returns the String representation of point
	 *
	 * @return String representation
	 */
	public String toString(){
		return "("+roundIt(getX(),3)+","+roundIt(getY(),3)+","+roundIt(getZ(),3)+")";
	}
	
	/**
	 * Returns an identical Point3D object 
	 *
	 * @return clone of the point
	 */
	public Object clone(){
		try {
			Point3D ret = (Point3D)super.clone();
			ret.x = x;
			ret.y = y;
			ret.z = z;
			return ret;
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
			return null;
		}
	}
	
	/**
	 * Returns true if the two 3D points are identical
	 * @param other the other point
	 * 
	 */
	public boolean equals(Object other){
		if (other == null) return false;
		if (other == this) return true;
		if (this.getClass() != other.getClass()) return false;
		Point3D p = (Point3D)other;
		if ((Math.abs(p.x-x)<0.0000001)&&(Math.abs(p.y-y)<0.0000001)&&(Math.abs(p.z-z)<0.0000001)) 
		return true;
		else return false;
	}
	
	/**
	 * Returns hash code
	 * 
	 */
	public int hashCode() {
		return (int)(x*10+y*10-z*10);
	}
	
	
	private double roundIt(double d, int digit){
		double N = Math.pow(10, digit);
		long L = (int)Math.round(d * N); 
		double ret = L / N;
		return  ret;
	}
}
