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

import java.io.Serializable;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

import edu.duke.donaldLab.share.io.HashCalculator;
import edu.duke.donaldLab.share.io.Transformer;
import edu.duke.donaldLab.share.protein.AtomAddress;

public class DistanceRestraint<T extends AtomAddress<T>> implements Serializable, AssignmentSource<T>, Iterable<Assignment<T>>
{
	/**************************
	 *   Definitions
	 **************************/
	
	private static final long serialVersionUID = -3533389135607795851L;
	
	
	/**************************
	 *   Data Members
	 **************************/
	
	private Set<T> m_lefts;
	private Set<T> m_rights;
	private double m_minDistance;
	private double m_maxDistance;
	
	
	/**************************
	 *   Constructors
	 **************************/
	
	public DistanceRestraint( )
	{
		m_lefts = new TreeSet<T>();
		m_rights = new TreeSet<T>();
		m_minDistance = 0.0f;
		m_maxDistance = 0.0f;
	}
	
	public DistanceRestraint( DistanceRestraint<T> other )
	{
		// deep copy the address lists
		m_lefts = new TreeSet<T>();
		for( T address : other.m_lefts )
		{
			m_lefts.add( address.newCopy() );
		}
		m_rights = new TreeSet<T>();
		for( T address : other.m_rights )
		{
			m_rights.add( address.newCopy() );
		}
		m_minDistance = other.m_minDistance;
		m_maxDistance = other.m_maxDistance;
	}
	
	
	/**************************
	 *   Accessors
	 **************************/
	
	@Override
	public Set<T> getLefts( )
	{
		return m_lefts;
	}
	public void setLefts( Set<T> value )
	{
		m_lefts = value;
	}
	public void setLefts( T ... values )
	{
		m_lefts = Transformer.toTreeSet( values );
	}
	
	@Override
	public Set<T> getRights( )
	{
		return m_rights;
	}
	public void setRights( Set<T> value )
	{
		m_rights = value;
	}
	public void setRights( T ... values )
	{
		m_rights = Transformer.toTreeSet( values );
	}
	
	public double getMinDistance( )
	{
		return m_minDistance;
	}
	public void setMinDistance( double value )
	{
		m_minDistance = value;
	}

	public double getMaxDistance( )
	{
		return m_maxDistance;
	}
	public void setMaxDistance( double value )
	{
		m_maxDistance = value;
	}
	
	
	/**************************
	 *   Methods
	 **************************/
	
	public boolean isAmbiguous( )
	{
		return isAtomAmbiguous() || isSubunitAmbiguous();
	}
	
	public boolean isSubunitAmbiguous( )
	{
		return isSubunitAmbiguous( m_lefts ) || isSubunitAmbiguous( m_rights );
	}
	
	public boolean isAtomAmbiguous( )
	{
		return m_lefts.size() > 1 || m_rights.size() > 1;
	}
	
	public int getNumAssignments( )
	{
		return m_lefts.size() * m_rights.size();
	}
	
	@Override
	public Iterator<Assignment<T>> iterator()
	{
		return new AssignmentIterator<T>( this );
	}
	
	public void swap( )
	{
		Set<T> swap = m_lefts;
		m_lefts = m_rights;
		m_rights = swap;
	}
	
	@Override
	public String toString( )
	{
		StringBuffer buf = new StringBuffer();
		
		buf.append( "[DistanceRestraint] " );
		buf.append( m_minDistance );
		buf.append( "," );
		buf.append( m_maxDistance );
		buf.append( "\t(" );
		renderAddresses( buf, m_lefts );
		buf.append( ")\t(" );
		renderAddresses( buf, m_rights );
		buf.append( ")" );
		
		return buf.toString();
	}
	
	@Override
	public boolean equals( Object other )
	{
		if( other == null )
		{
            return false;
		}
		if( other == this )
		{
			return true;
		}
		if( !( other instanceof DistanceRestraint<?> ) )
		{
			return false;
		}
		
		// UNDONE: there has to be a way to check this cast
		return equals( (DistanceRestraint<T>)other );
	}
	
	public boolean equals( DistanceRestraint<T> other )
	{
		return
			m_minDistance == other.m_minDistance
			&& m_maxDistance == other.m_maxDistance
			&&
			(
				(
					m_lefts.equals( other.m_lefts )
					&& m_rights.equals( other.m_rights )
				)
				||
				(
					m_lefts.equals( other.m_rights )
					&& m_rights.equals( other.m_lefts )
				)
			);
	}
	
	@Override
	public int hashCode( )
	{
		return HashCalculator.combineHashes(
			Double.valueOf( m_minDistance ).hashCode(),
			Double.valueOf( m_maxDistance ).hashCode(),
			HashCalculator.combineHashesCommutative( m_lefts.hashCode(), m_rights.hashCode() )
		);
	}
	
	
	/**************************
	 *   Functions
	 **************************/
	
	private void renderAddresses( StringBuffer buf, Set<T> addresses )
	{
		boolean renderComma = false;
		for( T address : addresses )
		{
			if( renderComma )
			{
				buf.append( "," );
			}
			buf.append( address );
			renderComma = true;
		}
	}
	
	private boolean isSubunitAmbiguous( Set<T> addresses )
	{
		T firstAddress = addresses.iterator().next();
		for( T address : addresses )
		{
			if( !firstAddress.isSameSubunit( address ) )
			{
				return true;
			}
		}
		
		return false;
	}
}
