/* 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
*/
 
/* 
 * This software was written by Phillip Lord (p.lord@hgmp.mrc.ac.uk)
 * whilst at the University of Manchester as a Pfizer post-doctoral 
 * Research Fellow. 
 *
 * The initial code base is copyright by Pfizer, or the University
 * of Manchester. Modifications to the initial code base are copyright
 * of their respective authors, or their employers as appropriate. 
 * Authorship of the modifications may be determined from the ChangeLog
 * placed at the end of this file
 */

package uk.ac.man.bioinf.apps.cinema.motif; // Package name inserted by JPack
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import uk.ac.man.bioinf.gui.viewer.AlignmentSelectionModel;
import uk.ac.man.bioinf.gui.viewer.NullAlignmentSelectionModel;
import uk.ac.man.bioinf.gui.viewer.SingleAlignmentSelectionModel;
import uk.ac.man.bioinf.gui.viewer.event.AlignmentSelectionEvent;
import uk.ac.man.bioinf.gui.viewer.event.AlignmentSelectionListener;
import uk.ac.man.bioinf.gui.viewer.event.AlignmentSelectionListenerSupport;
import uk.ac.man.bioinf.sequence.geom.SequenceAlignmentPoint;
import uk.ac.man.bioinf.sequence.geom.SequenceAlignmentRectangle;
import uk.ac.man.bioinf.util.Direction;
import java.util.Comparator;
import java.util.Collections;


/**
 * NamedAlignmentSelectionModel.java
 *
 * Stores multiple selections each one with a name. 
 *
 * Created: Wed Dec 27 15:20:30 2000
 *
 * @author Phillip Lord
 * @version $Id: NamedAlignmentSelectionModel.java,v 1.4 2001/04/11 17:04:42 lord Exp $
 */

public class NamedAlignmentSelectionModel 
  implements AlignmentSelectionModel, AlignmentSelectionListener
{
  // together these form a Map, of names to list
  private List selectionInfo = new ArrayList();
  
  private boolean isSelecting = false;
  private AlignmentSelectionListenerSupport listenerList = new AlignmentSelectionListenerSupport();
  
  private Comparator selectionComparator;
  
  public void sortMotifs()
  {
    // we need a comparator for this sort
    if( selectionComparator == null ){
      selectionComparator = new Comparator(){
          public int compare( Object obj1, Object obj2 )
          {
            SelectionInfo info1 = (SelectionInfo)obj1;
            SelectionInfo info2 = (SelectionInfo)obj2;
            
            SequenceAlignmentRectangle rect1 = info1.rect;
            SequenceAlignmentRectangle rect2 = info2.rect;
            return rect1.compareTo( rect2 );
          }
        };
    }
    
    // now do the sort
    Collections.sort(selectionInfo, selectionComparator );
    
    // and rename all selections
    currentSelectionNumber = 1;
    for( int i = 0; i < selectionInfo.size(); i++ ){
      ((SelectionInfo)selectionInfo.get( i )).name
        = "" + currentSelectionNumber++;
    }
  }
  

  public Iterator getSelectionNameIterator()
  {
    return new Iterator(){
        private Iterator iter = selectionInfo.iterator();
        public boolean hasNext()
        {
          return iter.hasNext();
        }
        
        public Object next()
        {
          return ((SelectionInfo)iter.next()).name;
        }
        
        public void remove()
        {
          iter.remove();
        }
      };
  }

  public String getCurrentSelectionName()
  {
    return ((SelectionInfo)selectionInfo.get( selectionInfo.size() - 1 )).name;
  }
  
  private int getSelectionNameIndex( String name )
  { 
    for( int i = 0; i < selectionInfo.size(); i++ )
      if ( name.equals( ((SelectionInfo)selectionInfo.get( i )).name ) ){
        return i;
      }
    return -1;
  }
  
    
  public SequenceAlignmentRectangle getSelectionForName( String name )
  {
    int index = getSelectionNameIndex( name );

    if( index == -1 ) return null;
    
    return ((SelectionInfo)selectionInfo.get( index ) ).rect;
  }
 
  public void setNameForSelection( String oldName, String newName )
  {
    int index = getSelectionNameIndex( oldName );
    
    if( index == -1 ) throw new NoSuchElementException( "There is no such selection " );
    
    if( getSelectionNameIndex( newName ) != -1 )
      throw new IllegalArgumentException( "Names of selection must be unique" );
    
    ((SelectionInfo)selectionInfo.get( index )).name = newName;
  }

  public void clearAllSelections()
  {
    while( selectionInfo.size() != 0 ){
      clearSelection( (SelectionInfo)selectionInfo.get( 0 ) );
    }
  }

  public void clearSelection( String name )
  {
    int index = getSelectionNameIndex( name );
    clearSelection( index );
  }
  
  
  private void clearSelection( SelectionInfo info )
  {
    // find and remove the selection
    clearSelection( selectionInfo.indexOf( info ), info );
  }

  private void clearSelection( int index )
  {
    clearSelection( index, (SelectionInfo)selectionInfo.get( index ) );
  }
  
  
  private void clearSelection( int index, SelectionInfo info )
  {
    if( index == -1 ) throw new NoSuchElementException( "There is no such selection " );
    
    selectionInfo.remove( index );
    
    // signal the event
    listenerList.fireAlignmentSelectionEvent
      ( new AlignmentSelectionEvent
        ( this, info.rect, false ) );
  }
  
  // extend or shrink the selections programmatically
  public void resizeSelection( String selection, Direction dir, int size )
  {
    int index = getSelectionNameIndex( selection );
    
    if( index == -1 ) throw new NoSuchElementException( "There is no such selection " );
    
    // if we are increasing the selection programmatically the
    // selection itself must be over. So the first thing that we want
    // to do make sure that the start is at top left, and stop is at
    // bottom right, as we don't care where the start and stop are any
    // more. 
    
    SelectionInfo info = (SelectionInfo)selectionInfo.get( index );
    
    SequenceAlignmentRectangle rect = info.rect;
    SequenceAlignmentPoint start = info.start;
    SequenceAlignmentPoint stop  = info.stop;
    
    start.setLocation( rect.getLocation() );
    stop.setLocation( rect.getLocation().getX() + rect.getWidth() - 1, 
                      rect.getLocation().getY() + rect.getHeight() - 1);
    
    // now we can move the start or stop appropriately.
    if( dir == Direction.NORTH ){
      start.setLocation( start.getX(), start.getY() - size );
    }
    else if( dir == Direction.EAST ){
      stop.setLocation( stop.getX() + size, stop.getY() );
    }
    else if( dir == Direction.SOUTH ){
      stop.setLocation( stop.getX(), stop.getY() + size );
    }
    else if( dir == Direction.WEST ){
      start.setLocation( start.getX() - size, start.getY() );
    }
    
    recalcSelectionRectangle( rect, start, stop );

    // and signal an event
    listenerList.fireAlignmentSelectionEvent
      ( new AlignmentSelectionEvent( this, rect, false ) );
  }
  
  
  // implementation of AlignmentSelectionModel
  public boolean isSelecting()
  {
    return isSelecting;
  }

  public SequenceAlignmentRectangle getCurrentSelection()
  {
    return ((SelectionInfo)selectionInfo.get( selectionInfo.size() - 1 )).rect;
  }
  
  public int getNumberSelections()
  {
    return selectionInfo.size();
  }
  
  public SequenceAlignmentRectangle getSelectionAt( int index )
    throws IndexOutOfBoundsException
  {
    return ((SelectionInfo)selectionInfo.get( index )).rect;
  }
  
  public boolean isPointSelected( SequenceAlignmentPoint point )
  {
    for( int i = 0; i < selectionInfo.size(); i++ ){
      if( ((SelectionInfo)selectionInfo.get( i )).rect.contains( point ) ){
        return true;
      }
    }
    return false;
  }
  
  public void clearSelection()
  {
    // we want to ignore this. 
  }
  
  private List rectAtPointList;
  public SequenceAlignmentRectangle[] getRectanglesAtPoint( SequenceAlignmentPoint point )
  {
    // have a cache list here, to save on object creation. 
    if( rectAtPointList == null ){
      rectAtPointList = new ArrayList();
    }
    
    // ask all of the SingleSelectionModels for their rectangles. 
    for( int i = 0; i < selectionInfo.size(); i++ ){
      SequenceAlignmentRectangle rect = ((SelectionInfo)selectionInfo.get( i )).rect;
      if( rect.contains( point ) ){
        rectAtPointList.add( rect );
      }
    }
    
    // create a new array to store the results, and populate it
    SequenceAlignmentRectangle[] retn = new SequenceAlignmentRectangle
      [ rectAtPointList.size() ];
    rectAtPointList.toArray( retn );
    
    // and clear the cache list to allow GC
    rectAtPointList.clear();
    
    return retn;
  }
  
  public SequenceAlignmentRectangle getRectangleAtPoint( SequenceAlignmentPoint point )
  {
    // ask all of the SingleSelectionModels for their rectangles. 
    for( int i = 0; i < selectionInfo.size(); i++ ){
      SequenceAlignmentRectangle rect = ((SelectionInfo)selectionInfo.get( i )).rect;
      if( rect.contains( point ) ){
        return rect;
      }
    }
    return null;
  }
  

  // biologists start at 1
  private int currentSelectionNumber = 1;
  
  private SequenceAlignmentRectangle currentRect;
  private SequenceAlignmentPoint currentStart;
  private SequenceAlignmentPoint currentStop;
  
  private void recalcSelectionRectangle()
  {
    recalcSelectionRectangle( currentRect, currentStart, currentStop );
  }
  
  private void recalcSelectionRectangle
    ( SequenceAlignmentRectangle rect,
      SequenceAlignmentPoint start, SequenceAlignmentPoint stop )
  {
    rect.setLocation( start );
    rect.setSize( 1, 1 );
    rect.add( stop );
  }
  
  public void extendSelection( SequenceAlignmentPoint point )
  {
    // if we have no current selection, or the current selection is
    // not selecting anymore, then start a new one...
    if( !isSelecting ){
      // we are now selecting
      isSelecting = true;
      
      // need new start stop and point
      currentRect = new SequenceAlignmentRectangle();
      currentStart = new SequenceAlignmentPoint( point );
      currentStop = new SequenceAlignmentPoint();
      
      // add all of these to the storage structures along with a
      // name. 
      SelectionInfo info = new SelectionInfo();
      selectionInfo.add( info );
      
      info.name = "" + currentSelectionNumber++;
      info.rect = currentRect;
      info.start= currentStart;
      info.stop = currentStop;
    }
    
    // store the point that we have extend to 
    currentStop = point;
    
    // recalc the rectangle
    recalcSelectionRectangle();
    
    // and signal an event
    listenerList.fireAlignmentSelectionEvent
      ( new AlignmentSelectionEvent( this, currentRect, isSelecting ) );
  }
  
  public void stopSelection( SequenceAlignmentPoint point )
  {
    // set this to false so that we start with a new selection next time.
    isSelecting = false;
    
    // store the final point
    currentStop = point;
    recalcSelectionRectangle();
    
    listenerList.fireAlignmentSelectionEvent
      ( new AlignmentSelectionEvent( this, currentRect, isSelecting ) );
  }
  
  public void addAlignmentSelectionListener( AlignmentSelectionListener listener )
  {
    listenerList.addAlignmentSelectionListener( listener );
  }
  
  public void removeAlignmentSelectionListener( AlignmentSelectionListener listener )
  {
    listenerList.removeAlignmentSelectionListener( listener );
  }
  
  // implementation of selection listener interface
  public void valueChanged( AlignmentSelectionEvent event )
  {
    listenerList.fireAlignmentSelectionEvent( event );
  }

  // struct in which to store the data
  class SelectionInfo
  {
    SequenceAlignmentPoint start;
    SequenceAlignmentPoint stop;
    SequenceAlignmentRectangle rect;
    String name;
  }
} // NamedAlignmentSelectionModel



/*
 * ChangeLog
 * $Log: NamedAlignmentSelectionModel.java,v $
 * Revision 1.4  2001/04/11 17:04:42  lord
 * Added License agreements to all code
 *
 * Revision 1.3  2001/01/23 18:02:37  lord
 * Changed internal implementation again. Now has a single list, with an
 * struct to hold the various information. The point is that this makes
 * it a lot easier to sort.
 *
 * Revision 1.2  2001/01/19 19:57:19  lord
 * Totally re-worked. This no longer uses the SingleAlignmentSelection model
 *
 */
