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

import net.sourceforge.rssowl.controller.GUI;

import java.util.ArrayList;
import java.util.Hashtable;

/**
 * Class for parsing the search pattern (Operators: AND, OR, NOT).
 * 
 * @author <a href="mailto:bpasero@rssowl.org">Benjamin Pasero </a>
 * @version 1.2.3
 */
public class SearchPatternParser {

  /** Search Pattern Boolean AND modifier */
  public static final String AND = "SEARCH_AND";

  /** Hashtable Key for the Must Key-Words */
  public static final String MUST_CONTAIN_KEY = "MUST_CONTAIN_KEY";

  /** Hashtable Key for the Must-Not Key-Words */
  public static final String MUSTNOT_CONTAIN_KEY = "MUSTNOT_CONTAIN_KEY";

  /** Search Pattern Boolean NOT modifier */
  public static final String NOT = "SEARCH_NOT";

  /** Search Pattern Boolean OR modifier */
  public static final String OR = "SEARCH_OR";

  /** This utility class constructor is hidden */
  private SearchPatternParser() {
  // Protect default constructor
  }

  /**
   * Get the Translation for the given Search Key, which may be Boolean AND, OR
   * and NOT.
   * 
   * @param key The Key of the Search delimiter.
   * @return String The Translation of the Search delimiter.
   */
  public static String getTranslation(String key) {
    StringBuffer strBuf = new StringBuffer();
    strBuf.append(" ").append(GUI.i18n.getTranslation(key)).append(" ");
    return strBuf.toString();
  }

  /**
   * Finds the next index of one of the keywords AND, OR
   * 
   * @param pattern The search pattern
   * @param notIndex Current position in search pattern
   * @return Closest index to next keyword
   */
  private static int getClosestKeyword(String pattern, int notIndex) {
    int end = -1;
    int nextAndIndex = pattern.indexOf(getTranslation(AND), notIndex);
    int nextOrIndex = pattern.indexOf(getTranslation(OR), notIndex);

    /** Next keyword is OR */
    if ((nextOrIndex < nextAndIndex && nextOrIndex > 0) || nextAndIndex < 0)
      end = nextOrIndex;

    /** Next keyword is AND */
    else if ((nextAndIndex < nextOrIndex && nextAndIndex > 0) || nextOrIndex < 0)
      end = nextAndIndex;

    if (end < 1)
      end = pattern.length();
    return end;
  }

  /**
   * Parse Words that should match the result (AND / OR)
   * 
   * @param pattern The search pattern (NOT words are removed)
   * @return ArrayList holding AND plus OR keywords
   */
  private static ArrayList parseMustAndMayWords(String pattern) {
    String[] andPattern = pattern.split(getTranslation(AND));
    ArrayList and = new ArrayList();
    ArrayList or;
    String orTranslation = getTranslation(OR);

    for (int a = 0; a < andPattern.length; a++) {
      or = new ArrayList();
      String[] orPattern = andPattern[a].split(orTranslation);
      for (int b = 0; b < orPattern.length; b++) {
        or.add(orPattern[b]);
      }
      and.add(or);
    }
    return and;
  }

  /**
   * Generate a RegEx from the AND / OR keywords.
   * 
   * @param keys List holding the AND / OR Keywords
   * @param isWholeWord If TRUE, search for the entire word and not part of it.
   * @return RegEx matching the keywords
   */
  static ArrayList generateRegEx(ArrayList keys, boolean isWholeWord) {
    ArrayList regex = new ArrayList();
    String wrapperRegExStart = "";
    String wrapperRegExEnd = "";

    /** In case only whole word is allowed */
    if (isWholeWord) {
      wrapperRegExStart = "(\\s|^)";
      wrapperRegExEnd = "(\\s|$)";
    }

    /** Generate RegEx for the words that should match the result */
    for (int a = 0; a < keys.size(); a++) {
      ArrayList and = (ArrayList) keys.get(a);

      /** Only 1 key */
      if (and.size() == 1) {
        StringBuffer sb = new StringBuffer(wrapperRegExStart);
        sb.append((String) and.get(0));
        sb.append(wrapperRegExEnd);
        regex.add(sb.toString());
      }

      /** More than 1 key. These are OR statements */
      else if (and.size() > 1) {
        StringBuffer sb = new StringBuffer(wrapperRegExStart + "(");
        sb.append((String) and.get(0));

        for (int b = 1; b < and.size(); b++) {
          sb.append("|");
          sb.append((String) and.get(b));
        }

        sb.append(")" + wrapperRegExEnd);
        regex.add(sb.toString());
      }
    }
    return regex;
  }

  /**
   * Parse the search pattern and return a Hashtable. The Hashtable contains one
   * List for the Keywords that should NOT match the result and one List that
   * should match the result (AND). The List for the AND-Keywords holds one List
   * for each OR-Keyword. For example: A pattern "Car AND Audi OR Mercedes"
   * would result in: [[Car], [Audi, Mercedes]] beeing equivalent to "Car &&
   * (Audi || Mercedes)"
   * 
   * @param pattern The search pattern (e.g. "Car AND Audi NOT Ferrari")
   * @return parsed pattern holding the Keywords for AND, OR and NOT
   */
  static Hashtable parsePattern(String pattern) {
    Hashtable parsedPattern = new Hashtable();
    String notTranslation = getTranslation(NOT);

    /** Parse Words that should NOT match the result */
    int notIndex = pattern.indexOf(notTranslation);

    ArrayList not = new ArrayList();
    while (notIndex > 0) {
      int start = notIndex;
      int end = getClosestKeyword(pattern, notIndex);
      String notPattern = pattern.substring(start, end);
      String notWords[] = notPattern.split(notTranslation);

      /** Remove the NOT statement from the pattern */
      pattern = pattern.replaceFirst(notPattern, "");

      /** Add NOT Keywords to List */
      for (int a = 1; a < notWords.length; a++) {
        if (!not.contains(notWords[a]))
          not.add(notWords[a]);
      }
      notIndex = pattern.indexOf(notTranslation, notIndex + 1);
    }

    ArrayList and = parseMustAndMayWords(pattern);
    parsedPattern.put(MUST_CONTAIN_KEY, and);
    parsedPattern.put(MUSTNOT_CONTAIN_KEY, not);
    return parsedPattern;
  }
}