/**
 * 
 * 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 jgromacs.db.AtomType;


/**
 * Objects of this class represent a single atom
 *
 */
public class Atom implements Cloneable{

	private int index = 0;
	private String name = "Noname";
	private AtomType atomType = new AtomType();
	private Point3D coordinates = new Point3D();
	private double occupancy = 0;
	private double bvalue = 0;
	
	// Constructor
	
	/**
	 * Constructs a new Atom object
	 *
	 * 
	 */
	public Atom(){
		super();
	}
	
	// Getters and setters
	
	
	/**
	 * Returns the type of atom
	 * @return Type of the atom
	 */
	public AtomType getAtomType() {
		return atomType;
	}

	/**
	 * Returns the index of atom
	 * @return Index of the atom
	 */
	public int getIndex() {
		return index;
	}

	/**
	 * Sets the index of atom
	 * @param index index of the atom
	 * 
	 */
	public void setIndex(int index) {
		this.index = index;
	}

	/**
	 * Returns the name of atom
	 * @return Name of the atom
	 */
	public String getName() {
		return name;
	}

	/**
	 * Sets the name of atom
	 * @param name name of the atom
	 * 
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Sets the type of atom
	 * @param atomType type of the atom
	 * 
	 */
	public void setAtomType(AtomType atomType) {
		this.atomType = atomType;
	}
	
	/**
	 * Returns the coordinates of atom
	 *
	 * @return Coordinates of atom
	 */
	public Point3D getCoordinates() {
		return coordinates;
	}

	/**
	 * Sets the coordinates of atom
	 * @param coordinates coordinates of the atom
	 * 
	 */
	public void setCoordinates(Point3D coordinates) {
		this.coordinates = coordinates;
	}

	/**
	 * Returns the X coordinate of atom
	 *
	 * @return X coordinate of atom
	 */
	public double getXCoordinate() {
		return coordinates.getX();
	}
	
	/**
	 * Sets the X coordinate of atom
	 * @param x X coordinate of the atom
	 * 
	 */
	public void setXCoordinate(double x) {
		coordinates.setX(x);
	}
	
	/**
	 * Returns the Y coordinate of atom
	 *
	 * @return Y coordinate of atom
	 */
	public double getYCoordinate() {
		return coordinates.getY();
	}
	
	/**
	 * Sets the Y coordinate of atom
	 * @param y Y coordinate of the atom
	 * 
	 */
	public void setYCoordinate(double y) {
		coordinates.setY(y);
	}
	
	/**
	 * Returns the Z coordinate of atom
	 * 
	 * @return Z coordinate of atom
	 */
	public double getZCoordinate() {
		return coordinates.getZ();
	}
	
	/**
	 * Sets the Z coordinate of atom
	 * @param z Z coordinate of the atom
	 * 
	 */
	public void setZCoordinate(double z) {
		coordinates.setZ(z);
	}

	/**
	 * Returns the occupancy of atom
	 * 
	 * @return occupancy of atom
	 */
	public double getOccupancy() {
		return occupancy;
	}

	/**
	 * Sets the occupancy of atom
	 * @param occupancy occupancy of the atom
	 * 
	 */
	public void setOccupancy(double occupancy) {
		this.occupancy = occupancy;
	}

	/**
	 * Returns the B-value of atom
	 * 
	 * @return B-value of atom
	 */
	public double getBvalue() {
		return bvalue;
	}

	/**
	 * Sets the B-value of atom
	 * @param bvalue B-value of the atom
	 * 
	 */
	public void setBvalue(double bvalue) {
		this.bvalue = bvalue;
	}	
	
	
	// is?

	/**
	 * Returns true if the atom is a Hydrogen
	 *
	 * 
	 */
	public boolean isHydrogenAtom(){
		return atomType.getCode().equals("H");
	}
	
	/**
	 * Returns true if it is a heavy atom (not hydrogen)
	 *
	 * 
	 */
	public boolean isHeavyAtom(){
		return !isHydrogenAtom();
	}
	
	/**
	 * Returns true if it is an alpha carbon atom
	 *
	 * 
	 */
	public boolean isAlphaCarbon(){
		return atomType.getCode().equals("C")&&name.equals("CA");
	}
	
	/**
	 * Returns true if it is a C-terminal carbon atom
	 *
	 * 
	 */
	public boolean isCTerminalCarbon(){
		return atomType.getCode().equals("C")&&name.equals("C");
	}
	
	/**
	 * Returns true if it is a beta carbon atom
	 *
	 * 
	 */
	public boolean isBetaCarbon(){
		return atomType.getCode().equals("C")&&name.equals("CB");
	}
	
	
	/**
	 * Returns true if it is a gamma carbon atom
	 *
	 * 
	 */
	public boolean isGammaCarbon(){
		return atomType.getCode().equals("C")&&(name.equals("CG")||name.equals("CG1")||name.equals("CG2"));
	}
	
	/**
	 * Returns true if it is a delta carbon atom
	 *
	 * 
	 */
	public boolean isDeltaCarbon(){
		return atomType.getCode().equals("C")&&(name.equals("CD")||name.equals("CD1")||name.equals("CD2"));
	}
	
	/**
	 * Returns true if it is an epsilon carbon atom
	 *
	 * 
	 */
	public boolean isEpsilonCarbon(){
		return atomType.getCode().equals("C")&&(name.equals("CE")||name.equals("CE1")||name.equals("CE2")||name.equals("CE3"));
	}
	
	/**
	 * Returns true if it is a zeta carbon atom
	 *
	 * 
	 */
	public boolean isZetaCarbon(){
		return atomType.getCode().equals("C")&&(name.equals("CZ")||name.equals("CZ1")||name.equals("CZ2")||name.equals("CZ3"));
	}
	
	
	/**
	 * Returns true if it is a carbonyl oxygen atom
	 *
	 * 
	 */
	public boolean isCarbonylOxygen(){
		return atomType.getCode().equals("O")&&name.equals("O");
	}
	
	/**
	 * Returns true if it is a terminal oxygen atom
	 *
	 * 
	 */
	public boolean isTerminalOxygen(){
		return atomType.getCode().equals("O")&&name.equals("OXT");
	}
	
	/**
	 * Returns true if it is an N-terminal nitrogen atom
	 *
	 * 
	 */
	public boolean isNTerminalNitrogen(){
		return atomType.getCode().equals("N")&&name.equals("N");
	}
	
	/**
	 * Returns true if it is a backbone atom
	 *
	 * 
	 */
	public boolean isBackboneAtom(){
		return isCTerminalCarbon()||isAlphaCarbon()||isNTerminalNitrogen();
	}
	
	/**
	 * Returns true if it is a main chain atom
	 *
	 * 
	 */
	public boolean isMainChainAtom(){
		return isCTerminalCarbon()||isAlphaCarbon()||isNTerminalNitrogen()||isCarbonylOxygen()||isTerminalOxygen();
	}
	
	/**
	 * Returns true if it is a main chain hydrogen atom
	 *
	 * 
	 */
	public boolean isMainChainHydrogenAtom(){
		return atomType.getCode().equals("H")&&(name.equals("H")||name.equals("HA")||name.equals("HA1")||name.equals("HA2")||name.equals("HO"));
	}
	
	/**
	 * Returns true if it is a main chain or hydrogen atom
	 *
	 * 
	 */
	public boolean isMainChainPlusHAtom(){
		return isCTerminalCarbon()||isAlphaCarbon()||isNTerminalNitrogen()||isCarbonylOxygen()||isTerminalOxygen()||isMainChainHydrogenAtom();
	}
	
	/**
	 * Returns true if it is a main chain or beta carbon atom
	 *
	 * 
	 */
	public boolean isMainChainPlusCbAtom(){
		return isCTerminalCarbon()||isAlphaCarbon()||isNTerminalNitrogen()||isCarbonylOxygen()||isTerminalOxygen()||isBetaCarbon();
	}
	
	/**
	 * Returns true if it is a side chain atom
	 *
	 * 
	 */
	public boolean isSideChainAtom(){
		return !isMainChainPlusHAtom();
	}
	
	/**
	 * Returns true if it is a side chain atom but not hydrogen
	 *
	 * 
	 */
	public boolean isSideChainMinusHAtom(){
		return isSideChainAtom()&&(!isHydrogenAtom());
	}
	
	// Distance
	
	/**
	 * Returns the Euclidean distance between this atom and another atom
	 * @param other another atom
	 * @return distance between atoms
	 */
	public double distance(Atom other){
		return getCoordinates().distance(other.getCoordinates());
	}
	
	// toString, clone and equals
	
	/**
	 * Returns the String representation of atom
	 *
	 * @return String representation
	 */
	public String toString(){
		return getName()+"\t"+getIndex()+"\t"+getAtomType()+"\t("+roundIt(getXCoordinate(),3)+"\t"+roundIt(getYCoordinate(),3)+"\t"+roundIt(getZCoordinate(),3)+")";
	}
	
	/**
	 * Returns an identical Atom object 
	 *
	 * @return clone of the atom
	 */
	public Object clone(){
		try {
			Atom ret = (Atom)super.clone();
			ret.index = index;
			ret.name = name;
			ret.atomType = (AtomType)atomType.clone();
			ret.coordinates = (Point3D)coordinates.clone();
			ret.occupancy = occupancy;
			ret.bvalue = bvalue;
			return ret;
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
			return null;
		}
			
	}
	
	/**
	 * Returns true if the two atoms are identical
	 * @param other the other atom
	 * 
	 */
	public boolean equals(Object other){
		if (other == null) return false;
		if (other == this) return true;
		if (this.getClass() != other.getClass()) return false;
		Atom atom = (Atom)other;
		return atom.atomType.equals(atomType)
		&&atom.coordinates.equals(coordinates)
		&&(atom.index==index)
		&&(atom.name.equals(name))
		&&(Math.abs(atom.occupancy-occupancy)<0.0000001)
		&&(Math.abs(atom.bvalue-bvalue)<0.0000001);
	}

	/**
	 * Returns hash code
	 * 
	 */
	public int hashCode() {
		return index+name.length();
	}
	
	
	private static 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;
	}
	
	
}
