/*
 *                    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.gui.sequence.tracklayout.SimpleTrackLayout;
import org.biojava.bio.gui.sequence.tracklayout.TrackLayout;
import org.biojava.bio.seq.*;
import org.biojava.bio.symbol.*;
import org.biojava.utils.*;

import java.awt.*;

import java.util.*;
import java.util.List;

import javax.swing.*;


/**
 * Handles multiple SequencePanels and Ranges so that a Sequence can be wrapped over more than one
 * line on screen. This is particularly useful for viewing Protein sequences that would be viewed
 * at a single residue resolution.
 * 
 * The interface is very similar to that of the SequencePanels that it wraps
 *
 * @author Mark Southern
 * @see SequencePanel
 * @since 1.5
 */
public class SequencePanelWrapper extends JPanel {

    private RangeLocation range;
    private Sequence sequence;
    private RangeLocation[] ranges = new RangeLocation[ 0 ];
    private SequencePanel[] seqPanels = new SequencePanel[ 0 ];
    private SequenceRenderer renderer;
    private double scale = 14.0;
    private java.awt.RenderingHints hints;
    private int direction = SequencePanel.HORIZONTAL;
    private TrackLayout trackLayout = new SimpleTrackLayout();
    private List viewerListeners = new ArrayList();
    private List motionListeners = new ArrayList();

    /**
     * Creates a new instance of WrappedSequencePanel
     */
    public SequencePanelWrapper() {
        initComponents();
    }

    protected void initComponents() {
        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
        setBackground(Color.WHITE);
    }

    /*
     * Sets the track (line) layout strategy. Layouts include a simple wrapping at a given 
     * number of residues and user-defined layouts that can contain arbitrary length rows.
     */
    public void setTrackLayout(TrackLayout tl) {
        this.trackLayout = tl;
        trackLayout.setSequence(getSequence());
        trackLayout.setRange(getRange());

        if (isActive()) {
            refreshSequencePanels();
        }
    }

    public TrackLayout getTrackLayout() {
        return this.trackLayout;
    }

    protected boolean isActive() {
        return (sequence != null) && (renderer != null) && (range != null);
    }

    public void setScale(double scale) {
        this.scale = scale;

        for (int i = 0; i < seqPanels.length; i++) {
            seqPanels[ i ].setScale(scale);
        }
    }

    public double getScale() {
        return scale;
    }

    public void setDirection(int direction) {
        this.direction = direction;

        for (int i = 0; i < seqPanels.length; i++) {
            seqPanels[ i ].setDirection(direction);
        }

        if (isActive()) {
            refreshSequencePanels();
        }
    }

    public int getDirection() {
        return direction;
    }

    /*
     * a convenience method. The wrap is passed through to the TrackLayout implementation.
     */
    public synchronized void setWrap(int w) {
        trackLayout.setWrap(w);

        if (! isActive()) {
            return;
        }

        refreshSequencePanels();
    }

    public int getWrap() {
        return trackLayout.getWrap();
    }

    public void setRenderingHints(java.awt.RenderingHints hints) {
        this.hints = hints;

        for (int i = 0; i < seqPanels.length; i++) {
            seqPanels[ i ].setRenderingHints(hints);
        }
    }

    public java.awt.RenderingHints getRenderingHints() {
        return hints;
    }

    public void setRenderer(SequenceRenderer renderer) {
        this.renderer = renderer;

        for (int i = 0; i < seqPanels.length; i++) {
            try {
                seqPanels[ i ].setRenderer(renderer);
            } catch (ChangeVetoException e) {
                // should never get here
                e.printStackTrace();
            }
        }
    }

    public SequenceRenderer getRenderer() {
        return renderer;
    }

    public void setSequence(Sequence seq) {
        if (seq == null) {
            removeSeqPanels();

            return;
        }

        trackLayout.setSequence(sequence = seq);
        trackLayout.setRange(range = new RangeLocation(1, seq.length()));
        refreshSequencePanels();
    }

    public Sequence getSequence() {
        return sequence;
    }

    public void setRange(RangeLocation loc) {
        trackLayout.setRange(range = loc);
        refreshSequencePanels();
    }

    public RangeLocation getRange() {
        return range;
    }

    private void removeSeqPanels() {
        for (int i = 0; i < seqPanels.length; i++) {
            remove(seqPanels[ i ]);
        }

        setSize(0, 0); // make sure view is refreshed properly
        setLayout(null);
        revalidate();
    }

    public void resizeAndValidate() {
        for (int i = 0; i < seqPanels.length; i++) {
            seqPanels[ i ].resizeAndValidate();
        }
    }

    protected void refreshSequencePanels() {
        removeSeqPanels();

        RangeLocation[] ranges = trackLayout.getRanges();
        seqPanels = new SequencePanel[ ranges.length ];

        if (getDirection() == SequencePanel.HORIZONTAL) {
            setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
        } else {
            setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
        }

        for (int i = 0; i < ranges.length; i++) {
            //logger.debug("Setting sequence panel " + i);
            seqPanels[ i ] = new SequencePanel();
            seqPanels[ i ].setFont(getFont());
            seqPanels[ i ].setAlignmentX(LEFT_ALIGNMENT);
            seqPanels[ i ].setAlignmentY(TOP_ALIGNMENT);
            seqPanels[ i ].setSequence(getSequence());

            //seqPanels[i].setRange( ranges[i] );
            // bug in biojava-1.4pre1 - add +1 to max range
            seqPanels[ i ].setRange(new RangeLocation(ranges[ i ].getMin(), ranges[ i ].getMax() +
                    1
                )
            );
            seqPanels[ i ].setDirection(getDirection());
            seqPanels[ i ].setScale(getScale());

            for (Iterator it = viewerListeners.iterator(); it.hasNext();) {
                seqPanels[ i ].addSequenceViewerListener(( SequenceViewerListener ) it.next());
            }

            for (Iterator jt = motionListeners.iterator(); jt.hasNext();) {
                seqPanels[ i ].addSequenceViewerMotionListener(( SequenceViewerMotionListener ) jt.next());
            }

            add(seqPanels[ i ]);
        }

        setRenderer(getRenderer());
    }

    public void addSequenceViewerListener(SequenceViewerListener l) {
        viewerListeners.add(l);

        for (int i = 0; i < seqPanels.length; i++) {
            seqPanels[ i ].addSequenceViewerListener(l);
        }
    }

    public void removeSequenceViewerListener(SequenceViewerListener l) {
        viewerListeners.remove(l);

        for (int i = 0; i < seqPanels.length; i++) {
            seqPanels[ i ].removeSequenceViewerListener(l);
        }
    }

    public void addSequenceViewerMotionListener(SequenceViewerMotionListener l) {
        motionListeners.add(l);

        for (int i = 0; i < seqPanels.length; i++) {
            seqPanels[ i ].addSequenceViewerMotionListener(l);
        }
    }

    public void removeSequenceViewerMotionListener(SequenceViewerMotionListener l) {
        motionListeners.remove(l);

        for (int i = 0; i < seqPanels.length; i++) {
            seqPanels[ i ].removeSequenceViewerMotionListener(l);
        }
    }

    public void setFont(Font f) {
        try {
            super.setFont(f);

            for (int i = 0; i < seqPanels.length; i++) {
                seqPanels[ i ].setFont(f);
                seqPanels[ i ].resizeAndValidate();
            }
        } catch (NullPointerException e) {
        }
    }

    public void paint(Graphics g) {
        // some wierd sequence graphics exception is going on in biojava
        try {
            super.paint(g);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("Caught sequence graphics exception in paint() " + e.toString());
            repaint();
        }
    }

    public void paintComponent(Graphics g) {
        // some wierd sequence graphics exception is going on in biojava-1.3
        try {
            super.paintComponent(g);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("Caught sequence graphics exception in paintComponent() " + e.toString());
            repaint();
        }
    }
} // class
