/*
 * 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.compiler.utils;

import java.sql.ResultSet;
import java.sql.SQLException;
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.m4.core.BaseAttribute;
import edu.udo.cs.miningmart.m4.core.Case;
import edu.udo.cs.miningmart.m4.core.Chain;
import edu.udo.cs.miningmart.m4.core.Column;
import edu.udo.cs.miningmart.m4.core.Columnset;
import edu.udo.cs.miningmart.m4.core.Concept;
import edu.udo.cs.miningmart.m4.core.Operator;
import edu.udo.cs.miningmart.m4.core.Parameter;
import edu.udo.cs.miningmart.m4.core.ParameterObject;
import edu.udo.cs.miningmart.m4.core.Step;
import edu.udo.cs.miningmart.m4.core.Value;
import edu.udo.cs.miningmart.m4.utils.Print;

/**
 * Abstract superclass for tools which create a MiningMart step
 * that uses FeatureConstruction for a number of attributes, where
 * this number is dependent on the number of values of another column.
 * 
 * Direct known subclasses: Binarify, PivotizeWithoutAggregation
 * 
 * @author Timm Euler
 * @version $Id: AttributeCreationTool.java,v 1.3 2006/04/11 14:10:19 euler Exp $
 */
public abstract class AttributeCreationTool
{
    protected final DB myDB;
	protected final Case myCase;
	protected final Concept myInputConcept;
	protected final Chain myChain;
	protected final Columnset myInputColumnset;
	protected final Column myIndexColumn;
	protected final BaseAttribute myIndexBA;
	protected Step myStep;
	protected Operator myOperator;
	protected int numOfLoops;
	
	/**
	 * Constructor for AttributeCreationTool. We expect the input concept,
	 * the BaseAttribute that contains the values that determine the number of
	 * output attributes, and the chain to which the new step will belong.
	 * 
	 * @param inputConcept The Input Concept for the Step to be created.
	 * @param indexBA The index attribute, used to determine the number of output attributes
	 * @param chain The Chain to which to add the new Step
	 */
	public AttributeCreationTool( edu.udo.cs.miningmart.m4.Concept inputConcept, 
			                      edu.udo.cs.miningmart.m4.BaseAttribute indexBA, 
								  edu.udo.cs.miningmart.m4.Chain chain) 
           throws M4Exception {
		
		// In the constructor all we do are lots of tests for input validity
		
		String msgIntro = "Constructor of " + this.getClass().getName() + ":\n";
		
		if (inputConcept == null || indexBA == null || chain == null) {
			throw new M4Exception(msgIntro + "<null> argument found!");
		}
		if (! inputConcept.equals(indexBA.getConcept())) {
			throw new M4Exception(msgIntro + "The specified index BaseAttribute does not belong to the InputConcept!");
		}		
		
		this.myDB  = inputConcept.getM4Db();
		this.myInputConcept = (Concept) inputConcept;
		this.myChain = (Chain) chain;
		this.myCase = (Case) inputConcept.getTheCase();
		this.myIndexBA = (BaseAttribute) indexBA;
		
		if (this.myCase == null || ! myCase.equals(chain.getTheCase())) {
			throw new M4Exception(msgIntro + "InputConcept and Chain are not part of the same Case!");
		}
		Collection csCollection = this.myInputConcept.getColumnSets();
		if (csCollection.size() != 1) {
			throw new M4Exception(msgIntro + "InputConcept must have exactly one Columnset, but has"+csCollection.size()+"!");
		}
		this.myInputColumnset = (Columnset) csCollection.iterator().next();
		if (this.myInputColumnset == null) {
			throw new M4Exception(msgIntro + "InputConcept has no Columnset (NULL entry in Columnset list)!");	
		}

		Collection indexColCollection = indexBA.getColumns();
		if (indexColCollection.size() != 1) {
			boolean exception = true;
			if (indexColCollection.size() > 1) {
				Iterator it = indexColCollection.iterator();
			 L: while (it.hasNext()) {
					Column col = (Column) it.next();
					if (col != null && this.myInputColumnset.equals(col.getColumnset())) {
						this.myDB.getCasePrintObject().doPrint(Print.MAX, msgIntro +
							"WARNING: found multiple Columns in Index BaseAttribute, choosing the one linked to the found Columnset!");
						indexColCollection.clear();
						indexColCollection.add(col);
						exception = false;
						break L;
					}
				}	
			}
			if (exception) {
				throw new M4Exception(msgIntro + "Found no (suitable) Column for Index BaseAttribute!");
			}
		}
		this.myIndexColumn = (Column) indexColCollection.iterator().next();
		if (this.myIndexColumn == null) {
			throw new M4Exception(msgIntro + "Index BaseAttribute has a null entry instead of the column!");
		}
	}

	public void execute() throws Exception {
		Iterator it = this.createOutputBAs().iterator();
		this.createStep();

		while (it.hasNext()) {
			Object[] array = (Object[]) it.next();
			String val = (String) array[0];
			BaseAttribute newBa = (BaseAttribute) array[1];
			this.createLoopForBa(newBa, val); // Long.parseLong(val));
		}
		this.updateLoopNr();
		
		this.myDB.getCasePrintObject().doPrint(Print.OPERATOR, this.getClass().getName() + ": Writing changes to M4 database!");		
		this.commit();		
	}
	
	/**
	 * @return the M4 Id of the operator that the new step uses
	 */
	protected abstract int getIdOfOperator();
	
	/**
	 * @return the Name of the operator that the new step uses
	 */
	protected abstract String getNameOfOperator();
	
	/**
	 * @return the name for the new Step; the Step number will be put before it
	 */
	protected abstract String getNameOfNewStep();
	
	/**
	 * @return the name of the conceptual datatype that the new attributes will be given
	 */
	protected abstract String getConceptualDatatypeNameOfNewAttributes() throws M4Exception;
	
	/**
	 * Create specific parameters for the new Step for the given loop number, starting
	 * with the given parameter number.
	 * 
	 * @param loopNr The loop number for which the specific parameters must be created
	 * @param parNr The first parameter number to be used; increment it for each additional parameter
	 * @param value The value of the index attribute for the given loop
	 */
	protected abstract void insertSpecificParameters(int loopNr, int parNr, String value) throws M4Exception;
	
	/**
	 * Create a step with the operator GenericFeatureConstruction
	 * and the input concept as parameter TheInputConcept.
	 */
	protected void createStep() throws M4Exception
	{
		Step newStep = (Step) this.myDB.createNewInstance(edu.udo.cs.miningmart.m4.core.Step.class);
		newStep.setTheCase(this.myCase);
		newStep.setTheChain(this.myChain);
		int stepNr = this.myCase.getTheSteps().size(); // the new step is already counted
		String stepName = stepNr + " " + this.getNameOfNewStep();
		newStep.setName(stepName);
		newStep.setNumber(stepNr);
		
		this.myOperator = (Operator) this.myDB.getM4Object(this.getIdOfOperator(), Operator.class);
		if (this.myOperator == null || ! this.myOperator.getName().equals(this.getNameOfOperator())) {
			throw new M4Exception(
				"PivotizeWithoutAggregation: Could not find operator " + this.getNameOfOperator() + ","
				+ " expected to have ID " + this.getIdOfOperator() + "!");	
		}
		newStep.setTheOperator(this.myOperator);

		// the number of loops is determined later, set it to 0 for the moment:
		this.numOfLoops = 0;
		newStep.setLoopCount(this.numOfLoops);

		this.myStep = newStep;

		// Add the input concept as the only not looped parameter:
		this.createParameterForNewStep(this.numOfLoops, 1, "TheInputConcept", true, this.myInputConcept);
	}
		
	/**
	 * Creates another loop for the step created with createStep() and increments the loop number.
	 * The target BaseAttribute needs to exist.
	 * 
	 * @param outputBaId the ID of the target BaseAttribute
 	 */	
	protected void createLoopForBa(BaseAttribute outputBa, String targetValS) throws M4Exception {
		
		final int loopNr = ++this.numOfLoops;
		int parNr = 2; // no 1 is the input concept

		// New Target Attribute parameter for the current loop nr.:
		this.createParameterForNewStep(loopNr, parNr++, "TheTargetAttribute", true, this.myIndexBA);
		
		// New Output Attribute parameter for the current loop nr.:
		this.createParameterForNewStep(loopNr, parNr++, "TheOutputAttribute", false, outputBa);
		
		this.insertSpecificParameters(loopNr, parNr, targetValS);
	}
	
	/**
	 * Create the new BaseAttributes that will be added to the input concept.
	 * 
	 * @return A <code>Collection</code> of arrays.
	 * element 0: a value of the index attribute as a <code>String</code>
	 * element 1: the corresponding new <code>BaseAttribute</code>
	 */
	protected Collection createOutputBAs() throws M4Exception, M4CompilerError
	{
		final Vector newBaCollection = new Vector();
		
		String query = "SELECT DISTINCT "
					 + this.myIndexColumn.getSQLDefinition()
					 + " FROM "
					 + this.myInputColumnset.getSQLDefinition();
		
		final String baNamePre = this.myIndexBA.getName() + "_";

		ResultSet rs = null;
		try {
			rs = this.myDB.executeBusinessSqlRead(query);	
			while (rs.next()) {
				String value = rs.getString(1);	
				if (!rs.wasNull()) {
					BaseAttribute newBa =
						(BaseAttribute) this.myDB.createNewInstance(edu.udo.cs.miningmart.m4.core.BaseAttribute.class);
	
					newBa.setName(baNamePre + value);
					newBa.setConceptualDataTypeName(this.myIndexBA.getConceptualDataTypeName());
					newBa.setDBAttrib(false);
					newBa.setConcept(this.myInputConcept);
				
					// Collect the IDs of the inserted BaseAttributes:
					Object[] baData = new Object[2];
					baData[0] = value;
					baData[1] = newBa;
					newBaCollection.add(baData);
				}
			}
		}
		catch (SQLException sqle) {
		    throw new M4Exception("PivotizeWithoutAggregation: SQLException caught when reading distinct values of index attribute: " + sqle.getMessage());
		}
		finally {
			DB.closeResultSet(rs);
//			this.myDB.closeResultSet(rs);
		}
		this.myDB.getCasePrintObject().doPrint(Print.OPERATOR, "PivotizeWithoutAggregation: Successfully created BaseAttributes for " + newBaCollection.size() + " values!");
		return newBaCollection;
	}
	
	/**
	 * Create a new Parameter Object with the given specifications and
	 * attach it to the new Step.
	 */
	protected void createParameterForNewStep( int loopNr, 
	                                          int parNr, 
	                                          String parName,
	                                          boolean isInput,
	                                          ParameterObject theParObject) throws M4Exception {
		
		Parameter newPar = (Parameter) this.myDB.createNewInstance(edu.udo.cs.miningmart.m4.core.Parameter.class);
		newPar.setName(parName + loopNr);
		newPar.setTheParameterObject(theParObject);
		newPar.setTheOperator(this.myOperator);
		newPar.setIsInputParam(isInput);
		newPar.setParamNr(parNr);
		newPar.setTheStep(this.myStep);
		newPar.setLoopNr(loopNr);
	}
	
	/**
	 * Creates a new looped parameter for a given step. The parameter is an input value,
	 * which is also created in the <i>value_t</i> table by this method.
	 * 
	 * @param parameterName the name of the parameter in the <code>PARAMETER_T</code> table
	 * @param loopNr the number of the loop this parameter belongs to
	 * @param value the value to be passed to the operator. Use no quotation.
	 * @param valCondDT the conditional datatype of the value
	 * @param parNr the number of the parameter. Not really used by the compiler, but should
	 *        be unique for each step.
	 */
	protected void createInputValueParameter(String parameterName, int loopNr, String value, int valCondDT, int parNr)
		throws M4Exception
	{
		// Create a Value first:
		Value valueObj = (Value) this.myDB.createNewInstance(edu.udo.cs.miningmart.m4.core.Value.class);
		String valueName = parameterName + "_" + loopNr;
		valueObj.setName(valueName); 	
		valueObj.setType(valCondDT);
		valueObj.setValue(value);

		// Create the corresponding parameter object:
		this.createParameterForNewStep(loopNr, parNr, parameterName, true, valueObj);
	}	
	
	// Update number of loops in step
	private void updateLoopNr() throws M4Exception {
		this.myStep.setLoopCount(this.numOfLoops);
	}

	private void commit() throws M4Exception {
		this.myDB.updateDatabase();
	}
}
/*
 * Historie
 * --------
 *
 * $Log: AttributeCreationTool.java,v $
 * Revision 1.3  2006/04/11 14:10:19  euler
 * Updated license text.
 *
 * Revision 1.2  2006/04/06 16:31:18  euler
 * Prepended license remark.
 *
 * Revision 1.1  2006/01/03 09:54:35  hakenjos
 * Initial version!
 *
 */
