/*
 * 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.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.TreeSet;
import java.util.logging.Level;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JOptionPane;
import javax.swing.text.BadLocationException;

import jnpad.GUIUtilities;
import jnpad.JNPadFrame;
import jnpad.config.Config;
import jnpad.text.EditPane;
import jnpad.text.JNPadTextArea;
import jnpad.text.Buffer;
import jnpad.ui.JNPadCheckBox;
import jnpad.ui.status.StatusDisplayable;
import jnpad.util.Utilities;

/**
 * The Class FindDialog.
 *
 * @version 0.3
 * @since   jNPad v0.1
 */
public class FindDialog extends SearchDialog {
  JCheckBox                 cbHighlightAll   = new JNPadCheckBox();
  JButton                   btCount          = new JButton();
  JButton                   btMarkAll        = new JButton();
  JButton                   btFindAll        = new JButton();

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

  /**
   * Instantiates a new find dialog.
   *
   * @param jNPad JNPadFrame
   */
  public FindDialog(JNPadFrame jNPad) {
    super(jNPad, SearchBundle.getString("FindDialog.title")); //$NON-NLS-1$
    try {
      jbInit();

      pack();
      setLocationRelativeTo(jNPad);
      setVisible(true);
    }
    catch (Exception ex) {
      LOGGER.log(Level.WARNING, ex.getMessage(), ex);
    }
  }

  /**
   * Component initialization.
   *
   * @throws Exception the exception
   */
  private void jbInit() throws Exception {
    cbHighlightAll.setText(SearchBundle.getString("FindDialog.highlightAll")); //$NON-NLS-1$

    btCount.setText(SearchBundle.getString("FindDialog.count")); //$NON-NLS-1$
    btCount.addActionListener(actionHandler);
    btMarkAll.setText(SearchBundle.getString("FindDialog.markAll")); //$NON-NLS-1$
    btMarkAll.addActionListener(actionHandler);
    btFindAll.setText(SearchBundle.getString("FindDialog.findAll")); //$NON-NLS-1$
    btFindAll.addActionListener(actionHandler);

    pnButtons.add(btFind, null);
    pnButtons.add(btCount, null);
    pnButtons.add(btMarkAll, null);
    pnButtons.add(btFindAll, null);
    pnButtons.add(btClose, null);

    pnOptionsN.add(pnFind, null);
    pnOptionsN.add(pnLookIn, null);

    pnOptionsSW.add(cbHighlightAll, null);

    GUIUtilities.setRelativeSize(lbFind, lbLookIn);

    loadState();
  }

  /**
   * Load state.
   *
   * @see jnpad.search.SearchDialog#loadState()
   */
  @Override
  void loadState() {
    GUIUtilities.setComboBoxList(cmbFind, Config.getFindList());

    int lookIn = Config.FIND_LOOKIN.getValue();
    if(lookIn >= 0 && lookIn <= 2)
      cmbLookIn.setSelectedIndex(lookIn);

    cbFindFromCursor.setSelected(Config.FIND_FROMCURSOR.getValue());
    cbMatchCase.setSelected(Config.FIND_MATCH_CASE.getValue());
    cbMatchWholeWord.setSelected(Config.FIND_MATCH_WHOLEWORD.getValue());
    cbHighlightAll.setSelected(Config.FIND_HIGHLIGHT_ALL.getValue());

    if (Config.FIND_DIRECTION_DOWN.getValue())
      rbFindFoward.setSelected(true);
    else
      rbFindBackward.setSelected(true);
  }

  /**
   * Save state.
   *
   * @see jnpad.search.SearchDialog#saveState()
   */
  @SuppressWarnings("unchecked")
  @Override
  void saveState() {
    Config.setFindList(GUIUtilities.getComboBoxList(cmbFind));

    Config.saveSearchHistorial();

    Config.FIND_LOOKIN.setValue(cmbLookIn.getSelectedIndex());
    Config.FIND_FROMCURSOR.setValue(cbFindFromCursor.isSelected());
    Config.FIND_MATCH_CASE.setValue(cbMatchCase.isSelected());
    Config.FIND_MATCH_WHOLEWORD.setValue(cbMatchWholeWord.isSelected());
    Config.FIND_HIGHLIGHT_ALL.setValue(cbHighlightAll.isSelected());
    Config.FIND_DIRECTION_DOWN.setValue(rbFindFoward.isSelected());
  }

  /**
   * Initialize.
   *
   * @param hide the hide
   * @return true, if successful
   * @see jnpad.search.SearchDialog#initialize(boolean)
   */
  @Override
  boolean initialize(boolean hide) {
    if (Utilities.isEmptyString(getSearchFor()) || !checkBuffer())
      return false;

    GUIUtilities.updateComboBoxList(cmbFind);

    if (hide) {
      searchManagerDialog.setMode(SearchManagerDialog.FIND, getSearchFor(), null);
    }

    saveState();

    if (hide) {
      setVisible(false);
    }

    resetFind();

    return true;
  }

  /**
   * Gets the search context.
   *
   * @return the search context
   * @see jnpad.search.SearchDialog#getSearchContext()
   */
  @Override
  SearchContext getSearchContext() {
    SearchContext context = new SearchContext(getSearchFor());
    context.setMatchCase(cbMatchCase.isSelected());
    context.setWholeWord(cbMatchWholeWord.isSelected());
    context.setSearchForward(rbFindFoward.isSelected());
    context.setHighlightAll(cbHighlightAll.isSelected());
    return context;
  }

  /**
   * Removes the highlight.
   *
   * @param editPane the edit pane
   * @param context the context
   * @see jnpad.search.SearchDialog#removeHighlight(jnpad.text.EditPane, jnpad.search.SearchContext)
   */
  @Override
  void removeHighlight(EditPane editPane, SearchContext context) {
    editPane.clearSearchHighlight();
    if(editPane.hasOccurrences()) {
      SearchContext context2 = editPane.getOccurrencesContext().getSearchContext();
      if (!context.equals2(context2)) {
        editPane.clearAllOccurrences();
      }
    }
  }

  /**
   * Find next.
   */
  public void findNext() {
    _buffer = jNPad.getActiveBuffer();
    doFindNext();
  }

  /**
   * Do find next.
   */
  private void doFindNext() {
    if (!searchNext()) {
      int option = reportWordNotFound2();

      if (option == 1 || option == JOptionPane.CLOSED_OPTION)
        return;

      resetFind(true);

      doFindNext(); // recursivo
    }
  }

  /**
   * Find previous.
   */
  public void findPrevious() {
    _buffer = jNPad.getActiveBuffer();
    doFindPrevious();
  }

  /**
   * Do find previous.
   */
  private void doFindPrevious() {
    if (!searchPrevious()) {
      int option = reportWordNotFound2();

      if (option == 1 || option == JOptionPane.CLOSED_OPTION)
        return;

      resetFind(false);

      doFindPrevious();  // recursivo
    }
  }

  /**
   * Find in open documments.
   */
  private void findInOpenDocumments() {
    for (Buffer buffer : jNPad.getViewer().getBuffers()) {
      resetFind();
      doFind(buffer, true);
    }
  }

  /**
   * Sets the visible.
   *
   * @param b the new visible
   * @see java.awt.Dialog#setVisible(boolean)
   */
  @Override
  public void setVisible(boolean b) {
    if (b) {
      lbStatusBar.setText(Utilities.SPACE_STRING);
      cmbFind.requestFocus();
      _buffer = jNPad.getActiveBuffer();
      try {
        if (_buffer != null && cmbLookIn.getSelectedIndex() != SELECTION) {
          String selectedText = _buffer.getSelectedTextArea().getSelectedText();
          if (Utilities.isNotEmptyString(selectedText)) {
            cmbFind.getEditor().setItem(selectedText);
            cmbFind.getEditor().selectAll();
          }
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }
    else {
      saveState();
    }
    super.setVisible(b);
  }


  /**
   * Handle key pressed.
   *
   * @param e the KeyEvent
   * @see jnpad.search.SearchDialog#handleKeyPressed(java.awt.event.KeyEvent)
   */
  @Override
  void handleKeyPressed(KeyEvent e) {
    Object obj = e.getSource();
    if (e.getKeyCode() == KeyEvent.VK_ENTER) {
      if (obj == cmbFind.getEditor().getEditorComponent()) {
        actionHandler.actionPerformed(new ActionEvent(btFind, ActionEvent.ACTION_PERFORMED, Utilities.EMPTY_STRING));
      }
    }
  }

  /**
   * Handle action performed.
   *
   * @param e the ActionEvent
   * @see jnpad.search.SearchDialog#handleActionPerformed(java.awt.event.ActionEvent)
   */
  @Override
  void handleActionPerformed(ActionEvent e) {
    Object obj = e.getSource();
    if (obj == cmbLookIn) {
      int selectedIndex = cmbLookIn.getSelectedIndex();
      boolean b = selectedIndex == ACTIVE_DOCUMENT || selectedIndex == SELECTION;
      btFind.setEnabled(b);
    }
    else if (obj == btMarkAll) {
      switch (cmbLookIn.getSelectedIndex()) {
        case ACTIVE_DOCUMENT: mark(false)           ; break;
        case OPEN_DOCUMENTS : markInOpenDocumments(); break;
        case SELECTION      : mark(true)            ; break;
        default: //Keep FindBugs happy
          break;
      }
      setVisible(false);
    }
    else if (obj == btCount && initialize(false)) {
      switch(cmbLookIn.getSelectedIndex()) {
        case ACTIVE_DOCUMENT: count(false)           ; break;
        case OPEN_DOCUMENTS : countInOpenDocumments(); break;
        case SELECTION      : count(true)            ; break;
      }
    }
    else if (obj == btFindAll && initialize(true)) {
      if (cmbLookIn.getSelectedIndex() == OPEN_DOCUMENTS)
        findInOpenDocumments();
      else
        find(true);
    }
    else {
      super.handleActionPerformed(e);
    }
  }

  // --- mark ------------------------------------------------------------------
  /**
   * Mark in open documments.
   */
  private void markInOpenDocumments() {
    int count = 0;
    for (Buffer buffer : jNPad.getViewer().getBuffers()) {
      count += doMark(buffer, false);
    }
    notifyMarksCount(count);
  }

  /**
   * Mark.
   *
   * @param selection the selection
   */
  private void mark(boolean selection) {
    if (!checkBuffer())
      return;
    notifyMarksCount(doMark(_buffer, selection));
  }

  /**
   * Notify marks count.
   *
   * @param count the count
   */
  private void notifyMarksCount(int count) {
    jNPad.setStatus(SearchBundle.getString("FindDialog.status.markAll", count), StatusDisplayable.TIMEOUT_DEFAULT); //$NON-NLS-1$
  }

  /**
   * Do mark.
   *
   * @param buffer the buffer
   * @param selection the selection
   * @return the int
   */
  private int doMark(Buffer buffer, boolean selection) {
    final EditPane     editPane = buffer.getSelectedEditPane();
    final JNPadTextArea textArea = buffer.getSelectedTextArea();

    String searchFor = getSearchFor();
    String searchIn = textArea.getText();

    char[] searchInArray;
    char[] searchForArray;

    if (cbMatchCase.isSelected()) {
      searchInArray = searchIn.toCharArray();
      searchForArray = searchFor.toCharArray();
    }
    else {
      searchInArray = searchIn.toLowerCase().toCharArray();
      searchForArray = searchFor.toLowerCase().toCharArray();
    }

    int minIndex, maxIndex;

    if (selection) {
      minIndex = textArea.getSelectionStart();
      maxIndex = textArea.getSelectionEnd();
    }
    else {
      minIndex = 0;
      maxIndex = textArea.getDocument().getLength() - 1;
    }

    int len = searchInArray.length;

    TreeSet<Integer> bookmarks = new TreeSet<Integer>();

    for (int index = minIndex; index < len && index <= maxIndex; index++) {
      if (searchInArray[index] != searchForArray[0] || (index + searchForArray.length) - 1 > maxIndex)
        continue;
      int j = index + 1;
      int ln;
      for (ln = 1; ln < searchForArray.length && searchInArray[j] == searchForArray[ln]; ln++)
        j++;

      if (ln != searchForArray.length)
        continue;

      if (!checkWholeWord(searchInArray, index, ln, minIndex, maxIndex))
        continue;

      try {
        int line = textArea.getLineOfOffset(index);
        bookmarks.add(line);
      }
      catch(BadLocationException ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }


    if(!bookmarks.isEmpty()) {
      editPane.addBookmarks(bookmarks);
    }

    return bookmarks.size();
  }
  // ---------------------------------------------------------------------------

  // --- count -----------------------------------------------------------------
  /**
   * Count in open documments.
   */
  private void countInOpenDocumments() {
    int count = 0;
    for (Buffer buffer : jNPad.getViewer().getBuffers()) {
      count += doCount(buffer, false);
    }
    notifyOccurrencesCount(count);
  }

  /**
   * Count.
   *
   * @param selection the selection
   */
  private void count(boolean selection) {
    if (!checkBuffer())
      return;
    notifyOccurrencesCount(doCount(_buffer, selection));
  }

  /**
   * Notify occurrences count.
   *
   * @param count the count
   */
  private void notifyOccurrencesCount(int count) {
    lbStatusBar.setText(SearchBundle.getString("FindDialog.status.count", count)); //$NON-NLS-1$
  }

  /**
   * Do count.
   *
   * @param buffer the buffer
   * @param selection the selection
   * @return the int
   */
  private int doCount(Buffer buffer, boolean selection) {
    final JNPadTextArea textArea = buffer.getSelectedTextArea();

    String searchFor = getSearchFor();
    String searchIn;

    int count = 0;

    if(!cbMatchWholeWord.isSelected()) {
      searchIn = selection ? textArea.getSelectedText() : textArea.getText();
      if (!cbMatchCase.isSelected()) {
        searchFor = searchFor.toLowerCase();
        searchIn = searchIn.toLowerCase();
      }
      count = Utilities.countMatches(searchIn, searchFor);
    }
    else {
      searchIn = textArea.getText();

      char[] searchInArray;
      char[] searchForArray;

      if (cbMatchCase.isSelected()) {
        searchInArray = searchIn.toCharArray();
        searchForArray = searchFor.toCharArray();
      }
      else {
        searchInArray = searchIn.toLowerCase().toCharArray();
        searchForArray = searchFor.toLowerCase().toCharArray();
      }

      int minIndex, maxIndex;

      if (selection) {
        minIndex = textArea.getSelectionStart();
        maxIndex = textArea.getSelectionEnd();
      }
      else {
        minIndex = 0;
        maxIndex = textArea.getDocument().getLength() - 1;
      }

      int len = searchInArray.length;

      for (int index = minIndex; index < len && index <= maxIndex; index++) {
        if (searchInArray[index] != searchForArray[0] || (index + searchForArray.length) - 1 > maxIndex)
          continue;
        int j = index + 1;
        int ln;
        for (ln = 1; ln < searchForArray.length && searchInArray[j] == searchForArray[ln]; ln++)
          j++;

        if (ln != searchForArray.length)
          continue;

        if (!checkWholeWord(searchInArray, index, ln, minIndex, maxIndex))
          continue;

        count++;
      }
    }

    return count;
  }
  // ---------------------------------------------------------------------------

}
