/*
 *                    BioJava development code
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  If you do not have a copy,
 * see:
 *
 *      http://www.gnu.org/copyleft/lesser.html
 *
 * Copyright for this code is held jointly by the individual
 * authors.  These should be listed in @author doc comments.
 *
 * For more information on the BioJava project and its aims,
 * or to join the biojava-l mailing list, visit the home page
 * at:
 *
 *      http://www.biojava.org/
 *
 */
package org.biojava.bio.gui.sequence;

import org.biojava.bio.seq.*;
import org.biojava.utils.*;

import java.util.*;


/**
 * A SequenceRenderer that renders a set of Features that match a FeatureFilter in such a way that
 * they do not overlap in the display.
 *
 * @author Mark Southern
 * @since 1.5
 */
public abstract class AbstractPeptideDigestRenderer extends MultiLineRenderer
{
    public static final ChangeType DIGEST = new ChangeType("The peptide digest has changed",
        "org.biojava.bio.gui.sequence.AbstractPeptideDigestRenderer", "DIGEST",
        SequenceRenderContext.REPAINT
    );
    public static final String LANE = "Lane";
    private FeatureSource source;
    private FeatureFilter digestFilter;
    private Map laneMap = new HashMap();
    private int laneCount = 0;
    private int distanceBetween = 0;
    
    public AbstractPeptideDigestRenderer()
    {
    	super();
    }

    public AbstractPeptideDigestRenderer(FeatureSource source)
    {
    	this();
        setFeatureSource(source);
    }

    public AbstractPeptideDigestRenderer(FeatureSource source, FeatureFilter filter)
    {
        this(source);
        setFilter(filter);
    }
    
    public AbstractPeptideDigestRenderer(FeatureSource source, FeatureFilter filter, int distanceBetweenFeatures)
    {
        this(source,filter);
        setDistanceBetweenFeatures(distanceBetweenFeatures);
    }
    
    public void setFeatureSource(FeatureSource source)
    {
    	this.source = source;
    }
    
    public FeatureSource getFeatureSource()
    {
    	return source;
    }

    public FeatureFilter getFilter()
    {
        return digestFilter;
    }

    public void setFilter(FeatureFilter filter)
    {
        digestFilter = filter;
    }

    /*
     * Sets the space between rendered features. Increase for greater visibility. 
     */
    public void setDistanceBetweenFeatures(int d)
    {
        distanceBetween = d;
    }

    public int getDistanceBetweenFeatures()
    {
        return distanceBetween;
    }

    public void sortPeptidesIntoLanes() throws ChangeVetoException
    {
        if (hasListeners(DIGEST))
        {
            ChangeSupport cs = getChangeSupport(SequenceRenderContext.REPAINT);

            synchronized (cs)
            {
                ChangeEvent ce = new ChangeEvent(this, DIGEST);
                cs.firePreChangeEvent(ce);
                doSortPeptides();
                doRefreshRenderers();
                cs.firePostChangeEvent(ce);
            }
        }
        else
        {
            doSortPeptides();
            doRefreshRenderers();
        }
    }

    protected void doRefreshRenderers() throws ChangeVetoException
    {
        super.clearRenderers();
        // resort peptide features into new lanes
        for (int j = 1; j <= laneCount; j++)
        {
            //logger.debug("Adding renderers for lane " + j);
            FeatureFilter ffilt = new FeatureFilter.And(getFilter(), new LaneFeatureFilter(j));
            FeatureBlockSequenceRenderer block = new FeatureBlockSequenceRenderer();
            block.setFeatureRenderer(createRenderer(j));
            PaddingRenderer pad = new PaddingRenderer();
            pad.setPadding(1);
            pad.setRenderer(new FilteringRenderer(block, ffilt, true));
            addRenderer(pad);
        }
    }
    
    /*
     * Method used to return the given FeatureRenderer for a given lane in the display.
     */
    public abstract FeatureRenderer createRenderer(int lane);
    
    protected void doSortPeptides()
    {
        // clear existing stored features
        laneMap.clear();

        //logger.debug("Feature Filter = " + getFilter());
        FeatureHolder fh = source.getFeatureHolder().filter(getFilter());
        List ranges = new LinkedList();

        for (Iterator i = fh.features(); i.hasNext();)
        {
            ranges.add(( Feature ) i.next());
        }

        Collections.sort(ranges, new ByLocationMinMaxFeatureComparator());

        Integer lane_id = new Integer(1);
        int i = 0;
        int pos = 0;

        while (ranges.size() > 0)
        {
            /*//logger.info("i=" + i + "\tpos=" + pos + "\tlane_id=" + lane_id + "\tsize=" +
               ranges.size()
               );
             */
            Feature f = ( Feature ) ranges.get(i);

            //logger.info("\tloc=" + f.getLocation());
            if (f.getLocation().getMin() > pos)
            {
                //logger.info("Adding location " + f.getLocation() + " to lane " + lane_id);
                pos = f.getLocation().getMax() + distanceBetween; // +1 so there are no adjoining peptides on a track

                // we can make distanceBetween 0 if we can differenciate between adjoining Features with the specific SequenceRenderer implementation
                ranges.remove(i);
                laneMap.put(f, lane_id);
            } else
            {
                i++;
            }

            if (i >= ranges.size())
            {
                i = 0;
                pos = 0;
                lane_id = new Integer(lane_id.intValue() + 1);
                //logger.info("RESET:\t" + i + "\t" + pos + "\t" + lane_id + "\t" + ranges.size());
            }
        }
        laneCount = lane_id.intValue();
    }

    private class LaneFeatureFilter implements FeatureFilter
    {
        private int lane;

        public LaneFeatureFilter(int lane)
        {
            this.lane = lane;
        }

        public boolean accept(Feature f)
        {
            Integer i = ( Integer ) laneMap.get(f);
            return (i != null) && (i.intValue() == lane);
        }
    }
}
