package org.jcon.df.request;

import org.jcon.util.GenLib;
import org.jcon.df.Schema;
import org.jcon.util.DataLib;
import java.sql.Statement;
import java.util.Enumeration;
import java.util.Vector;

/**
 * A data related request. Subclasses are a specific type
 * of request, such as CalcCount or ReadRowSet.
 * *** See DUPECODE in df.edit.task.DataRequestProp.
 *
 * @author Jack Harich
 */
abstract public class DataRequest
    implements Request, java.io.Serializable {

//---------- Private Fields ------------------------------

private String    failureText;      // Null if success
private String    failureSQL;
private Exception failureException;

private Vector columnIDs = new Vector();     // String
private Vector entityNames = new Vector();   // String
private Vector insertColumns = new Vector(); // ColumnExp
private Vector updateColumns = new Vector(); // ColumnExp
private Vector joins = new Vector(); // String
private Filter filter = new Filter(); // null if none
private String sortBy; // null if none

private String aggregateExpression;
private String aggregateType;

//---------- Protected Fields ----------------------------
protected transient RequestServices requestServices;

//---------- Inititialization ----------------------------
public void clear() {
    failureText = null;
    failureException = null;

    columnIDs.removeAllElements();
    entityNames.removeAllElements();
    insertColumns.removeAllElements();
    updateColumns.removeAllElements();
    joins.removeAllElements();
    filter = new Filter();
    sortBy = null;

    aggregateExpression = null;
    aggregateType = null;
}
public void copyConfig(DataRequest dataRequest) {
    columnIDs = (Vector)dataRequest.columnIDs.clone();
    entityNames = (Vector)dataRequest.entityNames.clone();
    insertColumns = (Vector)dataRequest.insertColumns.clone();
    updateColumns = (Vector)dataRequest.updateColumns.clone();
    joins = (Vector)dataRequest.joins.clone();
    filter = (Filter)dataRequest.filter.clone();

    if (dataRequest.aggregateExpression != null) {
        aggregateExpression = dataRequest.aggregateExpression.intern();
    }
    if (dataRequest.aggregateType != null) {
        aggregateType = dataRequest.aggregateType.intern();
    }
}
//---------- Request Implementation ----------------------
//----- Methods
public void prepare() {
    // Override to prepare sql using Translator
    // This is for server optimization
}
abstract public Object perform();

//----- Single Properties
abstract public boolean isMutator();

public boolean isSuccessful() {
    return (failureText == null ? true : false);
}
public void clearFailureData() {
    failureText = null;
    failureSQL = null;
    failureException = null;
}
public void setFailureText(String failureText) {
    this.failureText = failureText;
}
public String getFailureText() {
    return failureText;
}
public void setFailureSQL(String sql) {
    failureSQL = sql;
}
public String getFailureSQL() {
    return failureSQL;
}
public void setFailureException(Exception failureException) {
    this.failureException = failureException;
}
public Exception getFailureException() {
    return failureException;
}
public void setRequestServices(RequestServices services) {
    requestServices = services;
}
//---------- Properties ----------------------------------
//----- Various
public Vector getColumnIDs() {
    return columnIDs; // No clone for speed
}
public Vector getInsertColumns() {
    return insertColumns; // No clone for speed
}
public Vector getUpdateColumns() {
    return updateColumns; // No clone for speed
}
public Vector getJoins() {
    return joins;
}
public Vector getEntities() {
    return entityNames; // No clone for speed
}
/**
 * This must be the "prime" entity, such as the one we are
 * adding to or changing. It returns the first entityName
 * added or null if none.
 */
public String getPrimeEntity() {
    if (entityNames.isEmpty()) {
        return null;
    } else {
        return (String)entityNames.firstElement();
    }
}
public boolean isJoined() {
    return (! joins.isEmpty() ? true : false);
}
//----- filter
public void setFilter(Filter filter) {
    this.filter = filter;
}
public Filter getFilter() {
    return filter;
}
public void setFilterLine(String filterLine) {
    filter.parseRawFilter(filterLine);
}
public boolean isFiltered() {
    if (filter == null) {
        return false;
    } else if(filter.isEmpty()) {
        return false;
    } else {
        return true;
    }
}
//----- sortBy
/**
 * Sets the sortBy property. Currently this is the clause
 * following ORDER BY, complete with optional DESC or ASC.
 * For example "LastName, FirstName DESC"
 */
public void setSortBy(String sortBy) {
    this.sortBy = sortBy;
}
/**
 * Returns the sortBy property which is null if none.
 */
public String getSortBy() {
    return sortBy;
}
//----- aggregateExpression
/**
 * Sets the aggregate expression, which is usually just a
 * column name. (*** That's all that's supported now)
 * For COUNT the expression is usually "*".
 */
public void setAggregateExpression(String aggregateExpression) {
    this.aggregateExpression = aggregateExpression;
}
public String getAggregateExpression() {
    return aggregateExpression;
}
//----- aggregateType
/**
 * Sets the AggregateType property, which may be COUNT,
 * SUM, AVERAGE, MIN and MAX.
 */
public void setAggregateType(String aggregateType) {
    if (aggregateType.equals("COUNT") ||
        aggregateType.equals("SUM") ||
        aggregateType.equals("AVERAGE") ||
        aggregateType.equals("MIN") ||
        aggregateType.equals("MAX") ) {

        this.aggregateType = aggregateType;

    } else {
        throw new IllegalArgumentException(
            "Unknown aggregate type '" + aggregateType + "'.\n" +
            "Known types are COUNT, SUM, AVERAGE, MIN and MAX.");
    }
}
public String getAggregateType() {
    return aggregateType;
}
//---------- Public Methods ------------------------------
//----- Mutators
public void addEntity(String entityName) {
    entityNames.addElement(entityName);
}
// Comma delimited
public void addEntities(String entityNames) {
    String[] names = DataLib.convertDelimStringToArray(
        entityNames, ", ");
    for (int i = 0; i < names.length; i++) {
        addEntity(names[i]);
    }
}
/**
 * The columnID must be of the format "EntityName.ColumnName".
 */
public void addColumnID(String columnID) {
    columnIDs.addElement(columnID);
}
/**
 * The line must be of the format "EntityName.*". Actually
 * the "*" is ignored but the period is not.
 */
public void addAllColumnIDs(String line, Schema schema) {
    //print(".addAllColumnIDs() - line = '" + line + "'");
    String entityName = DataLib.getFirstDelimited(line, '.');
    Enumeration names = schema.getEntity(entityName).getColumnNames();
    while (names.hasMoreElements()) {
        String columnName = (String)names.nextElement();
        addColumnID(entityName + "." + columnName);
    }
}
// For simplicity we have insert and updateColumns Vectors,
// even though the two are never (?) used simultaneously.
public void addInsertColumn(String columnID, String value) {
    ColumnExp exp = new ColumnExp();
    exp.setColumnID(columnID);
    exp.setValue(value);
    exp.setOperator("=");
    insertColumns.addElement(exp);
}
public void addUpdateColumn(String columnID, String value) {
    ColumnExp exp = new ColumnExp();
    exp.setColumnID(columnID);
    exp.setValue(value);
    exp.setOperator("=");
    updateColumns.addElement(exp);
}
public void setInsertColumn(String columnID, String value) {
    ColumnExp exp = getInsertColumn(columnID);
    if (exp == null) {
        addInsertColumn(columnID, value);
    } else {
        exp.setValue(value);
    }
}
public void setUpdateColumn(String columnID, String value) {
    ColumnExp exp = getUpdateColumn(columnID);
    if (exp == null) {
        addUpdateColumn(columnID, value);
    } else {
        exp.setValue(value);
    }
}
public boolean removeUpdateColumn(String columnID) { // 99
    return removeColumnID(updateColumns, columnID);
 }
/**
 * Adds a join. The joinExpression must be of the form
 * "ColumnID1 operator ColumnID2" such as
 * "Award.AwardStatusMID = AwardStatus.MID".
 */
public void addJoin(String joinExpression) {
    joins.addElement(joinExpression);
}
// Other
/**
 * Returns the first ColumnExp in columnExps with the
 * columnID or null if not found.
 */
public static ColumnExp findColumnID(Vector columnExps,
        String columnID) {
    for (int i = 0; i < columnExps.size(); i++) {
        ColumnExp columnExp = (ColumnExp)columnExps.elementAt(i);
        if (columnExp.getColumnID().equals(columnID)) {
            return columnExp;
        }
    }
    return null;
}
/**
 * Removes the first occurance of the columnID in
 * columnExps. Returns true if was removed, false if not.
 * False occurs only if it was never there.
 */
public static boolean removeColumnID(Vector columnExps,
        String columnID) {
    for (int i = 0; i < columnExps.size(); i++) {
        ColumnExp columnExp = (ColumnExp)columnExps.elementAt(i);
        if (columnExp.getColumnID().equals(columnID)) {
            columnExps.removeElementAt(i);
            return true;
        }
    }
    return false;
}
//----- Other
public ColumnExp getInsertColumn(String columnID) {
    return findColumnID(insertColumns, columnID);
}
public ColumnExp getUpdateColumn(String columnID) {
    return findColumnID(updateColumns, columnID);
}
public boolean hasInsertColumn(String columnID) {
    return (getInsertColumn(columnID) != null ? true : false);
}
public boolean hasUpdateColumn(String columnID) {
    return (getUpdateColumn(columnID) != null ? true : false);
}
/**
 * Presents the failure text and exception to the user in
 * a uniform manner.
 */
public void presentFailure() {
    // Simulate problem occuring in subclass
    String className = GenLib.getClassLastName(this);
    presentFailure(className + ".perform()");
}
/**
 * Presents the failure text, SQL and exception to the user
 * in a uniform manner. Same as presentFailure() except it
 * uses the provided "ClassName.methodName()" provided.
 * This method is preferred since the exact methodID is
 * supplied.
 */
public void presentFailure(String methodID) {
    String text = "Failure due to: " + failureText;
    if (failureSQL != null) {
        text += "\n" + failureSQL;
    }
    if (failureException == null) {
        GenLib.helpfulHint(failureText + "\n\n" +
            "Detected by " + methodID);
    } else {
        GenLib.exception(methodID, text, failureException);
    }
}
//---------- Private Methods -----------------------------
//--- Std
private static void print(String text) {
    System.out.println("DataRequest" + text);
}

} // End class
