package org.jcon.df.request;

// import org.jcon.util.GenLib;
import java.util.Vector;

/**
 * Provides behavior needed for a filter. Designed for
 * the WHERE clause in a SQL statement. Must contain one
 * or more expressions to not be empty.
 *
 * @author Jack Harich
 */
public class Filter implements Cloneable, java.io.Serializable {

//---------- Private Fields ------------------------------
private Vector columnExps = new Vector();
private Vector tokens = new Vector();

//---------- Initialization ------------------------------
public void clear() {
    columnExps.removeAllElements();
    tokens.removeAllElements();
}
//---------- Superclass Overrides ------------------------
public Object clone() {
    Filter filter = new Filter();
    filter.columnExps = (Vector)columnExps.clone();
    filter.tokens = (Vector)tokens.clone();
    return filter;
}
public static void main(String args[]) {
    new Filter().runUnitTest();
}
//---------- Properties ----------------------------------
public boolean isEmpty() {
    return (columnExps.size() == 0 ? true : false);
}
/**
 * Returns the Vector of ColumnExps, which are the expressions
 * for the filter. This allows their values to be formatted.
 * Call this method after building the filter with the
 * add methods and before getClause().
 */
public Vector getExpressions() {
    return columnExps;
}
//---------- Public Methods ------------------------------
//----- Mutators
public void addExpression(String columnID,
        String operator, String value) {

    ColumnExp columnExp = new ColumnExp();
    columnExp.setColumnID(columnID);
    columnExp.setOperator(operator);
    columnExp.setValue(value);

    columnExps.addElement(columnExp);
    tokens.addElement(columnExp);
}
public void addOpenParen() {
    tokens.addElement("(");
}
public void addCloseParen() {
    tokens.addElement(")");
}
// Possibly addWrapParams() later

public void addAnd() {
    if (! tokens.isEmpty()) tokens.addElement(" AND ");
}
public void addOr() {
    if (! tokens.isEmpty()) tokens.addElement(" OR ");
}
//----- Readers
/**
 * Builds and returns the filter clause which is "" if empty.
 * Avoid calling this method if the filter is empty by
 * first checking with isEmpty(). The expressions must be
 * formatted first so that the clause is correct.
 */
public String buildClause() {
    String clause = "";
    for (int i = 0; i < tokens.size(); i++) {
        Object token = tokens.elementAt(i);
        if (token instanceof String) {
            clause += (String)token;
        } else {
            ColumnExp ColumnEmp = (ColumnExp)token;
            clause += ColumnEmp.getFormattedExpression();
        }
    }
    //print(".getClause() - clause: " + clause);
    return clause;
}
//----- Other
public void setFirstEntityIfMissing(String entityName) {
    for (int i = 0; i < columnExps.size(); i++) {
        ColumnExp exp = (ColumnExp)columnExps.elementAt(i);
        String columnID = exp.getColumnID();
        if (columnID.indexOf(".") < 0) {
            exp.setColumnID(entityName + "." + columnID);
        }
    }
}
/**
 * Parses the SQL style text into the Filter. Example:
 * (Age = 39 OR height <= 60) AND User.Name = Sherry.
 * Values should not be surrounded by quotes or such,
 * since the system will handle that. Only simple values
 * are supported.
 * <p>
 * Use "NOTLIKE" rather than "NOT LIKE". It will be
 * expanded correctly and makes parsing much easier.
 * <p>
 * This is a crude first pass implementation. ***
 * Eventually support quotes on broken strings.
 */
public void parseRawFilter(String text) {
    text = text.trim() + " "; // To add final expresion
    String state = "InColumn";
    String word = "";
    String columnID = "", operator = "" , value = "";
    char[] letters = text.toCharArray();

    for (int i = 0; i < letters.length; i++) {
        char letter = letters[i];
        if (letter == '(') {
            if (state == "InValue") {
                value = word; // DUPECODE
                state = "InColumn";
                addExpression(columnID, operator, value);
                word = "";
            }
            addOpenParen();
        } else if (letter == ')') {
            if (state == "InValue") {
                value = word; // DUPECODE
                state = "InColumn";
                addExpression(columnID, operator, value);
                word = "";
            }
            addCloseParen();
        } else if (letter == ' ') {
            // End of word
            if (word == "") {
                // Do nothing
            } else if (word.equalsIgnoreCase("OR")) {
                addOr();
            } else if (word.equalsIgnoreCase("AND")) {
                addAnd();

            } else if (state == "InColumn") {
                columnID = word;
                state = "InOperator";
            } else if (state == "InOperator") {
                operator = word;
                state = "InValue";
            } else if (state == "InValue") {
                value = word; // DUPECODE
                state = "InColumn";
                addExpression(columnID, operator, value);
            } else {
                print(" unknown state for blank");
            }
            word = "";
        } else {
            // Not end of word so add letter
            word += String.valueOf(letter);
        }
        //print(" letter " + letter + ", word " + word
        //    + ", state " + state);
    }
}
public void runUnitTest() {
    Filter f = new Filter();
    test(f, "Age = 39");
    test(f, "Age = 39 AND Height = 123");
    test(f, "(A = 1) AND B = 2");
    test(f, "(Age = 39 OR Height <= 2)");
    test(f, "(Age = 39 or Height <= 2) and Name = Sherry");

    f.setFirstEntityIfMissing("Ent");
    print(" " + f.buildClause());
    print(" - filter is empty = " + f.isEmpty());
}
//---------- Private Methods -----------------------------
private void test(Filter filter, String text) {
    filter.clear();
    System.out.println("Text: " + text);
    filter.parseRawFilter(text);

    // Fake formatting
    Vector exps = filter.getExpressions();
    for (int i = 0; i < exps.size(); i++) {
        ColumnExp exp = (ColumnExp)exps.elementAt(i);
        exp.setFormattedValue(exp.getValue());
    }
    // Output should be identical to input, except
    // and & or are capitalized.
    System.out.println("--->: " + filter.buildClause() + "\n");
}
//--- Std
private static void print(String text) {
    System.out.println("Filter" + text);
}

} // End class
