/*
 * 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.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.text.Element;

import jnpad.GUIUtilities;
import jnpad.action.ActionManager;
import jnpad.action.JNPadActions;
import jnpad.config.Config;
import jnpad.ui.JNPadMenuItem;

/**
 * The Class Gutter.
 *
 * @version 0.3
 * @since   jNPad v0.1
 */
public class Gutter extends SimpleGutter {
  boolean                     bracketScopeVisible = true;
  Color                       bracketScopeColor   = Color.ORANGE;

  /** Logger */
  private static final Logger LOGGER              = Logger.getLogger(Gutter.class.getName());

  /** UID */
  private static final long   serialVersionUID    = -5244882856595180262L;

  /**
   * Instantiates a new gutter.
   *
   * @param textArea the text area
   */
  public Gutter(JNPadTextArea textArea) {
    this(textArea, 3);
  }

  /**
   * Instantiates a new gutter.
   *
   * @param textArea the text area
   * @param minimumDisplayDigits the minimum display digits
   */
  public Gutter(JNPadTextArea textArea, int minimumDisplayDigits) {
    super(textArea, minimumDisplayDigits);

    doConfigure(CFG_ALL);
  }

  /**
   * Configure.
   *
   * @param cfg the cfg
   * @see jnpad.text.SimpleGutter#configure(int)
   */
  @Override
  public void configure(final int cfg) {
    super.configure(cfg);
    doConfigure(cfg);
  }
  
  /**
   * Do configure.
   *
   * @param cfg the cfg
   */
  private void doConfigure(final int cfg) {
    if ( (cfg & CFG_COLOR) != 0) {
      setBracketScopeColor(Config.GUTTER_BRACKET_SCOPE_COLOR.getValue());
    }
    if ( (cfg & CFG_VIEW) != 0) {
      setBracketScopeVisible(Config.GUTTER_BRACKET_SCOPE_VISIBLE.getValue());
    }
  }

  /**
   * Checks if is bracket scope visible.
   *
   * @return true, if is bracket scope visible
   */
  public boolean isBracketScopeVisible() {
    return bracketScopeVisible;
  }

  /**
   * Sets the bracket scope visible.
   *
   * @param b the new bracket scope visible
   */
  public void setBracketScopeVisible(boolean b) {
    bracketScopeVisible = b;
  }

  /**
   * Gets the bracket scope color.
   *
   * @return the bracket scope color
   */
  public Color getBracketScopeColor() {
    return bracketScopeColor;
  }

  /**
   * Sets the bracket scope color.
   *
   * @param color the new bracket scope color
   */
  public void setBracketScopeColor(Color color) {
    if (color != null) {
      bracketScopeColor = color;
    }
  }
  
  /**
   *  Draw the gutter.
   * 
   * @param g Graphics
   * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
   */
  @Override
  protected void paintComponent(Graphics g) {
    GUIUtilities.setRenderingHints(g);
    superPaintComponent(g);

    // bookmarks area
    g.setColor(getBookmarkBackground());
    if (textArea.isMain()) {
      g.fillRect(0, 0, getBookmarkAreaWidth(), getHeight());
    }
    else {
      g.drawLine(getBookmarkAreaWidth(), 0, getBookmarkAreaWidth(), getHeight());
    }

    // Determine the width of the space available to draw the line number
    FontMetrics fontMetrics = textArea.getFontMetrics(textArea.getFont());
    Insets insets = getInsets();
    int availableWidth = getSize().width - insets.left - insets.right;

    // Determine the rows to draw within the clipped bounds.
    Rectangle clip = g.getClipBounds();
    int rowStartOffset = textArea.viewToModel(new Point(0, clip.y));
    int endOffset = textArea.viewToModel(new Point(0, clip.y + clip.height));

    while (rowStartOffset <= endOffset) {
      try {
        final Rectangle r = textArea.modelToView(rowStartOffset);

        final boolean isCurrentLine = isCurrentLine(rowStartOffset);

        // Paint current line background 
        if (isCurrentLine && isCurrentLineBackgroundEnabled()
            && !editPane.isActiveLineVisible()) { //[added v0.3] 
          g.setColor(getCurrentLineBackground());
          g.fillRect(r.x + getBookmarkAreaWidth(), r.y, getWidth(), r.height);
        }
        
        // Paint the bracket scope
        if(isBracketScopeVisible()) {
          paintBracketScope(g, rowStartOffset, r);
        }

        // Paint the line number
        g.setColor(isCurrentLine ? getCurrentLineForeground() : getForeground());
        String lineNumber = getTextLineNumber(rowStartOffset);
        int stringWidth = fontMetrics.stringWidth(lineNumber);
        int x = getOffsetX(availableWidth, stringWidth) + insets.left;
        int y = getOffsetY(rowStartOffset, fontMetrics);
        g.drawString(lineNumber, x, y);

        // Move to the next row
        rowStartOffset = javax.swing.text.Utilities.getRowEnd(textArea, rowStartOffset) + 1;
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }

    Graphics2D g2D = (Graphics2D) g;

    final Object oldRendering    = g2D.getRenderingHint(RenderingHints.KEY_RENDERING);
    final Object oldAntialiasing = g2D.getRenderingHint(RenderingHints.KEY_ANTIALIASING);

    g2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

    try {
      // Paint bookmarks
      if (editPane.hasBookmarks()) {
        DocumentRange[] ranges = editPane.getBookmarks();
        for (DocumentRange range : ranges) {
          paintBookmark(g, range.getStartOffset());
        }
      }

      // Paint selection
      if (textArea.hasSelection() && getSelectionWidth() > 1) {
        paintSelection(g);
      }
    }
    finally {
      g2D.setRenderingHint(RenderingHints.KEY_RENDERING, oldRendering);
      g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAntialiasing);
    }
  }

  /**
   * Paint bracket scope.
   *
   * @param g Graphics
   * @param rowStartOffset the row start offset
   * @param r Rectangle
   */
  void paintBracketScope(Graphics g, int rowStartOffset, Rectangle r) {
    try {
      final int iScopeStart = editPane.getBracketLineIndex();
      final int iScopeEnd   = editPane.getOtherBracketLineIndex();
      final int iLineIndex  = textArea.getLineOfOffset(rowStartOffset);

      if (iScopeStart >= 0) {
        g.setColor(getBracketScopeColor());
        
        int iX = getBookmarkAreaWidth() + 2; //r.x + getBookmarkAreaWidth();
        int iY = r.y;
        int iW = 2;
        int iH = r.height;
        
        if (iScopeStart == iScopeEnd) {
          // Do nothing.
        }
        else if (iLineIndex == iScopeStart) {
          g.fillRect(iX, iY + iH / 2, 2 * iW, iW);
          g.fillRect(iX, iY + iH / 2 + iW, iW, iH - iH / 2);
        }
        else if (iLineIndex == iScopeEnd) {
          g.fillRect(iX, iY, iW, iH / 2);
          g.fillRect(iX, iY + iH / 2, 2 * iW, iW);
        }
        else if (iLineIndex > iScopeStart && iLineIndex < iScopeEnd) {
          g.fillRect(iX, iY, iW, iH);
        }
      }
    }
    catch (Exception ex) {
      LOGGER.log(Level.WARNING, ex.getMessage(), ex);
    }
  }

  /**
   * Check for repaint.
   *
   * @see jnpad.text.SimpleGutter#checkForRepaint()
   */
  @Override
  void checkForRepaint() {
    super.checkForRepaint();
    editPane.updateSelection();
  }

  /**
   * Handle mouse released.
   *
   * @param e the MouseEvent
   * @see jnpad.text.SimpleGutter#handleMouseReleased(java.awt.event.MouseEvent)
   */
  @Override
  void handleMouseReleased(final MouseEvent e) {
    Object obj = e.getSource();
    if (obj == Gutter.this) {
      Rectangle bounds = new Rectangle(0, 0, getBookmarkAreaWidth(), Gutter.this.getHeight());
      if (bounds.contains(e.getPoint()) && e.isPopupTrigger()) {
        showTogglePopup(e);
      }
    }
  }

  /**
   * Handle mouse pressed.
   *
   * @param e the MouseEvent
   * @see jnpad.text.SimpleGutter#handleMousePressed(java.awt.event.MouseEvent)
   */
  @Override
  void handleMousePressed(final MouseEvent e) {
    Object obj = e.getSource();
    if (obj == textArea) {
      checkForRepaint();
    }
    else if (obj == this) {
      Rectangle bounds = new Rectangle(0, 0, getBookmarkAreaWidth(), getHeight());
      if (bounds.contains(e.getPoint())) {
        if(SwingUtilities.isLeftMouseButton(e)) 
          toggleBookmark(e);
        else if (e.isPopupTrigger()) 
          showTogglePopup(e);
      }
      else if (e.isControlDown() && e.getClickCount() == 2) {
        toggleBookmark(e);
      }
      else if (!e.isControlDown() && e.getClickCount() == 1) {
        if (e.isShiftDown()) {
          int rowPos = textArea.viewToModel(new Point(0, e.getY()));
          Element line = textArea.getJNPadDocument().getParagraphElement(rowPos);
          selectionEnd = line.getEndOffset() - 1; // -1
          if (selectionBegin <= selectionEnd)
            textArea.select(selectionBegin, selectionEnd);
          else
            textArea.select(selectionEnd, selectionBegin);
        }
        else {
          int rowPos = textArea.viewToModel(new Point(0, e.getY()));
          Element line = textArea.getJNPadDocument().getParagraphElement(rowPos);
          selectionBegin = line.getStartOffset();
          selectionEnd = line.getEndOffset() - 1; // -1
          textArea.select(selectionBegin, selectionEnd);
        }
      }
    }
  }
  
  /**
   * Show toggle popup.
   * 
   * @param e the MouseEvent
   */
  private void showTogglePopup(final MouseEvent e) {
    JPopupMenu popupMenu = new JPopupMenu();
    popupMenu.add(new JNPadMenuItem(ActionManager.INSTANCE.get(JNPadActions.ACTION_NAME_NEXT_BOOKMARK)));
    popupMenu.add(new JNPadMenuItem(ActionManager.INSTANCE.get(JNPadActions.ACTION_NAME_PREVIOUS_BOOKMARK)));
    //popupMenu.add(new JNPadMenuItem(ActionManager.INSTANCE.get(JNPadActions.ACTION_NAME_TOGGLE_BOOKMARK)));
    popupMenu.add(new JNPadMenuItem(ActionManager.INSTANCE.get(JNPadActions.ACTION_NAME_CLEAR_ALL_BOOKMARKS)));
    SwingUtilities.updateComponentTreeUI(popupMenu);
    popupMenu.pack();
    popupMenu.show(e.getComponent(), e.getX(), e.getY());
  }

  /**
   * Toggle_bookmark.
   * 
   * @param e MouseEvent
   */
  private void toggleBookmark(final MouseEvent e) {
    int row = textArea.viewToModel(new Point(0, e.getY()));
    editPane.toggleBookmark(TextUtilities.getLineNumber(textArea.getDocument(), row));
  }
}
