/*
 * MiningMart Version 1.0
 * 
 * Copyright (C) 2006 Martin Scholz, Timm Euler, 
 *                    Daniel Hakenjos, Katharina Morik
 *
 * Contact: miningmart@ls8.cs.uni-dortmund.de
 *
 * A list of contributing developers (other than the copyright 
 * holders) can be found at
 * http://mmart.cs.uni-dortmund.de/downloads/download.html
 * 
 * This program is free software; 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.
 * 
 * This program 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 this program, see the file MM_HOME/LICENSE; if not, write
 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301, USA.
 */
package edu.udo.cs.miningmart.m4.core;
import java.util.Collection;
import java.util.Iterator;
import java.util.Vector;

import edu.udo.cs.miningmart.db.DB;
import edu.udo.cs.miningmart.exception.M4CompilerError;
import edu.udo.cs.miningmart.exception.M4Exception;
import edu.udo.cs.miningmart.exception.OperatorRuntimeConditionViolated;
import edu.udo.cs.miningmart.exception.UserError;
import edu.udo.cs.miningmart.m4.BaseAttribute;
import edu.udo.cs.miningmart.m4.Column;
import edu.udo.cs.miningmart.m4.Concept;
import edu.udo.cs.miningmart.m4.Feature;
import edu.udo.cs.miningmart.m4.MultiColumnFeature;
import edu.udo.cs.miningmart.m4.ParameterObject;
import edu.udo.cs.miningmart.m4.utils.M4Info;
import edu.udo.cs.miningmart.m4.utils.M4InfoEntry;
import edu.udo.cs.miningmart.m4.utils.Print;
import edu.udo.cs.miningmart.m4.utils.XmlInfo;
import edu.udo.cs.miningmart.operator.ExecutableOperator;
/**
 * Objects of this class represent M4 conditions.
 * These conditions are tuples stored in the table <code>OP_COND_T</code>.
 * 
 * @author Martin Scholz
 * @version $Id: Condition.java,v 1.9 2006/04/11 14:10:13 euler Exp $
 */
public class Condition extends M4Data implements XmlInfo, edu.udo.cs.miningmart.m4.Condition {

	/** The name of the corresponding M4 table. */
	public static final String M4_TABLE_NAME = "op_cond_t";

	/** db level: name of the condition's id attribute */
	public static final String ATTRIB_COND_ID = "cond_id";

	/** db level: name of the operator id attribute */
	public static final String ATTRIB_OPERATOR_ID = "cond_opid";

	/** db level: name of the attribute specifying the condition type */
	public static final String ATTRIB_COND_TYPE = "cond_type";

	/** db level: name of the attribute for first argument */
	public static final String ATTRIB_COND_OBJ1 = "cond_obj1";

	/** db level: name of the attribute for second argument */
	public static final String ATTRIB_COND_OBJ2 = "cond_obj2";


	/** Cache for getM4Info() */
	private static M4Info m4Info = null;

	/** @see M4Table.getM4TableName() */
	public String getM4TableName() {
		return M4_TABLE_NAME;	
	}

	/** @see M4Table.getIdAttributeName() */
	public String getIdAttributeName() {
		return ATTRIB_COND_ID;
	}

	/** @see M4Table.getM4Info() */
	public M4Info getM4Info() {
		if (m4Info == null) {
			M4InfoEntry[] m4i = {
				new M4InfoEntry(ATTRIB_COND_ID,     "getId",          "setId",                long.class,      NOT_NULL),
				new M4InfoEntry(ATTRIB_OPERATOR_ID, "getTheOperator", "primitiveSetOperator", edu.udo.cs.miningmart.m4.Operator.class,  NOT_NULL),
				new M4InfoEntry(ATTRIB_COND_TYPE,   "getType",        "setType",              String.class,    NOT_NULL),
				new M4InfoEntry(ATTRIB_COND_OBJ1,   "getObj1",        "setObj1",              String.class,    NOT_NULL),
				new M4InfoEntry(ATTRIB_COND_OBJ2,   "getObj2",        "setObj2",              String.class)
			};
			m4Info = new M4Info(m4i);
		}
		return m4Info;
	}

	/** Cache for getXmlInfo() */
	private static M4Info xmlInfo = null;

	/** @see M4Table.getM4Info() */
	public M4Info getXmlInfo() {
		if (xmlInfo == null) {
			M4InfoEntry[] m4i = {
				new M4InfoEntry("Operator", "getTheOperator",   "setTheOperator",   edu.udo.cs.miningmart.m4.Operator.class),
				new M4InfoEntry("Type",     "getType",          "setType",          String.class),
				new M4InfoEntry("Obj1",     "getObj1",          "setObj1",          String.class),
				new M4InfoEntry("Obj2",     "getObj2",          "setObj2",          String.class),
				new M4InfoEntry("Docu",     "getDocumentation", "setDocumentation", String.class)
			};
			xmlInfo = new M4Info(m4i);
		}
		return xmlInfo;
	}

	// The private fields of assertion objects:
	private Operator myOperator;
	private String myType;
	private String myObj1;
	private String myObj2;

	/**
	 * @see edu.udo.cs.miningmart.m4.core.M4Data#Constructor
	 */
	public Condition(DB db) {
		super(db);	
	}

	/**
	 * @see M4Object#print()
	 */
	public void print() {
		edu.udo.cs.miningmart.m4.Operator op = this.getTheOperator();
		String opName = (op == null) ? null : op.getName();

		this.doPrint(Print.M4_OBJECT, "Condition (Id = " + this.getId() + ";"
				+ " Name = " + this.getName() + "; TYPE = " + this.getType()
				+ " OBJ1 = " + this.getObj1() + "; OBJ2 = " + this.getObj2()
				+ " Operator = " + opName + ")");

	}
	
	/**
	 * @see edu.udo.cs.miningmart.m4.core.M4Data#getObjectsInNamespace(Class)
	 */
	protected Collection getObjectsInNamespace(Class typeOfObjects) throws M4Exception {
		return null;
	}

	/**
	 * Gets the myObj1.
	 * @return Returns a String
	 */
	public String getObj1() {
		return myObj1;
	}

	/**
	 * Gets the myObj2.
	 * @return Returns a String
	 */
	public String getObj2() {
		return myObj2;
	}

	/**
	 * Gets the Operator
	 * @return Returns an <code>Operator</code>
	 */
	public edu.udo.cs.miningmart.m4.Operator getTheOperator() {
		return myOperator;
	}

	/**
	 * Gets the myType.
	 * @return Returns a String
	 */
	public String getType() {
		return myType;
	}

	/**
	 * Sets the myObj1.
	 * @param myObj1 The myObj1 to set
	 */
	public void setObj1(String myObj1) {
		this.setDirty();
		this.myObj1 = myObj1;
	}

	/**
	 * Sets the myObj2.
	 * @param myObj2 The myObj2 to set
	 */
	public void setObj2(String myObj2) {
		this.setDirty();
		this.myObj2 = myObj2;
	}

	/**
	 * Sets the Operator.
	 * @param operator The <code>Operator</code> to set
	 */
	public void setTheOperator(edu.udo.cs.miningmart.m4.Operator operator) throws M4Exception {
		Operator.op2cond.updateReferenceTo(this, operator);
	}

	/** Primitive setter method. Do not use it! */
	public void primitiveSetOperator(edu.udo.cs.miningmart.m4.Operator operator) {
		this.setDirty();
		this.myOperator = (Operator) operator;	
	}

	/**
	 * Sets the myType.
	 * @param myType The myType to set
	 */
	public void setType(String myType) {
		this.setDirty();
		this.myType = myType;
	}

	/** @see M4Data#removeAllM4References() */
	protected void removeAllM4References() throws M4Exception {
		this.setTheOperator(null);
		this.removeDocObject();
	}
	
	public void check(ExecutableOperator op)
		throws UserError, M4CompilerError
	{
		String conditionType = this.getType();
		for (int loop=0; loop < op.getNumberOfLoops(); loop++) {
			if (conditionType.equals(CONDITION_NOT_NULL))
				checkNotNull(op, loop);
			else if (conditionType.equals(CONDITION_UNIQUE))
				checkUniqueness(op, loop);
			else if (conditionType.equals(CONDITION_HAS_VALUES))
				checkHasValues(op, loop);
			else if (conditionType.equals(CONDITION_LOWER_BOUND))
				checkLowerBound(op, loop);
			else if (conditionType.equals(CONDITION_UPPER_BOUND))
				checkUpperBound(op, loop);
		}
	}

	// -------------------------------------------
	private void checkNotNull(ExecutableOperator exOp, int loop)
		throws M4CompilerError
	{
		try {
			String obj1 = this.getObj1();
			ParameterObject[] parArray = exOp.getParameter(obj1, loop);
			Collection columns = getColumnsForParameter(parArray);
			
			Iterator it = columns.iterator();
			while (it.hasNext()) {
				Column column = (Column) it.next();
				int numMissing = column.readOrComputeNumMissingValues();
				if (numMissing > 0) {
					throw new OperatorRuntimeConditionViolated(createExceptionText(exOp));
				}
			}
		}
		catch (M4Exception m4e) {
			throw new M4CompilerError(m4e.getMessage());
		}
	}

	private void checkUniqueness(ExecutableOperator exOp, int loop) throws M4CompilerError {
		try {
			String obj1 = this.getObj1();
			ParameterObject[] parArray = exOp.getParameter(obj1, loop);
			Collection columns = getColumnsForParameter(parArray);
			
			Iterator it = columns.iterator();
			while (it.hasNext()) {
				Column column = (Column) it.next();
				int numUnique = column.readOrComputeUniqueValues();
				String countS = column.getColumnset().readOrComputeCount();
				try {
					long count = Long.parseLong(countS);
					if (numUnique != count) {
						throw new OperatorRuntimeConditionViolated(createExceptionText(exOp));
					}				
				}
				catch (NumberFormatException e) {
					this.doPrint(Print.WARNING, "Could not compute count for Columnset with ID "
												+ column.getColumnset().getId());
				}
			}
		}
		catch (M4Exception m4e) {
			throw new M4CompilerError(m4e.getMessage());
		}		
	}
	
	private void checkHasValues(ExecutableOperator exOp, int loop) 
	throws M4CompilerError {
		final int compareTo;
		{
			String obj2 = this.getObj2();
			if (obj2 == null || obj2.trim().length() == 0) {
				compareTo = 1;
			}
			else {
				int num = 1;
				try {
					num = Integer.parseInt(obj2);
				}
				catch (NumberFormatException e) {}
				compareTo = num;
			}
		}
		
		try {
			String obj1 = this.getObj1();
			ParameterObject[] parArray = exOp.getParameter(obj1, loop);
			Collection columns = getColumnsForParameter(parArray);
			
			Iterator it = columns.iterator();
			while (it.hasNext()) {
				Column column = (Column) it.next();
				int numMissing = column.readOrComputeNumMissingValues();
				String countS = column.getColumnset().readOrComputeCount();
				try {
					long count = Long.parseLong(countS);
					if ((count - numMissing) < compareTo) {
						throw new OperatorRuntimeConditionViolated(createExceptionText(exOp));
					}				
				}
				catch (NumberFormatException e) {
					this.doPrint(Print.WARNING, "Could not compute count for Columnset with ID "
												+ column.getColumnset().getId());
				}
			}
		}
		catch (M4Exception m4e) {
			throw new M4CompilerError(m4e.getMessage());
		}
	}
	
	private void checkLowerBound(ExecutableOperator exOp, int loop)
	throws M4CompilerError {
		String obj2 = this.getObj2();
		double lowerBound = 0;
		try {
			lowerBound = Double.parseDouble(obj2);
		}
		catch (NumberFormatException e) {
			// TODO: support date entries
			return; // ignore condition if reference value is not numeric 
		}
		
		try {
			String obj1 = this.getObj1();
			ParameterObject[] parArray = exOp.getParameter(obj1, loop);
			Collection columns = getColumnsForParameter(parArray);
			
			Iterator it = columns.iterator();
			while (it.hasNext()) {
				Column column = (Column) it.next();
				String min = column.readOrComputeMinimum();
				try {
					double d = Double.parseDouble(min);
					if (d < lowerBound) {
						throw new OperatorRuntimeConditionViolated(createExceptionText(exOp));
					}
				}
				catch (NumberFormatException e) {}
			}
		}
		catch (M4Exception m4e) {
			throw new M4CompilerError(m4e.getMessage());
		}
	}
	
	private void checkUpperBound(ExecutableOperator exOp, int loop)
	throws M4CompilerError {
		String obj2 = this.getObj2();
		double upperBound = 0;
		try {
			upperBound = Double.parseDouble(obj2);
		}
		catch (NumberFormatException e) {
			// TODO: support date entries
			return; // ignore condition if reference value is not numeric 
		}
		
		try {
			String obj1 = this.getObj1();
			ParameterObject[] parArray = exOp.getParameter(obj1, loop);
			Collection columns = getColumnsForParameter(parArray);
			
			Iterator it = columns.iterator();
			while (it.hasNext()) {
				Column column = (Column) it.next();
				String max = column.readOrComputeMaximum();
				try {
					double d = Double.parseDouble(max);
					if (d > upperBound) {
						throw new OperatorRuntimeConditionViolated(createExceptionText(exOp));
					}
				}
				catch (NumberFormatException e) {}
			}
		}
		catch (M4Exception m4e) {
			throw new M4CompilerError(m4e.getMessage());
		}
	}

	private void checkAverage(ExecutableOperator exOp, int loop)
	throws M4CompilerError {
		String obj2 = this.getObj2();
		double average = 0;
		try {
			average = Double.parseDouble(obj2);
		}
		catch (NumberFormatException e) {
			// TODO: support date entries
			return; // ignore condition if reference value is not numeric 
		}
	
		try {
			String obj1 = this.getObj1();
			ParameterObject[] parArray = exOp.getParameter(obj1, loop);
			Collection columns = getColumnsForParameter(parArray);
			
			Iterator it = columns.iterator();
			while (it.hasNext()) {
				Column column = (Column) it.next();
				String avgS = column.readOrComputeAverage();
				try {
					double avg = Double.parseDouble(avgS);
					if (avg != average) {
						throw new OperatorRuntimeConditionViolated(createExceptionText(exOp));
					}
				}
				catch (NumberFormatException e) {}
			}
		}
		catch (M4Exception m4e) {
			throw new M4CompilerError(m4e.getMessage());
		}
	}

	private void checkStdDev(ExecutableOperator exOp, int loop)
	throws M4CompilerError {
		String obj2 = this.getObj2();
		final double stddev = Double.parseDouble(obj2);

		try {
			String obj1 = this.getObj1();
			ParameterObject[] parArray = exOp.getParameter(obj1, loop);
			Collection columns = getColumnsForParameter(parArray);
			
			Iterator it = columns.iterator();
			while (it.hasNext()) {
				Column column = (Column) it.next();
				String sdS = column.readOrComputeStdDev();
				try {
					double sd = Double.parseDouble(sdS);
					if (sd != stddev) {
						throw new OperatorRuntimeConditionViolated(createExceptionText(exOp));
					}
				}
				catch (NumberFormatException e) {}
			}
		}
		catch (M4Exception m4e) {
			throw new M4CompilerError(m4e.getMessage());
		}
	}

	
	// ---------------------------------

	private String createExceptionText(ExecutableOperator exOp) {
		return "Step " + exOp.getStep().getName() + "(ID: " + exOp.getStep().getId()
			+ ") violates the following runtime condition:\n"
			+ "Embedded operator is " + exOp.getOperator().getName()
			+ ",\nType of condition is " + this.getType()
			+ " (" + (this.getObj1() == null ? "NULL" : this.getObj1())
			+ ", " + (this.getObj2() == null ? "NULL" : this.getObj2())
			+ ")\n";
	}
	
	public static Collection getColumnsForParameter(edu.udo.cs.miningmart.m4.ParameterObject[] parArray)
		throws M4Exception
    {
		Vector columns = new Vector();
		if (parArray== null)
			return columns;
		
		for (int i=0; i<parArray.length; i++) {
			edu.udo.cs.miningmart.m4.ParameterObject par = parArray[i];
			if (par instanceof Feature) {
				columns.addAll(getColumnsForFeature((Feature) par));
			}
			else if (par instanceof Concept) {
				columns.addAll(getColumnsForConcept((Concept) par));
			}
		}
		return columns;
    }
	
	public static Collection getColumnsForConcept(Concept con)
		throws M4Exception
	{
		Vector res = new Vector();
		if (con.getFeatures() != null) {
			Iterator it = con.getFeatures().iterator();
			while (it.hasNext()) {
				Feature feature = (Feature) it.next();
				res.addAll(getColumnsForFeature(feature));
			}		
		}
		return res;
	}

	public static Collection getColumnsForFeature(Feature feature)
		throws M4Exception
	{
		Vector res = new Vector();
		if (feature instanceof edu.udo.cs.miningmart.m4.BaseAttribute) {
			res.addAll(((BaseAttribute) feature).getColumns());
		}
		else if (feature instanceof MultiColumnFeature) {
			MultiColumnFeature mcf = (MultiColumnFeature) feature;
			if (mcf.getBaseAttributes() != null) {
				Iterator mcfIt = mcf.getBaseAttributes().iterator();
				while (mcfIt.hasNext()) {
					res.addAll(((BaseAttribute) mcfIt.next()).getColumns());
				}
			}
		}
		return res;
	}

	public static Collection getColumnsForBa(BaseAttribute ba)
		throws M4Exception
	{
		Vector res = new Vector();
		if (ba != null && ba.getColumns() != null) {
			res.addAll(ba.getColumns());
		}
		return res;
	}

	// -------------------------------------------------
	private static final String CONDITION_NOT_NULL    = "NOT_NULL"; // implemented
	private static final String CONDITION_HAS_VALUES  = "HAS_VALUES"; // implemented
	private static final String CONDITION_UNIQUE      = "UNIQUE"; // implemented
	private static final String CONDITION_LOWER_BOUND = "LOWERBOUND"; // implemented
	private static final String CONDITION_UPPER_BOUND = "UPPERBOUND"; // implemented
	private static final String CONDITION_AVG         = "AVG"; // implemented
	private static final String CONDITION_STD_DEV     = "STD_DEV"; // implemented

	private static final String CONDITION_HAS_NULLS   = "HAS_NULLS"; // needs special treatment, not yet implemented
	private static final String CONDITION_ORDERED     = "ORDERED";
	private static final String CONDITION_EQUIDIST    = "EQUIDIST";

	// Never used (correctly):
	private static final String CONDITION_LE 			= "LE";
	private static final String CONDITION_LT 			= "LT";
	private static final String CONDITION_GE 			= "GE";
	private static final String CONDITION_GT			= "GT";
	
}
/*
 * Historie
 * --------
 * 
 * $Log: Condition.java,v $
 * Revision 1.9  2006/04/11 14:10:13  euler
 * Updated license text.
 *
 * Revision 1.8  2006/04/06 16:31:13  euler
 * Prepended license remark.
 *
 * Revision 1.7  2006/03/23 11:13:45  euler
 * Improved exception handling.
 *
 * Revision 1.6  2006/03/20 16:02:41  scholz
 * robustification against null pointers
 *
 * Revision 1.5  2006/03/20 14:03:06  scholz
 * changed methods also useful for Assertion to public static
 *
 * Revision 1.4  2006/03/20 13:23:01  scholz
 * implemented further methods for checking operator conditions
 *
 * Revision 1.3  2006/03/20 12:39:22  scholz
 * changed upper_/lower_bound to upper/lowerbound
 *
 * Revision 1.2  2006/03/19 21:16:51  scholz
 * conditions are now checked before executing an operator
 *
 * Revision 1.1  2006/01/03 09:54:17  hakenjos
 * Initial version!
 *
 */
