/*******************************************************************************
 * This library 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.
 * 
 * This library 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 library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 * 
 * Contact Info:
 * 	Bruce Donald
 * 	Duke University
 * 	Department of Computer Science
 * 	Levine Science Research Center (LSRC)
 * 	Durham
 * 	NC 27708-0129 
 * 	USA
 * 	brd@cs.duke.edu
 * 
 * Copyright (C) 2011 Jeffrey W. Martin and Bruce R. Donald
 * 
 * <signature of Bruce Donald>, April 2011
 * Bruce Donald, Professor of Computer Science
 ******************************************************************************/


package edu.duke.donaldLab.share.bond;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.TreeMap;

import edu.duke.donaldLab.share.protein.AminoAcid;
import edu.duke.donaldLab.share.protein.Atom;
import edu.duke.donaldLab.share.protein.AtomAddressInternal;
import edu.duke.donaldLab.share.protein.Protein;
import edu.duke.donaldLab.share.protein.Residue;
import edu.duke.donaldLab.share.protein.ResidueType;
import edu.duke.donaldLab.share.protein.Subunit;

public class BondGraphBuilder
{
	/**************************
	 *   Definitions
	 **************************/
	
	private static final String CanonicalBondsPath = "/resources/canonical.bonds";
	private static final String BackboneBondsPath = "/resources/backbone.bonds";
	
	
	/**************************
	 *   Data Members
	 **************************/
	
	private TreeMap<AminoAcid,ArrayList<Bond>> m_canonicalBonds;
	private TreeMap<ResidueType,ArrayList<Bond>> m_backboneBonds;
	
	private static BondGraphBuilder m_instance;
	
	
	/**************************
	 *   Constructors
	 **************************/
	
	static
	{
		m_instance = null;
	}
	
	public BondGraphBuilder( String canonicalBondsPath, String backboneBondsPath )
	throws IOException
	{
		// read in canonical bonds
		InputStream canonicalIn = getClass().getResourceAsStream( canonicalBondsPath );
		assert( canonicalIn != null ) : canonicalBondsPath;
		HashMap<String,ArrayList<Bond>> canonicalBonds = new BondReader().read( canonicalIn );
		m_canonicalBonds = new TreeMap<AminoAcid,ArrayList<Bond>>();
		for( String key : canonicalBonds.keySet() )
		{
			m_canonicalBonds.put(
				AminoAcid.getByAbbreviation( key ),
				canonicalBonds.get( key )
			);
		}
		
		// read in backbone bonds
		InputStream backboneIn = getClass().getResourceAsStream( backboneBondsPath );
		assert( backboneIn != null ) : backboneBondsPath;
		HashMap<String,ArrayList<Bond>> backboneBonds = new BondReader().read( backboneIn );
		m_backboneBonds = new TreeMap<ResidueType,ArrayList<Bond>>();
		for( String key : backboneBonds.keySet() )
		{
			m_backboneBonds.put(
				ResidueType.valueOf( key ),
				backboneBonds.get( key )
			);
		}
	}
	
	
	/**************************
	 *   Static Methods
	 **************************/
	
	public static BondGraphBuilder getInstance( )
	throws IOException
	{
		if( m_instance == null )
		{
			m_instance = new BondGraphBuilder( CanonicalBondsPath, BackboneBondsPath );
		}
		
		return m_instance;
	}
	
	
	/**************************
	 *   Methods
	 **************************/
	
	public ArrayList<BondGraph> build( Protein protein )
	{
		return build( protein, null );
	}
	
	public ArrayList<BondGraph> build( Protein protein, String extraBondsPath )
	{
		ArrayList<BondGraph> bondGraphs = new ArrayList<BondGraph>( protein.getSubunits().size() );
		
		for( Subunit subunit : protein.getSubunits() )
		{
			bondGraphs.add( build( subunit, extraBondsPath ) );
		}
		
		return bondGraphs;
	}
	
	public BondGraph build( Subunit subunit )
	{
		return build( subunit, null );
	}
	
	public BondGraph build( Subunit subunit, String extraBondsPath )
	{
		BondGraph bonds = new BondGraph();
		
		// UNDONE: extra bonds aren't implemented yet
		if( extraBondsPath != null )
		{
			throw new UnsupportedOperationException( "Extra bonds aren't implemented yet!" );
		}
		
		// for each residue...
		for( Residue residue : subunit.getResidues() )
		{
			// get the next residue if needed
			Residue nextResidue = null;
			if( residue.getId() < subunit.getResidues().size() - 1 )
			{
				nextResidue = subunit.getResidues().get( residue.getId() + 1 );
			}
			
			// get a copy of the backbone bonds
			ResidueType residueType = ResidueType.valueOf( subunit, residue );
			ArrayList<Bond> backboneBonds = new ArrayList<Bond>( m_backboneBonds.get( residueType ) );
			
			// TEMP
			//System.out.println( "Processing residue... " + residue.getAminoAcid() + ", " + residueType );
			
			// HACKHACK: Glycine doesn't have CA:HA bonds
			if( residue.getAminoAcid() == AminoAcid.Glycine )
			{
				// TEMP
				//System.out.println( "Removing CA:HA bond..." );
				
				Iterator<Bond> iterBond = backboneBonds.iterator();
				while( iterBond.hasNext() )
				{
					Bond bond = iterBond.next();
					if( bond.getLeftName().equals( "CA" ) && bond.getRightName().equals( "HA" ) )
					{
						iterBond.remove();
						break;
					}
				}
			}
			
			// apply the backbone bonds
			addBonds( backboneBonds, bonds, subunit, residue, nextResidue );
			
			// handle r-group bonds
			ArrayList<Bond> rgroupBonds = m_canonicalBonds.get( residue.getAminoAcid() );
			// Jeff: 01/16/2009 - warn if using undefined r-group bonds
			if( rgroupBonds == null || rgroupBonds.size() == 0 )
			{
				System.err.println( "WARNING: no bonds defined for " + residue.getAminoAcid() );
			}
			addBonds( rgroupBonds, bonds, subunit, residue, nextResidue );
		}
		
		return bonds;
	}
	
	
	/**************************
	 *   Functions
	 **************************/
	
	private void addBonds( ArrayList<Bond> bondList, BondGraph bonds, Subunit subunit, Residue residue, Residue nextResidue )
	{
		if( bondList == null )
		{
			return;
		}
		
		// for each bond...
		for( Bond bond : bondList )
		{
			// copy the bond
			Bond newBond = new Bond( bond );
			
			// get the right residues and redo atom names if needed
			Residue leftResidue = residue;
			if( newBond.getLeftName().charAt( 0 ) == '+' )
			{
				leftResidue = nextResidue;
				newBond.setLeftName( newBond.getLeftName().substring( 1 ) );
			}
			Residue rightResidue = residue;
			if( newBond.getRightName().charAt( 0 ) == '+' )
			{
				rightResidue = nextResidue;
				newBond.setRightName( newBond.getRightName().substring( 1 ) );
			}
			
			// link up the atoms to the bond
			newBond.setLeftAddress( getAddress( subunit, leftResidue, newBond.getLeftName() ) );
			newBond.setRightAddress( getAddress( subunit, rightResidue, newBond.getRightName() ) );
			
			// TEMP
			//System.out.println( leftResidue.getNumber() + ":" + newBond.getLeftName() + " - " + rightResidue.getNumber() + ":" + newBond.getRightName() );
			
			// if either side of the bond is null, that atom doesn't exist in this protein. Just forget about the bond
			if( newBond.getLeftAddress() != null && newBond.getRightAddress() != null )
			{
				bonds.addBond( newBond );
			}
		}
	}
	
	private AtomAddressInternal getAddress( Subunit subunit, Residue residue, String name )
	{
		// try to find the atom
		Atom atom = getAtom( name, residue );
		if( atom == null )
		{
			return null;
		}
		
		return new AtomAddressInternal( subunit.getId(), residue.getId(), atom.getId() );
	}
	
	private Atom getAtom( String name, Residue residue )
	{
		// resides only have 20 or so atoms, so a sequential search is fine here
		for( Atom atom : residue.getAtoms() )
		{
			if( atom.getName().equalsIgnoreCase( name ) )
			{
				return atom;
			}
		}
		
		return null;
	}
}
