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

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
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.JNPadComboBox;
import jnpad.ui.JNPadLabel;
import jnpad.util.Utilities;

/**
 * The Class ReplaceDialog.
 *
 * @version 0.3
 * @since   jNPad v0.1
 */
public class ReplaceDialog extends SearchDialog {
  JButton                   btReplace        = new JButton();
  JButton                   btReplaceAll     = new JButton();
  JPanel                    pnReplace        = new JPanel();
  JLabel                    lbReplace        = new JNPadLabel();
  JComboBox                 cmbReplace       = new JNPadComboBox();

  private Text              _text;

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

  /**
   * Instantiates a new replace dialog.
   *
   * @param jNPad JNPadFrame
   */
  public ReplaceDialog(JNPadFrame jNPad) {
    super(jNPad, SearchBundle.getString("ReplaceDialog.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 {
    lbReplace.setText(SearchBundle.getString("ReplaceDialog.replaceWith")); //$NON-NLS-1$

    cmbReplace.setEditable(true);
    cmbReplace.getEditor().getEditorComponent().addKeyListener(keyHandler);

    btReplace.setText(SearchBundle.getString("ReplaceDialog.replace")); //$NON-NLS-1$
    btReplace.addActionListener(actionHandler);
    btReplaceAll.setText(SearchBundle.getString("ReplaceDialog.replaceAll")); //$NON-NLS-1$
    btReplaceAll.addActionListener(actionHandler);

    pnButtons.add(btFind, null);
    pnButtons.add(btReplace, null);
    pnButtons.add(btReplaceAll, null);
    pnButtons.add(btClose, null);

    pnReplace.setLayout(new BorderLayout(5, 5));
    pnReplace.add(lbReplace, BorderLayout.WEST);
    pnReplace.add(cmbReplace, BorderLayout.CENTER);

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

    GUIUtilities.setRelativeSize(lbFind, lbReplace, lbLookIn);

    loadState();
  }

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

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

    cbFindFromCursor.setSelected(Config.REPLACE_FROMCURSOR.getValue());
    cbMatchCase.setSelected(Config.REPLACE_MATCH_CASE.getValue());
    cbMatchWholeWord.setSelected(Config.REPLACE_MATCH_WHOLEWORD.getValue());

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

    if (_text != null)
      _text = null;
  }

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

    Config.saveSearchHistorial();

    Config.REPLACE_LOOKIN.setValue(cmbLookIn.getSelectedIndex());
    Config.REPLACE_FROMCURSOR.setValue(cbFindFromCursor.isSelected());
    Config.REPLACE_MATCH_CASE.setValue(cbMatchCase.isSelected());
    Config.REPLACE_MATCH_WHOLEWORD.setValue(cbMatchWholeWord.isSelected());
    Config.REPLACE_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);
    GUIUtilities.updateComboBoxList(cmbReplace);

    if (hide) {
      searchManagerDialog.setMode(SearchManagerDialog.REPLACE, getSearchFor(), getReplaceWith());
    }

    resetReplace();

    saveState();

    if (hide) {
      setVisible(false);
    }

    resetFind();

    return true;
  }

  /**
   * Gets the replace with.
   *
   * @return the replace with
   */
  String getReplaceWith() {
    return GUIUtilities.getItem(cmbReplace);
  }

  /**
   * Gets the search context.
   *
   * @return the search context
   * @see jnpad.search.SearchDialog#getSearchContext()
   */
  @Override
  SearchContext getSearchContext() {
    SearchContext context = new SearchContext(getSearchFor(), getReplaceWith());
    context.setMatchCase(cbMatchCase.isSelected());
    context.setWholeWord(cbMatchWholeWord.isSelected());
    context.setSearchForward(rbFindFoward.isSelected());
    context.setHighlightAll(false);
    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();
    editPane.clearAllOccurrences();
  }

  /**
   * Reset replace.
   *
   * @see jnpad.search.SearchDialog#resetReplace()
   */
  @Override
  void resetReplace() {
    _text = null;
  }

  /**
   * Replace in open documments.
   */
  private void replaceInOpenDocumments() {
    for (Buffer eview : jNPad.getViewer().getBuffers()) {
      resetFind();
      resetReplace();
      doReplace(eview, true);
    }
  }

  /**
   * Replace next.
   */
  public void replaceNext() {
    _buffer = jNPad.getActiveBuffer();
    doReplaceNext();
  }

  /**
   * Do replace next.
   */
  private void doReplaceNext() {
    if (!replace(false)) {
      int option = reportWordNotFound2();

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

      resetFind(true);

      doReplace(_buffer, false);
    }
  }

  /**
   * Replace.
   *
   * @param all the all
   * @return true, if successful
   * @see jnpad.search.SearchDialog#replace(boolean)
   */
  @Override
  boolean replace(boolean all) {
    return checkBuffer() && doReplace(_buffer, all);
  }

  /**
   * Gets the char text.
   *
   * @param textArea the text area
   * @return the char text
   */
  private char[] getCharText(JNPadTextArea textArea) {
    String text = textArea.getText();
    if (cbMatchCase.isSelected()) {
      return text.toCharArray();
    }
    return text.toLowerCase().toCharArray();
  }

  /**
   * Adjust max position.
   *
   * @param textArea the text area
   */
  private void adjustMaxPosition(JNPadTextArea textArea) {
    final boolean selection = cmbLookIn.getSelectedIndex() == SELECTION;
    if (selection) {
      _maxPosition += _text.getContext().getReplaceWith().length() - _text.getContext().getSearchFor().length();
    }
    else {
      _maxPosition = textArea.getDocument().getLength() - 1;
    }
    if (_maxPosition < 0)
      _maxPosition = 0;
  }

  /**
   * Do replace.
   *
   * @param buffer the buffer
   * @param all the all
   * @return true, if successful
   */
  private boolean doReplace(Buffer buffer, boolean all) {
    final EditPane     editPane = buffer.getSelectedEditPane();
    final JNPadTextArea textArea = buffer.getSelectedTextArea();
    final SearchContext context = getSearchContext();

    removeHighlight(editPane, context);

    if (_text != null) {
      buffer.replace(_text.getStartPosition(), _text.getEndPosition(), _text.getContext().getReplaceWith());
      adjustMaxPosition(textArea);
      _text = null;
    }

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

    char[] searchInArray;
    char[] searchForArray;

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

    int count = 0;

    if (all) { // replace all
      buffer.beginCompoundEdit();
    }

    int len = searchInArray.length;

    for (; _actualPosition < len && _actualPosition <= _maxPosition && _actualPosition >= _minPosition; _actualPosition += _inc) {
      if (searchInArray[_actualPosition] != searchForArray[0] || (_actualPosition + searchForArray.length) - 1 > _maxPosition)
        continue;
      int endPosition = _actualPosition + 1;
      int ln;
      for (ln = 1; ln < searchForArray.length && searchInArray[endPosition] == searchForArray[ln]; ln++)
        endPosition++;

      if (ln != searchForArray.length)
        continue;

      if (!checkWholeWord(searchInArray, _actualPosition, ln, _minPosition, _maxPosition))
        continue;

      // replace all
      if (all) {
        textArea.select(_actualPosition, endPosition);
        textArea.replaceSelection(getReplaceWith());
        searchInArray = getCharText(textArea);
        len = searchInArray.length;
        adjustMaxPosition(textArea);
        count++;
        continue;
      }

      // replace text
      try {
        editPane.highlightSearch(context, _actualPosition, endPosition);

        String line = textArea.getLineTextOfOffset(_actualPosition);
        line = Utilities.removeNewLine(line);
        _text = new Text(line, context, Utilities.EMPTY_STRING, _actualPosition, endPosition);
      }
      catch (BadLocationException ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
      _actualPosition += _inc;
      return true;
    }

    if (all) { // replace all
      buffer.endCompoundEdit();
      String status = SearchBundle.getString("ReplaceDialog.status.replaceAll", count); //$NON-NLS-1$
      lbStatusBar.setText(status); // jNPad.setStatus(status, JNPadFrame.TIMEOUT_DEFAULT);
    }

    return false;
  }

  /**
   * 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) {
          if (cmbLookIn.getSelectedIndex() != SELECTION) {
            String selectedText = _buffer.getSelectedTextArea().getSelectedText();
            if (Utilities.isNotEmptyString(selectedText)) {
              cmbFind.getEditor().setItem(selectedText);
              cmbFind.getEditor().selectAll();
            }
          }
          boolean isEditable = !_buffer.isReadOnly();
          lbReplace.setEnabled(isEditable);
          cmbReplace.setEnabled(isEditable);
          btReplace.setEnabled(isEditable);
          btReplaceAll.setEnabled(isEditable);
        }
      }
      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(btReplace, 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);
      btReplace.setEnabled(b && checkBuffer() && !_buffer.isReadOnly());
    }
    else if (obj == btReplace && initialize(true)) {
      if (replace(false)) {
        searchManagerDialog.loadState();
      }
      else
        reportWordNotFound();
    }
    else if (obj == btReplaceAll && initialize(false)) {
      if (cmbLookIn.getSelectedIndex() == OPEN_DOCUMENTS) {
        replaceInOpenDocumments();
      }
      else {
        replace(true);
      }
    }
    else {
      super.handleActionPerformed(e);
    }
  }

}
