import ca.ualberta.stothard.cgview.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.*;
import java.text.NumberFormat;
import java.util.*;
import java.util.ArrayList;
import javax.swing.*;

public class CGView extends JApplet implements CgviewConstants, ActionListener {

  private JProgressBar progressBar;

  private BufferedImage biFullView;
  private BufferedImage bi;

  private Graphics2D big;

  private boolean giveLoadingMessage = true;
  private boolean redraw = true;
  private boolean firstZoom = true;

  private final double ROTATION_CONSTANT = 10.0d; // larger values give smoother rotation.

  // private double ZOOM_VALUES[] = {1.0d, 5.0d, 10.0d, 20.0d, 30.0d, 40.0d, 50.0d, 60.0d, 70.0d,
  // 80.0d, 90.0d, 100.0d, 200.0d, 300.0d, 400.0d, 500.0d, 600.0d, 700.0d, 800.0d, 900.0d, 1000.0d,
  // 2000.0d, 3000.0d, 4000.0d, 5000.0d, 6000.0d, 7000.0d, 8000.0d, 9000.0d, 10000.0d, 20000.0d,
  // 30000.0d, 40000.0d, 50000.0d, 60000.0d, 70000.0d, 80000.0d, 90000.0d, 100000.0d, 200000.0d,
  // 300000.0d, 400000.0d, 500000.0d, 600000.0d, 700000.0d, 800000.0d, 900000.0d, 1000000.0d,
  // 2000000.0d, 3000000.0d, 4000000.0d, 5000000.0d, 6000000.0d, 7000000.0d, 8000000.0d, 9000000.0d,
  // 10000000.0d};

  // private double ZOOM_VALUES[] = {1.0d, 5.0d, 10.0d, 50.0d, 100.0d, 500.0d, 1000.0d, 5000.0d,
  // 10000.0d, 50000.0d, 100000.0d, 500000.0d, 1000000.0d, 5000000.0d, 10000000.0d};

  private double ZOOM_VALUES[] = {
    1.0d,
    5.0d,
    25.0d,
    125.0d,
    625.0d,
    3125.0d,
    15625.0d,
    78125.0d,
    390625.0d,
    1953125.0d,
    9765625.0d
  };

  private String TOP_LABEL_MESSAGE = "CGView - Circular Genome Viewer";
  private String LOADING_MAP_DATA_MESSAGE = "Please wait while the feature data is loaded...";
  private String RENDERING_MESSAGE = "Preparing zoomed version of map...";
  private String RENDERING_MESSAGE_AFTER_ZOOM = "Redrawing map...";
  private String FULLY_ZOOMED_MESSAGE = "This is a fully zoomed view.";
  private String TICK_MESSAGE = "Click tick marks to zoom in.";
  private String ABOUT_MESSAGE =
      "CGView - v1.0 (2004) Paul Stothard http://wishart.biology.ualberta.ca/cgview/";
  private String ZOOM_MINUS_TIP = "Zoom out";
  private String ZOOM_PLUS_TIP = "Zoom in";
  private String FULL_VIEW_TIP = "View entire map";
  private String ROTATE_MINUS_TIP = "Move counterclockwise";
  private String ROTATE_PLUS_TIP = "Move clockwise";
  private String INFO_TIP = "Information";
  private String HELP_TIP = "About";
  private String EXTENSION_MESSAGE = "The input file should end with a '.tab' or '.ptt'";

  private ArrayList fullViewLabelBounds = new ArrayList();
  private ArrayList currentViewLabelBounds = new ArrayList();

  private MapPanel map;

  private JPanel bottomPanel;

  private JLabel topLabel;
  private JTextField bottomLabel;
  private String tempBottomText;

  private JButton zoom_minus;
  private JButton zoom_plus;
  private JButton full_view;
  private JButton rotate_minus;
  private JButton rotate_plus;
  private JButton info;
  private JButton help;

  // use these to adjust appearance of CGView map
  private Cgview cgview;
  private int zoomCenter = 1;
  private int length = 0;
  private String title = "";
  private int zoomIndex = 0;
  private int zoomCenterStep = 0;

  private int mapWidth = 0;
  private int mapHeight = 0;
  private int mapSmallest = 0;

  private String mapProblem;

  private NumberFormat nf = NumberFormat.getInstance();

  private int MAX_MOUSEOVER_LENGTH = 100;
  private int MAX_TITLE_LENGTH = 50;
  private int MAX_SEQUENCE_LENGTH = 20000000;

  private int MAXLABELS = 1000;

  // for zoomed in maps
  private float zoomedFeatureSlotSpacing = 4.0f;
  private double zoomedLabelLineLength = 50.0d;

  public void init() {
    // execute on the event-dispatching thread
    try {
      javax.swing.SwingUtilities.invokeAndWait(
          new Runnable() {
            public void run() {
              createGUI();
            }
          });
    } catch (Exception e) {
      System.err.println("The GUI did not successfully complete");
    }

    // load the feature data in a separate thread
    final SwingWorker worker =
        new SwingWorker() {

          public Object construct() {
            // wait for GUI to finish
            while ((mapWidth == 0) || (mapHeight == 0) || (mapSmallest == 0)) {
              Dimension d = map.getSize();
              mapWidth = d.width;
              mapHeight = d.height;
              mapSmallest = Math.min(mapWidth, mapHeight);
            }

            buildCgview();
            if (mapProblem == null) {
              adjustZoomValues();
              drawFullMap();
            } else {
              TICK_MESSAGE = mapProblem;
            }

            return new Integer(1); // return value not used by this program
          }

          // Runs on the event-dispatching thread.
          public void finished() {
            giveLoadingMessage = false;
            map.renderMap();

            progressBar.setIndeterminate(false);

            bottomLabel.setText(TICK_MESSAGE);

            if (mapProblem == null) {
              setFullViewButtons();
            } else {
              setAllButtonsOff();
            }

            setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
            redraw = false;
          }
        };
    worker.start();
  }

  public void createGUI() {

    Font newFont = this.getFont().deriveFont(Font.PLAIN);

    GridBagLayout layout = new GridBagLayout();
    this.getContentPane().setLayout(layout);

    GridBagConstraints c = new GridBagConstraints();

    c.gridx = 0;
    c.gridy = 0;

    c.gridwidth = 7;
    c.gridheight = 1;

    c.weightx = 1.0;
    c.weighty = 0.0;

    c.fill = GridBagConstraints.BOTH;
    c.ipady = 10;

    topLabel = new JLabel();
    topLabel.setText(TOP_LABEL_MESSAGE);
    topLabel.setFont(newFont);
    topLabel.setHorizontalAlignment(JLabel.CENTER);
    layout.setConstraints(topLabel, c);
    this.getContentPane().add(topLabel);

    GridBagConstraints d = new GridBagConstraints();

    d.gridx = 0;
    d.gridy = 1;

    d.gridwidth = 1;
    d.gridheight = 1;

    d.weightx = 1.0;
    d.weighty = 0.0;

    d.fill = GridBagConstraints.BOTH;

    try {
      URL picURL = getClass().getResource("ZoomOut24.gif");
      ImageIcon icon = new ImageIcon(picURL);
      zoom_minus = new JButton(icon);
    } catch (Exception e) {
      zoom_minus = new JButton("Zoom -");
    }
    zoom_minus.setToolTipText(ZOOM_MINUS_TIP);
    zoom_minus.addActionListener(this);
    zoom_minus.setFont(newFont);
    layout.setConstraints(zoom_minus, d);
    this.getContentPane().add(zoom_minus);

    try {
      URL picURL = getClass().getResource("ZoomIn24.gif");
      ImageIcon icon = new ImageIcon(picURL);
      zoom_plus = new JButton(icon);
    } catch (Exception e) {
      zoom_plus = new JButton("Zoom +");
    }

    d.gridx = 1;
    zoom_plus.setToolTipText(ZOOM_PLUS_TIP);
    zoom_plus.addActionListener(this);
    zoom_plus.setFont(newFont);
    layout.setConstraints(zoom_plus, d);
    this.getContentPane().add(zoom_plus);

    try {
      URL picURL = getClass().getResource("Home24.gif");
      ImageIcon icon = new ImageIcon(picURL);
      full_view = new JButton(icon);
    } catch (Exception e) {
      full_view = new JButton("Full view");
    }

    d.gridx = 2;
    full_view.setToolTipText(FULL_VIEW_TIP);
    full_view.addActionListener(this);
    full_view.setFont(newFont);
    layout.setConstraints(full_view, d);
    this.getContentPane().add(full_view);

    try {
      URL picURL = getClass().getResource("Counter24.gif");
      ImageIcon icon = new ImageIcon(picURL);
      rotate_minus = new JButton(icon);
    } catch (Exception e) {
      rotate_minus = new JButton("Rotate -");
    }

    d.gridx = 3;
    rotate_minus.setToolTipText(ROTATE_MINUS_TIP);
    rotate_minus.addActionListener(this);
    rotate_minus.setFont(newFont);
    layout.setConstraints(rotate_minus, d);
    this.getContentPane().add(rotate_minus);

    try {
      URL picURL = getClass().getResource("Clock24.gif");
      ImageIcon icon = new ImageIcon(picURL);
      rotate_plus = new JButton(icon);
    } catch (Exception e) {
      rotate_plus = new JButton("Rotate +");
    }

    d.gridx = 4;
    rotate_plus.setToolTipText(ROTATE_PLUS_TIP);
    rotate_plus.addActionListener(this);
    rotate_plus.setFont(newFont);
    layout.setConstraints(rotate_plus, d);
    this.getContentPane().add(rotate_plus);

    try {
      URL picURL = getClass().getResource("Information24.gif");
      ImageIcon icon = new ImageIcon(picURL);
      info = new JButton(icon);
    } catch (Exception e) {
      info = new JButton("Information");
    }

    d.gridx = 5;
    info.setToolTipText(INFO_TIP);
    info.addActionListener(this);
    info.setFont(newFont);
    info.setEnabled(true);
    layout.setConstraints(info, d);
    this.getContentPane().add(info);

    try {
      URL picURL = getClass().getResource("Help24.gif");
      ImageIcon icon = new ImageIcon(picURL);
      help = new JButton(icon);
    } catch (Exception e) {
      help = new JButton("Help");
    }

    d.gridx = 6;
    help.setToolTipText(HELP_TIP);
    help.addActionListener(this);
    help.setFont(newFont);
    help.setEnabled(true);
    layout.setConstraints(help, d);
    this.getContentPane().add(help);

    GridBagConstraints e = new GridBagConstraints();

    e.gridx = 0;
    e.gridy = 2;

    e.gridwidth = 7;
    e.gridheight = 1;

    e.weightx = 1.0;
    e.weighty = 1.0;

    e.fill = GridBagConstraints.BOTH;

    map = new MapPanel();
    layout.setConstraints(map, e);
    map.setBackground(Color.white);
    this.getContentPane().add(map);

    GridBagConstraints f = new GridBagConstraints();

    f.gridx = 0;
    f.gridy = 3;

    f.gridwidth = 7;
    f.gridheight = 1;

    f.weightx = 1.0;
    f.weighty = 0.0;

    f.fill = GridBagConstraints.BOTH;

    bottomPanel = new JPanel();
    bottomPanel.setBorder(BorderFactory.createLineBorder(Color.black));
    layout.setConstraints(bottomPanel, f);
    this.getContentPane().add(bottomPanel);

    GridBagLayout bottomLayout = new GridBagLayout();
    bottomPanel.setLayout(bottomLayout);

    GridBagConstraints g = new GridBagConstraints();

    g.gridx = 0;
    g.gridy = 0;

    g.gridwidth = 1;
    g.gridheight = 1;

    g.weightx = 1.0;
    g.weighty = 0.0;

    g.fill = GridBagConstraints.BOTH;
    g.ipady = 0;

    bottomLabel = new JTextField();
    bottomLabel.setColumns(1);
    bottomLabel.setText(LOADING_MAP_DATA_MESSAGE);
    bottomLabel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
    bottomLabel.setEditable(false);
    bottomLabel.setHorizontalAlignment(JTextField.LEFT);
    bottomLabel.setFont(newFont);
    bottomLayout.setConstraints(bottomLabel, g);
    bottomPanel.add(bottomLabel);

    g.gridx = 0;
    g.gridy = 1;
    g.ipady = 0;

    progressBar = new JProgressBar();
    progressBar.setIndeterminate(true);
    progressBar.setStringPainted(false);
    progressBar.setBorderPainted(false);
    bottomLayout.setConstraints(progressBar, g);
    bottomPanel.add(progressBar);

    bottomLabel.setBackground(progressBar.getBackground());

    this.validate();
    this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
  }

  public void setFullViewButtons() {
    zoom_minus.setEnabled(false);
    zoom_plus.setEnabled(true);
    full_view.setEnabled(false);
    rotate_minus.setEnabled(false);
    rotate_plus.setEnabled(false);
  }

  public void setZoomedInButtons() {
    zoom_minus.setEnabled(true);
    zoom_plus.setEnabled(true);
    full_view.setEnabled(true);
    rotate_minus.setEnabled(true);
    rotate_plus.setEnabled(true);
  }

  public void setFullyZoomedInButtons() {
    zoom_minus.setEnabled(true);
    zoom_plus.setEnabled(false);
    full_view.setEnabled(true);
    rotate_minus.setEnabled(true);
    rotate_plus.setEnabled(true);
  }

  public void setAllButtonsOff() {
    zoom_minus.setEnabled(false);
    zoom_plus.setEnabled(false);
    full_view.setEnabled(false);
    rotate_minus.setEnabled(false);
    rotate_plus.setEnabled(false);
    info.setEnabled(false);
    help.setEnabled(false);
  }

  public void setRenderQuality(Graphics2D gg) {
    gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    gg.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
    gg.setRenderingHint(
        RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
    gg.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    gg.setRenderingHint(
        RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
    gg.setRenderingHint(
        RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
    gg.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
    gg.setRenderingHint(
        RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
  }

  public void updateMessage(String message) {
    bottomLabel.setText(message);
  }

  public void paint(Graphics g) {
    super.paint(g);
  }

  public void buildCgview() {
    buildCgview(false);
  }

  public void buildCgview(boolean partialMap) {

    String file = null;
    String extension = null;
    URL url = null;
    boolean hasFileParam = false;

    String hideLegendValue;
    boolean hideLegend = false;

    String hideFullViewLabelsValue;
    boolean hideFullViewLabels = false;

    String rulerFontSizeValue;
    int rulerFontSize = -1;

    String labelFontSizeValue;
    int labelFontSize = -1;

    String legendFontSizeValue;
    int legendFontSize = -1;

    // try using applet parameter
    try {
      file = getParameter("file");
      hasFileParam = true;

      if (file.length() >= 3) {
        extension = file.substring(file.length() - 3);
      }
    } catch (Exception e) {
      hasFileParam = false;
    }

    if (hasFileParam) {
      try {
        url = new URL(getDocumentBase(), file);
      } catch (Exception e) {
        mapProblem = e.toString();
      }
    }

    // try using test file
    if (!hasFileParam) {
      // file = "data_in.tab";
      file = "test_input.ptt";
      if (file.length() >= 3) {
        extension = file.substring(file.length() - 3);
      }
      try {
        url = getClass().getResource(file);
      } catch (Exception e) {
        mapProblem = "Unable to load feature data file.";
      }
    }

    // look for hideLegend parameter
    try {
      hideLegendValue = getParameter("hideLegend");
      if (hideLegendValue != null) {
        if ((hideLegendValue.equalsIgnoreCase("t")) || (hideLegendValue.equalsIgnoreCase("true"))) {
          hideLegend = true;
        }
      }
    } catch (Exception e) {

    }

    try {
      // look for hideFullViewLabels parameter
      hideFullViewLabelsValue = getParameter("hideFullViewLabels");
      if (hideFullViewLabelsValue != null) {
        if ((hideFullViewLabelsValue.equalsIgnoreCase("t"))
            || (hideFullViewLabelsValue.equalsIgnoreCase("true"))) {
          hideFullViewLabels = true;
        }
      }
    } catch (Exception e) {

    }

    try {
      // look for rulerFontSize parameter
      rulerFontSizeValue = getParameter("rulerFontSize");
      if (rulerFontSizeValue != null) {
        rulerFontSize = Integer.parseInt(rulerFontSizeValue);
      }
    } catch (Exception e) {
    }

    try {
      // look for labelFontSize parameter
      labelFontSizeValue = getParameter("labelFontSize");
      if (labelFontSizeValue != null) {
        labelFontSize = Integer.parseInt(labelFontSizeValue);
      }
    } catch (Exception e) {
    }

    try {
      // look for legendFontSize parameter
      legendFontSizeValue = getParameter("legendFontSize");
      if (legendFontSizeValue != null) {
        legendFontSize = Integer.parseInt(legendFontSizeValue);
      }
    } catch (Exception e) {
    }

    if (mapProblem == null) {

      if (extension.equalsIgnoreCase("tab")) {
        try {

          CgviewFactoryTab cgviewFactory = new CgviewFactoryTab();
          cgviewFactory.setReadDimension(false);
          cgviewFactory.setWidth(mapWidth);
          cgviewFactory.setHeight(mapHeight);

          if (rulerFontSize > 0) {
            cgviewFactory.setRulerFontSize(rulerFontSize);
          }
          if (labelFontSize > 0) {
            cgviewFactory.setLabelFontSize(labelFontSize);
          }
          if (legendFontSize > 0) {
            cgviewFactory.setLegendFontSize(legendFontSize);
          }

          if (partialMap) {
            cgviewFactory.setMapItemSizeAdjustment(0.5f);
          }

          cgview = cgviewFactory.createCgviewFromURL(url);

          // override some values
          cgview.setLabelPlacementQuality(5);
          cgview.setLabelsToKeep(MAXLABELS);
          // bugfix
          cgview.setMoveInnerLabelsToOuter(false);
          cgview.setUseInnerLabels(INNER_LABELS_AUTO);
          if (!partialMap) {
            cgview.setLabelLineLength(20.0d);
          } else {
            if (!(rulerFontSize > 0)) {
              cgview.setRulerFont(new Font("SansSerif", Font.PLAIN, 8));
            }
            cgview.setTickLength(6.0f);
            cgview.setLabelLineLength(zoomedLabelLineLength);
          }
          // cgview.setTickLength(6.0f);
          length = cgview.getSequenceLength();
          title = cgview.getTitle();
          cgview.setShowBorder(false);

          if (hideFullViewLabels) {
            cgview.setGlobalLabel(LABEL_ZOOMED);
          }

          if (hideLegend) {
            cgview.setDrawLegends(false);
          }

        } catch (Exception e) {
          // e.printStackTrace(System.out);
          mapProblem = e.toString();
        }
      } else if (extension.equalsIgnoreCase("ptt")) {
        try {
          CgviewFactoryPtt cgviewFactory = new CgviewFactoryPtt();
          cgviewFactory.setReadDimension(false);
          cgviewFactory.setWidth(mapWidth);
          cgviewFactory.setHeight(mapHeight);

          if (rulerFontSize > 0) {
            cgviewFactory.setRulerFontSize(rulerFontSize);
          }
          if (labelFontSize > 0) {
            cgviewFactory.setLabelFontSize(labelFontSize);
          }
          if (legendFontSize > 0) {
            cgviewFactory.setLegendFontSize(legendFontSize);
          }

          if (partialMap) {
            cgviewFactory.setMapItemSizeAdjustment(0.5f);
          }

          cgview = cgviewFactory.createCgviewFromURL(url);

          // override some values
          cgview.setLabelPlacementQuality(5);
          cgview.setLabelsToKeep(MAXLABELS);
          // bugfix
          cgview.setMoveInnerLabelsToOuter(false);
          cgview.setUseInnerLabels(INNER_LABELS_AUTO);
          if (!partialMap) {
            cgview.setLabelLineLength(20.0d);
          } else {
            if (!(rulerFontSize > 0)) {
              cgview.setRulerFont(new Font("SansSerif", Font.PLAIN, 8));
            }
            cgview.setTickLength(6.0f);
            cgview.setLabelLineLength(zoomedLabelLineLength);
          }
          // cgview.setTickLength(6.0f);
          length = cgview.getSequenceLength();
          title = cgview.getTitle();
          cgview.setShowBorder(false);

          if (hideFullViewLabels) {
            cgview.setGlobalLabel(LABEL_ZOOMED);
          }

          if (hideLegend) {
            cgview.setDrawLegends(false);
          }

        } catch (Exception e) {
          // e.printStackTrace(System.out);
          mapProblem = e.toString();
        }
      } else {
        mapProblem = EXTENSION_MESSAGE;
      }
    }
  }

  public void adjustZoomValues() {

    int checked = 0;
    double maxZoom = cgview.getZoomMax();
    for (int i = 0; i < ZOOM_VALUES.length; i = i + 1) {
      if (ZOOM_VALUES[i] <= maxZoom) {
        checked++;
      }
    }

    double checkedZoom[] = new double[checked++];
    for (int i = 0; i < checkedZoom.length; i = i + 1) {
      checkedZoom[i] = ZOOM_VALUES[i];
    }
    ZOOM_VALUES = checkedZoom;
  }

  public void drawFullMap() {

    biFullView = new BufferedImage(mapWidth, mapHeight, BufferedImage.TYPE_INT_RGB);

    big = biFullView.createGraphics();

    cgview.draw(big);
    fullViewLabelBounds.clear();
    ArrayList returnedBounds = cgview.getLabelBounds();
    for (int i = 0; i < returnedBounds.size(); i = i + 1) {
      LabelBounds currentLabelBounds = (LabelBounds) returnedBounds.get(i);
      BoundsInfo newBounds =
          new BoundsInfo(
              currentLabelBounds.getBounds(),
              currentLabelBounds.getBase(),
              currentLabelBounds.getType(),
              currentLabelBounds.getMouseover(),
              currentLabelBounds.getHyperlink());
      fullViewLabelBounds.add(newBounds);
      // big.setColor(Color.red);
      // big.draw(currentLabelBounds.getBounds());
    }

    // now adjust the cgview for zoomed in maps
    cgview.setFeatureSlotSpacing(zoomedFeatureSlotSpacing);
    cgview.setLabelLineLength(zoomedLabelLineLength);
  }

  public void drawPartialMap() {

    if (firstZoom) {
      // rebuild the map using different appearance settings
      // this is done once.
      // there shouldn't be any problems because feature file has been checked
      cgview = null;

      buildCgview(true);

      if (mapProblem == null) {
        firstZoom = false;
        RENDERING_MESSAGE = RENDERING_MESSAGE_AFTER_ZOOM;
      } else {
        TICK_MESSAGE = "Zoomed map preparation failed: " + mapProblem;
      }
    }

    if (mapProblem == null) {
      if (bi == null) {
        bi = new BufferedImage(mapWidth, mapHeight, BufferedImage.TYPE_INT_RGB);
      }
      big = bi.createGraphics();
      cgview.drawZoomed(big, ZOOM_VALUES[zoomIndex], zoomCenter);
      currentViewLabelBounds.clear();
      ArrayList returnedBounds = cgview.getLabelBounds();
      for (int i = 0; i < returnedBounds.size(); i = i + 1) {
        LabelBounds currentLabelBounds = (LabelBounds) returnedBounds.get(i);
        BoundsInfo newBounds =
            new BoundsInfo(
                currentLabelBounds.getBounds(),
                currentLabelBounds.getBase(),
                currentLabelBounds.getType(),
                currentLabelBounds.getMouseover(),
                currentLabelBounds.getHyperlink());
        currentViewLabelBounds.add(newBounds);
        // big.setColor(Color.red);
        // big.draw(currentLabelBounds.getBounds());
      }

      // adjust rotation for new map
      zoomCenterStep =
          (int) Math.floor(length / (ZOOM_VALUES[zoomIndex] * ROTATION_CONSTANT) + 0.5d);
      if (zoomCenterStep < 1) {
        zoomCenterStep = 1;
      }
    }
  }

  public void actionPerformed(ActionEvent e) {
    if ((e.getSource() == zoom_minus) && (redraw == false)) {
      if (zoomIndex > 0) {
        redraw = true;
        zoomIndex--;
        if (zoomIndex == 0) {
          setFullViewButtons();
          zoomCenter = 1;
        } else {
          setZoomedInButtons();
        }
        // set message for drawing
        bottomLabel.setText(RENDERING_MESSAGE);
        progressBar.setIndeterminate(true);

        // start drawing thread
        final SwingWorker worker =
            new SwingWorker() {

              public Object construct() {
                if (zoomIndex != 0) {
                  drawPartialMap();
                }
                return new Integer(1); // return value not used by this program
              }

              // Runs on the event-dispatching thread.
              public void finished() {
                map.renderMap();
                progressBar.setIndeterminate(false);
                bottomLabel.setText(TICK_MESSAGE);
                redraw = false;
              }
            };
        worker.start();
      }
    } else if ((e.getSource() == zoom_plus) && (redraw == false)) {
      if (zoomIndex < ZOOM_VALUES.length - 1) {
        redraw = true;
        zoomIndex++;
        if (zoomIndex == ZOOM_VALUES.length - 1) {
          setFullyZoomedInButtons();
        } else {
          setZoomedInButtons();
        }

        // set message for drawing
        bottomLabel.setText(RENDERING_MESSAGE);
        progressBar.setIndeterminate(true);

        // start drawing thread
        final SwingWorker worker =
            new SwingWorker() {

              public Object construct() {
                drawPartialMap();
                return new Integer(1); // return value not used by this program
              }

              // Runs on the event-dispatching thread.
              public void finished() {
                map.renderMap();
                progressBar.setIndeterminate(false);
                if (zoomIndex == ZOOM_VALUES.length - 1) {
                  bottomLabel.setText(FULLY_ZOOMED_MESSAGE);
                } else {
                  bottomLabel.setText(TICK_MESSAGE);
                }
                redraw = false;
              }
            };
        worker.start();
      }
    } else if ((e.getSource() == full_view) && (redraw == false)) {

      if (!((zoomIndex == 0) && (zoomCenter == 1))) {
        redraw = true;
        zoomIndex = 0;
        zoomCenter = 1;

        setFullViewButtons();
        // set message for drawing
        redraw = true;
        bottomLabel.setText(RENDERING_MESSAGE);
        progressBar.setIndeterminate(true);

        // start drawing thread
        final SwingWorker worker =
            new SwingWorker() {

              public Object construct() {
                return new Integer(1); // return value not used by this program
              }

              // Runs on the event-dispatching thread.
              public void finished() {
                map.renderMap();
                progressBar.setIndeterminate(false);
                bottomLabel.setText(TICK_MESSAGE);
                redraw = false;
              }
            };
        worker.start();
      }
    } else if ((e.getSource() == rotate_minus) && (redraw == false)) {
      if (zoomIndex > 0) {
        redraw = true;
        zoomCenter = zoomCenter - zoomCenterStep;
        if (zoomCenter < 0) {
          zoomCenter = length + zoomCenter;
        } else if (zoomCenter > length) {
          zoomCenter = 1 + (zoomCenter - length);
        }

        // set message for drawing

        bottomLabel.setText(RENDERING_MESSAGE);
        progressBar.setIndeterminate(true);

        // start drawing thread
        final SwingWorker worker =
            new SwingWorker() {

              public Object construct() {
                drawPartialMap();
                return new Integer(1); // return value not used by this program
              }

              // Runs on the event-dispatching thread.
              public void finished() {
                map.renderMap();
                progressBar.setIndeterminate(false);
                if (zoomIndex == ZOOM_VALUES.length - 1) {
                  bottomLabel.setText(FULLY_ZOOMED_MESSAGE);
                } else {
                  bottomLabel.setText(TICK_MESSAGE);
                }
                redraw = false;
              }
            };
        worker.start();
      }
    } else if ((e.getSource() == rotate_plus) && (redraw == false)) {
      if (zoomIndex > 0) {
        redraw = true;
        zoomCenter = zoomCenter + zoomCenterStep;
        if (zoomCenter < 0) {
          zoomCenter = length + zoomCenter;
        } else if (zoomCenter > length) {
          zoomCenter = 1 + (zoomCenter - length);
        }

        // set message for drawing

        bottomLabel.setText(RENDERING_MESSAGE);
        progressBar.setIndeterminate(true);

        // start drawing thread
        final SwingWorker worker =
            new SwingWorker() {

              public Object construct() {
                drawPartialMap();
                return new Integer(1); // return value not used by this program
              }

              // Runs on the event-dispatching thread.
              public void finished() {
                map.renderMap();
                progressBar.setIndeterminate(false);
                if (zoomIndex == ZOOM_VALUES.length - 1) {
                  bottomLabel.setText(FULLY_ZOOMED_MESSAGE);
                } else {
                  bottomLabel.setText(TICK_MESSAGE);
                }
                redraw = false;
              }
            };
        worker.start();
      }
    } else if ((e.getSource() == info) && (redraw == false)) {
      redraw = true;
      tempBottomText =
          "Current zoom: "
              + nf.format((long) ZOOM_VALUES[zoomIndex])
              + "; centered on base: "
              + nf.format((long) zoomCenter)
              + ".";
      if (tempBottomText.length() > MAX_MOUSEOVER_LENGTH) {
        tempBottomText = tempBottomText.substring(0, MAX_MOUSEOVER_LENGTH) + "...";
      }
      bottomLabel.setText(tempBottomText);

      redraw = false;
    } else if ((e.getSource() == help) && (redraw == false)) {
      redraw = true;
      tempBottomText = ABOUT_MESSAGE;
      if (tempBottomText.length() > MAX_MOUSEOVER_LENGTH) {
        tempBottomText = tempBottomText.substring(0, MAX_MOUSEOVER_LENGTH) + "...";
      }
      bottomLabel.setText(tempBottomText);
      redraw = false;
    }
  }

  public String[][] getParameterInfo() {
    String[][] info = {
      // Parameter Name     Kind of Value   Description
      {"file", "URL", "the feature data file"},
    };
    return info;
  }

  public String getAppletInfo() {
    return "CGView by Paul Stothard";
  }

  private class BoundsInfo {

    Rectangle2D bounds;
    int base;
    int type;
    String text;
    String hyperlink;

    public BoundsInfo(Rectangle2D bounds, int base, int type, String text, String hyperlink) {
      this.bounds = bounds;
      this.base = base;
      this.type = type;
      this.text = text;
      this.hyperlink = hyperlink;
    }

    public Rectangle2D getBounds() {
      return bounds;
    }

    public int getBase() {
      return base;
    }

    public int getType() {
      return type;
    }

    public String getText() {
      return text;
    }

    public String getHyperlink() {
      return hyperlink;
    }
  }

  private class MapPanel extends JPanel {

    int width;
    int height;

    public MapPanel() {
      this.setBackground(Color.white);
      this.addMouseListener(
          new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {

              if ((zoomIndex < ZOOM_VALUES.length - 1) && (redraw == false)) {
                ArrayList boundsToCheck;
                if (zoomIndex == 0) {
                  boundsToCheck = fullViewLabelBounds;
                } else {
                  boundsToCheck = currentViewLabelBounds;
                }

                for (int i = 0; i < boundsToCheck.size(); i = i + 1) {
                  BoundsInfo currentLabelBounds = (BoundsInfo) boundsToCheck.get(i);
                  if (currentLabelBounds.getBounds().contains(e.getX(), e.getY())) {
                    if (currentLabelBounds.getType() == BOUNDS_RULER) {
                      redraw = true;
                      zoomIndex++;
                      zoomCenter = currentLabelBounds.getBase();
                      if (zoomIndex == ZOOM_VALUES.length - 1) {
                        setFullyZoomedInButtons();
                      } else {
                        setZoomedInButtons();
                      }

                      bottomLabel.setText(RENDERING_MESSAGE);
                      progressBar.setIndeterminate(true);

                      // start drawing thread
                      final SwingWorker worker =
                          new SwingWorker() {

                            public Object construct() {
                              drawPartialMap();
                              return new Integer(1); // return value not used by this program
                            }

                            // Runs on the event-dispatching thread.
                            public void finished() {
                              map.renderMap();
                              progressBar.setIndeterminate(false);
                              if (zoomIndex == ZOOM_VALUES.length - 1) {
                                bottomLabel.setText(FULLY_ZOOMED_MESSAGE);
                              } else {
                                bottomLabel.setText(TICK_MESSAGE);
                              }
                              redraw = false;
                            }
                          };
                      worker.start();
                    } else if (currentLabelBounds.getType() == BOUNDS_FEATURE) {
                      if (currentLabelBounds.getHyperlink() != null) {
                        try {
                          getAppletContext()
                              .showDocument(
                                  new URL(currentLabelBounds.getHyperlink()), "_CGViewLink");
                        } catch (MalformedURLException ex) {
                          bottomLabel.setText("URL is not properly formed.");
                        } catch (NullPointerException ex) {
                          bottomLabel.setText("Cannot obtain applet context.");
                        }
                      }
                    }
                    break;
                  }
                }
              }
            }
          });

      this.addMouseMotionListener(
          new MouseMotionAdapter() {
            public void mouseMoved(MouseEvent e) {

              if (redraw == false) {
                boolean onItem = false;
                ArrayList boundsToCheck;
                if (zoomIndex == 0) {
                  boundsToCheck = fullViewLabelBounds;
                } else {
                  boundsToCheck = currentViewLabelBounds;
                }

                for (int i = 0; i < boundsToCheck.size(); i = i + 1) {
                  BoundsInfo currentLabelBounds = (BoundsInfo) boundsToCheck.get(i);

                  if (currentLabelBounds.getBounds().contains(e.getX(), e.getY())) {
                    onItem = true;
                    if (currentLabelBounds.getType() == BOUNDS_RULER) {
                      if (zoomIndex == ZOOM_VALUES.length - 1) {
                        bottomLabel.setText(FULLY_ZOOMED_MESSAGE);
                      } else {
                        setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
                        bottomLabel.setText(TICK_MESSAGE);
                      }
                    } else if (currentLabelBounds.getType() == BOUNDS_FEATURE) {
                      if (currentLabelBounds.getText() != null) {
                        bottomLabel.setText(currentLabelBounds.getText());
                      }
                      if (currentLabelBounds.getHyperlink() != null) {
                        setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
                      }
                    }

                    break;
                  }
                }
                if (!(onItem)) {
                  setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
                  if (zoomIndex == ZOOM_VALUES.length - 1) {
                    bottomLabel.setText(FULLY_ZOOMED_MESSAGE);
                  } else {
                    bottomLabel.setText(TICK_MESSAGE);
                  }
                }
              }
            }
          });
    }

    public void renderMap() {
      repaint();
    }

    public void paintComponent(Graphics g) {
      super.paintComponent(g);
      Graphics2D g2 = (Graphics2D) g;

      if (giveLoadingMessage) {
        Dimension d = this.getSize();
        mapWidth = d.width;
        mapHeight = d.height;
        mapSmallest = Math.min(mapWidth, mapHeight);
      } else if ((zoomCenter == 1) && (zoomIndex == 0) && (mapProblem == null)) {
        g2.drawImage(biFullView, 0, 0, this);
      } else if (mapProblem == null) {
        if (bi != null) {
          g2.drawImage(bi, 0, 0, this);
        } else if (biFullView != null) {
          g2.drawImage(biFullView, 0, 0, this);
        }
      }
    }
  }

  public static void main(String[] argv) {

    JFrame frame = new JFrame("CGView - Circular Genome Viewer");
    frame.addWindowListener(
        new WindowAdapter() {
          public void windowClosing(WindowEvent e) {
            System.exit(0);
          }
        });

    JApplet applet = new CGView();
    frame.getContentPane().add(BorderLayout.CENTER, applet);
    applet.init();
    frame.setSize(900, 920);
    frame.show();
  }
}
