package uk.ac.cam.ch.wwmm.opsin;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import nu.xom.Element;


/** Holds the Fragments during the construction of the molecule,
 * handles the building of new fragments and handles the creation/deletion of atoms/bonds
 *
 * @author ptc24
 * @author dl387
 *
 */
class FragmentManager {
	/** All of the atom-containing fragments in the molecule */
	private final Set<Fragment> fragPile;
	/** All of the inter-fragment bonds */
	private final Set<Bond> bondPile;
	/** A mapping between fragments and inter fragment bonds */
	private final Map<Fragment,LinkedHashSet<Bond>> fragToInterFragmentBond;
	/** A builder for fragments specified as SMILES */
	private final SMILESFragmentBuilder sBuilder;
	/** A builder for fragments specified as references to a CML data file */
	private final CMLFragmentBuilder cmlBuilder;
	/** A source of unique integers */
	private final IDManager idManager;

	/** Sets up a new Fragment mananger, containing no fragments.
	 *
	 * @param sBuilder A SMILESFragmentBuilder - dependency injection.
	 * @param cmlBuilder A CMLFragmentBuilder - dependency injection.
	 * @param idManager An IDManager.
	 */
	FragmentManager(SMILESFragmentBuilder sBuilder, CMLFragmentBuilder cmlBuilder, IDManager idManager) {
		if (sBuilder == null || cmlBuilder == null || idManager == null ){
			throw new IllegalArgumentException("FragmentManager was parsed a null object in its constructor!");
		}
		this.sBuilder = sBuilder;
		this.cmlBuilder = cmlBuilder;
		this.idManager = idManager;
		fragPile = new LinkedHashSet<Fragment>();
		bondPile = new LinkedHashSet<Bond>();
		fragToInterFragmentBond = new HashMap<Fragment, LinkedHashSet<Bond>>();
	}

	/** Builds a fragment, based on a reference to a CML data file
	 *
	 * @param idStr The name of the fragment in the CML file
	 * @param type The fragment type
	 * @param subType The fragment subType
	 * @return The fragment
	 * @throws StructureBuildingException If the fragment can't be built
	 */
	Fragment buildCML(String idStr, String type, String subType) throws StructureBuildingException {
		Fragment newFrag = cmlBuilder.build(idStr, type, subType, this);
		addFragment(newFrag);
		return newFrag;
	}

	/** Builds a fragment, based on an SMILES string
	 *
	 * @param smiles The fragment to build
	 * @return The built fragment
	 * @throws StructureBuildingException
	 */
	Fragment buildSMILES(String smiles) throws StructureBuildingException {
		return buildSMILES(smiles, "", "");
	}

	/** Builds a fragment, based on an SMILES string
	 *
	 * @param smiles The fragment to build
	 * @param type The fragment type
	 * @param labelMapping How to label the fragment
	 * @return The built fragment
	 * @throws StructureBuildingException
	 */
	Fragment buildSMILES(String smiles, String type, String labelMapping) throws StructureBuildingException {
		return buildSMILES(smiles, type, "", labelMapping);
	}

	/** Builds a fragment, based on an SMILES string
	 *
	 * @param smiles The fragment to build
	 * @param type The fragment type
	 * @param subType The fragment subType
	 * @param labelMapping How to label the fragment
	 * @return The built fragment
	 * @throws StructureBuildingException
	 */
	Fragment buildSMILES(String smiles, String type, String subType, String labelMapping) throws StructureBuildingException {
		Fragment newFrag = sBuilder.build(smiles, type, subType, labelMapping, this);
		addFragment(newFrag);
		return newFrag;
	}

	/**Creates a new fragment, containing all of the atoms and bonds
	 * of all of the other fragments - i.e. the whole molecule. This updates
	 * which fragments the atoms think they are in to the new super fragment
	 * but does not remove change the contents of the original fragments.
	 * Hence the original fragments remain associated with their atoms
	 * Atoms and Bonds are not copied.
	 *
	 * @return The unified fragment
	 * @throws StructureBuildingException 
	 */
	Fragment getUnifiedFragment() throws StructureBuildingException {
		Fragment outFrag = new Fragment();
		List<Fragment> fragments = new ArrayList<Fragment>(fragPile);
		addFragment(outFrag);
		for(Fragment f : fragments) {
			incorporateFragment(f, outFrag);//merge all fragments into one
		}
		return outFrag;
	}

	/** Incorporates a fragment, usually a suffix, into a parent fragment
	 * This does:
	 * Imports all of the atoms and bonds from another fragment into this one.
	 * Also imports outAtoms and functionalAtoms
	 * Reassigns inter fragment bonds of the parent fragment as either intra fragment bonds
	 * of the parent fragment or as inter fragment bonds of the parent fragment
	 *
	 * The original fragment still maintains its original atomList/bondList/interFragmentBondList which is necessary for stereochemistry handling
	 *
	 * @param childFrag The fragment to be incorporated
	 * @param parentFrag The parent fragment
     * @throws StructureBuildingException
	 */
	void incorporateFragment(Fragment childFrag, Fragment parentFrag) throws StructureBuildingException {
		for(Atom atom : childFrag.getAtomList()) {
			parentFrag.addAtom(atom);
		}
		for(Bond bond : childFrag.getBondSet()) {
			parentFrag.addBond(bond);
		}
		parentFrag.addOutAtoms(childFrag.getOutAtoms());
		parentFrag.addFunctionalAtoms(childFrag.getFunctionalAtoms());

		for (Bond bond : fragToInterFragmentBond.get(childFrag)) {//reassign inter fragment bonds of child
			if (bond.getFromAtom().getFrag() ==parentFrag || bond.getToAtom().getFrag() ==parentFrag){
				if (bond.getFromAtom().getFrag() ==parentFrag && bond.getToAtom().getFrag() ==parentFrag){
					//bond is now enclosed within parentFrag so make it an intra fragment bond
					//and remove it from the interfragment list of the parentFrag
					parentFrag.addBond(bond);
					fragToInterFragmentBond.get(parentFrag).remove(bond);
				}
				else{
					//bond was an interfragment bond between the childFrag and another frag
					//It is now between the parentFrag and another frag
					addInterFragmentBond(bond);
				}
			}
		}
		if (!fragPile.remove(childFrag)){
			throw new StructureBuildingException("Fragment not found in fragPile");
		}
	}
	
	/** Incorporates a fragment, usually a suffix, into a parent fragment, creating a bond between them.
	 *
	 * @param childFrag The fragment to be incorporated
	 * @param fromAtom An atom on that fragment
	 * @param parentFrag The parent fragment
	 * @param toAtom An atom on that fragment
	 * @param bondOrder The order of the joining bond
     * @throws StructureBuildingException
	 */
	void incorporateFragment(Fragment childFrag, Atom fromAtom, Fragment parentFrag, Atom toAtom, int bondOrder) throws StructureBuildingException {
		if (!fromAtom.getFrag().equals(childFrag)){
			throw new StructureBuildingException("OPSIN Bug: fromAtom was not associated with childFrag!");
		}
		if (!toAtom.getFrag().equals(parentFrag)){
			throw new StructureBuildingException("OPSIN Bug: toAtom was not associated with parentFrag!");
		}
		incorporateFragment(childFrag, parentFrag);
		createBond(fromAtom, toAtom, bondOrder);
	}

	/** Converts an atom in a fragment to a different atomic symbol described by a SMILES string
	 * Charged atoms can also be specified eg. [NH4+]
	 *
	 * @param a The atom to change to a heteroatom
	 * @param smiles The SMILES for one atom
	 * @throws StructureBuildingException
	 */
	void replaceAtomWithSmiles(Atom a, String smiles) throws StructureBuildingException {
		replaceAtomWithAtom(a, getHeteroatom(smiles), false);
	}

	/**
	 * Converts the smiles for a heteroatom to an atom
	 * @param smiles
	 * @return
	 * @throws StructureBuildingException
	 */
	Atom getHeteroatom(String smiles) throws StructureBuildingException {
		Fragment heteroAtomFrag = sBuilder.build(smiles, this);
		List<Atom> atomList = heteroAtomFrag.getAtomList();
		if (atomList.size()!=1){
			throw new StructureBuildingException("Heteroatom smiles described a fragment with multiple SMILES!");
		}
		return atomList.get(0);
	}
	
	/** Uses the information given in the given heteroatom to change the atomic symbol
	 * and charge of the given atom
	 *
	 * @param a The atom to change to a heteroatom
	 * @param heteroAtom The atom to copy element/charge properties from
	 * @param assignLocant Whether a locant should be assigned to the heteroatom if the locant is not used elsewhere
	 * @throws StructureBuildingException if a charge disagreement occurs
	 */
	void replaceAtomWithAtom(Atom a, Atom heteroAtom, boolean assignLocant) throws StructureBuildingException {
		String elementSymbol =heteroAtom.getElement();
		int replacementCharge =heteroAtom.getCharge();
		if (replacementCharge!=0){
			if (a.getCharge()==0){
				a.addChargeAndProtons(replacementCharge, heteroAtom.getProtonsExplicitlyAddedOrRemoved());
			}
			else if (a.getCharge()==replacementCharge){
				a.setProtonsExplicitlyAddedOrRemoved(heteroAtom.getProtonsExplicitlyAddedOrRemoved());
			}
			else{
				throw new StructureBuildingException("Charge conflict between replacement term and atom to be replaced");
			}
		}
		a.setElement(elementSymbol);
		a.removeElementSymbolLocants();
		if (assignLocant){
			String primes ="";
			while (a.getFrag().getAtomByLocant(elementSymbol+primes)!=null){//if element symbol already assigned, add a prime and try again
				primes+="'";
			}
			a.addLocant(elementSymbol +primes);
		}
	}

	/** Gets an atom, given an id number
	 * Use this if you don't know what fragment the atom is in
	 * @param id The id of the atom
	 * @return The atom, or null if no such atom exists.
	 */
	Atom getAtomByID(int id) {
		for(Fragment f : fragPile) {
			Atom a = f.getAtomByID(id);
			if(a != null) return a;
		}
		return null;
	}

	/** Gets an atom, given an id number, throwing if fails.
	 * Use this if you don't know what fragment the atom is in
	 * @param id The id of the atom
	 * @return The atom
     * @throws StructureBuildingException
	 */
	Atom getAtomByIDOrThrow(int id) throws StructureBuildingException {
		Atom a = getAtomByID(id);
		if(a == null) throw new StructureBuildingException("Couldn't get atom by id");
		return a;
	}

	/**Turns all of the spare valencies in the fragments into double bonds.
	 *
	 * @throws StructureBuildingException
	 */
	void convertSpareValenciesToDoubleBonds() throws StructureBuildingException {
		for(Fragment f : fragPile) {
			FragmentTools.convertSpareValenciesToDoubleBonds(f);
		}
	}

	/**
	 * Checks valencies are all chemically reasonable. An exception is thrown if any are not
	 * @throws StructureBuildingException
	 */
	void checkValencies() throws StructureBuildingException {
		for(Fragment f : fragPile) {
			f.checkValencies();
		}
	}

	Set<Bond> getBondPile() {
		return Collections.unmodifiableSet(bondPile);
	}

	Set<Fragment> getFragPile() {
		return Collections.unmodifiableSet(fragPile);
	}

	/**
	 * Adds a fragment to the fragPile
	 * @param frag
	 */
	private void addFragment(Fragment frag)  {
		fragPile.add(frag);
		fragToInterFragmentBond.put(frag, new LinkedHashSet<Bond>());
	}

	/**
	 * Removes a fragment from the fragPile and inter fragment bonds associated with it from the bondpile/fragToInterFragmentBond.
	 * Throws an exception if fragment wasn't present
	 * @param frag
	 * @throws StructureBuildingException
	 */
	void removeFragment(Fragment frag) throws StructureBuildingException {
		if (!fragPile.remove(frag)){
			throw new StructureBuildingException("Fragment not found in fragPile");
		}
		List<Bond> interFragmentBondsInvolvingFragment = new ArrayList<Bond>(fragToInterFragmentBond.get(frag));
		for (Bond bond : interFragmentBondsInvolvingFragment) {
			if (bond.getFromAtom().getFrag() ==frag){
				fragToInterFragmentBond.get(bond.getToAtom().getFrag()).remove(bond);
			}
			else{
				fragToInterFragmentBond.get(bond.getFromAtom().getFrag()).remove(bond);
			}
			bondPile.remove(bond);
		}
		fragToInterFragmentBond.remove(frag);
	}

	int getOverallCharge() {
		int totalCharge=0;
		for (Fragment frag : fragPile) {
			totalCharge+=frag.getCharge();
		}
		return totalCharge;
	}

	/**
	 * Creates a copy of a fragment by copying data
	 * labels the atoms using new ids from the idManager
	 * @param originalFragment
	 * @return the clone of the fragment
	 * @throws StructureBuildingException
	 */
	Fragment copyFragment(Fragment originalFragment) throws StructureBuildingException {
		return copyAndRelabelFragment(originalFragment, 0);
	}


	/**
	 * Creates a copy of a fragment by copying data
	 * labels the atoms using new ids from the idManager
	 * @param originalFragment
	 * @param primesToAdd: The minimum number of primes to add to the cloned atoms. More primes will be added if necessary to keep the locants unique e.g. N in the presence of N' becomes N'' when this is 1
	 * @return the clone of the fragment
	 * @throws StructureBuildingException
	 */
	Fragment copyAndRelabelFragment(Fragment originalFragment, int primesToAdd) throws StructureBuildingException {
		Fragment newFragment =new Fragment(originalFragment.getType(), originalFragment.getSubType());
		HashMap<Atom, Atom> oldToNewAtomMap = new HashMap<Atom, Atom>();//maps old Atom to new Atom
		List<Atom> atomList =originalFragment.getAtomList();
		List<OutAtom> outAtoms =originalFragment.getOutAtoms();
		List<FunctionalAtom> functionalAtoms =originalFragment.getFunctionalAtoms();
		Atom defaultInAtom =originalFragment.getDefaultInAtom();
		for (Atom atom : atomList) {
			int id = idManager.getNextID();
			ArrayList<String> newLocants = new ArrayList<String>(atom.getLocants());
			if (primesToAdd !=0){
				for (int i = 0; i < newLocants.size(); i++) {
					String currentLocant = newLocants.get(i);
					int currentPrimes = StringTools.countTerminalPrimes(currentLocant);
					String locantSansPrimes = currentLocant.substring(0, currentLocant.length()-currentPrimes);
					int highestNumberOfPrimesWithThisLocant = currentPrimes;
					while (originalFragment.getAtomByLocant(locantSansPrimes + StringTools.multiplyString("'", highestNumberOfPrimesWithThisLocant +1 ))!=null){
						highestNumberOfPrimesWithThisLocant++;
					}
					newLocants.set(i, locantSansPrimes + StringTools.multiplyString("'", ((highestNumberOfPrimesWithThisLocant +1)*primesToAdd) + currentPrimes));
				}
			}
			Atom newAtom =new Atom(id, atom.getElement(), newFragment);
			for (String newLocant : newLocants) {
				newAtom.addLocant(newLocant);
			}
			newAtom.setCharge(atom.getCharge());
			newAtom.setIsotope(atom.getIsotope());
			newAtom.setSpareValency(atom.hasSpareValency());
			newAtom.setProtonsExplicitlyAddedOrRemoved(atom.getProtonsExplicitlyAddedOrRemoved());
			newAtom.setLambdaConventionValency(atom.getLambdaConventionValency());
			//outValency is derived from the outAtoms so is automatically cloned
			newAtom.setAtomIsInACycle(atom.getAtomIsInACycle());
			newAtom.setType(atom.getType());//may be different from fragment type if the original atom was formerly in a suffix
			newAtom.setMinimumValency(atom.getMinimumValency());
			newFragment.addAtom(newAtom);
			oldToNewAtomMap.put(atom, newAtom);
		}
		for (Atom atom : atomList) {
			if (atom.getAtomParity() != null){
				Atom[] oldAtomRefs4 = atom.getAtomParity().getAtomRefs4();
				Atom[] newAtomRefs4 = new Atom[4];
				for (int i = 0; i < oldAtomRefs4.length; i++) {
					Atom oldAtom = oldAtomRefs4[i];
					if (oldAtom.equals(AtomParity.hydrogen)){
						newAtomRefs4[i] = AtomParity.hydrogen;
					}
					else if (oldAtom.equals(AtomParity.deoxyHydrogen)){
						newAtomRefs4[i] = AtomParity.deoxyHydrogen;
					}
					else{
						newAtomRefs4[i] = oldToNewAtomMap.get(oldAtom);
					}
				}
				AtomParity newAtomParity =new AtomParity(newAtomRefs4, atom.getAtomParity().getParity());
				oldToNewAtomMap.get(atom).setAtomParity(newAtomParity);
			}
			if (atom.getProperty(Atom.AMBIGUOUS_ELEMENT_ASSIGNMENT)!=null){
				Set<Atom> oldAtoms = atom.getProperty(Atom.AMBIGUOUS_ELEMENT_ASSIGNMENT);
				Set<Atom> newAtoms = new HashSet<Atom>();
				for (Atom oldAtom : oldAtoms) {
					newAtoms.add(oldToNewAtomMap.get(oldAtom));
				}
				oldToNewAtomMap.get(atom).setProperty(Atom.AMBIGUOUS_ELEMENT_ASSIGNMENT, newAtoms);
			}
			if (atom.getProperty(Atom.SMILES_HYDROGEN_COUNT)!=null){
				oldToNewAtomMap.get(atom).setProperty(Atom.SMILES_HYDROGEN_COUNT, atom.getProperty(Atom.SMILES_HYDROGEN_COUNT));
			}
			if (atom.getProperty(Atom.OXIDATION_NUMBER)!=null){
				oldToNewAtomMap.get(atom).setProperty(Atom.OXIDATION_NUMBER, atom.getProperty(Atom.OXIDATION_NUMBER));
			}
			if (atom.getProperty(Atom.ISALDEHYDE)!=null){
				oldToNewAtomMap.get(atom).setProperty(Atom.ISALDEHYDE, atom.getProperty(Atom.ISALDEHYDE));
			}
		}
        for (OutAtom outAtom : outAtoms) {
            newFragment.addOutAtom(oldToNewAtomMap.get(outAtom.getAtom()), outAtom.getValency(), outAtom.isSetExplicitly());
            if (outAtom.getLocant() !=null){
            	newFragment.getOutAtom(newFragment.getOutAtoms().size() -1).setLocant(outAtom.getLocant() + StringTools.multiplyString("'", primesToAdd) );
            }
        }
		for (FunctionalAtom functionalAtom : functionalAtoms) {
			newFragment.addFunctionalAtom(oldToNewAtomMap.get(functionalAtom.getAtom()));
		}
		newFragment.setDefaultInAtom(oldToNewAtomMap.get(defaultInAtom));
		Set<Bond> bondSet =originalFragment.getBondSet();
		for (Bond bond : bondSet) {
			Bond newBond = createBond(oldToNewAtomMap.get(bond.getFromAtom()), oldToNewAtomMap.get(bond.getToAtom()), bond.getOrder());
			newBond.setSmilesStereochemistry(bond.getSmilesStereochemistry());
			if (bond.getBondStereo() != null){
				Atom[] oldAtomRefs4 = bond.getBondStereo().getAtomRefs4();
				Atom[] newAtomRefs4 = new Atom[4];
				for (int i = 0; i < oldAtomRefs4.length; i++) {
					newAtomRefs4[i] = oldToNewAtomMap.get(oldAtomRefs4[i]);
				}
				newBond.setBondStereoElement(newAtomRefs4, bond.getBondStereo().getBondStereoValue());
			}
		}
		List<Atom> indicatedHydrogenAtoms = originalFragment.getIndicatedHydrogen();
		for (Atom atom : indicatedHydrogenAtoms) {
			newFragment.addIndicatedHydrogen(oldToNewAtomMap.get(atom));
		}
		addFragment(newFragment);
		return newFragment;
	}

	/**
	 * Takes an element and produces a copy of it. Groups and suffixes are copied so that the new element
	 * has its own group and suffix fragments
	 * @param elementToBeCloned
	 * @param state The current buildstate
	 * @return
	 * @throws StructureBuildingException
	 */
	Element cloneElement(BuildState state, Element elementToBeCloned) throws StructureBuildingException {
		return cloneElement(state, elementToBeCloned, 0);
	}

	/**
	 * Takes an element and produces a copy of it. Groups and suffixes are copied so that the new element
	 * has its own group and suffix fragments
	 * @param elementToBeCloned
	 * @param state The current buildstate
	 * @param primesToAdd: The minimum number of primes to add to the cloned atoms. More primes will be added if necessary to keep the locants unique e.g. N in the presence of N' becomes N'' when this is 1
	 * @return
	 * @throws StructureBuildingException
	 */
	Element cloneElement(BuildState state, Element elementToBeCloned, int primesToAdd) throws StructureBuildingException {
		Element clone = new Element(elementToBeCloned);
		List<Element> originalGroups = XOMTools.getDescendantElementsWithTagName(elementToBeCloned, XmlDeclarations.GROUP_EL);
		List<Element> clonedGroups = XOMTools.getDescendantElementsWithTagName(clone,  XmlDeclarations.GROUP_EL);
		HashMap<Fragment,Fragment> oldNewFragmentMapping  =new HashMap<Fragment, Fragment>();
		for (int i = 0; i < originalGroups.size(); i++) {
			Fragment originalFragment =state.xmlFragmentMap.get(originalGroups.get(i));
			Fragment newFragment = copyAndRelabelFragment(originalFragment, primesToAdd);
			oldNewFragmentMapping.put(originalFragment, newFragment);
			state.xmlFragmentMap.put(clonedGroups.get(i), newFragment);
			List<Fragment> originalSuffixes =state.xmlSuffixMap.get(originalGroups.get(i));
			List<Fragment> newSuffixFragments =new ArrayList<Fragment>();
			for (Fragment suffix : originalSuffixes) {
				newSuffixFragments.add(state.fragManager.copyFragment(suffix));
			}
			state.xmlSuffixMap.put(clonedGroups.get(i), newSuffixFragments);
		}
		Set<Bond> interFragmentBondsToClone = new HashSet<Bond>();
		for (Fragment originalFragment : oldNewFragmentMapping.keySet()) {//add inter fragment bonds to cloned fragments
			for (Bond bond : fragToInterFragmentBond.get(originalFragment)) {
				interFragmentBondsToClone.add(bond);
			}
		}
		for (Bond bond : interFragmentBondsToClone) {
			Atom originalFromAtom = bond.getFromAtom();
			Atom originalToAtom = bond.getToAtom();
			Fragment originalFragment1 = originalFromAtom.getFrag();
			Fragment originalFragment2 = originalToAtom.getFrag();
			if (!oldNewFragmentMapping.containsKey(originalFragment1) || (!oldNewFragmentMapping.containsKey(originalFragment2))){
				throw new StructureBuildingException("An element that was a clone contained a bond that went outside the scope of the cloning");
			}
			Fragment newFragment1 = oldNewFragmentMapping.get(originalFragment1);
			Fragment newFragment2 = oldNewFragmentMapping.get(originalFragment2);
			Atom fromAtom = newFragment1.getAtomList().get(originalFragment1.getAtomList().indexOf(originalFromAtom));
			Atom toAtom = newFragment2.getAtomList().get(originalFragment2.getAtomList().indexOf(originalToAtom));
			createBond(fromAtom, toAtom, bond.getOrder());
		}
		return clone;
	}
	
	/**
	 * Takes an atom, removes it and bonds everything that was bonded to it to the replacementAtom with the original bond orders.
	 * Non element symbol locants are copied to the replacement atom
	 * @param atomToBeReplaced
	 * @param replacementAtom
	 */
	void replaceAtomWithAnotherAtomPreservingConnectivity(Atom atomToBeReplaced, Atom replacementAtom) {
		atomToBeReplaced.removeElementSymbolLocants();
		List<String> locants = new ArrayList<String>(atomToBeReplaced.getLocants());
		for (String locant : locants) {
			atomToBeReplaced.removeLocant(locant);
			replacementAtom.addLocant(locant);
		}
		Set<Bond> bonds = atomToBeReplaced.getBonds();
		for (Bond bond : bonds) {
			createBond(replacementAtom, bond.getOtherAtom(atomToBeReplaced), bond.getOrder());
		}
		removeAtomAndAssociatedBonds(atomToBeReplaced);
	}

	/**
	 * Checks if this bond is an inter fragment bond and if it is removes it
	 * @param bond
	 */
	private void removeInterFragmentBondIfPresent(Bond bond) {
		if (bondPile.remove(bond)){
			fragToInterFragmentBond.get(bond.getFromAtom().getFrag()).remove(bond);
			fragToInterFragmentBond.get(bond.getToAtom().getFrag()).remove(bond);
		}
	}
	
	/**
	 * Adds a bond to the inter fragment bond list and fragment to inter-fragment bond mappings
	 * @param bond
	 */
	private void addInterFragmentBond(Bond bond) {
		bondPile.add(bond);
		fragToInterFragmentBond.get(bond.getFromAtom().getFrag()).add(bond);
		fragToInterFragmentBond.get(bond.getToAtom().getFrag()).add(bond);
	}

	/**
	 * Gets a set of the inter fragment bonds a fragment is involved in
	 * @param frag
	 * @return set of inter fragment bonds
	 */
	Set<Bond> getInterFragmentBonds(Fragment frag) {
		return fragToInterFragmentBond.get(frag);
	}

	/**
	 * Create a new Atom of the given element belonging to the given fragment
	 * @param elementSymbol
	 * @param frag
	 * @return Atom
	 * @throws StructureBuildingException
	 */
	Atom createAtom(String elementSymbol, Fragment frag) throws StructureBuildingException {
		Atom a = new Atom(idManager.getNextID(), elementSymbol, frag);
		frag.addAtom(a);
		return a;
	}
	
	/**
	 * Create a new bond between two atoms.
	 * The bond is associated with these atoms.
	 * It is also listed as an inter-fragment bond or associated with a fragment
	 * @param fromAtom
	 * @param toAtom
	 * @param bondOrder
	 * @return Bond
	 */
	Bond createBond(Atom fromAtom, Atom toAtom, int bondOrder) {
		Bond b = new Bond(fromAtom, toAtom, bondOrder);
		fromAtom.addBond(b);
		toAtom.addBond(b);
		if (fromAtom.getFrag() == toAtom.getFrag()){
			fromAtom.getFrag().addBond(b);
		}
		else{
			addInterFragmentBond(b);
		}
		return b;
	}
	
	void removeAtomAndAssociatedBonds(Atom atom){
		ArrayList<Bond> bondsToBeRemoved=new ArrayList<Bond>(atom.getBonds());
		for (Bond bond : bondsToBeRemoved) {
			removeBond(bond);
		}
		atom.getFrag().removeAtom(atom);
	}
	
	void removeBond(Bond bond){
		bond.getFromAtom().getFrag().removeBond(bond);
		bond.getFromAtom().removeBond(bond);
		bond.getToAtom().removeBond(bond);
		removeInterFragmentBondIfPresent(bond);
	}
}