/*
 * Copyright 2000-2014 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.find;

import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.search.SearchScope;
import com.intellij.util.PatternUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

/**
 * Represents the settings of a Find, Replace, Find in Path or Replace in Path
 * operations.
 */
public class FindModel extends UserDataHolderBase implements Cloneable {
  public static void initStringToFindNoMultiline(FindModel findModel, String s) {
    if (!StringUtil.isEmpty(s)) {
      if (!s.contains("\r") && !s.contains("\n")) {
        findModel.setStringToFind(s);
      }
      else {
        findModel.setStringToFind(StringUtil.escapeToRegexp(s));
        findModel.setRegularExpressions(true);
      }
    }
  }

  public interface FindModelObserver {
    void findModelChanged(FindModel findModel);
  }

  private final List<FindModelObserver> myObservers = ContainerUtil.createLockFreeCopyOnWriteList();

  public void addObserver(FindModelObserver observer) {
    myObservers.add(observer);
  }

  public void removeObserver(FindModelObserver observer) {
    myObservers.remove(observer);
  }

  private void notifyObservers() {
    for (FindModelObserver observer : myObservers) {
      observer.findModelChanged(this);
    }
  }

  private String myStringToFind = "";
  private String myStringToReplace = "";
  private boolean isSearchHighlighters = false;
  private boolean isReplaceState = false;
  private boolean isWholeWordsOnly = false;
  private SearchContext searchContext = SearchContext.ANY;
  private boolean isFromCursor = true;
  private boolean isForward = true;
  private boolean isGlobal = true;
  private boolean isRegularExpressions = false;
  private boolean isCaseSensitive = false;
  private boolean isMultipleFiles = false;
  private boolean isPromptOnReplace = true;
  private boolean isReplaceAll = false;
  private boolean isOpenNewTab = false;
  private boolean isOpenInNewTabEnabled = false;
  private boolean isOpenNewTabVisible = false;
  private boolean isProjectScope = true;
  private boolean isFindAll = false;
  private boolean isFindAllEnabled = false;
  private String moduleName;
  private String directoryName = null;
  private boolean isWithSubdirectories = true;
  private String fileFilter;
  private String customScopeName;
  private SearchScope customScope;
  private boolean isCustomScope = false;
  private boolean isMultiline = false;
  private boolean mySearchInProjectFiles;

  public boolean isMultiline() {
    return isMultiline;
  }

  public void setMultiline(boolean multiline) {
    if (multiline != isMultiline) {
      if (!multiline) {
        initStringToFindNoMultiline(this, getStringToFind());
      }
      isMultiline = multiline;
      notifyObservers();
    }
  }

  /**
   * Gets the Preserve Case flag.
   *
   * @return the value of the Preserve Case flag.
   */
  public boolean isPreserveCase() {
    return isPreserveCase;
  }

  /**
   * Sets the Preserve Case flag.
   *
   * @param preserveCase the value of the Preserve Case flag.
   */
  public void setPreserveCase(boolean preserveCase) {
    boolean changed = isPreserveCase != preserveCase;
    isPreserveCase = preserveCase;
    if (changed) {
      notifyObservers();
    }
  }

  private boolean isPreserveCase = false;

  /**
   * Copies all the settings from the specified model.
   *
   * @param model the model to copy settings from.
   */
  public void copyFrom(FindModel model) {
    boolean changed = !equals(model);
    myStringToFind = model.myStringToFind;
    myStringToReplace = model.myStringToReplace;
    isReplaceState = model.isReplaceState;
    isWholeWordsOnly = model.isWholeWordsOnly;
    isFromCursor = model.isFromCursor;
    isForward = model.isForward;
    isGlobal = model.isGlobal;
    isRegularExpressions = model.isRegularExpressions;
    isCaseSensitive = model.isCaseSensitive;
    isMultipleFiles = model.isMultipleFiles;
    isPromptOnReplace = model.isPromptOnReplace;
    isReplaceAll = model.isReplaceAll;
    isOpenNewTab = model.isOpenNewTab;
    isOpenInNewTabEnabled = model.isOpenInNewTabEnabled;
    isOpenNewTabVisible = model.isOpenNewTabVisible;
    isProjectScope = model.isProjectScope;
    directoryName = model.directoryName;
    isWithSubdirectories = model.isWithSubdirectories;
    isPreserveCase = model.isPreserveCase;
    fileFilter = model.fileFilter;
    moduleName = model.moduleName;
    customScopeName = model.customScopeName;
    customScope = model.customScope;
    isCustomScope = model.isCustomScope;
    isFindAll = model.isFindAll;
    searchContext = model.searchContext;
    isMultiline = model.isMultiline;
    if (changed) {
      notifyObservers();
    }
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    FindModel findModel = (FindModel)o;

    if (isCaseSensitive != findModel.isCaseSensitive) return false;
    if (isCustomScope != findModel.isCustomScope) return false;
    if (isFindAll != findModel.isFindAll) return false;
    if (isFindAllEnabled != findModel.isFindAllEnabled) return false;
    if (isForward != findModel.isForward) return false;
    if (isFromCursor != findModel.isFromCursor) return false;
    if (isGlobal != findModel.isGlobal) return false;
    if (searchContext != findModel.searchContext) return false;

    if (isMultiline != findModel.isMultiline) return false;
    if (isMultipleFiles != findModel.isMultipleFiles) return false;
    if (isOpenInNewTabEnabled != findModel.isOpenInNewTabEnabled) return false;
    if (isOpenNewTab != findModel.isOpenNewTab) return false;
    if (isOpenNewTabVisible != findModel.isOpenNewTabVisible) return false;
    if (isPreserveCase != findModel.isPreserveCase) return false;
    if (isProjectScope != findModel.isProjectScope) return false;
    if (isPromptOnReplace != findModel.isPromptOnReplace) return false;
    if (isRegularExpressions != findModel.isRegularExpressions) return false;
    if (isReplaceAll != findModel.isReplaceAll) return false;
    if (isReplaceState != findModel.isReplaceState) return false;
    if (isSearchHighlighters != findModel.isSearchHighlighters) return false;
    if (isWholeWordsOnly != findModel.isWholeWordsOnly) return false;
    if (isWithSubdirectories != findModel.isWithSubdirectories) return false;
    if (customScope != null ? !customScope.equals(findModel.customScope) : findModel.customScope != null) return false;
    if (customScopeName != null ? !customScopeName.equals(findModel.customScopeName) : findModel.customScopeName != null) return false;
    if (directoryName != null ? !directoryName.equals(findModel.directoryName) : findModel.directoryName != null) return false;
    if (fileFilter != null ? !fileFilter.equals(findModel.fileFilter) : findModel.fileFilter != null) return false;
    if (moduleName != null ? !moduleName.equals(findModel.moduleName) : findModel.moduleName != null) return false;
    if (myStringToFind != null ? !myStringToFind.equals(findModel.myStringToFind) : findModel.myStringToFind != null) return false;
    if (myStringToReplace != null ? !myStringToReplace.equals(findModel.myStringToReplace) : findModel.myStringToReplace != null) {
      return false;
    }
    if (mySearchInProjectFiles != findModel.mySearchInProjectFiles) return false;

    return true;
  }

  @Override
  public int hashCode() {
    int result = 0;
    result = 31 * result + (myStringToFind != null ? myStringToFind.hashCode() : 0);
    result = 31 * result + (myStringToReplace != null ? myStringToReplace.hashCode() : 0);
    result = 31 * result + (isSearchHighlighters ? 1 : 0);
    result = 31 * result + (isReplaceState ? 1 : 0);
    result = 31 * result + (isWholeWordsOnly ? 1 : 0);
    result = 31 * result + (searchContext.ordinal());
    result = 31 * result + (isFromCursor ? 1 : 0);
    result = 31 * result + (isForward ? 1 : 0);
    result = 31 * result + (isGlobal ? 1 : 0);
    result = 31 * result + (isRegularExpressions ? 1 : 0);
    result = 31 * result + (isCaseSensitive ? 1 : 0);
    result = 31 * result + (isMultipleFiles ? 1 : 0);
    result = 31 * result + (isPromptOnReplace ? 1 : 0);
    result = 31 * result + (isReplaceAll ? 1 : 0);
    result = 31 * result + (isOpenNewTab ? 1 : 0);
    result = 31 * result + (isOpenInNewTabEnabled ? 1 : 0);
    result = 31 * result + (isOpenNewTabVisible ? 1 : 0);
    result = 31 * result + (isProjectScope ? 1 : 0);
    result = 31 * result + (isFindAll ? 1 : 0);
    result = 31 * result + (isFindAllEnabled ? 1 : 0);
    result = 31 * result + (moduleName != null ? moduleName.hashCode() : 0);
    result = 31 * result + (directoryName != null ? directoryName.hashCode() : 0);
    result = 31 * result + (isWithSubdirectories ? 1 : 0);
    result = 31 * result + (fileFilter != null ? fileFilter.hashCode() : 0);
    result = 31 * result + (customScopeName != null ? customScopeName.hashCode() : 0);
    result = 31 * result + (customScope != null ? customScope.hashCode() : 0);
    result = 31 * result + (isCustomScope ? 1 : 0);
    result = 31 * result + (isMultiline ? 1 : 0);
    result = 31 * result + (isPreserveCase ? 1 : 0);
    result = 31 * result + (mySearchInProjectFiles ? 1 : 0);
    result = 31 * result + (myPattern != null ? myPattern.hashCode() : 0);
    return result;
  }

  /**
   * Gets the string to find.
   *
   * @return the string to find.
   */
  @NotNull
  public String getStringToFind() {
    return myStringToFind;
  }

  /**
   * Sets the string to find.
   *
   * @param s the string to find.
   */
  public void setStringToFind(@NotNull String s) {
    boolean changed = !StringUtil.equals(s, myStringToFind);
    myStringToFind = s;
    myPattern = PatternUtil.NOTHING;
    if (changed) {
      notifyObservers();
    }
  }

  /**
   * Gets the string to replace with.
   *
   * @return the string to replace with.
   */
  @NotNull
  public String getStringToReplace() {
    return myStringToReplace;
  }

  /**
   * Sets the string to replace with.
   *
   * @param s the string to replace with.
   */
  public void setStringToReplace(@NotNull String s) {
    boolean changed = !StringUtil.equals(s, myStringToReplace);
    myStringToReplace = s;
    if (changed) {
      notifyObservers();
    }
  }

  /**
   * Gets the value indicating whether the operation is a Find or a Replace.
   *
   * @return true if the operation is a Replace, false if it is a Find.
   */
  public boolean isReplaceState() {
    return isReplaceState;
  }

  /**
   * Sets the value indicating whether the operation is a Find or a Replace.
   *
   * @param val true if the operation is a Replace, false if it is a Find.
   */
  public void setReplaceState(boolean val) {
    boolean changed = val != isReplaceState;
    isReplaceState = val;
    if (changed) {
      notifyObservers();
    }
  }

  /**
   * Gets the origin for the find.
   *
   * @return true if the origin is From Cursor, false if it is Entire Scope.
   */
  public boolean isFromCursor() {
    return isFromCursor;
  }

  /**
   * Sets the origin for the find.
   *
   * @param val true if the origin is From Cursor, false if it is Entire Scope.
   */
  public void setFromCursor(boolean val) {
    boolean changed = val != isFromCursor;
    isFromCursor = val;
    if (changed) {
      notifyObservers();
    }
  }

  /**
   * Gets the direction for the find.
   *
   * @return true if the find is forward, false if it is backward.
   */
  public boolean isForward() {
    return isForward;
  }

  /**
   * Sets the direction for the find.
   *
   * @param val true if the find is forward, false if it is backward.
   */
  public void setForward(boolean val) {
    boolean changed = val != isForward;
    isForward = val;
    if (changed) {
      notifyObservers();
    }
  }

  /**
   * Gets the Regular Expressions flag.
   *
   * @return the value of the Regular Expressions flag.
   */
  public boolean isRegularExpressions() {
    return isRegularExpressions;
  }

  /**
   * Sets the Regular Expressions flag.
   *
   * @param val the value of the Regular Expressions flag.
   */
  public void setRegularExpressions(boolean val) {
    boolean changed = val != isRegularExpressions;
    isRegularExpressions = val;
    if (changed) {
      notifyObservers();
    }
  }

  /**
   * Gets the Case Sensitive flag.
   *
   * @return the value of the Case Sensitive flag.
   */
  public boolean isCaseSensitive() {
    return isCaseSensitive;
  }

  /**
   * Sets the Case Sensitive flag.
   *
   * @param val the value of the Case Sensitive flag.
   */
  public void setCaseSensitive(boolean val) {
    boolean changed = val != isCaseSensitive;
    isCaseSensitive = val;
    if (changed) {
      myPattern = PatternUtil.NOTHING;
      notifyObservers();
    }
  }

  /**
   * Checks if the find or replace operation affects multiple files.
   *
   * @return true if the operation affects multiple files, false if it affects a single file.
   */
  public boolean isMultipleFiles() {
    return isMultipleFiles;
  }

  /**
   * Sets the value indicating whether the find or replace operation affects multiple files.
   *
   * @param val true if the operation affects multiple files, false if it affects a single file.
   */
  public void setMultipleFiles(boolean val) {
    boolean changed = val != isMultipleFiles;
    isMultipleFiles = val;
    if (changed) {
      notifyObservers();
    }
  }

  /**
   * Gets the Prompt on Replace flag.
   *
   * @return the value of the Prompt on Replace flag.
   */
  public boolean isPromptOnReplace() {
    return isPromptOnReplace;
  }

  /**
   * Sets the Prompt on Replace flag.
   *
   * @param val the value of the Prompt on Replace flag.
   */
  public void setPromptOnReplace(boolean val) {
    boolean changed = val != isPromptOnReplace;
    isPromptOnReplace = val;
    if (changed) {
      notifyObservers();
    }
  }

  /**
   * Gets the Whole Words Only flag.
   *
   * @return the value of the Whole Words Only flag.
   */
  public boolean isWholeWordsOnly() {
    return isWholeWordsOnly;
  }

  /**
   * Sets the Whole Words Only flag.
   *
   * @param isWholeWordsOnly the value of the Whole Words Only flag.
   */
  public void setWholeWordsOnly(boolean isWholeWordsOnly) {
    boolean changed = isWholeWordsOnly != this.isWholeWordsOnly;
    this.isWholeWordsOnly = isWholeWordsOnly;
    if (changed) {
      notifyObservers();
    }
  }

  /**
   * Gets the scope of the operation.
   *
   * @return true if the operation affects the entire file, false if it affects the selected text.
   */
  public boolean isGlobal() {
    return isGlobal;
  }

  /**
   * Sets the scope of the operation.
   *
   * @param isGlobal true if the operation affects the entire file, false if it affects the selected text.
   */
  public void setGlobal(boolean isGlobal) {
    boolean changed = this.isGlobal != isGlobal;
    this.isGlobal = isGlobal;
    if (changed) {
      notifyObservers();
    }
  }

  /**
   * Gets the Replace All flag.
   *
   * @return the value of the Replace All flag.
   */
  public boolean isReplaceAll() {
    return isReplaceAll;
  }

  /**
   * Sets the Replace All flag.
   *
   * @param replaceAll the value of the Replace All flag.
   */
  public void setReplaceAll(boolean replaceAll) {
    isReplaceAll = replaceAll;
    notifyObservers();
  }

  /**
   * Gets the Open in New Tab flag.
   *
   * @return the value of the Open in New Tab flag.
   */
  public boolean isOpenInNewTab() {
    return isOpenNewTab;
  }

  /**
   * Sets the Open in New Tab flag.
   *
   * @param showInNewTab the value of the Open in New Tab flag.
   */
  public void setOpenInNewTab(boolean showInNewTab) {
    boolean changed = showInNewTab != isOpenNewTab;
    isOpenNewTab = showInNewTab;
    if (changed) {
      notifyObservers();
    }
  }

  /**
   * Gets the value indicating whether the Open in New Tab flag is enabled for the operation.
   *
   * @return true if Open in New Tab is enabled, false otherwise.
   */
  public boolean isOpenInNewTabEnabled() {
    return isOpenInNewTabEnabled;
  }

  /**
   * Sets the value indicating whether the Open in New Tab flag is enabled for the operation.
   *
   * @param showInNewTabEnabled true if Open in New Tab is enabled, false otherwise.
   */
  public void setOpenInNewTabEnabled(boolean showInNewTabEnabled) {
    boolean changed = isOpenInNewTabEnabled != showInNewTabEnabled;
    isOpenInNewTabEnabled = showInNewTabEnabled;
    if (changed) {
      notifyObservers();
    }
  }

  /**
   * Gets the value indicating whether the Open in New Tab flag is visible for the operation.
   *
   * @return true if Open in New Tab is visible, false otherwise.
   */
  public boolean isOpenInNewTabVisible() {
    return isOpenNewTabVisible;
  }

  /**
   * Sets the value indicating whether the Open in New Tab flag is enabled for the operation.
   *
   * @param showInNewTabVisible true if Open in New Tab is visible, false otherwise.
   */
  public void setOpenInNewTabVisible(boolean showInNewTabVisible) {
    boolean changed = showInNewTabVisible != isOpenNewTabVisible;
    isOpenNewTabVisible = showInNewTabVisible;
    if (changed) {
      notifyObservers();
    }
  }

  /**
   * Gets the directory used as a scope for Find in Path / Replace in Path.
   *
   * @return the directory used as a scope, or null if the selected scope is not "Directory".
   */
  @Nullable
  public String getDirectoryName() {
    return directoryName;
  }

  /**
   * Sets the directory used as a scope for Find in Path / Replace in Path.
   *
   * @param directoryName the directory scope.
   */
  public void setDirectoryName(@Nullable String directoryName) {
    boolean changed = !StringUtil.equals(directoryName, directoryName);
    this.directoryName = directoryName;
    if (changed) {
      notifyObservers();
    }
    if (directoryName != null) {
      String path = FileUtil.toSystemIndependentName(directoryName);
      if (path.endsWith("/.idea") || path.contains("/.idea/")) {
        setSearchInProjectFiles(true);
      }
    }

  }

  /**
   * Gets the Recursive Search flag for Find in Path / Replace in Path.
   *
   * @return true if directories are searched recursively, false otherwise.
   */
  public boolean isWithSubdirectories() {
    return isWithSubdirectories;
  }

  /**
   * Sets the Recursive Search flag for Find in Path / Replace in Path.
   *
   * @param withSubdirectories true if directories are searched recursively, false otherwise.
   */
  public void setWithSubdirectories(boolean withSubdirectories) {
    boolean changed = withSubdirectories != isWithSubdirectories;
    isWithSubdirectories = withSubdirectories;
    if (changed) {
      notifyObservers();
    }
  }

  /**
   * Gets the flag indicating whether the Whole Project scope is selected for Find in Path /
   * Replace in Path.
   *
   * @return true if the whole project scope is selected, false otherwise.
   */
  public boolean isProjectScope() {
    return isProjectScope;
  }

  /**
   * Sets the flag indicating whether the Whole Project scope is selected for Find in Path /
   * Replace in Path.
   *
   * @param projectScope true if the whole project scope is selected, false otherwise.
   */
  public void setProjectScope(boolean projectScope) {
    boolean changed = projectScope != isProjectScope;
    isProjectScope = projectScope;
    if (changed) {
      notifyObservers();
    }
  }

  @Override
  public FindModel clone() {
    return (FindModel)super.clone();
  }


  @Override
  public String toString() {
    return "--- FIND MODEL ---\n" +
           "myStringToFind =" + myStringToFind + "\n" +
           "myStringToReplace =" + myStringToReplace + "\n" +
           "isReplaceState =" + isReplaceState + "\n" +
           "isWholeWordsOnly =" + isWholeWordsOnly + "\n" +
           "searchContext =" + searchContext + "\n" +
           "isFromCursor =" + isFromCursor + "\n" +
           "isForward =" + isForward + "\n" +
           "isGlobal =" + isGlobal + "\n" +
           "isRegularExpressions =" + isRegularExpressions + "\n" +
           "isCaseSensitive =" + isCaseSensitive + "\n" +
           "isMultipleFiles =" + isMultipleFiles + "\n" +
           "isPromptOnReplace =" + isPromptOnReplace + "\n" +
           "isReplaceAll =" + isReplaceAll + "\n" +
           "isOpenNewTab =" + isOpenNewTab + "\n" +
           "isOpenInNewTabEnabled =" + isOpenInNewTabEnabled + "\n" +
           "isOpenNewTabVisible =" + isOpenNewTabVisible + "\n" +
           "isProjectScope =" + isProjectScope + "\n" +
           "directoryName =" + directoryName + "\n" +
           "isWithSubdirectories =" + isWithSubdirectories + "\n" +
           "fileFilter =" + fileFilter + "\n" +
           "moduleName =" + moduleName + "\n" +
           "customScopeName =" + customScopeName + "\n" +
           "searchInProjectFiles =" + mySearchInProjectFiles + "\n";
  }

  /**
   * Gets the flag indicating whether the search operation is Find Next / Find Previous
   * after Highlight Usages in Files.
   *
   * @return true if the operation moves between highlighted regions, false otherwise.
   */
  public boolean searchHighlighters() {
    return isSearchHighlighters;
  }

  /**
   * Sets the flag indicating whether the search operation is Find Next / Find Previous
   * after Highlight Usages in Files.
   *
   * @param search true if the operation moves between highlighted regions, false otherwise.
   */
  public void setSearchHighlighters(boolean search) {
    boolean changed = search != isSearchHighlighters;
    isSearchHighlighters = search;
    if (changed) {
      notifyObservers();
    }
  }

  /**
   * Gets the file name filter used for Find in Path / Replace in Path operation.
   *
   * @return the file name filter text.
   */
  public String getFileFilter() {
    return fileFilter;
  }

  /**
   * Sets the file name filter used for Find in Path / Replace in Path operation.
   *
   * @param fileFilter the file name filter text.
   */
  public void setFileFilter(String fileFilter) {
    boolean changed = !StringUtil.equals(fileFilter, this.fileFilter);
    this.fileFilter = fileFilter;
    if (changed) {
      notifyObservers();
    }
  }

  /**
   * Gets the name of the module used as the scope for the Find in Path / Replace
   * in Path operation.
   *
   * @return the module name, or null if the selected scope is not "Module".
   */
  @Nullable
  public String getModuleName() {
    return moduleName;
  }

  /**
   * Sets the name of the module used as the scope for the Find in Path / Replace
   * in Path operation.
   *
   * @param moduleName the name of the module used as the scope.
   */
  public void setModuleName(String moduleName) {
    boolean changed = !StringUtil.equals(moduleName, this.moduleName);
    this.moduleName = moduleName;
    if (changed) {
      notifyObservers();
    }
  }

  /**
   * Gets the flag indicating whether "Find All" button was used to initiate the find
   * operation.
   *
   * @return true if the operation is a "Find All", false otherwise.
   * @since 5.1
   */
  public boolean isFindAll() {
    return isFindAll;
  }

  /**
   * Sets the flag indicating whether "Find All" button was used to initiate the find
   * operation.
   *
   * @param findAll true if the operation is a "Find All", false otherwise.
   * @since 5.1
   */
  public void setFindAll(final boolean findAll) {
    boolean changed = isFindAll != findAll;
    isFindAll = findAll;
    if (changed) {
      notifyObservers();
    }
  }

  /**
   * Gets the flag indicating whether "Find All" button is allowed for the operation.
   *
   * @return true if "Find All" is enabled, false otherwise.
   * @since 5.1
   */
  public boolean isFindAllEnabled() {
    return isFindAllEnabled;
  }

  /**
   * Sets the flag indicating whether "Find All" button is allowed for the operation.
   *
   * @param findAllEnabled true if "Find All" is enabled, false otherwise.
   * @since 5.1
   */
  public void setFindAllEnabled(final boolean findAllEnabled) {
    boolean changed = isFindAllEnabled != findAllEnabled;
    isFindAllEnabled = findAllEnabled;
    if (changed) {
      notifyObservers();
    }
  }

  public String getCustomScopeName() {
    return customScopeName;
  }

  public void setCustomScopeName(String customScopeName) {
    boolean changed = !StringUtil.equals(customScopeName, this.customScopeName);
    this.customScopeName = customScopeName;
    if (changed) {
      notifyObservers();
    }
  }

  public SearchScope getCustomScope() {
    return customScope;
  }

  public void setCustomScope(final SearchScope customScope) {
    boolean changed = this.customScope != null ? this.customScope.equals(customScope) : customScope != null;
    this.customScope = customScope;
    if (changed) {
      notifyObservers();
    }
  }

  public boolean isCustomScope() {
    return isCustomScope;
  }

  public void setCustomScope(boolean customScope) {
    boolean changed = isCustomScope != customScope;
    isCustomScope = customScope;
    if (changed) {
      notifyObservers();
    }
  }

  public enum SearchContext {
    ANY, IN_STRING_LITERALS, IN_COMMENTS, EXCEPT_STRING_LITERALS, EXCEPT_COMMENTS, EXCEPT_COMMENTS_AND_STRING_LITERALS
  }

  public boolean isInStringLiteralsOnly() {
    return searchContext == SearchContext.IN_STRING_LITERALS;
  }

  public boolean isExceptComments() {
    return searchContext == SearchContext.EXCEPT_COMMENTS;
  }

  public boolean isExceptStringLiterals() {
    return searchContext == SearchContext.EXCEPT_STRING_LITERALS;
  }

  public boolean isInCommentsOnly() {
    return searchContext == SearchContext.IN_COMMENTS;
  }

  public boolean isExceptCommentsAndStringLiterals() {
    return searchContext == SearchContext.EXCEPT_COMMENTS_AND_STRING_LITERALS;
  }

  @Deprecated
  public void setInCommentsOnly(boolean inCommentsOnly) {
    doApplyContextChange(inCommentsOnly, SearchContext.IN_COMMENTS);
  }

  @Deprecated
  public void setInStringLiteralsOnly(boolean inStringLiteralsOnly) {
    doApplyContextChange(inStringLiteralsOnly, SearchContext.IN_STRING_LITERALS);
  }

  private void doApplyContextChange(boolean newOptionValue, SearchContext option) {
    boolean changed = false;
    if (newOptionValue) {
      changed = searchContext != option;
      searchContext = option;
    } else if (searchContext == option) { // do not reset unrelated value
      changed = true;
      searchContext = SearchContext.ANY;
    }

    if (changed) {
      notifyObservers();
    }
  }

  @NotNull
  public SearchContext getSearchContext() {
    return searchContext;
  }

  public void setSearchContext(@NotNull SearchContext _searchContext) {
    doSetContext(_searchContext);
  }

  private void doSetContext(SearchContext newSearchContext) {
    boolean changed = newSearchContext != searchContext;
    searchContext = newSearchContext;
    if (changed) {
      notifyObservers();
    }
  }

  public boolean isSearchInProjectFiles() {
    return mySearchInProjectFiles;
  }

  public void setSearchInProjectFiles(boolean searchInProjectFiles) {
    boolean changed = mySearchInProjectFiles != searchInProjectFiles;
    mySearchInProjectFiles = searchInProjectFiles;
    if (changed) {
      notifyObservers();
    }
  }

  private Pattern myPattern = PatternUtil.NOTHING;

  public Pattern compileRegExp() {
    String toFind = getStringToFind();

    Pattern pattern = myPattern;
    if (pattern == PatternUtil.NOTHING) {
      try {
        myPattern = pattern = Pattern.compile(toFind, isCaseSensitive() ? Pattern.MULTILINE : Pattern.MULTILINE | Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
      }
      catch (PatternSyntaxException e) {
        myPattern = pattern = null;
      }
    }

    return pattern;
  }
}