/*******************************************************************************
 * 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.mapping;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;

import edu.duke.donaldLab.share.nmr.DistanceRestraint;
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.AtomAddressReadable;
import edu.duke.donaldLab.share.protein.Residue;
import edu.duke.donaldLab.share.protein.Subunit;
import edu.duke.donaldLab.share.pseudoatoms.PseudoatomBuilder;
import edu.duke.donaldLab.share.pseudoatoms.Pseudoatoms;

public class AddressMapper
{
	/**************************
	 *   Static Methods
	 **************************/
	
	public static Integer mapSubunitNameToId( Protein protein, char subunitName )
	{
		return protein.getSubunitId( subunitName );
	}
	
	public static Character mapSubunitIdToName( Protein protein, int subunitId )
	{
		Subunit subunit = protein.getSubunits().get( subunitId );
		if( subunit == null )
		{
			return null;
		}
		return subunit.getName();
	}
	
	public static Integer mapResidueNumberToId( Protein protein, int subunitId, int residueNumber )
	{
		Subunit subunit = protein.getSubunit( subunitId );
		if( subunit == null )
		{
			return null;
		}
		return mapResidueNumberToId( subunit, residueNumber );
	}
	
	public static Integer mapResidueNumberToId( Subunit subunit, int residueNumber )
	{
		return subunit.getResidueId( residueNumber );
	}
	
	public static Integer mapResidueIdToNumber( Protein protein, int subunitId, int residueId )
	{
		Subunit subunit = protein.getSubunit( subunitId );
		if( subunit == null )
		{
			return null;
		}
		return mapResidueIdToNumber( subunit, residueId );
	}
	
	public static Integer mapResidueIdToNumber( Subunit subunit, int residueId )
	{
		Residue residue = subunit.getResidue( residueId );
		if( residue == null )
		{
			return null;
		}
		return residue.getNumber();
	}
	
	public static Integer mapAtomNameToId( Protein protein, int subunitId, int residueId, String atomName )
	{
		Subunit subunit = protein.getSubunit( subunitId );
		if( subunit == null )
		{
			return null;
		}
		return mapAtomNameToId( subunit, residueId, atomName );
	}
	
	public static Integer mapAtomNameToId( Subunit subunit, int residueId, String atomName )
	{
		// get the residue
		Residue residue = subunit.getResidue( residueId );
		if( residue == null )
		{
			return null;
		}
		
		// do a sequential search through the atoms of the residue to find the atom
		for( Atom atom : residue.getAtoms() )
		{
			if( atom.getName().equalsIgnoreCase( atomName ) )
			{
				return atom.getId();
			}
		}
		
		return null;
	}
	
	public static ArrayList<Integer> mapAmbiguousAtomNameToIds( Protein protein, int subunitId, int residueId, String atomName )
	{
		return mapAmbiguousAtomNameToIds( protein.getSubunit( subunitId ), residueId, atomName );
	}
	
	public static ArrayList<Integer> mapAmbiguousAtomNameToIds( Subunit subunit, int residueId, String atomName )
	{
		// get the residue
		if( residueId < 0 || residueId >= subunit.getResidues().size() )
		{
			return null;
		}
		Residue residue = subunit.getResidues().get( residueId );
		
		String pattern = "^" + atomName.replaceAll( "[#\\*]", "\\\\d+" ) + "$";
		Pattern regex = Pattern.compile( pattern, Pattern.CASE_INSENSITIVE );
		
		// do a sequential search through the atoms of the residue to find the matching atoms
		ArrayList<Integer> atomIds = new ArrayList<Integer>();
		for( Atom atom : residue.getAtoms() )
		{
			// transform the atom name into a regex
			if( regex.matcher( atom.getName() ).matches() )
			{
				atomIds.add( atom.getId() );
			}
		}
		
		return atomIds;
	}
	
	public static String mapAtomIdToName( Protein protein, AtomAddressInternal address )
	{
		Atom atom = protein.getAtom( address );
		if( atom == null )
		{
			return null;
		}
		return atom.getName();
	}
	
	public static String mapAtomIdToName( Protein protein, int subunitId, int residueId, int atomId )
	{
		Atom atom = protein.getAtom( subunitId, residueId, atomId );
		if( atom == null )
		{
			return null;
		}
		return atom.getName();
	}
	
	public static String mapAtomIdToName( Subunit subunit, AtomAddressInternal address )
	{
		Atom atom = subunit.getAtom( address );
		if( atom == null )
		{
			return null;
		}
		return atom.getName();
	}
	
	public static ArrayList<AtomAddressInternal> mapAddress( Protein protein, AtomAddressReadable readableAddress )
	{
		Integer subunitId = mapSubunitNameToId( protein, readableAddress.getSubunitName() );
		if( subunitId == null )
		{
			return null;
		}
		return mapAddress( protein.getSubunit( subunitId ), readableAddress );
	}
	
	public static ArrayList<AtomAddressInternal> mapAddress( Subunit subunit, AtomAddressReadable readableAddress )
	{
		// just in case...
		if( subunit == null )
		{
			return new ArrayList<AtomAddressInternal>();
		}
		
		Integer residueId = mapResidueNumberToId( subunit, readableAddress.getResidueNumber() );
		if( residueId == null )
		{
			return new ArrayList<AtomAddressInternal>();
		}
		
		// get the atom IDs
		ArrayList<Integer> atomIds = mapAmbiguousAtomNameToIds( subunit, residueId, readableAddress.getAtomName() );
		if( atomIds == null )
		{
			return new ArrayList<AtomAddressInternal>();
		}
		
		// build the list of addresses
		ArrayList<AtomAddressInternal> addresses = new ArrayList<AtomAddressInternal>();
		for( Integer atomId : atomIds )
		{
			addresses.add( new AtomAddressInternal( subunit.getId(), residueId, atomId ) );
		}
		
		return addresses;
	}
	
	public static AtomAddressInternal mapAddressToResidue( Protein protein, AtomAddressReadable readableAddress )
	{
		Integer subunitId = mapSubunitNameToId( protein, readableAddress.getSubunitName() );
		if( subunitId == null )
		{
			return null;
		}
		Integer residueId = mapResidueNumberToId( protein, subunitId, readableAddress.getResidueNumber() );
		if( residueId == null )
		{
			return null;
		}
		
		return new AtomAddressInternal( subunitId, residueId, -1 );
	}
	
	public static AtomAddressReadable mapAddress( Protein protein, AtomAddressInternal address )
	{
		return new AtomAddressReadable(
			mapSubunitIdToName( protein, address.getSubunitId() ),
			mapResidueIdToNumber( protein, address.getSubunitId(), address.getResidueId() ),
			mapAtomIdToName( protein, address )
		);
	}
	
	public static AtomAddressReadable mapAddress( Subunit subunit, AtomAddressInternal address )
	{
		return new AtomAddressReadable(
			subunit.getName(),
			mapResidueIdToNumber( subunit, address.getResidueId() ),
			mapAtomIdToName( subunit, address )
		);
	}
	
	public static TreeSet<AtomAddressInternal> mapReadableAtomAddresses( Set<AtomAddressReadable> ins, Protein protein )
	{
		TreeSet<AtomAddressInternal> outs = new TreeSet<AtomAddressInternal>();
		for( AtomAddressReadable in : ins )
		{
			outs.addAll( mapAddress( protein, in ) );
		}
		return outs;
	}
	
	public static TreeSet<AtomAddressReadable> mapAtomAddresses( Set<AtomAddressInternal> ins, Protein protein )
	{
		TreeSet<AtomAddressReadable> outs = new TreeSet<AtomAddressReadable>();
		for( AtomAddressInternal in : ins )
		{
			outs.add( mapAddress( protein, in ) );
		}
		return outs;
	}
	
	public static void mapPseudoaomNamesToMasks( Protein protein, List<DistanceRestraint<AtomAddressReadable>> restraints )
	{
		for( DistanceRestraint<AtomAddressReadable> restraint : restraints )
		{
			mapPseudoaomNamesToMasks( protein, restraint );
		}
	}
	
	public static void mapPseudoaomNamesToMasks( Protein protein, DistanceRestraint<AtomAddressReadable> restraint )
	{
		mapPseudoaomNamesToMasks( protein, restraint.getLefts() );
		mapPseudoaomNamesToMasks( protein, restraint.getRights() );
	}
	
	public static void mapPseudoaomNamesToMasks( Protein protein, Set<AtomAddressReadable> addresses )
	{
		Pseudoatoms pseudoatoms = PseudoatomBuilder.getPseudoatoms();		
		
		for( AtomAddressReadable address : addresses )
		{
			// get the amino acid
			AminoAcid aminoAcid = protein.getResidue( mapAddressToResidue( protein, address ) ).getAminoAcid();
			
			// convert the names
			for( String pseudoatomName : pseudoatoms.getPseudoatomNames( aminoAcid ) )
			{
				if( address.getAtomName().equalsIgnoreCase( pseudoatomName ) )
				{
					String mask = pseudoatoms.getMask( aminoAcid, pseudoatomName );
					if( mask != null )
					{
						address.setAtomName( mask );
					}
				}
			}
		}
	}
}
