/*
 * 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.search;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.AbstractButton;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.text.Document;

import jnpad.GUIUtilities;
import jnpad.JNPadFrame;
import jnpad.config.Config;
import jnpad.text.EditPane;
import jnpad.text.JNPadTextArea;
import jnpad.text.Buffer;
import jnpad.text.TextUtilities;
import jnpad.ui.JNPadLabel;
import jnpad.ui.plaf.LAFUtils;
import jnpad.util.Utilities;

/**
 * The Class IncrementalSearchPanel.
 *
 * @version 0.3
 * @since   jNPad v0.1
 */
public class IncrementalSearchPanel extends JPanel {
  JPanel                      pnFind           = new JPanel();
  JLabel                      lbFind           = new JNPadLabel();
  JTextField                  tfFind           = new JTextField();
  JButton                     btFindPrevious   = new JButton();
  JButton                     btFindNext       = new JButton();
  JToggleButton               tbHighlightAll   = new JToggleButton();
  JToggleButton               tbWholeWord      = new JToggleButton();
  JToggleButton               tbMatchCase      = new JToggleButton();
  JLabel                      lbNotFound       = new JNPadLabel();
  JPanel                      pnButton         = new JPanel();
  JButton                     btClose          = new JButton();

  private Buffer              _buffer;
  private int                 _actualPosition  = -1;

  private boolean             notFound;

  private static final Color  NOT_FOUND_BG     = new Color(255, 102, 102);

  private JNPadFrame          jNPad;

  private ChangeListener      changeHandler    = new ChangeHandler();

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

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

  /**
   * Instantiates a new incremental search panel.
   *
   * @param jNPad JNPadFrame
   */
  public IncrementalSearchPanel(JNPadFrame jNPad) {
    super(new BorderLayout());
    try {
      this.jNPad = jNPad;

      jbInit();
    }
    catch (Exception ex) {
      LOGGER.log(Level.WARNING, ex.getMessage(), ex);
    }
  }

  /**
   * Component initialization.
   *
   * @throws Exception the exception
   */
  private void jbInit() throws Exception {
    lbFind.setText(SearchBundle.getString("IncrementalSearchPanel.find")); //$NON-NLS-1$
    tfFind.setColumns(30);

    pnFind.setLayout(new FlowLayout(FlowLayout.LEFT));
    pnFind.add(lbFind, null);
    pnFind.add(tfFind, null);
    pnFind.add(btFindPrevious, null);
    pnFind.add(btFindNext, null);
    pnFind.add(Box.createRigidArea(new Dimension(10, 5)), null);
    pnFind.add(tbMatchCase, null);
    pnFind.add(tbWholeWord, null);
    pnFind.add(tbHighlightAll, null);
    pnFind.add(lbNotFound, null);

    pnButton.setLayout(new FlowLayout(FlowLayout.RIGHT));
    pnButton.add(btClose, null);

    add(pnFind, BorderLayout.CENTER);
    add(pnButton, BorderLayout.EAST);

    ActionListener actionHandler = new ActionHandler();

    setButtonMargin(btFindPrevious);
    btFindPrevious.setIcon(GUIUtilities.loadIcon("find-previous.png")); //$NON-NLS-1$
    btFindPrevious.setToolTipText(SearchBundle.getString("IncrementalSearchPanel.findPrevious")); //$NON-NLS-1$
    btFindPrevious.addActionListener(actionHandler);

    setButtonMargin(btFindNext);
    btFindNext.setIcon(GUIUtilities.loadIcon("find-next.png")); //$NON-NLS-1$
    btFindNext.setToolTipText(SearchBundle.getString("IncrementalSearchPanel.findNext")); //$NON-NLS-1$
    btFindNext.addActionListener(actionHandler);

    setButtonMargin(tbMatchCase);
    tbMatchCase.setIcon(GUIUtilities.loadIcon("match-case.png")); //$NON-NLS-1$
    tbMatchCase.setToolTipText(SearchBundle.getString("IncrementalSearchPanel.matchCase")); //$NON-NLS-1$
    tbMatchCase.setSelected(Config.FIND_MATCH_CASE.getValue());
    tbMatchCase.addActionListener(actionHandler);

    setButtonMargin(tbWholeWord);
    tbWholeWord.setIcon(GUIUtilities.loadIcon("match-whole-word.png")); //$NON-NLS-1$
    tbWholeWord.setToolTipText(SearchBundle.getString("IncrementalSearchPanel.matchWholeWord")); //$NON-NLS-1$
    tbWholeWord.setSelected(Config.FIND_MATCH_WHOLEWORD.getValue());
    tbWholeWord.addActionListener(actionHandler);

    setButtonMargin(tbHighlightAll);
    tbHighlightAll.setIcon(GUIUtilities.loadIcon("highlight-all.png")); //$NON-NLS-1$
    tbHighlightAll.setToolTipText(SearchBundle.getString("IncrementalSearchPanel.highlightAll")); //$NON-NLS-1$
    tbHighlightAll.setSelected(Config.FIND_HIGHLIGHT_ALL.getValue());
    tbHighlightAll.addActionListener(actionHandler);

    setButtonMargin(btClose);
    btClose.setIcon(GUIUtilities.loadIcon("cancel.png")); //$NON-NLS-1$
    btClose.addActionListener(actionHandler);

    tfFind.addKeyListener(new KeyAdapter() {
      @Override
      public void keyReleased(KeyEvent ke) {
        if(checkBuffer()) {
          if (ke.getKeyCode() == KeyEvent.VK_ENTER) {
            searchNext(true, false);
          }
          else {
            _actualPosition = _buffer.getCaretPosition() - 1;
            searchNext(true, true, false);
          }
        }
      }
    });
  }

  /**
   * Adds the notify.
   *
   * @see javax.swing.JComponent#addNotify()
   */
  @Override
  public void addNotify() {
    super.addNotify();
    jNPad.getActiveBufferSet().addChangeListener(changeHandler);
    setBuffer();
  }

  /**
   * Removes the notify.
   *
   * @see javax.swing.JComponent#removeNotify()
   */
  @Override
  public void removeNotify() {
    super.removeNotify();
    jNPad.getActiveBufferSet().removeChangeListener(changeHandler);
  }

  /**
   * Sets the button margin.
   *
   * @param bt the new button margin
   */
  private static void setButtonMargin(AbstractButton bt) {
    if (LAFUtils.isMetalLAF() || LAFUtils.isMotifLAF()) {
      bt.setMargin(new Insets(0, 0, 0, 0));
    }
    else if (LAFUtils.isWindowsLAF()) {
      bt.setMargin(new Insets(2, 2, 2, 2));
    }
    else if (LAFUtils.isNimbusLAF()) {
      bt.setMargin(new Insets(0, -7, 0, -7));
    }
    else {
      bt.setMargin(new Insets(1, 1, 1, 1));
    }
  }

  /**
   * Gets the context.
   *
   * @param forward the forward
   * @param highlightAll the highlight all
   * @return the context
   */
  private SearchContext getContext(boolean forward, boolean highlightAll) {
    SearchContext context = new SearchContext(tfFind.getText());
    context.setMatchCase(tbMatchCase.isSelected());
    context.setWholeWord(tbWholeWord.isSelected());
    context.setSearchForward(forward);
    context.setHighlightAll(highlightAll);
    return context;
  }

  /**
   * Check buffer.
   *
   * @return true, if successful
   */
  private boolean checkBuffer() {
    if (_buffer == null) {
      _buffer = jNPad.getActiveBuffer();
    }
      return _buffer != null;
  }

  /**
   * Sets the buffer.
   */
  private void setBuffer() {
    Buffer buffer = jNPad.getActiveBuffer();
    if (buffer != null) {
      _buffer = buffer;
      _actualPosition = _buffer.getCaretPosition();
    }
  }

  /**
   * Search next.
   *
   * @param forward the forward
   * @param notifyNotFound the notify not found
   */
  private void searchNext(boolean forward, boolean notifyNotFound) {
    searchNext(forward, notifyNotFound, tbHighlightAll.isSelected());
  }

  /**
   * Search next.
   *
   * @param forward the forward
   * @param notifyNotFound the notify not found
   * @param highlightAll the highlight all
   */
  private void searchNext(boolean forward, boolean notifyNotFound, boolean highlightAll) {
    final EditPane      editPane = _buffer.getSelectedEditPane();
    final JNPadTextArea  textArea = _buffer.getSelectedTextArea();
    final SearchContext context  = getContext(forward, highlightAll);

    String searchFor = context.getSearchFor();
    if (Utilities.isEmptyString(searchFor)) {
      return;
    }

    // remove highlight
    editPane.clearSearchHighlight();
    if(highlightAll && editPane.hasOccurrences()) {
      SearchContext context2 = editPane.getOccurrencesContext().getSearchContext();
      if (!context.equals2(context2)) {
        editPane.clearAllOccurrences();
      }
    }

    int incr = 1;
    if (!forward) {
      incr = -1;
    }
    _actualPosition = search(textArea.getDocument(), searchFor, _actualPosition + incr, !forward);

    if (_actualPosition > Utilities.INDEX_NOT_FOUND) {
      // resaltar b?squeda
      try {
        editPane.highlightSearch(context, _actualPosition, _actualPosition + searchFor.length());
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }

      if (notFound && notifyNotFound) {
        tfFind.setBackground(LAFUtils.getTextFieldBackground());
        tfFind.setForeground(LAFUtils.getTextFieldForeground());
        notFound = false;
        lbNotFound.setText(Utilities.EMPTY_STRING);
      }
    }
    else if (notifyNotFound) {
      tfFind.setBackground(NOT_FOUND_BG);
      tfFind.setForeground(Color.WHITE);

      notFound = true;

      lbNotFound.setText(SearchBundle.getString("IncrementalSearchPanel.notFound")); //$NON-NLS-1$

      tfFind.requestFocusInWindow();
    }
    else {
      searchNext(forward, notifyNotFound, highlightAll);
    }
  }

  /**
   * Search.
   *
   * @param doc the doc
   * @param searchFor the search for
   * @param from the from
   * @param backward the backward
   * @return the int
   */
  private int search(Document doc, String searchFor, int from, boolean backward) {
    if (backward) {
      return searchBackward(doc, searchFor, from, !tbMatchCase.isSelected(), tbWholeWord.isSelected());
    }
    return searchFoward(doc, searchFor, from, !tbMatchCase.isSelected(), tbWholeWord.isSelected());
  }

  /**
   * Search foward.
   *
   * @param doc the doc
   * @param searchFor the search for
   * @param from the from
   * @param ignoreCase the ignore case
   * @param wholeWord the whole word
   * @return the int
   */
  private static int searchFoward(Document doc, String searchFor, int from, boolean ignoreCase, boolean wholeWord) {
    if (from < 0) {
      from = 0;
    }

    String searchIn = TextUtilities.getTextFromTo(doc, from, doc.getLength());
    int pos = Utilities.indexOf(searchIn, searchFor, ignoreCase, wholeWord);
    if (pos != Utilities.INDEX_NOT_FOUND) {
      return pos + from;
    }

    searchIn = TextUtilities.getTextFromTo(doc, 0, from);
    pos = Utilities.indexOf(searchIn, searchFor, ignoreCase, wholeWord);
    if (pos != Utilities.INDEX_NOT_FOUND) {
      return pos;
    }

    return Utilities.INDEX_NOT_FOUND;
  }

  /**
   * Search backward.
   *
   * @param doc the doc
   * @param searchFor the search for
   * @param from the from
   * @param ignoreCase the ignore case
   * @param wholeWord the whole word
   * @return the int
   */
  private static int searchBackward(Document doc, String searchFor, int from, boolean ignoreCase, boolean wholeWord) {
    if (from < 0) {
      from = doc.getLength();
    }

    String searchIn = TextUtilities.getTextFromTo(doc, 0, from);
    int pos = Utilities.lastIndexOf(searchIn, searchFor, ignoreCase, wholeWord);
    if (pos != Utilities.INDEX_NOT_FOUND) {
      return pos;
    }

    searchIn = TextUtilities.getTextFromTo(doc, from, doc.getLength());
    pos = Utilities.lastIndexOf(searchIn, searchFor, ignoreCase, wholeWord);
    if (pos != Utilities.INDEX_NOT_FOUND) {
      return pos + from;
    }

    return Utilities.INDEX_NOT_FOUND;
  }

  /**
   * Request focus.
   *
   * @see javax.swing.JComponent#requestFocus()
   */
  @Override
  public void requestFocus() {
    if (tfFind != null) {
      tfFind.requestFocus();
    }
    else {
      super.requestFocus();
    }
  }

  /**
   * Request focus in window.
   *
   * @return true, if successful
   * @see javax.swing.JComponent#requestFocusInWindow()
   */
  @Override
  public boolean requestFocusInWindow() {
    if (tfFind != null) {
      return tfFind.requestFocusInWindow();
    }
    return super.requestFocusInWindow();
  }

  //////////////////////////////////////////////////////////////////////////////
  /**
   * The Class ActionHandler.
   */
  private class ActionHandler implements ActionListener {
    /**
     * Action performed.
     *
     * @param e the ActionEvent
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      Object obj = e.getSource();
      if (obj == btClose) {
        jNPad.setIncrementalSearchVisible(false);
      }
      else if (obj == btFindPrevious && checkBuffer()) {
        _actualPosition = _buffer.getCaretPosition();
        searchNext(false, false);
        _buffer.requestFocus();
      }
      else if (obj == btFindNext && checkBuffer()) {
        _actualPosition = _buffer.getCaretPosition();
        searchNext(true, false);
        _buffer.requestFocus();
      }
      else if ((obj == tbMatchCase || obj == tbWholeWord) && checkBuffer()) {
        _actualPosition = -1; // desde el principio
        searchNext(true, true);
        _buffer.requestFocus();
      }
      else if (obj == tbHighlightAll && checkBuffer()) {
        final EditPane editPane = _buffer.getSelectedEditPane();
        if (tbHighlightAll.isSelected()) {
          editPane.highlightAllOccurrences(getContext(true, true));
        }
        else {
          editPane.clearAllOccurrences();
        }
        _buffer.requestFocus();
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  /**
   * The Class ChangeHandler.
   */
  private class ChangeHandler implements ChangeListener, Serializable {
    /** UID */
    private static final long serialVersionUID = 5433934188372069571L;

    /**
     * State changed.
     *
     * @param e the ChangeEvent
     * @see javax.swing.event.ChangeListener#stateChanged(javax.swing.event.ChangeEvent)
     */
    public void stateChanged(ChangeEvent e) {
      setBuffer();
    }
  }
  //////////////////////////////////////////////////////////////////////////////

}
