/**
 * 
 * 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 java.util.TreeSet;


/**
 * Objects of this class represent a single structure
 *
 */
public class Structure implements Cloneable {

	private String name = "Noname";
	private ArrayList<Residue> residues = new ArrayList<Residue>();
	
	// Constructors
	
	/**
	 * Constructs a new Structure object
	 *
	 * 
	 */
	public Structure(){
		
	}
	
	/**
	 * Constructs a new Structure object of given name
	 *
	 * 
	 */
	public Structure(String name) {
		super();
		this.name = name;
	}

	// Getters and setters
	
	/**
	 * Returns the name of structure
	 * @return name of structure
	 * 
	 */
	public String getName() {
		return name;
	}
	
	/**
	 * Sets the name of structure
	 * @param name of structure
	 * 
	 */
	public void setName(String name) {
		this.name = name;
	}
	
	/**
	 * Returns residues as an ArrayList object
	 * @return residues as an ArrayList
	 * 
	 */
	public ArrayList<Residue> getResiduesAsArrayList(){
		return residues;
	}
	
	/**
	 * Returns the number of residues in the structure
	 * @return number of residues
	 * 
	 */
	public int getNumberOfResidues() {
		return residues.size();
	}
	
	/**
	 * Returns the number of atoms in the structure
	 * @return number of atoms
	 * 
	 */
	public int getNumberOfAtoms(){
		int ret = 0;
		for (int i = 0; i < getNumberOfResidues(); i++) {
			ret+= getResidue(i).getNumberOfAtoms();
		}
		return ret;
	}
	
	/**
	 * Returns the number of chains in the structure
	 * @return number of chains
	 * 
	 */
	public int getNumberOfChains(){
		TreeSet<String> ids = new TreeSet<String>();
		for (int i = 0; i < getNumberOfResidues(); i++) {
			String id = getResidue(i).getChainID();
			ids.add(id);
		}
		return ids.size();
	}
	
	/**
	 * Returns the list of chain IDs in the structure
	 * @return list of chain IDs
	 * 
	 */
	public ArrayList<String> getChainIDs(){
		TreeSet<String> ids = new TreeSet<String>();
		for (int i = 0; i < getNumberOfResidues(); i++) {
			String id = getResidue(i).getChainID();
			ids.add(id);
		}
		return new ArrayList<String>(ids);
	}
	
	/**
	 * Returns residue #i of the structure
	 * @return residue #i
	 * 
	 */
	public Residue getResidue(int i){
		return residues.get(i);
	}
	
	
	/**
	 * Returns the residue of given index and given chain ID
	 * @param index index of residue 
	 * @param chainID chain ID of residue
	 * @return residue of given index and chain ID
	 * 
	 */
	public Residue getResidueByIndex(int index, String chainID) {
		for (int i = 0; i < residues.size(); i++) {
			Residue res = residues.get(i);
			if ((res.getIndex()==index)&&(res.getChainID().equals(chainID))) 
				return res;
		}
		return null;	
	}

	/**
	 * Returns the residue of given index
	 * @param index index of residue 
	 * @return residue of given index
	 * 
	 */
	public Residue getResidueByIndex(int index) {
		for (int i = 0; i < residues.size(); i++) {
			Residue res = residues.get(i);
			if (res.getIndex()==index) 
				return res;
		}
		return null;	
	}
	
	/**
	 * Returns atom #i of the structure
	 * @return atom #i
	 * 
	 */
	public Atom getAtom(int i){
		int counter = 0;
		for (int j = 0; j < getNumberOfResidues(); j++) {
			int numOfAtoms = getResidue(j).getNumberOfAtoms();
			for (int k = 0; k < numOfAtoms; k++) {
				if (counter==i) return getResidue(j).getAtom(k);
				counter++;
			}
		}
		return null;
	}
	
	/**
	 * Returns the atom of given index
	 * @param index index of atom 
	 * @return atom of given index
	 * 
	 */
	public Atom getAtomByIndex(int index) {
		for (int j = 0; j < getNumberOfResidues(); j++) {
			Residue res = getResidue(j);
			int numOfAtoms = res.getNumberOfAtoms();
			for (int k = 0; k < numOfAtoms; k++) {
				if (res.getAtom(k).getIndex()==index) return res.getAtom(k);
			}
		}
		return null;
	}
	
	/**
	 * Returns atom #i of the residue of given index and chain ID
	 * @param residueindex index of residue 
	 * @param chainID chain ID of residue
	 * @return atom #i in residue of given index and chain ID
	 * 
	 */
	public Atom getAtomInResidueOfIndex(int residueindex, String chainID, int i){
		Residue aa = getResidueByIndex(residueindex, chainID);
		return aa.getAtom(i);
	}
	
	/**
	 * Returns atom #i of the residue of given index
	 * @param residueindex index of residue 
	 * @return atom #i in residue of given index
	 * 
	 */
	public Atom getAtomInResidueOfIndex(int residueindex, int i){
		Residue aa = getResidueByIndex(residueindex);
		return aa.getAtom(i);
	}
		
	// Modifications
		
	/**
	 * Adds a new residue to the structure
	 * @param residue new residue
	 * 
	 */
	public void addResidue(Residue residue) {
		residues.add(residue);
	}

	/**
	 * Removes a residue from the structure
	 * @param residue the residue to be removed
	 * 
	 */
	public void removeResidue(Residue residue) {
		residues.remove(residue);
	}
	
	/**
	 * Removes residue #i from the structure
	 * 
	 */
	public void removeResidue(int i) {
		residues.remove(i);
	}
	
	/**
	 * Replaces residue #i with a new residue
	 * @param residue new residue
	 * 
	 */
	public void setResidue(int i, Residue residue){
		residues.set(i, residue);
	}

	/**
	 * Removes atom #i from the structure
	 * 
	 */
	public void removeAtom(int i){
		int counter = 0;
		for (int k = 0; k < getNumberOfResidues(); k++) {
			Residue res = getResidue(k);
			for (int j = 0; j < res.getNumberOfAtoms(); j++) {
				if (counter==i) {
					res.removeAtom(j);
					counter++;
					break;
				}
			counter++;
			}
			if (res.getNumberOfAtoms()==0)
				removeResidue(k);
		}
	}
	
	/**
	 * Removes the atom of given index from the structure
	 * @param index index of atom
	 */
	public void removeAtomByIndex(int index){
		for (int k = 0; k < getNumberOfResidues(); k++) {
			Residue res = getResidue(k);
			for (int j = 0; j < res.getNumberOfAtoms(); j++) {
				Atom atom = res.getAtom(j);
				if (atom.getIndex()==index) {
					res.removeAtom(j);
					j--;
				}
			}
			if (res.getNumberOfAtoms()==0){
				removeResidue(k);
				k--;
			}
		}
	}
	
	/**
	 * Removes the given atom from the structure
	 * @param atom the atom to be removed
	 */
	public void removeAtom(Atom atom){
		for (int k = 0; k < getNumberOfResidues(); k++) {
			Residue res = getResidue(k);
			for (int j = 0; j < res.getNumberOfAtoms(); j++) {
				Atom atomJ = res.getAtom(j);
				if (atomJ.equals(atom)) {
					res.removeAtom(j);
					j--;
				}
			}
			if (res.getNumberOfAtoms()==0){
				removeResidue(k);
				k--;
			}
		}
	}
    
	/**
	 * Adds the given atom to residue #i
	 * @param atom new atom
	 */
	public void addAtomToResidue(int i, Atom atom){
		getResidue(i).addAtom(atom);
	}
	
	/**
	 * Adds the given atom to the residue of given index
	 * @param index index of residue
	 * @param atom new atom
	 */
	public void addAtomToResidueOfIndex(int index, Atom atom){
		getResidueByIndex(index).addAtom(atom);
	}
    
	/**
	 * Replaces atom #j of residue #i with a new atom
	 * @param atom new atom
	 * 
	 */
	public void setAtomInResidue(int i, int j, Atom atom){
		getResidue(i).setAtom(j, atom);
	}
	
	/**
	 * Replaces atom #i of the residue of given index with a new atom
	 * @param index index of residue
	 * @param atom new atom
	 * 
	 */
	public void setAtomInResidueOfIndex(int index, int i, Atom atom){
		getResidueByIndex(index).setAtom(i, atom);
	}
	
	// Getting important index sets
	
	/**
	 * Returns the index set of all atoms in the system
	 * @return index set of all atoms
	 */
	public IndexSet getSystemIndexSet(){
		IndexSet ret = new IndexSet();
		for (int i = 0; i < getNumberOfResidues(); i++) {
			Residue aa = getResidue(i);
			ret = ret.union(aa.getAtomIndices());
		}
		ret.setName("System");
		return ret;
	}
	
	/**
	 * Returns the index set of protein atoms
	 * @return index set of protein atoms
	 */
	public IndexSet getProteinIndexSet(){
		IndexSet ret = new IndexSet();
		for (int i = 0; i < getNumberOfResidues(); i++) {
			Residue aa = getResidue(i);
			if (aa.isAminoAcid()) ret = ret.union(aa.getAtomIndices());
		}
		ret.setName("Protein");
		return ret;
	}
	
	/**
	 * Returns the index set of protein atoms except of hydrogen atoms
	 * @return index set of protein atoms except of hydrogen atoms
	 *
	 */
	public IndexSet getHeavyProteinIndexSet(){
		IndexSet ret = new IndexSet();
		for (int i = 0; i < getNumberOfResidues(); i++) {
			Residue aa = getResidue(i);
			if (aa.isAminoAcid()) ret = ret.union(aa.getHeavyAtomIndices());
		}
		ret.setName("Protein-H");
		return ret;
	}	
		
	/**
	 * Returns the index set of alpha carbon atoms
	 * @return index set of alpha carbon atoms
	 */
	public IndexSet getAlphaCarbonIndexSet(){
		IndexSet ret = new IndexSet();
		for (int i = 0; i < getNumberOfResidues(); i++) {
			Residue aa = getResidue(i);
			if (aa.isAminoAcid()) {
				for (int k = 0; k < aa.getNumberOfAtoms(); k++) {
					if (aa.getAtom(k).isAlphaCarbon())
						ret.addIndex(aa.getAtom(k).getIndex());
				}	
			}
		}
		ret.setName("C-alpha");
		return ret;
	}
	
	/**
	 * Returns the index set of backbone atoms
	 * @return index set of backbone atoms
	 */
	public IndexSet getBackboneIndexSet(){
		IndexSet ret = new IndexSet();
		for (int i = 0; i < getNumberOfResidues(); i++) {
			Residue aa = getResidue(i);
			if (aa.isAminoAcid()) ret = ret.union(aa.getBackBoneAtomIndices());
		}
		ret.setName("Backbone");
		return ret;
	}
	
	/**
	 * Returns the index set of main chain atoms
	 * @return index set of main chain atoms
	 */
	public IndexSet getMainChainIndexSet(){
		IndexSet ret = new IndexSet();
		for (int i = 0; i < getNumberOfResidues(); i++) {
			Residue aa = getResidue(i);
			if (aa.isAminoAcid()) ret = ret.union(aa.getMainChainAtomIndices());
		}
		ret.setName("MainChain");
		return ret;
	}
	
	/**
	 * Returns the index set of main chain plus beta carbon atoms
	 * @return index set of main chain plus beta carbon atoms
	 */
	public IndexSet getMainChainPlusCbIndexSet(){
		IndexSet ret = new IndexSet();
		for (int i = 0; i < getNumberOfResidues(); i++) {
			Residue aa = getResidue(i);
			if (aa.isAminoAcid()) ret = ret.union(aa.getMainChainPlusCbAtomIndices());
		}
		ret.setName("MainChain+Cb");
		return ret;
	}
	
	/**
	 * Returns the index set of main chain plus hydrogen atoms
	 * @return index set of main chain plus hydrogen atoms
	 */
	public IndexSet getMainChainPlusHIndexSet(){
		IndexSet ret = new IndexSet();
		for (int i = 0; i < getNumberOfResidues(); i++) {
			Residue aa = getResidue(i);
			if (aa.isAminoAcid()) ret = ret.union(aa.getMainChainPlusHAtomIndices());
		}
		ret.setName("MainChain+H");
		return ret;
	}

	/**
	 * Returns the index set of side chain atoms
	 * @return index set of side chain atoms
	 */
	public IndexSet getSideChainIndexSet(){
		IndexSet ret = new IndexSet();
		for (int i = 0; i < getNumberOfResidues(); i++) {
			Residue aa = getResidue(i);
			if (aa.isAminoAcid()) ret = ret.union(aa.getSideChainAtomIndices());
		}
		ret.setName("SideChain");
		return ret;
	}
	
	/**
	 * Returns the index set of side chain atoms except of hydrogen atoms
	 * @return index set of side chain atoms except of hydrogen atoms
	 */
	public IndexSet getSideChainMinusHIndexSet(){
		IndexSet ret = new IndexSet();
		for (int i = 0; i < getNumberOfResidues(); i++) {
			Residue aa = getResidue(i);
			if (aa.isAminoAcid()) ret = ret.union(aa.getSideChainMinusHAtomIndices());
		}
		ret.setName("SideChain-H");
		return ret;
	}
	
	/**
	 * Returns the index set of non-protein atoms
	 * @return index set of non-protein atoms
	 */
	public IndexSet getNonProteinIndexSet(){
		IndexSet ret = new IndexSet();
		for (int i = 0; i < getNumberOfResidues(); i++) {
			Residue aa = getResidue(i);
			if (!aa.isAminoAcid()) ret = ret.union(aa.getAtomIndices());
		}
		ret.setName("non-Protein");
		return ret;
	}
	
	/**
	 * Returns the index set of water atoms
	 * @return index set of water atoms
	 */
	public IndexSet getWaterIndexSet(){
		IndexSet ret = new IndexSet();
		for (int i = 0; i < getNumberOfResidues(); i++) {
			Residue aa = getResidue(i);
			if (aa.isWater()) ret = ret.union(aa.getAtomIndices());
		}
		ret.setName("Water");
		return ret;
	}
	
	/**
	 * Returns the list of default index sets
	 * @return list of default index sets
	 */
	public IndexSetList getDefaultIndexSetList(){
		IndexSetList ret = new IndexSetList();
		IndexSet set;
		set = getSystemIndexSet();
		if (set.getNumberOfIndices()>0) ret.addIndexSet(set);
		set = getProteinIndexSet();
		if (set.getNumberOfIndices()>0) ret.addIndexSet(set);
		set = getHeavyProteinIndexSet();
		if (set.getNumberOfIndices()>0) ret.addIndexSet(set);
		set = getAlphaCarbonIndexSet();
		if (set.getNumberOfIndices()>0) ret.addIndexSet(set);
		set = getBackboneIndexSet();
		if (set.getNumberOfIndices()>0) ret.addIndexSet(set);
		set = getMainChainIndexSet();
		if (set.getNumberOfIndices()>0) ret.addIndexSet(set);
		set = getMainChainPlusCbIndexSet();
		if (set.getNumberOfIndices()>0) ret.addIndexSet(set);
		set = getMainChainPlusHIndexSet();
		if (set.getNumberOfIndices()>0) ret.addIndexSet(set);
		set = getSideChainIndexSet();
		if (set.getNumberOfIndices()>0) ret.addIndexSet(set);
		set = getSideChainMinusHIndexSet();
		if (set.getNumberOfIndices()>0) ret.addIndexSet(set);
		set = getNonProteinIndexSet();
		if (set.getNumberOfIndices()>0) ret.addIndexSet(set);
		set = getWaterIndexSet();
		if (set.getNumberOfIndices()>0) ret.addIndexSet(set);
		return ret;
	}
	
	/**
	 * Returns the index set of atoms of a given chain ID
	 * @param chainID selected chain ID
	 * @return index set defined by chain ID
	 */
	public IndexSet getIndexSetOfChainID(String chainID){
		IndexSet ret = new IndexSet();
		for (int i = 0; i < getNumberOfResidues(); i++) {
			Residue aa = getResidue(i);
			if (aa.getChainID().equals(chainID)) ret = ret.union(aa.getAtomIndices());
		}
		ret.setName("chain_"+chainID);
		return ret;
	}

	// Coordinates
	
	/**
	 * 	Sets the coordinates of atom #i
	 *  @param coordinates new atomic coordinates
	 */
	public void setAtomCoordinates(int i, Point3D coordinates){
		getAtom(i).setCoordinates(coordinates);
	}
	
	/**
	 * 	Sets the coordinates of atom of given index
	 *  @param coordinates new atomic coordinates
	 */
	public void setAtomOfIndexCoordinates(int index, Point3D coordinates){
		getAtomByIndex(index).setCoordinates(coordinates);
	}
	
	/**
	 * 	Sets the coordinates of all atoms
	 *  @param pointlist list of new atomic coordinates
	 */
	public void setAllAtomCoordinates(PointList pointlist){
		int numofatoms = getNumberOfAtoms();
		for (int i = 0; i < numofatoms; i++) {
			setAtomCoordinates(i, pointlist.getPoint(i));
		}
	}
	
	/**
	 * 	Returns the coordinates of all atoms
	 *  @return list of atomic coordinates
	 */
	public PointList getAllAtomCoordinates(){
		PointList ret = new PointList();
		for (int j = 0; j < getNumberOfResidues(); j++) {
			Residue res = getResidue(j);
			int numOfAtoms = res.getNumberOfAtoms();
			for (int k = 0; k < numOfAtoms; k++)
				ret.addPoint(res.getAtom(k).getCoordinates());
		}
		return ret;
	}
	
	// Extracting sequence
	
	/**
	 * 	Returns the amino acid sequence of the structure
	 *  @return amino acid sequence
	 */
	public Sequence getSequence(){
		 Sequence ret = new Sequence();
		 ret.setName("Sequence("+name+")");
		 	for (int i = 0; i < getNumberOfResidues(); i++){
		 		Residue res = getResidue(i);
		 		if (res.isAminoAcid())
		 		ret.addPosition(res.getIndex(), res.getResidueType());	
		 	}
		 return ret;
	}

	// Retrieving different chains 
	
	/**
	 * 	Returns all chains in an array of Structure objects
	 *  @return array of chains
	 */
	public Structure[] getChains(){
		ArrayList<String> ids = getChainIDs();
		Structure[] ret = new Structure[ids.size()];
		for (int i = 0; i < ids.size(); i++) {
			IndexSet g = getIndexSetOfChainID(ids.get(i));
			ret[i] = getSubStructure(g);
		}
		return ret;
	}
	
	// Getting substructures
	
	/**
	 * 	Returns the substructure defined by the given index set
	 *  @param indices index set of substructure
	 *  @return substructure
	 */
	public Structure getSubStructure(IndexSet indices){
		Structure ret = (Structure)clone();
		ret.setName(indices.getName()+"(from:"+name+")");
		for (int i = 0; i < ret.getNumberOfResidues(); i++) {
			Residue res = ret.getResidue(i);
			for (int j = 0; j < res.getNumberOfAtoms(); j++) {
				Atom atom = res.getAtom(j);
				if (!indices.isIndexIn(atom.getIndex())) {
					res.removeAtom(j);
					j--;
				}
			}
			if (res.getNumberOfAtoms()==0) {
				ret.removeResidue(i);
				i--;
			}
		}
		return ret;
	}
	
	// Converting indices
	
	/**
	 *  Converts atomic indices into list indices
	 *  @param indices set of atomic indices
	 *  @return list indices
	 */
	public ArrayList<Integer> convertIndicesToArrayListIndices(IndexSet indices){
		ArrayList<Integer> ret = new ArrayList<Integer>();
		int counter = 0;
		for (int i = 0; i < getNumberOfResidues(); i++) {
			Residue res = getResidue(i);
			for (int j = 0; j < res.getNumberOfAtoms(); j++) {
				Atom atom = res.getAtom(j);
				if (indices.isIndexIn(atom.getIndex()))	ret.add(counter);
			counter++;
			}
		}
		return ret;
	}
	
	// Mapping residue indices to atom indices and back
	
	/**
	 *  Maps atomic indices to residue indices (i.e. returns which residues contain the
	 *  given atoms). If an atom is not present in any residue it is mapped to residue index -1
	 *  @param indices set of atomic indices
	 *  @return residue indices
	 */
	public ArrayList<Integer> mapAtomIndicesToResidueIndices(IndexSet indices){
		ArrayList<Integer> ret = new ArrayList<Integer>();
		ArrayList<Integer> atoms = indices.getAsArrayList();
		for (int k = 0; k < atoms.size() ; k++){
			Atom atom = getAtomByIndex(atoms.get(k));
			boolean found = false;
			for (int i = 0; i < getNumberOfResidues(); i++) {
				Residue res = getResidue(i);
				if (res.isAtomIn(atom)) {
					ret.add(res.getIndex());
					found = true;
				}
			}
			if (!found) ret.add(-1);
		}
		return ret;
	}
	
	/**
	 *  Maps residue indices to atomic indices (i.e. returns which atoms are present in the
	 *  given residues).
	 *  @param indices ArrayList of residue indices
	 *  @return IndexSet of atomic indices
	 */
	public IndexSet mapResidueIndicesToAtomIndices(ArrayList<Integer> indices){
		IndexSet ret = new IndexSet();
		for (int i = 0; i < indices.size(); i++) {
			Residue res = getResidueByIndex(indices.get(i));
			IndexSet atoms = res.getAtomIndices();
			ret = ret.union(atoms);
		}
		return ret;
	}
		
	// toString, clone, equals
	
	/**
	 * Returns the String representation of structure
	 *
	 * @return String representation
	 */
	public String toString(){	
		return toStringAsGRO();	
	}
	
	/**
	 * Returns the String representation of structure in GRO format
	 *
	 * @return String representation in GRO format
	 */
	public String toStringAsGRO(){
		String title = "";
		if (name.equals("")) title = "NO TITLE";
		else title = name;
		StringBuffer buf = new StringBuffer(title+"\n");
		buf.append(makeRecordRight(String.valueOf(getNumberOfAtoms()), 5)+"\n");
		for (int i = 0; i < getNumberOfResidues(); i++) {
			Residue res = getResidue(i);
			for (int j = 0; j < res.getNumberOfAtoms(); j++)
				   buf.append(makeGROLine(this, i, j)+"\n");
		}
		buf.append("   0.00000   0.00000   0.00000\n");
		return buf.toString();
	}
	
	/**
	 * Returns the String representation of structure in PDB format
	 *
	 * @return String representation in PDB format
	 */
	public String toStringAsPDB(){
		String title = "";
		if (!name.equals("")) title = name;
		StringBuffer buf = new StringBuffer("TITLE     "+title+"\n");
		for (int i = 0; i < getNumberOfResidues(); i++) {
			Residue res = getResidue(i);
			for (int j = 0; j < res.getNumberOfAtoms(); j++)
			    	buf.append(makePDBLine(this, i, j)+"\n");	
		}
		return buf.toString();
	}
	
	/**
	 * Returns summary information about the structure
	 *
	 * @return summary information
	 */
	public String toStringInfo(){
		return "Name: "+name+" | ("+getNumberOfResidues()+" residues, "+getNumberOfAtoms()+" atoms)";
	}

	/**
	 * Returns an identical Structure object 
	 *
	 * @return clone of the structure
	 */
	public Object clone(){
		try {
			Structure ret = (Structure)super.clone();
			ret.name = name;
			ret.residues = new ArrayList<Residue>();
			for (int i = 0; i < residues.size(); i++)
			ret.residues.add((Residue)residues.get(i).clone());
			return ret;
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
			return null;
		}
	}
	
	/**
	 * Returns true if the two structures are identical
	 * @param other the other structure
	 * 
	 */
	public boolean equals(Object other){
		if (other == null) return false;
		if (other == this) return true;
		if (this.getClass() != other.getClass()) return false;
		Structure s = (Structure)other;
		if (!s.name.equals(name)) return false;
		if (s.residues.size()!=residues.size()) return false;
		for (int i = 0; i < residues.size(); i++) {
			if (!s.getResidue(i).equals(getResidue(i))) return false;
		}
		return true;
	}
	
	/**
	 * Returns hash code
	 * 
	 */
	public int hashCode() {
		return residues.size()+name.length();
	}
	
	
	private static String makeGROLine( Structure s,  int resi,  int atomi){
		Residue res = s.getResidue(resi);
		Atom atom = res.getAtom(atomi);
		String resindex = String.valueOf(res.getIndex());
		String resname = res.getName().toUpperCase();
		String atomindex = String.valueOf(atom.getIndex());
		String atomname = atom.getName();
		String x = correctCoordinate(String.valueOf(roundIt(atom.getXCoordinate(),3)));
		String y = correctCoordinate(String.valueOf(roundIt(atom.getYCoordinate(),3)));
		String z = correctCoordinate(String.valueOf(roundIt(atom.getZCoordinate(),3)));
		resindex = makeRecordRight(resindex, 5);
		resname = makeRecordLeft(resname, 5);
		atomindex = makeRecordRight(atomindex, 5);
		atomname = makeRecordRight(atomname, 5);
		x = makeRecordRight(x, 8);
		y = makeRecordRight(y, 8);
		z = makeRecordRight(z, 8);
		return resindex+resname+atomname+atomindex+x+y+z;
	}
	
	private static String makePDBLine( Structure s,  int resi,  int atomi){
		Residue res = s.getResidue(resi);
		Atom atom = res.getAtom(atomi);
		String resindex = String.valueOf(res.getIndex());
		String resname = res.getName().toUpperCase();
		String atomindex = String.valueOf(atom.getIndex());
		String atomname = atom.getName();
		String chainID = res.getChainID();
		String occup = String.valueOf(roundIt(atom.getOccupancy(),2));
		String bval = String.valueOf(roundIt(atom.getBvalue(),2));
		String x = correctCoordinate(String.valueOf(roundIt(10*atom.getXCoordinate(),3)));
		String y = correctCoordinate(String.valueOf(roundIt(10*atom.getYCoordinate(),3)));
		String z = correctCoordinate(String.valueOf(roundIt(10*atom.getZCoordinate(),3)));
		resindex = makeRecordRight(resindex, 4);
		resname = makeRecordLeft(resname, 3);
		atomindex = makeRecordRight(atomindex, 5);
		String atomnameGood = "";
		if (atom.getAtomType().getCode().length()==2)
			atomnameGood = makeRecordLeft(atomname, 4);
		else {
			if (atomname.length()<4)
			atomnameGood = " "+makeRecordLeft(atomname, 3);
			else atomnameGood = makeRecordLeft(atomname, 4);
		}
		x = makeRecordRight(x, 8);
		y = makeRecordRight(y, 8);
		z = makeRecordRight(z, 8);
		occup = makeRecordRight(correctOccupancy(occup), 6);
		bval = makeRecordRight(correctOccupancy(bval), 6);
		return "ATOM  "+atomindex+" "+atomnameGood+" "+resname+" "+chainID+resindex+"    "+x+y+z+occup+bval+"          "+makeRecordRight(atom.getAtomType().toString(), 2);
	}
	
	private static String makeRecordRight( String s,  int length){
		int spaces = length-s.length();
		if (spaces<0) {
			return s.substring(-spaces);
		} else {
			StringBuffer buf = new StringBuffer();
			for (int i = 0; i < spaces; i++) buf.append(" ");
			return buf.toString()+s;		
		}
	}

	private static String makeRecordLeft( String s,  int length){
		int spaces = length-s.length();
		if (spaces<0) {
			return s.substring(-spaces);
		} else {
			StringBuffer buf = new StringBuffer();
			for (int i = 0; i < spaces; i++) buf.append(" ");
			return s+buf.toString();		
		}
	}
	
	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;
	}
	
	private static String correctCoordinate(String c){
		StringBuffer buf = new StringBuffer(c);
	    int n = c.length()-c.indexOf(".")-1;
	    if (n<3) for (int i = 0; i < 3-n; i++) buf.append("0");
	    if (n>3) buf = new StringBuffer(c.substring(0,c.indexOf(".")+4));
		return buf.toString();
	}
	
	private static String correctOccupancy(String c){
		StringBuffer buf = new StringBuffer(c);
		int n = c.length()-c.indexOf(".")-1;
	    if (n<2) for (int i = 0; i < 2-n; i++) buf.append("0");
	    if (n>2)  buf = new StringBuffer(c.substring(0,c.indexOf(".")+3));
		return buf.toString();
	}

}
