/*
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (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.mozilla.org/MPL/>.
 * 
 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
 * language governing rights and limitations under the License.
 * 
 * The Original Code is the Venice Web Communities System.
 * 
 * The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
 * for Silverwrist Design Studios.  Portions created by Eric J. Bowersox are
 * Copyright (C) 2003 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */

options
{
  STATIC = false;
  JAVA_UNICODE_ESCAPE = true;
  USER_CHAR_STREAM = true;
}

PARSER_BEGIN(Parser)

package com.silverwrist.dynamo.index;

import java.io.*;
import java.util.*;
import org.apache.lucene.index.Term;
import org.apache.lucene.analysis.*;
import org.apache.lucene.document.*;
import org.apache.lucene.search.*;

/**
 * N.B.: A lot of this is based on the Lucene QueryParser code, but streamlined to fit the needs of the Dynamo
 * indexing system.
 */
class Parser
{
  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private Analyzer m_analyzer;

  /*--------------------------------------------------------------------------------
   * Internal operations
   *--------------------------------------------------------------------------------
   */

  private static final Query createWildcardQuery(String data)
  {
    Term t = new Term("text",data.toLowerCase());
    return new WildcardQuery(t);

  } // end createWildcardQuery

  private static final Query createPrefixQuery(String data)
  {
    Term t = new Term("text",data.toLowerCase());
    return new PrefixQuery(t);

  } // end createPrefixQuery

  private static final Query createFuzzyQuery(String data)
  {
    Term t = new Term("text",data);
    return new FuzzyQuery(t);

  } // end createFuzzyQuery

  private final Query createNormalQuery(String data)
  {
    TokenStream tstm = m_analyzer.tokenStream("text",new StringReader(data));
    ArrayList toks = new ArrayList();
    org.apache.lucene.analysis.Token t = null;

    for (;;)
    { // use the Lucene TokenStream to find all the tokens and eliminate stopwords
      try
      { // get the next token from the input
        t = tstm.next();

      } // end try
      catch (IOException e)
      { // whoops!
        t = null;

      } // end catch

      if (t==null)
        break;  // done scanning the string

      toks.add(t.termText());

    } // end for (ever)

    if (toks.size()==0)
      return null;  // no query
    if (toks.size()==1)  // single term query
      return new TermQuery(new Term("text",(String)(toks.get(0))));

    // Build a PhraseQuery and return that.
    PhraseQuery rc = new PhraseQuery();
    rc.setSlop(0);
    for (int i=0; i<toks.size(); i++)
      rc.add(new Term("text",(String)(toks.get(i))));
    return rc;

  } // end createNormalQuery
  
  /*--------------------------------------------------------------------------------
   * External static operations
   *--------------------------------------------------------------------------------
   */

  public static Query parse(String s) throws ParseException
  {
    try
    { // quickie parse
      Parser p = new Parser(new StaticCharStream(s));
      return p.search();

    } // end try
    catch (TokenMgrError tme)
    { // translate into a ParseException
      throw new ParseException(tme.getMessage());

    } // end catch

  } // end parse

} // end class Parser

PARSER_END(Parser)

/*--------------------------------------------------------------------------------
 * Token (lexer) definitions
 *--------------------------------------------------------------------------------
 */

<*> TOKEN:
{
    <#_DIGIT: ["0"-"9"]>
  | <#_WHITESPACE: [" ", "\t"]>
  | <#_RESERVED: ["+", "-", "(", ")", "~", "^", "\"", "*", "?"]>
  | <#_ESCAPED: "\\" ( <_RESERVED> | "\\" )>
  | <#_VALID: ~["+", "-", "(", ")", "~", "^", "\"", "*", "?", " ", "\t"]>
  | <#_TERMCHAR: <_VALID> | <_ESCAPED>>

} // end token definitions

<DEFAULT> SKIP:
{
  <<_WHITESPACE>>

} // end skip definition

<DEFAULT> TOKEN:
{
    <AND:        ("AND" | "&&")>                           // AND query
  | <OR:         ("OR" | "||")>                            // OR query
  | <PLUS:       "+">                                      // plus sign
  | <MINUS:      "-">                                      // minus sign
  | <LPAREN:     "(">                                      // left parenthesis
  | <RPAREN:     ")">                                      // right parenthesis
  | <FUZZY:      "~">                                      // "fuzzy" operator
  | <WEIGHT:     "^"> : Weight                             // "weighting" operator
  | <QSTRING:    "\"" (~["\""])+ "\"">                     // quoted string
  | <TERM:       <_TERMCHAR> (<_TERMCHAR>)*>               // simple term
  | <PREFIXTERM: <_TERMCHAR> (<_TERMCHAR>)* "*">           // prefix term
  | <WILDTERM:   <_TERMCHAR> (<_TERMCHAR> | "?" | "*")*>   // term with wildcards

} // end default token definitions

<Weight> TOKEN:
{
  <WEIGHTVAL: (<_DIGIT>)+ ( "." (<_DIGIT>)+ )?> : DEFAULT
}

/*--------------------------------------------------------------------------------
 * BNF (parser) definitions
 *--------------------------------------------------------------------------------
 */

Query search():
{
  Query rc = null;

} // end search declarations
{
  (
    <EOF>
  | rc=search_expression() <EOF>
  )
  { return rc; }

} // end search

Query search_expression(): 
{
  ArrayList clauses = new ArrayList();
  BooleanClause x = null;

} // end search_expression declarations
{
  x=or_expression() { if (x!=null) clauses.add(x); }
  ( (<AND>)? x=or_expression() { if (x!=null) clauses.add(x); } )*
  {
    if (clauses.size()==0)
      return null;
    if (clauses.size()==1)
    {
      x = (BooleanClause)(clauses.get(0));
      if (!(x.prohibited))
        return x.query;
      BooleanQuery rc = new BooleanQuery();
      rc.add(x);
      return rc;

    } // end if

    BooleanQuery q = new BooleanQuery();
    for (int i=0; i<clauses.size(); i++)
    { // get the clauses
      x = (BooleanClause)(clauses.get(i));
      if (!(x.prohibited))
        x.required = true;
      q.add(x);

    } // end for

    return q;

  } // end block

} // end search_expression rule

BooleanClause or_expression():
{
  ArrayList clauses = new ArrayList();
  BooleanClause x = null;

} // end or_expression declarations
{
  x=boolean_expression() { if (x!=null) clauses.add(x); }
  ( <OR> x=boolean_expression() { if (x!=null) clauses.add(x); } )*
  {
    if (clauses.size()==0)
      return null;
    if (clauses.size()==1)
      return (BooleanClause)(clauses.get(0));
    BooleanQuery q = new BooleanQuery();
    for (int i=0; i<clauses.size(); i++)
    { // get the clauses
      x = (BooleanClause)(clauses.get(i));
      if (!(x.prohibited))
        x.required = false;
      q.add(x);

    } // end for

    return new BooleanClause(q,false,false);

  } // end block

} // end or_expression

BooleanClause boolean_expression():
{
  Query q = null;
  boolean prohibited = false;
  boolean required = false;

} // end boolean_expression declarations
{
  [ <PLUS> { required = true; } | <MINUS> { prohibited = true; } ] q=simple_expression()
  {
    return (q==null) ? null : new BooleanClause(q,required,prohibited);

  } // end block

} // end boolean_expression

Query simple_expression():
{
  Query rc = null;
  Token wght = null;
  Token data = null;
  boolean is_prefix = false;
  boolean is_wildcard = false;
  boolean is_fuzzy = false;

} // end simple_expression declarations
{
  (
      (
          data=<TERM>
        | data=<PREFIXTERM> { is_prefix = true; }
        | data=<WILDTERM>   { is_wildcard = true; }
        | data=<WEIGHTVAL>
      )
      [ <FUZZY> { is_fuzzy = true; } ] [ <WEIGHT> wght=<WEIGHTVAL> [ <FUZZY> { is_fuzzy = true; } ] ]
      { // "data" contains the search term value
        if (is_wildcard)
          rc = createWildcardQuery(data.image);
        else if (is_prefix)
          rc = createPrefixQuery(data.image.substring(0,data.image.length()-1));
        else if (is_fuzzy)
          rc = createFuzzyQuery(data.image);
        else
          rc = createNormalQuery(data.image);

      } // end block

    | data=<QSTRING> [ <WEIGHT> wght=<WEIGHTVAL> ]
      { // "data" contains the search term value
        rc = createNormalQuery(data.image.substring(1,data.image.length()-1));

      } // end block

    | <LPAREN> rc=search_expression() <RPAREN> [ <WEIGHT> wght=<WEIGHTVAL> ]
  )
  {
    if (wght!=null)
    { // set the weight of this query
      float weightval = 1.0F;
      try
      { // parse the float value
        weightval = Float.parseFloat(wght.image);

      } // end try
      catch (Exception e)
      { // ignore exceptions
      } // end catch

      if (rc!=null)
        rc.setBoost(weightval);

    } // end if

    return rc;

  } // end block

} // end simple_expression
