/* $RCSfile$
 * $Author$    
 * $Date$    
 * $Revision$
 * 
 * Copyright (C) 1997-2007  Edgar Luttmann <edgar@uni-paderborn.de>
 * 
 *  Contact: cdk-devel@lists.sourceforge.net
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 * All we ask is that proper credit is given for our work, which includes
 * - but is not limited to - adding the above copyright notice to the beginning
 * of your source code files, and to any copyright notice that you may distribute
 * with programs based on this work.
 * 
 * This program 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 Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *  */
package org.openscience.cdk;

import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IBioPolymer;
import org.openscience.cdk.interfaces.IMonomer;
import org.openscience.cdk.interfaces.IStrand;

import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;

/**
 * A BioPolymer is a subclass of a Polymer which is supposed to store
 * additional informations about the Polymer which are connected to BioPolymers.
 *
 * @cdk.module  data
 * @cdk.githash
 *
 * @author      Edgar Luttmann <edgar@uni-paderborn.de>
 * @author      Martin Eklund
 * @cdk.created 2001-08-06 
 *
 * @cdk.keyword polymer
 * @cdk.keyword biopolymer
 */
public class BioPolymer extends Polymer implements java.io.Serializable, IBioPolymer
{

	/**
     * Determines if a de-serialized object is compatible with this class.
     *
     * This value must only be changed if and only if the new version
     * of this class is incompatible with the old version. See Sun docs
     * for <a href="http://java.sun.com/products/jdk/1.1/docs/guide/serialization/spec/version.doc.html">details</a>.
	 */
	private static final long serialVersionUID = -5001873073769634393L;

	private Map<String, IStrand> strands;	// the list of all the contained Strands.
	
	/**
	 * Constructs a new Polymer to store the Strands.
	 */	
	public BioPolymer() {
		super();
		// Strand stuff
		strands = new Hashtable<String, IStrand>();
	}
	
	/**
	 * Adds the atom oAtom to a specified Strand, whereas the Monomer is unspecified. Hence
	 * the atom will be added to a Monomer of type UNKNOWN in the specified Strand.
	 *
	 * @param oAtom   The atom to add
	 * @param oStrand The strand the atom belongs to
	 */
	public void addAtom(IAtom oAtom, IStrand oStrand) {
		
		int atomCount = super.getAtomCount();
		
		// Add atom to AtomContainer
		super.addAtom(oAtom);

		if (atomCount != super.getAtomCount() && 
		    oStrand != null) {	// Maybe better to throw null pointer exception here, so user realises that
								// Strand == null and Atom only gets added to this BioPolymer, but not to a Strand.
			oStrand.addAtom(oAtom);	
			if (!strands.containsKey(oStrand.getStrandName())) {
				strands.put(oStrand.getStrandName(), oStrand);
			}
		}
		/* notifyChanged() is called by addAtom in
		 AtomContainer */
	}
	
	/**
	 * Adds the atom to a specified Strand and a specified Monomer.
	 * 
	 * @param oAtom
	 * @param oMonomer
	 * @param oStrand
	 */
	public void addAtom(IAtom oAtom, IMonomer oMonomer, IStrand oStrand)	{
		
		int atomCount = super.getAtomCount();
		
		// Add atom to AtomContainer
		super.addAtom(oAtom);

		if(atomCount != super.getAtomCount() && // OK, super did not yet contain the atom
			   // Add atom to Strand (also adds the atom to the monomer).
		       oStrand != null)	{
			oStrand.addAtom(oAtom, oMonomer);	// Same problem as above: better to throw nullpointer exception?
			if (!strands.containsKey(oStrand.getStrandName())) {
				strands.put(oStrand.getStrandName(), oStrand);
			}
		}
		/* The reasoning above is: 
		 * All Monomers have to belong to a Strand and all atoms belonging to strands have to belong to a Monomer =>
		 * ? oMonomer != null and oStrand != null, oAtom is added to BioPolymer and to oMonomer in oStrand
		 * ? oMonomer == null and oStrand != null, oAtom is added to BioPolymer and default Monomer in oStrand
		 * ? oMonomer != null and oStrand == null, oAtom is added to BioPolymer, but not to a Monomer or Strand (especially good to maybe throw exception in this case)
		 * ? oMonomer == null and oStrand == null, oAtom is added to BioPolymer, but not to a Monomer or Strand
		 * */
	}
	
	/**
	 * Returns the number of monomers present in BioPolymer.
	 *
	 * @return number of monomers
	 */
	public int getMonomerCount() {
		Iterator<String> keys = strands.keySet().iterator();
		int number = 0;
		
		while(keys.hasNext())	{
			Strand tmp = (Strand)strands.get(keys.next());	// Cast exception?!
			number += (tmp.getMonomers()).size() - 1;
		}
		return number;
	}
	
	/**
	 * Retrieves a Monomer object by specifying its name. [You have to specify the strand to enable
	 * monomers with the same name in different strands. There is at least one such case: every
	 * strand contains a monomer called "".]
	 *
	 * @param monName  The name of the monomer to look for
	 * @return The Monomer object which was asked for
	 *
	 */
	public IMonomer getMonomer(String monName, String strandName) {
	    Strand strand = (Strand)strands.get(strandName); 
	    
	    if(strand != null)	{
	        return (Monomer)strand.getMonomer(monName);
	    }
	    else	{
	        return null;
	    } 
	}
	
	/*	Could look like this if you ensured individual name giving for ALL monomers:
	 * 	
	 public Monomer getMonomer(String cName) {
	 Enumeration keys = strands.keys();
	 Monomer oMonomer = null;
	 
	 while(keys.hasMoreElements())	{
	 
	 if(((Strand)strands.get(keys.nextElement())).getMonomers().containsKey(cName))	{
	 Strand oStrand = (Strand)strands.get(keys.nextElement());
	 oMonomer = oStrand.getMonomer(cName);
	 break;
	 }
	 }
	 return oMonomer;
	 }
	 */	
	
	/**
	 * Returns a collection of the names of all <code>Monomer</code>s in this
	 * BioPolymer.
	 *
	 * @return a <code>Collection</code> of all the monomer names.
	 */
	public Collection<String> getMonomerNames() {
		Iterator<String> keys = strands.keySet().iterator();
		Map<String, IMonomer> monomers = new Hashtable<String, IMonomer>();
		
		while(keys.hasNext())	{
			Strand oStrand = (Strand)strands.get(keys.next());
			monomers.putAll(oStrand.getMonomers());
		}		
		return monomers.keySet();
	}
	
	/**
	 *
	 * Returns the number of strands present in the BioPolymer.
	 *
	 * @return number of strands
	 *
	 */
	public int getStrandCount() {
		return strands.size();
	}
	
	/**
	 *
	 * Retrieves a Monomer object by specifying its name.
	 *
	 * @param cName  The name of the monomer to look for
	 * @return The Monomer object which was asked for
	 *
	 */
	public IStrand getStrand(String cName) {
		return (Strand)strands.get(cName);
	}
	
	/**
	 * Returns a collection of the names of all <code>Strand</code>s in this
	 * BioPolymer.
	 *
	 * @return a <code>Collection</code> of all the strand names.
	 */
	public Collection<String> getStrandNames() {
		return strands.keySet();
	}
	
	/**
	 * Removes a particular strand, specified by its name.
	 * 
	 * @param name name of the strand to remove
	 */
	public void removeStrand(String name)	{
		if (strands.containsKey(name))	{
			Strand strand = (Strand)strands.get(name);
			this.remove(strand);
			strands.remove(name);
		}
	}
	
	/**
	 * @return hashtable containing the monomers in the strand.
	 */
	public Map<String, IStrand> getStrands()	{
		return strands;
	}
	
	public String toString() {
        StringBuffer stringContent = new StringBuffer();
        stringContent.append("BioPolymer(");
        stringContent.append(this.hashCode()).append(", ");
        stringContent.append(super.toString());
        stringContent.append(')');
        return stringContent.toString();
    }

    public Object clone() throws CloneNotSupportedException {
    	BioPolymer clone = (BioPolymer)super.clone();
        clone.strands.clear();
        for (Iterator<String> strands = clone.getStrandNames().iterator(); strands.hasNext();) {
            Strand strand = (Strand)clone.getStrand(strands.next().toString()).clone();
            for (Iterator<String> iter = strand.getMonomerNames().iterator(); iter.hasNext();) {
            	IMonomer monomer = strand.getMonomer(iter.next().toString());
            	Iterator<IAtom> atoms = monomer.atoms().iterator();
            	while (atoms.hasNext()) {
                    clone.addAtom((IAtom)atoms.next(), monomer, strand);
                } 
            }
        }
        return clone;
    }
}
