/*
 * jNPad v0.3 - jNPad's an Simple Text Editor written in Java
 *
 * Copyright (C) 2014-2017  rgs
 *
 * Require JDK 1.6 (or later)
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 *
 * Info, Questions, Suggestions & Bugs Report to rgsevero@gmail.com
 */

package jnpad.text;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.Serializable;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.JComponent;
import javax.swing.ToolTipManager;
import javax.swing.UIManager;
import javax.swing.text.BadLocationException;

import jnpad.GUIUtilities;
import jnpad.config.Config;
import jnpad.config.Controlable;

/**
 * The Class MarkStrip.
 *
 * @version 0.3
 * @since   jNPad v0.1
 */
public class MarkStrip extends JComponent implements Controlable {
  private Color             occurrenceMarkerColor       = Config.MARKER_OCCURRENCE_COLOR.getDefault();
  private Color             occurrenceMarkerBorderColor = Config.MARKER_OCCURRENCE_COLOR.getDefault();
  private Color             bookmarkMarkerColor         = Config.MARKER_BOOKMARK_COLOR.getDefault();
  private Color             bookmarkMarkerBorderColor   = Config.MARKER_BOOKMARK_BORDER_COLOR.getDefault();
  private Color             searchMarkerColor           = Config.MARKER_SEARCH_COLOR.getDefault();
  private Color             searchMarkerBorderColor     = Config.MARKER_SEARCH_BORDER_COLOR.getDefault();

  private Color             caretMarkerColor            = Config.MARKER_CARET_COLOR.getDefault();

  private int               caretLineY                  = -1;
  private int               lastLineY                   = -1;

  private boolean           showOccurrences             = true;
  private boolean           showBookmarks               = true;
  private boolean           showSearched                = true;
  private boolean           followCaret                 = true;

  private Rectangle         visibleRect                 = new Rectangle();

  private static final int  PREFERRED_WIDTH             = Config.MARKER_PREFERRED_WIDTH.getValue();

  private EditPane          editPane;
  private JNPadTextArea     textArea;

  /** UID */
  private static final long serialVersionUID            = 5411365801461770494L;

  /**
   *
   * @param editPane EditPane
   */
  public MarkStrip(EditPane editPane) {
    this.editPane = editPane;
    textArea = editPane.getTextArea();

    configure(CFG_ALL);

    ToolTipManager.sharedInstance().registerComponent(this);

    addMouseListener(new MouseAdapter() {
      @Override public void mouseClicked(final MouseEvent e) {
        int line = yToLine(e.getY());
        if (line > -1) {
          try {
            int offs = textArea.getLineStartOffset(line);
            textArea.setCaretPosition(offs);
          }
          catch (BadLocationException ex) { // Never happens
            UIManager.getLookAndFeel().provideErrorFeedback(textArea);
          }
        }
      }
    });

    setLayout(null); // Manually layout Markers as they can overlap
  }

  /**
   * Caret update.
   */
  void caretUpdate() {
    if (getFollowCaret()) {
      int line = textArea.getCaretLineNumber();
      float percent = line / ( (float) textArea.getLineCount());
      textArea.computeVisibleRect(visibleRect);
      caretLineY = (int) (visibleRect.height * percent);
      if (caretLineY != lastLineY) {
        repaint(0, lastLineY, getWidth(), 2); // Erase old position
        repaint(0, caretLineY, getWidth(), 2);
        lastLineY = caretLineY;
      }
    }
  }

  /**
   * Configure.
   *
   * @param cfg int
   * @see jnpad.config.Configurable#configure(int)
   */
  @Override
  public void configure(final int cfg) {
    if ( (cfg & CFG_COLOR) != 0) {
      setBookmarkMarkerColor(Config.MARKER_BOOKMARK_COLOR.getValue());
      setBookmarkMarkerBorderColor(Config.MARKER_BOOKMARK_BORDER_COLOR.getValue());
      setOccurrenceMarkerColor(Config.MARKER_OCCURRENCE_COLOR.getValue());
      setOccurrenceMarkerBorderColor(Config.MARKER_OCCURRENCE_BORDER_COLOR.getValue());
      setSearchMarkerColor(Config.MARKER_SEARCH_COLOR.getValue());
      setSearchMarkerBorderColor(Config.MARKER_SEARCH_BORDER_COLOR.getValue());
      setCaretMarkerColor(Config.MARKER_CARET_COLOR.getValue());
    }
  }

  /**
   * Update controls.
   *
   * @see jnpad.config.Updatable#updateControls()
   */
  @Override
  public void updateControls() {
    updateControls(CTRLS_ALL);
  }

  /**
   * Update controls.
   *
   * @param ctrls int
   * @see jnpad.config.Updatable#updateControls(int)
   */
  @Override
  public void updateControls(final int ctrls) {
    if ( (ctrls & CTRLS_TEXT_CHANGED) != 0) {
      if ((getShowOccurrences() && editPane.hasOccurrences()) ||
          (getShowBookmarks() && editPane.hasBookmarks())) {
        refreshMarkers();
      }
    }

    if ( (ctrls & CTRLS_OCCURRENCE) != 0) {
      if (getShowOccurrences()) {
        refreshMarkers();
      }
    }

    if ( (ctrls & CTRLS_BOOKMARKING) != 0) {
      if (getShowBookmarks()) {
        refreshMarkers();
      }
    }

    if ( (ctrls & CTRLS_SEARCH) != 0) {
      if (getShowSearched()) {
        refreshMarkers();
      }
    }
  }

  /**
   * Do layout.
   *
   * @see java.awt.Container#doLayout()
   */
  @Override
  public void doLayout() {
    for (int i = 0; i < getComponentCount(); i++) {
      Marker m = (Marker) getComponent(i);
      m.updateLocation();
    }
    caretUpdate(); // Force recalculation of caret line pos
  }

  /**
   * Gets the follow caret.
   *
   * @return the follow caret
   */
  public boolean getFollowCaret() {
    return followCaret;
  }

  /**
   * Sets the follow caret.
   *
   * @param follow boolean
   */
  public void setFollowCaret(boolean follow) {
    if (followCaret != follow) {
      if (followCaret) {
        repaint(0, caretLineY, getWidth(), 2); // Erase
      }
      caretLineY = -1;
      lastLineY = -1;
      followCaret = follow;
      caretUpdate(); // Possibly repaint
    }
  }

  /**
   * Gets the caret marker color.
   *
   * @return the caret marker color
   */
  public Color getCaretMarkerColor() {
    return caretMarkerColor;
  }

  /**
   * Sets the caret marker color.
   *
   * @param color the new caret marker color
   */
  public void setCaretMarkerColor(Color color) {
    if (color != null) {
      caretMarkerColor = color;
    }
  }

  /**
   * Gets the bookmark marker color.
   *
   * @return the bookmark marker color
   */
  public Color getBookmarkMarkerColor() {
    return bookmarkMarkerColor;
  }

  /**
   * Sets the bookmark marker color.
   *
   * @param color the new bookmark marker color
   */
  public void setBookmarkMarkerColor(Color color) {
    if (color != null) {
      bookmarkMarkerColor = color;
    }
  }

  /**
   * Gets the bookmark marker border color.
   *
   * @return the bookmark marker border color
   */
  public Color getBookmarkMarkerBorderColor() {
    return bookmarkMarkerBorderColor;
  }

  /**
   * Sets the bookmark marker border color.
   *
   * @param color the new bookmark marker border color
   */
  public void setBookmarkMarkerBorderColor(Color color) {
    if (color != null) {
      bookmarkMarkerBorderColor = color;
    }
  }

  /**
   * Gets the occurrence marker color.
   *
   * @return the occurrence marker color
   */
  public Color getOccurrenceMarkerColor() {
    return occurrenceMarkerColor;
  }

  /**
   * Sets the occurrence marker color.
   *
   * @param color the new occurrence marker color
   */
  public void setOccurrenceMarkerColor(Color color) {
    if (color != null) {
      occurrenceMarkerColor = color;
    }
  }

  /**
   * Gets the occurrence marker border color.
   *
   * @return the occurrence marker border color
   */
  public Color getOccurrenceMarkerBorderColor() {
    return occurrenceMarkerBorderColor;
  }

  /**
   * Sets the occurrence marker border color.
   *
   * @param color the new occurrence marker border color
   */
  public void setOccurrenceMarkerBorderColor(Color color) {
    if (color != null) {
      occurrenceMarkerBorderColor = color;
    }
  }

  /**
   * Gets the search marker color.
   *
   * @return the search marker color
   */
  public Color getSearchMarkerColor() {
    return searchMarkerColor;
  }

  /**
   * Sets the search marker color.
   *
   * @param color the new search marker color
   */
  public void setSearchMarkerColor(Color color) {
    if (color != null) {
      searchMarkerColor = color;
    }
  }

  /**
   * Gets the search marker border color.
   *
   * @return the search marker border color
   */
  public Color getSearchMarkerBorderColor() {
    return searchMarkerBorderColor;
  }

  /**
   * Sets the search marker border color.
   *
   * @param color the new search marker border color
   */
  public void setSearchMarkerBorderColor(Color color) {
    if (color != null) {
      searchMarkerBorderColor = color;
    }
  }

  /**
   * Gets the show occurrences.
   *
   * @return the show occurrences
   */
  public boolean getShowOccurrences() {
    return showOccurrences;
  }

  /**
   * Sets the show occurrences.
   *
   * @param b the new show occurrences
   */
  public void setShowOccurrences(boolean b) {
    if (b != showOccurrences) {
      showOccurrences = b;
      if (isDisplayable()) { // Skip this when we're first created
        refreshMarkers();
      }
    }
  }

  /**
   * Gets the show bookmarks.
   *
   * @return the show bookmarks
   */
  public boolean getShowBookmarks() {
    return showBookmarks;
  }

  /**
   * Sets the show bookmarks.
   *
   * @param b the new show bookmarks
   */
  public void setShowBookmarks(boolean b) {
    if (b != showBookmarks) {
      showBookmarks = b;
      if (isDisplayable()) { // Skip this when we're first created
        refreshMarkers();
      }
    }
  }

  /**
   * Gets the show searched.
   *
   * @return the show searched
   */
  public boolean getShowSearched() {
    return showSearched;
  }

  /**
   * Sets the show searched.
   *
   * @param b the new show searched
   */
  public void setShowSearched(boolean b) {
    if (b != showSearched) {
      showSearched = b;
      if (isDisplayable()) { // Skip this when we're first created
        refreshMarkers();
      }
    }
  }

  /**
   * Refresh markers.
   */
  private void refreshMarkers() {
    removeAll();

    Map<Integer, Marker> markerMap = new HashMap<Integer, Marker>();

    if (getShowOccurrences() && editPane.hasOccurrences()) {
      DocumentRange[] ranges = editPane.getOccurrences();
      for (DocumentRange range : ranges) {
        int line;
        try {
          line = textArea.getLineOfOffset(range.getStartOffset());
        }
        catch (BadLocationException ble) {
          continue;
        }
        Notice notice = new OccurrenceNotice(range, getOccurrenceMarkerColor(), getOccurrenceMarkerBorderColor());
        Integer key = line;
        Marker m = markerMap.get(key);
        if (m == null) {
          m = new Marker(notice);
          markerMap.put(key, m);
          add(m);
        }
        else {
          if (!m.containsNotice(notice)) {
            m.addNotice(notice);
          }
        }
      }
    }

    if (getShowBookmarks() && editPane.hasBookmarks()) {
      DocumentRange[] ranges = editPane.getBookmarks();
      for (DocumentRange range : ranges) {
        int line;
        try {
          line = textArea.getLineOfOffset(range.getStartOffset());
        }
        catch (BadLocationException ble) {
          continue;
        }
        Notice notice = new BookmarkNotice(range, getBookmarkMarkerColor(), getBookmarkMarkerBorderColor());
        Integer key = line;
        Marker m = markerMap.get(key);
        if (m == null) {
          m = new Marker(notice);
          markerMap.put(key, m);
          add(m);
        }
        else {
          if (!m.containsNotice(notice)) {
            m.addNotice(notice);
          }
        }
      }
    }

    if (getShowSearched() && editPane.hasSearched()) {
      DocumentRange range = editPane.getSearched();
      if (range == null)
        return;
      int line = 0;
      try {
        line = textArea.getLineOfOffset(range.getStartOffset());
      }
      catch (BadLocationException ex) {
        //ignored
      }
      Notice notice = new SearchNotice(range, getSearchMarkerColor(), getSearchMarkerBorderColor());
      Integer key = line;
      Marker m = markerMap.get(key);
      if (m == null) {
        m = new Marker(notice);
        markerMap.put(key, m);
        add(m);
      }
      else {
        if (!m.containsNotice(notice)) {
          m.addNotice(notice);
        }
      }
    }

    revalidate();
    repaint();
  }

  /**
   * Paint component.
   *
   * @param g Graphics
   * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
   */
  @Override
  protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    if (caretLineY > -1) {
      g.setColor(getCaretMarkerColor());
      g.fillRect(0, caretLineY, getWidth(), 2);
    }
  }

  /**
   * Gets the preferred size.
   *
   * @return Dimension
   * @see javax.swing.JComponent#getPreferredSize()
   */
  @Override
  public Dimension getPreferredSize() {
    int height = textArea.getPreferredScrollableViewportSize().height;
    return new Dimension(PREFERRED_WIDTH, height);
  }

  /**
   * Gets the tool tip text.
   *
   * @param e MouseEvent
   * @return String
   * @see javax.swing.JComponent#getToolTipText(java.awt.event.MouseEvent)
   */
  @Override
  public String getToolTipText(MouseEvent e) {
    String text = null;
    int line = yToLine(e.getY());
    if (line > -1) {
      text = TextBundle.getString("MarkStrip.line"); //$NON-NLS-1$
      text = MessageFormat.format(text, line + 1);
    }
    return text;
  }

  /**
   * Line to y.
   *
   * @param line int
   * @return int
   */
  private int lineToY(int line) {
    int h = textArea.getVisibleRect().height;
    float lineCount = textArea.getLineCount();
    return (int) ( (line / lineCount) * h) - 2;
  }

  /**
   * Y to line.
   *
   * @param y int
   * @return int
   */
  private int yToLine(int y) {
    int line = -1;
    int h = textArea.getVisibleRect().height;
    if (y < h) {
      float at = y / (float) h;
      line = (int) (textArea.getLineCount() * at);
    }
    return line;
  }

  //////////////////////////////////////////////////////////////////////////////
  /**
   * The Class Marker.
   */
  private class Marker extends JComponent {
    private List<Notice>      notices          = new ArrayList<Notice>(1); // Usually just 1

    private MouseListener     mouseHandler     = new MouseHandler();

    /** UID */
    private static final long serialVersionUID = -7849793495614737486L;
 
    /**
     *
     * @param notice Notice
     */
    public Marker(Notice notice) {
      addNotice(notice);
      setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
      setSize(getPreferredSize());
      ToolTipManager.sharedInstance().registerComponent(this);
      addMouseListener(mouseHandler);
    }

    /**
     *
     * @param notice Notice
     */
    void addNotice(Notice notice) {
      notices.add(notice);
    }

    /**
     * Gets the colors.
     *
     * @return the colors
     */
    Color[] getColors() {
      Color[] c = new Color[2];
      int lowestLevel = Integer.MAX_VALUE;
      for (Notice notice : notices) {
        if (notice.getLevel() < lowestLevel) {
          lowestLevel = notice.getLevel();
          c[0] = notice.getColor();
          c[1] = notice.getBorderColor();
        }
      }
      return c;
    }

    /**
     * Gets the preferred size.
     *
     * @return Dimension
     * @see javax.swing.JComponent#getPreferredSize()
     */
    @Override
    public Dimension getPreferredSize() {
      int w = PREFERRED_WIDTH - 4; // 2-pixel empty border
      return new Dimension(w, 5);
    }

    /**
     * Gets the tool tip text.
     *
     * @return String
     * @see javax.swing.JComponent#getToolTipText()
     */
    @Override
    public String getToolTipText() {
      String text;

      if (notices.size() == 1) {
        text = notices.get(0).getMessage();
      }
      else { // > 1
        StringBuilder sb = new StringBuilder("<html>"); //$NON-NLS-1$
        sb.append(TextBundle.getString("MarkStrip.multipleMarkers")); //$NON-NLS-1$
        sb.append("<br>"); //$NON-NLS-1$
        for (Notice pn : notices) {
          sb.append("&nbsp;&nbsp;&nbsp;- "); //$NON-NLS-1$
          sb.append(pn.getMessage());
          sb.append("<br>"); //$NON-NLS-1$
        }
        text = sb.toString();
      }

      return text;
    }

    /**
     * Paint component.
     *
     * @param g Graphics
     * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
     */
    @Override
    protected void paintComponent(Graphics g) {
      int w = getWidth();
      int h = getHeight();

      Color[] colors = getColors();

      g.setColor(colors[0] != null ? colors[0] : Color.LIGHT_GRAY);
      g.fillRect(0, 0, w, h);

      if (colors[1] != null) {
        GUIUtilities.drawBorder(g, colors[1], 0, 0, w, h);
      }
    }

    /**
     * Removes the notify.
     *
     * @see javax.swing.JComponent#removeNotify()
     */
    @Override
    public void removeNotify() {
      super.removeNotify();
      ToolTipManager.sharedInstance().unregisterComponent(this);
      removeMouseListener(mouseHandler);
    }

    /**
     * Update location.
     */
    void updateLocation() {
      int line = notices.get(0).getLine();
      int y = lineToY(line);
      setLocation(2, y);
    }

    /**
     * Contains notice.
     *
     * @param not the notice
     * @return true, if successful
     */
    boolean containsNotice(Notice not) {
      boolean result = false;
      for (Notice notice : notices) {
        if (notice.equals(not)) {
          result = true;
          break;
        }
      }
      return result;
    }
    
    /**
     * Handle mouse clicked.
     *
     * @param e MouseEvent
     */
    void handleMouseClicked(final MouseEvent e) {
      Notice n = notices.get(0);
      int offs = n.getOffset();
      int len = n.getLength();
      if (offs > -1 && len > -1) { // These values are optional
        textArea.setSelectionStart(offs);
        textArea.setSelectionEnd(offs + len /*- 1*/);
      }
      else {
        int line = n.getLine();
        try {
          offs = textArea.getLineStartOffset(line);
          textArea.setCaretPosition(offs);
        }
        catch (BadLocationException ble) { // Never happens
          UIManager.getLookAndFeel().provideErrorFeedback(textArea);
        }
      }
    }
    
    ////////////////////////////////////////////////////////////////////////////
    /**
     * The Class MouseHandler.
     */
    private class MouseHandler extends MouseAdapter implements Serializable {
      /** UID */
      private static final long serialVersionUID = 8877013585055386988L;

      /**
       * Mouse clicked.
       *
       * @param e MouseEvent
       * @see java.awt.event.MouseAdapter#mouseClicked(java.awt.event.MouseEvent)
       */
      @Override 
      public void mouseClicked(final MouseEvent e) {
        handleMouseClicked(e);
      }
    }
    ////////////////////////////////////////////////////////////////////////////
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  /**
   * The Interface Notice.
   */
  private interface Notice extends Comparable<Notice> {
    /**
     *
     * @param pos int
     * @return boolean
     */
    boolean containsPosition(int pos);

    /**
     * Gets the color.
     *
     * @return the color
     */
    Color getColor();

    /**
     * Gets the border color.
     *
     * @return the border color
     */
    Color getBorderColor();

    /**
     * Gets the length.
     *
     * @return the length
     */
    int getLength();

    /**
     * Gets the line.
     *
     * @return the line
     */
    int getLine();

    /**
     * Gets the message.
     *
     * @return the message
     */
    public String getMessage();

    /**
     * Gets the offset.
     *
     * @return the offset
     */
    int getOffset();

    /**
     * Gets the tool tip text.
     *
     * @return the tool tip text
     */
    String getToolTipText();

    /**
     * Gets the level.
     *
     * @return the level
     */
    int getLevel();

    /**
     * Gets the range.
     *
     * @return the range
     */
    DocumentRange getRange();
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  /**
   * The Class AbstractNotice.
   */
  private abstract class AbstractNotice implements Notice {
    DocumentRange range;
    Color         color;
    Color         borderColor;

    /**
     * Instantiates a new abstract notice.
     *
     * @param range the range
     * @param color the color
     * @param borderColor the border color
     */
    AbstractNotice(DocumentRange range, Color color, Color borderColor) {
      this.range = range;
      this.color = color;
      this.borderColor = borderColor;
    }

    /**
     * Contains position.
     *
     * @param pos the pos
     * @return true, if successful
     * @see jnpad.text.MarkStrip.Notice#containsPosition(int)
     */
    @Override
    public boolean containsPosition(int pos) {
      return pos >= range.getStartOffset() && pos < range.getEndOffset();
    }

    /**
     * Hash code.
     *
     * @return the int
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
      return range.hashCode();
    }

    /**
     * Gets the color.
     *
     * @return the color
     * @see jnpad.text.MarkStrip.Notice#getColor()
     */
    @Override
    public Color getColor() {
      return color;
    }

    /**
     * Gets the border color.
     *
     * @return the border color
     * @see jnpad.text.MarkStrip.Notice#getBorderColor()
     */
    @Override
    public Color getBorderColor() {
      return borderColor;
    }

    /**
     * Gets the length.
     *
     * @return the length
     * @see jnpad.text.MarkStrip.Notice#getLength()
     */
    @Override
    public int getLength() {
      return range.getEndOffset() - range.getStartOffset();
    }

    /**
     * Gets the line.
     *
     * @return the line
     * @see jnpad.text.MarkStrip.Notice#getLine()
     */
    @Override
    public int getLine() {
      try {
        return textArea.getLineOfOffset(range.getStartOffset());
      }
      catch (BadLocationException ble) {
        return 0;
      }
    }

    /**
     * Gets the offset.
     *
     * @return the offset
     * @see jnpad.text.MarkStrip.Notice#getOffset()
     */
    @Override
    public int getOffset() {
      return range.getStartOffset();
    }
   
    /**
     * Gets the tool tip text.
     *
     * @return the tool tip text
     * @see jnpad.text.MarkStrip.Notice#getToolTipText()
     */
    @Override
    public String getToolTipText() {
      return null;
    }

    /**
     * Gets the range.
     *
     * @return the range
     * @see jnpad.text.MarkStrip.Notice#getRange()
     */
    @Override
    public DocumentRange getRange() {
      return range;
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  /**
   * The Class OccurrenceNotice.
   */
  private class OccurrenceNotice extends AbstractNotice {
    /**
     * Instantiates a new occurrence notice.
     *
     * @param range the range
     * @param color the color
     * @param borderColor the border color
     */
    OccurrenceNotice(DocumentRange range, Color color, Color borderColor) {
      super(range, color, borderColor);
    }

    /**
     * Compare to.
     *
     * @param other Notice
     * @return int
     * @see java.lang.Comparable#compareTo(java.lang.Object)
     */
    @Override
    public int compareTo(Notice other) {
      if (other == null || !(other instanceof OccurrenceNotice)) {
        return 1;
      }
      return range.compareTo(((OccurrenceNotice) other).range);
    }

    /**
     * Equals.
     *
     * @param o the o
     * @return true, if successful
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object o) {
      return o instanceof OccurrenceNotice && compareTo((Notice) o) == 0;
    }

    /**
     * Gets the message.
     *
     * @return the message
     * @see jnpad.text.MarkStrip.Notice#getMessage()
     */
    @Override
    public String getMessage() {
      String text = null;
      try {
        String word = textArea.getText(range.getStartOffset(), getLength());
        text = TextBundle.getString("MarkStrip.occurrence"); //$NON-NLS-1$
        text = MessageFormat.format(text, word);
      }
      catch (BadLocationException ble) {
        UIManager.getLookAndFeel().provideErrorFeedback(textArea);
      }
      return text;
    }

    /**
     * Gets the level.
     *
     * @return the level
     * @see jnpad.text.MarkStrip.Notice#getLevel()
     */
    @Override
    public int getLevel() {
      return 2;
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  /**
   * The Class SearchNotice.
   */
  private class SearchNotice extends AbstractNotice {
    /**
     * Instantiates a new search notice.
     *
     * @param range the range
     * @param color the color
     * @param borderColor the border color
     */
    SearchNotice(DocumentRange range, Color color, Color borderColor) {
      super(range, color, borderColor);
    }

    /**
     * Compare to.
     *
     * @param other Notice
     * @return int
     * @see java.lang.Comparable#compareTo(java.lang.Object)
     */
    @Override
    public int compareTo(Notice other) {
      if (other == null || !(other instanceof SearchNotice)) {
        return 1;
      }
      return range.compareTo(((SearchNotice) other).range);
    }

    /**
     * Equals.
     *
     * @param o the o
     * @return true, if successful
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object o) {
      return o instanceof SearchNotice && compareTo((Notice) o) == 0;
    }

    /**
     * Gets the message.
     *
     * @return the message
     * @see jnpad.text.MarkStrip.Notice#getMessage()
     */
    @Override
    public String getMessage() {
      String text = null;
      try {
        String word = textArea.getText(range.getStartOffset(), getLength());
        text = TextBundle.getString("MarkStrip.search"); //$NON-NLS-1$
        text = MessageFormat.format(text, word);
      }
      catch (BadLocationException ble) {
        UIManager.getLookAndFeel().provideErrorFeedback(textArea);
      }
      return text;
    }

    /**
     * Gets the level.
     *
     * @return the level
     * @see jnpad.text.MarkStrip.Notice#getLevel()
     */
    @Override
    public int getLevel() {
      return 1;
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  /**
   * The Class BookmarkNotice.
   */
  private class BookmarkNotice extends AbstractNotice {
    /**
     * Instantiates a new bookmark notice.
     *
     * @param range the range
     * @param color the color
     * @param borderColor the border color
     */
    BookmarkNotice(DocumentRange range, Color color, Color borderColor) {
      super(range, color, borderColor);
    }

    /**
     * Compare to.
     *
     * @param other Notice
     * @return int
     * @see java.lang.Comparable#compareTo(java.lang.Object)
     */
    @Override
    public int compareTo(Notice other) {
      if (other == null || ! (other instanceof BookmarkNotice)) {
        return 1;
      }
      return range.compareTo( ( (BookmarkNotice) other).range);
    }

    /**
     * Equals.
     *
     * @param o Object
     * @return  boolean
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object o) {
      return o instanceof BookmarkNotice && compareTo((Notice) o) == 0;
    }

    /**
     * Gets the message.
     *
     * @return the message
     * @see jnpad.text.MarkStrip.Notice#getMessage()
     */
    @Override
    public String getMessage() {
      String text = null;
      try {
        String word = textArea.getText(range.getStartOffset(), getLength());
        text = TextBundle.getString("MarkStrip.bookmark"); //$NON-NLS-1$
        text = MessageFormat.format(text, word);
      }
      catch (BadLocationException ble) {
        UIManager.getLookAndFeel().provideErrorFeedback(textArea);
      }
      return text;
    }

    /**
     * Gets the level.
     *
     * @return the level
     * @see jnpad.text.MarkStrip.Notice#getLevel()
     */
    @Override
    public int getLevel() {
      return 0;
    }
  }
  //////////////////////////////////////////////////////////////////////////////

}
