/*   **********************************************************************  **
 **   Copyright notice                                                       **
 **                                                                          **
 **   (c) 2003-2006 RSSOwl Development Team                                  **
 **   http://www.rssowl.org/                                                 **
 **                                                                          **
 **   All rights reserved                                                    **
 **                                                                          **
 **   This program and the accompanying materials are made available under   **
 **   the terms of the Eclipse Public License 1.0 which accompanies this     **
 **   distribution, and is available at:                                     **
 **   http://www.rssowl.org/legal/epl-v10.html                               **
 **                                                                          **
 **   A copy is found in the file epl-v10.html and important notices to the  **
 **   license from the team is found in the textfile LICENSE.txt distributed **
 **   in this package.                                                       **
 **                                                                          **
 **   This copyright notice MUST APPEAR in all copies of the file!           **
 **                                                                          **
 **   Contributors:                                                          **
 **     RSSOwl - initial API and implementation (bpasero@rssowl.org)         **
 **                                                                          **
 **  **********************************************************************  */

package net.sourceforge.rssowl.controller.dialog;

import net.sourceforge.rssowl.controller.GUI;
import net.sourceforge.rssowl.model.Category;
import net.sourceforge.rssowl.util.GlobalSettings;
import net.sourceforge.rssowl.util.search.SearchDefinition;
import net.sourceforge.rssowl.util.shop.FontShop;
import net.sourceforge.rssowl.util.shop.LayoutDataShop;
import net.sourceforge.rssowl.util.shop.LayoutShop;
import net.sourceforge.rssowl.util.shop.PaintShop;
import net.sourceforge.rssowl.util.shop.RegExShop;
import net.sourceforge.rssowl.util.shop.StringShop;
import net.sourceforge.rssowl.util.shop.WidgetShop;

import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

import java.util.ArrayList;

/**
 * Class displays a Dialog to search for a phrase in a newsfeed or a category of
 * newsfeeds. Search features: Entire word only, Regard case-sensitivity, Use
 * regular expression
 * 
 * @author <a href="mailto:bpasero@rssowl.org">Benjamin Pasero </a>
 * @version 1.2.3
 */
public class SearchDialog extends Dialog {

  /** Min. width of the dialog in DLUs */
  private static final int dialogMinWidth = 300;

  /** Remember all done searches in the running session */
  private static final ArrayList lastSearches = new ArrayList();

  private String dialogMessage;
  private CLabel errorMessageLabel;
  private Button matchCaseSensitive;
  private Combo scopeCombo;
  private SearchDefinition searchDefinition;
  private Button selectCategory;
  private String title;
  Category category;
  Button okButton;
  Button onlyWholeWords;
  Button regExSearch;
  Combo searchCombo;

  /**
   * Creates an input dialog with OK and Cancel buttons. Prompts for a search
   * phrase. Note that the dialog will have no visual representation (no
   * widgets) until it is told to open.
   * <p>
   * Note that the <code>open</code> method blocks for input dialogs.
   * </p>
   * 
   * @param parentShell the parent shell
   * @param category If the search is running on a Category, or NULL if not.
   * @param dialogTitle the dialog title, or <code>null</code> if none
   * @param dialogMessage the dialog dialogMessage, or <code>null</code> if
   * none
   */
  public SearchDialog(Shell parentShell, Category category, String dialogTitle, String dialogMessage) {
    super(parentShell);
    this.title = dialogTitle;
    this.dialogMessage = dialogMessage;
    this.category = category;
  }

  /**
   * Creates an input dialog with OK and Cancel buttons. Prompts for a search
   * phrase. Note that the dialog will have no visual representation (no
   * widgets) until it is told to open.
   * <p>
   * Note that the <code>open</code> method blocks for input dialogs.
   * </p>
   * 
   * @param parentShell the parent shell
   * @param dialogTitle the dialog title, or <code>null</code> if none
   * @param dialogMessage the dialog dialogMessage, or <code>null</code> if
   * none
   */
  public SearchDialog(Shell parentShell, String dialogTitle, String dialogMessage) {
    this(parentShell, null, dialogTitle, dialogMessage);
  }

  /**
   * If this is a search on a Category, get the selected Category.
   * 
   * @return Category that is selected if this search is running on a Category.
   */
  public Category getCategory() {
    return category;
  }

  /**
   * Return the Pattern and Scope of the Search.
   * 
   * @return SearchDefinition Scope and Pattern of the Search.
   */
  public SearchDefinition getValue() {
    return searchDefinition;
  }

  /**
   * Reflect the state of a Search Definition in this Dialog.
   * 
   * @param searchDefinition The Search Definition to display.
   */
  private void showSearch(SearchDefinition searchDefinition) {
    searchCombo.setText(searchDefinition.getPattern());
    searchCombo.setSelection(new Point(0, searchDefinition.getPattern().length()));
    scopeCombo.select(searchDefinition.getScope());
    matchCaseSensitive.setSelection(searchDefinition.isCaseSensitive());
    onlyWholeWords.setSelection(searchDefinition.isWholeWord());
    regExSearch.setSelection(searchDefinition.isPatternRegEx());

    /** Disable this Check in case RegEx Search is performed */
    onlyWholeWords.setEnabled(!regExSearch.getSelection());
  }

  /**
   * @see org.eclipse.jface.dialogs.Dialog#buttonPressed(int)
   */
  protected void buttonPressed(int buttonId) {

    /** User has pressed the OK Button */
    if (buttonId == IDialogConstants.OK_ID) {

      /** Create the Search Definition */
      searchDefinition = new SearchDefinition(searchCombo.getText(), scopeCombo.getSelectionIndex());
      searchDefinition.setPatternIsRegEx(regExSearch.getSelection());
      searchDefinition.setIsWholeWord(onlyWholeWords.getSelection());
      searchDefinition.setIsCaseSensitive(matchCaseSensitive.getSelection());

      /** Check if search term is a valid RegEx */
      if (searchDefinition.isPatternRegEx()) {
        String result = RegExShop.compileRegEx(searchDefinition.getPattern());

        /** Expression is invalid */
        if (!result.equals(RegExShop.REGEX_OK)) {
          setErrorMessage(result.split(System.getProperty("line.separator"))[0]);

          /** Do not close the Search dialog */
          return;
        }
      }

      /** Add to history of searches, overwirte old */
      if (lastSearches.contains(searchDefinition))
        lastSearches.remove(searchDefinition);
      lastSearches.add(searchDefinition);
    }

    /** Tell mother class */
    super.buttonPressed(buttonId);
  }

  /**
   * @see org.eclipse.jface.window.Window#configureShell(org.eclipse.swt.widgets.Shell)
   */
  protected void configureShell(Shell shell) {
    super.configureShell(shell);

    /** On Mac do not set Shell Image since it will change the Dock Image */
    if (!GlobalSettings.isMac())
      shell.setImages(PaintShop.iconOwl);

    shell.setText(title);
  }

  /**
   * @see org.eclipse.jface.dialogs.Dialog#createButtonsForButtonBar(org.eclipse.swt.widgets.Composite)
   */
  protected void createButtonsForButtonBar(Composite parent) {

    /** Button order on Mac is different */
    if (GUI.display.getDismissalAlignment() == SWT.RIGHT) {

      /** Create cancel Button */
      createButton(parent, IDialogConstants.CANCEL_ID, GUI.i18n.getTranslation("BUTTON_CANCLE"), false).setFont(FontShop.dialogFont);

      /** Create OK Button */
      okButton = createButton(parent, IDialogConstants.OK_ID, GUI.i18n.getTranslation("BUTTON_OK"), true);
    } else {

      /** Create OK Button */
      okButton = createButton(parent, IDialogConstants.OK_ID, GUI.i18n.getTranslation("BUTTON_OK"), true);

      /** Create cancel Button */
      createButton(parent, IDialogConstants.CANCEL_ID, GUI.i18n.getTranslation("BUTTON_CANCLE"), false).setFont(FontShop.dialogFont);
    }

    okButton.setFont(FontShop.dialogFont);
    okButton.setEnabled(lastSearches.size() > 0);

    /** Show the last Search if available */
    if (lastSearches.size() > 0)
      showSearch((SearchDefinition) lastSearches.get(lastSearches.size() - 1));
  }

  /**
   * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite)
   */
  protected Control createDialogArea(Composite parent) {

    /** Composite to hold all components */
    Composite composite = (Composite) super.createDialogArea(parent);
    composite.setLayout(new GridLayout(2, false));

    /** Title label */
    Label titleLabel = new Label(composite, SWT.NONE);
    titleLabel.setText(GUI.i18n.getTranslation("SEARCH_DIALOG_SEARCH_FOR") + ": ");
    titleLabel.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
    titleLabel.setFont(FontShop.dialogFont);

    /** Search text input combo */
    searchCombo = new Combo(composite, SWT.DROP_DOWN);
    searchCombo.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, false, false));
    searchCombo.setFont(FontShop.dialogFont);
    searchCombo.setFocus();

    /** Fill Combo with previous searches */
    for (int a = lastSearches.size() - 1; a >= 0; a--)
      searchCombo.add(((SearchDefinition) lastSearches.get(a)).getPattern());

    /** Modifylistener to set state of OK Button */
    searchCombo.addModifyListener(new ModifyListener() {

      /** Set state of OK Button */
      public void modifyText(ModifyEvent e) {
        okButton.setEnabled(!searchCombo.getText().equals(""));
      }
    });

    /** Select All on Ctrl+A (Cmd+A on Mac) */
    searchCombo.addKeyListener(new KeyAdapter() {
      public void keyReleased(KeyEvent e) {
        if (((e.stateMask & SWT.CTRL) != 0 || (e.stateMask & SWT.COMMAND) != 0) && (e.keyCode == 'a' || e.keyCode == 'A'))
          searchCombo.setSelection(new Point(0, searchCombo.getText().length()));
      }
    });

    /** Scope Label */
    Label scopeLabel = new Label(composite, SWT.NONE);
    scopeLabel.setText(GUI.i18n.getTranslation("LABEL_SEARCH_IN") + ": ");
    scopeLabel.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
    scopeLabel.setFont(FontShop.dialogFont);

    /** Combo to select a Scope for the Search */
    scopeCombo = new Combo(composite, SWT.DROP_DOWN | SWT.READ_ONLY);
    scopeCombo.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, false, false));
    scopeCombo.setFont(FontShop.dialogFont);
    scopeCombo.setVisibleItemCount(SearchDefinition.SCOPE_NAMES.length);

    /** Fill with available Scopes */
    for (int i = 0; i < SearchDefinition.SCOPE_NAMES.length; i++)
      scopeCombo.add(GUI.i18n.getTranslation(SearchDefinition.SCOPE_NAMES[i]));

    /** Select first by default */
    if (lastSearches.size() == 0)
      scopeCombo.select(0);

    /** If this search is running on a selected Category allow to change it */
    if (category != null && !category.isRoot()) {

      /** Category Label */
      Label categoryLabel = new Label(composite, SWT.NONE);
      categoryLabel.setText(GUI.i18n.getTranslation("LABEL_CATEGORY") + ": ");
      categoryLabel.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
      categoryLabel.setFont(FontShop.dialogFont);

      /** Composite to hold input for category and change button */
      Composite categorySelectHolder = new Composite(composite, SWT.NONE);
      categorySelectHolder.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 1));
      categorySelectHolder.setLayout(LayoutShop.createGridLayout(2, 0, 0));

      /** Text to select the category */
      final Text inputCategory = new Text(categorySelectHolder, SWT.READ_ONLY | SWT.BORDER);
      inputCategory.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 1));
      inputCategory.setFont(FontShop.dialogFont);
      inputCategory.setText(category.toCatPath(true));
      inputCategory.setData(category.toCatPath());

      /** Button to open a dialog for category selection */
      selectCategory = new Button(categorySelectHolder, SWT.NONE);
      selectCategory.setFont(FontShop.dialogFont);
      selectCategory.setText(GUI.i18n.getTranslation("BUTTON_CHANGE") + "...");
      selectCategory.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END));
      selectCategory.addSelectionListener(new SelectionAdapter() {
        public void widgetSelected(SelectionEvent e) {

          /** Create a new select category dialog */
          SelectCategoryDialog selectCatDialog = new SelectCategoryDialog(getShell(), GUI.i18n.getTranslation("DIALOG_TITLE_CATEGORY"));
          selectCatDialog.setReadonly(true);
          selectCatDialog.setCatPath((String) inputCategory.getData());

          /** If user has pressed OK, retrieve category path */
          if (selectCatDialog.open() == Window.OK) {
            inputCategory.setText((selectCatDialog.getCatPath() != null) ? selectCatDialog.getCatPath().replaceAll(StringShop.CAT_TOKENIZER, " | ") : "");
            inputCategory.setData((selectCatDialog.getCatPath() != null) ? selectCatDialog.getCatPath() : "");

            category = Category.fromPath(selectCatDialog.getCatPath());
          }
        }
      });
    }

    /** Composite holding Group of options */
    Composite optionsGroupContainer = new Composite(composite, SWT.NONE);
    optionsGroupContainer.setLayout(LayoutShop.createGridLayout(1, 0, 10));
    optionsGroupContainer.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 2));

    /** Group holding the options */
    Group optionsGroup = new Group(optionsGroupContainer, SWT.NONE);
    optionsGroup.setLayout(new GridLayout(1, true));
    optionsGroup.setText(GUI.i18n.getTranslation("LABEL_OPTIONS"));
    optionsGroup.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 1));
    optionsGroup.setFont(FontShop.dialogFont);

    /** Composite one of the check buttons */
    Composite checkGroupOne = new Composite(optionsGroup, SWT.NONE);
    checkGroupOne.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 1));
    checkGroupOne.setLayout(LayoutShop.createGridLayout(1, 5, 1));

    /** Only whole words button */
    onlyWholeWords = new Button(checkGroupOne, SWT.CHECK);
    onlyWholeWords.setFont(FontShop.dialogFont);
    onlyWholeWords.setText(GUI.i18n.getTranslation("SEARCH_DIALOG_EINTRE_WORDS"));

    /** Composite two of the check buttons */
    Composite checkGroupTwo = new Composite(optionsGroup, SWT.NONE);
    checkGroupTwo.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 1));
    checkGroupTwo.setLayout(LayoutShop.createGridLayout(1, 5, 1));

    /** Match case sensitive button */
    matchCaseSensitive = new Button(checkGroupTwo, SWT.CHECK);
    matchCaseSensitive.setText(GUI.i18n.getTranslation("SEARCH_DIALOG_CASESENSITIVE"));
    matchCaseSensitive.setFont(FontShop.dialogFont);

    /** Composite three of the check buttons */
    Composite checkGroupThree = new Composite(optionsGroup, SWT.NONE);
    checkGroupThree.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 1));
    checkGroupThree.setLayout(LayoutShop.createGridLayout(1, 5, 1));

    /** Use RegEx button */
    regExSearch = new Button(checkGroupThree, SWT.CHECK);
    regExSearch.setText(GUI.i18n.getTranslation("SEARCH_DIALOG_REGEX"));
    regExSearch.setFont(FontShop.dialogFont);
    regExSearch.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        onlyWholeWords.setEnabled(!regExSearch.getSelection());
      }
    });

    /** Error Message */
    errorMessageLabel = new CLabel(composite, SWT.NONE);
    errorMessageLabel.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 2));
    errorMessageLabel.setFont(FontShop.dialogFont);

    /** Use the error message to display an info message how to use the Search */
    errorMessageLabel.setImage(PaintShop.iconInfo);
    errorMessageLabel.setText(dialogMessage);

    /** Holder for the separator to the OK and Cancel buttons */
    Composite sepHolder = new Composite(parent, SWT.NONE);
    sepHolder.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 2));
    sepHolder.setLayout(LayoutShop.createGridLayout(1, 0, 0));

    /** Separator */
    Label separator = new Label(sepHolder, SWT.SEPARATOR | SWT.HORIZONTAL);
    separator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

    /** Set Mnemonics to Buttons */
    if (WidgetShop.isset(selectCategory))
      WidgetShop.initMnemonics(new Button[] { selectCategory, onlyWholeWords, matchCaseSensitive, regExSearch });
    else
      WidgetShop.initMnemonics(new Button[] { onlyWholeWords, matchCaseSensitive, regExSearch });

    return composite;
  }

  /**
   * @see org.eclipse.jface.window.Window#getShellStyle()
   */
  protected int getShellStyle() {
    int style = SWT.TITLE | SWT.BORDER | SWT.APPLICATION_MODAL | getDefaultOrientation();

    /** Follow Apple's Human Interface Guidelines for Application Modal Dialogs */
    if (!GlobalSettings.isMac())
      style |= SWT.CLOSE;

    return style;
  }

  /**
   * @see org.eclipse.jface.dialogs.Dialog#initializeBounds()
   */
  protected void initializeBounds() {
    Point bestSize = getShell().computeSize(convertHorizontalDLUsToPixels(dialogMinWidth), SWT.DEFAULT);
    Point location = getInitialLocation(bestSize);
    getShell().setBounds(location.x, location.y, bestSize.x, bestSize.y);
  }

  /**
   * Set the layout data of the button to a GridData with appropriate widths
   * This method was slightly modified so that it is not setting a heightHint.
   * 
   * @param button The button to layout
   */
  protected void setButtonLayoutData(Button button) {
    GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
    int widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
    data.widthHint = Math.max(widthHint, button.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x);
    button.setLayoutData(data);
  }

  /**
   * Set / unset the warning dialogMessage on the dialog
   * 
   * @param errorMessage The warning dialogMessage or NULL for no warning
   */
  protected void setErrorMessage(String errorMessage) {
    errorMessageLabel.setImage(errorMessage == null ? null : PaintShop.iconError);
    errorMessageLabel.setText(errorMessage == null ? "" : errorMessage);
    errorMessageLabel.getParent().layout();
  }
}