/*
 * ECatalog is a database front-end, with two main features:
 * 1. Use of preferences
 *  A preference-based approach, where the user is allowed to define the importance of each criterion.
 *  Then the items are ranked accordingly to his criteria.
 * 2. Trade-off analysis
 *  A cooperative database approach, where the system "argues" with the user about his criteria.
 *  When there are no matching items, the system explains the minimal conflicting set and
 *  give some possible strong and weak relaxations about his criteria.
 * This package also containts the software and the set-up details used for our User Study,
 * comparing the use or not of the two previous features mentioned above.
 *
 * Copyright (C) 2006 David Portabella Clotet, Artificial Intelligence Laboratory, EPFL
 * 
 * This file is part of ecatalog-1.0.zip
 * 
 * ECatalog is free software and a free user study set-up;
 * you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * ECatalog is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with ECatalog; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * @version 1.0
 * @author David Portabella
 * To contact the author:
 * email: david@portabella.name and david.portabella@epfl.ch
 * 
 * More information about ECatalog:
 *  http://sourceforge.net/projects/ecatalog/
 *  http://icwww.epfl.ch/~portabel/ecatalogs/
 */

package ecatalog.db;

import ecatalog.ECatalog;

import dpc.utils.SqlUtil;
import dpc.utils.DebugUtil;

import java.sql.ResultSet;
import java.sql.SQLException;

//import org.apache.bsf.BSFException;


/* This is a super class for equal, lessThan and moreThan constraints which only need one valueIndex as the details */
abstract public class SimpleAttributeConstraint extends AttributeConstraint {
    int valueIndex;

    NumberAttributePenaltyFunction penaltyFunction;
    String withConstraintStringFunction;
    String relaxConstraintStringFunction;

    /* Current Number Of Compatible Items if the value (from the list of attribute.valuesId) were selected.
       This pressuposes that the current details of this contraint (if defined) are ignored.
       The index corresponds to attribute.valuesId.
    */
    int[] currentNbrCompatibleItemsIfValueSelected;
    int currentNbrCompatibleItemsIfExampleValueSelected;
    boolean[] valueSatisfiesConstraint = null;
    int currentNbrCompatibleItemsExcludingThisConstraint;

    public SimpleAttributeConstraint(Attribute attribute, Database db, Weight weight, 
				     NumberAttributePenaltyFunction penaltyFunction) {
	super(attribute, db, weight); 
	this.penaltyFunction = penaltyFunction;
	valueIndex = -1;
	withConstraintStringFunction = attribute.config.getWithConstraintStringFunction();
	relaxConstraintStringFunction = attribute.config.getRelaxConstraintStringFunction();
	update();
    }


    public void removeDetails() {
	valueIndex = -1;
    }

    public boolean areDetailsDefined() {
	return (valueIndex != -1);
    }

    public void setValueIndex(int valueIndex) {
	this.valueIndex = valueIndex;
    }

    public int getValueIndex() {
	return valueIndex;
    }

    public void importDetails(Constraint sourceConstraint) {
	removeDetails();
	if (!sourceConstraint.areDetailsDefined())
	    return;
	
	if (sourceConstraint instanceof SimpleAttributeConstraint) {
	    valueIndex = ((SimpleAttributeConstraint)sourceConstraint).valueIndex;
	} else if (sourceConstraint instanceof MultipleValuesConstraint) {
	    valueIndex = ((MultipleValuesConstraint)sourceConstraint).getValueIndexAt(0);
	}
    }

    /* update (or mark to be updated) the proper context variables */
    public void update() {
	valueSatisfiesConstraint = null;
	currentNbrCompatibleItemsExcludingThisConstraint = -1;
	currentNbrCompatibleItemsIfValueSelected = null;
	currentNbrCompatibleItemsIfExampleValueSelected = -1;
    }

    public boolean doesAttributeValueSatisfyConstraint(Attribute attribute, int valueIndex) {
	if (!areDetailsDefined() || this.attribute != attribute)
	    return true;

	if (valueSatisfiesConstraint == null)
	    computeValueSatisfiesConstraint();
	return valueSatisfiesConstraint[valueIndex];
    }

    //TODO: precompute penalties for all items
    public double getPenaltyForItem(Item item) throws SQLException {
	if (penaltyFunction == null)
	    return doesAttributeValueSatisfyConstraint(attribute, attribute.getValueIndex(item)) ? 0.0 : -1.5;

	double constraintValue = ((NumberAttribute)attribute).getValueId(valueIndex);
	double itemValue = ((NumberAttribute)attribute).getValueId(attribute.getValueIndex(item));
	return penaltyFunction.getPenaltyForValue(constraintValue, itemValue);
    }

    abstract void computeValueSatisfiesConstraint();

    /* Current Number Of Compatible Items, Excluding This Constraint; Is there a better name for this, than nbrItems??   */
    public int getCurrentNbrCompatibleItemsExcludingThisConstraint() throws SQLException { 
	if (currentNbrCompatibleItemsExcludingThisConstraint == -1)
	    computeCurrentNbrCompatibleItemsExcludingThisConstraint();
	return currentNbrCompatibleItemsExcludingThisConstraint; 
    }

    void computeCurrentNbrCompatibleItemsExcludingThisConstraint() throws SQLException{
	computeCurrentNbrCompatibleItemsIfValueSelected();
    }

    public int getCurrentNbrCompatibleItemsIfValueSelected(int valueIndex) throws SQLException {
	if (currentNbrCompatibleItemsIfValueSelected != null)
	    return currentNbrCompatibleItemsIfValueSelected[valueIndex];

	if (valueIndex == attribute.getCurrentExampleValueIndex()) {
	    if (currentNbrCompatibleItemsIfExampleValueSelected == -1)
		computeCurrentNbrCompatibleItemsIfExampleValueSelected();
	    return currentNbrCompatibleItemsIfExampleValueSelected;

	} else {
	    computeCurrentNbrCompatibleItemsIfValueSelected();
	    return currentNbrCompatibleItemsIfValueSelected[valueIndex];
	}
    }

    void computeCurrentNbrCompatibleItemsIfValueSelected() throws SQLException {
	ECatalog.lowlevelMsg("EXPENSIVE: constraint[" + attribute.getLabel() + "].computeCurrentNbrCompatibleItemsIfValueSelected()");
	String query="SELECT " + attribute.id + ", COUNT(*) FROM " + db.fromClause + " WHERE " + db.getConstraintsClauseExcept(this) + " GROUP BY " + attribute.id + " ORDER BY " + attribute.id;
	ResultSet rs = db.executeQuery(query);

	currentNbrCompatibleItemsExcludingThisConstraint = 0;
	currentNbrCompatibleItemsIfValueSelected = new int[attribute.nbrDistinctValues];  // reset to zero
	while (rs.next()) {
	    int index = attribute.getValueIndex(rs, 1);
	    assert(index != -1);
	    int nbr = rs.getInt(2);
	    currentNbrCompatibleItemsIfValueSelected[index] = nbr;
	    currentNbrCompatibleItemsExcludingThisConstraint += nbr;
	}
	rs.close();
    }

    void computeCurrentNbrCompatibleItemsIfExampleValueSelected() throws SQLException {
	ECatalog.lowlevelMsg("EXPENSIVE: constraint[" + attribute.getLabel() + "].computeCurrentNbrCompatibleItemsIfExampleValueSelected()");

	//Get the WHERE clause for the example value
	SimpleAttributeConstraint tmpC;
	try { tmpC = (SimpleAttributeConstraint) this.clone(); }
	catch (CloneNotSupportedException e) { 
	    ECatalog.error(new Error(e)); 
	    throw new Error(); //it is already done, but to avoid the java compiling error
	}
	tmpC.setValueIndex(attribute.getCurrentExampleValueIndex());
	String exampleClause = tmpC.getSqlClauseTerm();

	//get currentNbrCompatibleItemsIfExampleValueSelected
	String query="SELECT COUNT(*) FROM " + db.fromClause + " WHERE " + db.getConstraintsClauseExcept(this) + " AND " + exampleClause;
	currentNbrCompatibleItemsIfExampleValueSelected = SqlUtil.getUniqueInt(db.executeQuery(query));
    }


    abstract public String getTextClauseTerm(int valueIndex);

    public String getWithConstraintString() {
	return getWithConstraintString(valueIndex);
    }

    public String getWithConstraintString(int valueIndex) {
	//DebugUtil.printTrace();
	if (withConstraintStringFunction == null)
	    return getTextClauseTerm(valueIndex);

	try {
	    String params = "valueId = " + attribute.getDbValueString(valueIndex, true) + "; constraintType = \"" + this.getClass().getName() + "\"; ";
	    Object result = db.scriptEval(params + withConstraintStringFunction);
	    if (result == null)
		return getTextClauseTerm();
	    return result.toString();
	} catch (Exception e) { 
	    ECatalog.error(new Error(e)); 
	    throw new Error(); //it is already done, but to avoid the java compiling error
	}
    }


    public String getRelaxConstraintString() {
	//DebugUtil.printTrace();
	if (relaxConstraintStringFunction == null)
	    return getTextClauseTerm();

	try{
	    String params = "valueId = " + attribute.getDbValueString(valueIndex, true) + "; constraintType = \"" + this.getClass().getName() + "\"; ";
	    Object result = db.scriptEval(params + relaxConstraintStringFunction);
	    if (result == null)
		return getTextClauseTerm();
	    return result.toString();
	} catch (Exception e) { 
	    ECatalog.error(new Error(e)); 
	    throw new Error(); //it is already done, but to avoid the java compiling error
	}
    }
}

