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

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.BaseAttribute;
import edu.udo.cs.miningmart.m4.Columnset;
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.Value;
import edu.udo.cs.miningmart.m4.utils.Print;

/**
 * This operator provides a UNION of Concepts as part of SQL at the
 * relational level. So far no mapping is supported within the operator.
 * 
 * @author Martin Scholz
 * @version $Id: Union.java,v 1.4 2006/04/11 14:10:11 euler Exp $
 */
public class Union extends SingleCSOperator {

	/**
	 * This attribute is used to avoid the aggregation of tuples with equal attribute values.
	 * The attribute name should not occur elsewhere in one of the underlying views.
	 */
	private static final String ARTIFICIAL_BAG_ATTRIBUTE = "MMART_BAG_ATTRIB";

	/**
	 * @see SingleCSOperator#getTypeOfNewColumnSet()
	 */
	public String getTypeOfNewColumnSet() {
		return Columnset.CS_TYPE_VIEW;
	}

	/**
	 * @see SingleCSOperator#generateSQLDefinition(String)
	 */
	public String generateSQLDefinition(String selectPart)
		throws M4CompilerError
	{	
		
		final Concept[] otherConcepts = this.getTheConcepts();
		final Concept inputConcept = this.getInputConcept();
		Columnset mainInputColumnset;			
		
		{ // Necessary constraint: The input concept needs to be connected to a (current) Columnset

			try {
				if (inputConcept == null || (mainInputColumnset = inputConcept.getCurrentColumnSet()) == null)
				{
					throw new M4CompilerError("Operator 'Union': No valid input concept/columnset found!");
				}
			}
			catch (M4Exception e) {
				throw new M4CompilerError(
					"Operator 'Union': M4Exception when trying to read the input concept's current columnset!\n"
					+ e.getMessage());
			}
		}
		
		try {
			if (otherConcepts == null || otherConcepts.length == 0)
				return mainInputColumnset.getCompleteSQLQuery();
			else {
				// Fetch the list of BaseAttributes that need to be represented in all views:
				this.inputBaseAttributes = this.gatherBaseAttributes(inputConcept);
				
				// Initializing the bag vs. set data mode:				
				boolean bagMode = (this.getDataMode() == DATAMODE_BAG);
				if (bagMode == true && this.getM4Dbms() != DB.ORACLE) {
					this.doPrint(Print.MAX,
						"Warning: With the current implementation the selected data mode "
						+ "'Multi-Set' for Operator Union is only available for ORACLE databases, yet!"
					);
					bagMode = false;
				}
				int bagViewCount = 0;

				
				// Handling the main input concept demands having columns in the same order as the baseattributes:
				StringBuffer sbuf = new StringBuffer(
					"(SELECT " + this.createSelectPartWrtInputConcept(inputConcept)
					+ (bagMode ? ( ", (" + bagViewCount++ + " || '_' || ROWNUM) AS " + ARTIFICIAL_BAG_ATTRIBUTE) : "")
					//+ " FROM " + mainInputColumnset.getSQLDefinition() + ")");
					+ " FROM " + mainInputColumnset.getSchemaPlusName() + ")");
				
				// Any further concept needs more attention to ensure UNION-compatibility:
				for (int i=0; i<otherConcepts.length; i++) {
					Concept concept = otherConcepts[i];
					Columnset cs = concept.getCurrentColumnSet();
					if (cs != null && cs.getColumns() != null && cs.getColumns().size() > 0) {
						sbuf.append(
							" UNION (SELECT " + this.createSelectPartWrtInputConcept(concept)
							+ (bagMode ? ( ", (" + bagViewCount++ + " || '_' || ROWNUM) AS " + ARTIFICIAL_BAG_ATTRIBUTE) : "")
							//+ " FROM " + cs.getSQLDefinition() + ")");
							+ " FROM " + cs.getSchemaPlusName() + ")");
					}
					else {
						this.doPrint(Print.OPERATOR,
							"Warning: Input Concept '" + concept.getName()
							+ "' skipped because no valid 'current' Columnset exists!");
					}
				}
				return "(" + sbuf.toString() + ")";
				// the brackets are necessary, because the "create view"-method strips one leading and trailing bracket
			}
		}
		catch (M4Exception e) {
			throw new M4CompilerError(
				"Operator 'Union' received an M4Exception when trying to getCompleteSQLQuery() for a\n"
				+ "Concept's current Columnset!\n" + e.getMessage());
		}
	}

	/** Holds the relevant BaseAttributes of the (main) input concept after
	 * <code>gatherInputBaseAttributes()</code> has been called. */
	private Collection inputBaseAttributes;

	/**
	 * Gathers the base attributes of a given concept that are not deselected.
	 * @param concept the <code>Concept</code> to get the BAs of
	 * @return a <code>Collection</code> of the relevant <code>BaseAttribute</code>s
	 */
	private Collection gatherBaseAttributes(Concept concept)
		throws M4Exception, M4CompilerError
	{
		final Vector relevantBAs = new Vector();
		Collection feaCol = concept.getFeatures();
		Iterator it = feaCol.iterator();
		while (it.hasNext()) {
			Feature f = (Feature) it.next();
			if (f instanceof BaseAttribute && ! ((BaseAttribute) f).isDeselected()) {
				relevantBAs.add(f);
			}
			else if (f instanceof MultiColumnFeature) {
				Iterator baIt = ((MultiColumnFeature) f).getBaseAttributes().iterator();
				while (baIt.hasNext()) {
					BaseAttribute next = (BaseAttribute) baIt.next();
					if (next != null && ! next.isDeselected())
						relevantBAs.add(next);
				}
			}
		}
		return relevantBAs;
	}

	/** 
	 * This helper method is used to generate the select-part for concepts other
	 * than the (main) input concept in a way that all the intermediate views are
	 * UNION-compatible. Therefore, the order of columns and the column names need
	 * to be the same as in a reference concept, the main input concept in the case
	 * of this operator. Missing attributes are filled up by a constant NULL attribute
	 * with the same name, which does not work for all datatypes!
	 * 
	 * @param concept the <code>Concept</code> to create the select-part for
	 * @return a <code>String</code> that can directly be put between the SELECT and
	 * the FROM keyword in the result SQL statement.
	 */	
	private String createSelectPartWrtInputConcept(Concept concept)
		throws M4Exception, M4CompilerError
	{
		Collection baCol = this.gatherBaseAttributes(concept);
		StringBuffer sbuf = new StringBuffer();
		
		Iterator inputIt = this.inputBaseAttributes.iterator();
		while (inputIt.hasNext()) {
			BaseAttribute inBa = (BaseAttribute) inputIt.next();
			BaseAttribute correspondingBa = null;
			
			// In case of many features this should be implemented more efficiently:
			Iterator currentIt = baCol.iterator();
			while (currentIt.hasNext() && correspondingBa == null) {
				BaseAttribute next = (BaseAttribute) currentIt.next();
				if (next.correspondsTo(inBa)) {
					correspondingBa = next;	
				}
			}
		
			String attributeSql;
			if (correspondingBa == null || correspondingBa.getCurrentColumn() == null) {
				attributeSql = "NULL"; // try to create an empty column on the fly to be UNION compatible
			}
			else { // corresponding attribute found, use the SQL definition in the select-part
				attributeSql = 	correspondingBa.getCurrentColumn().getSQLDefinition();
			}

			// Makes sure the name of the attribute is equal to the name of
			// the input concept's attribute:
			String inColName = inBa.getCurrentColumn().getName();
			if ( ! attributeSql.equals(inColName) ) {
				attributeSql += " AS " + inColName;
			}
			sbuf.append(attributeSql + ", ");
		}
		
		return sbuf.substring(0, sbuf.length() - 2); // remove the last ", "
	}

	/**
	 * All output features should have a column of the same type
	 * as their corresponding feature in the input concept
	 * (unless the input concept's feature was deselected).
	 * 
	 * @see ConceptOperator#mustCopyFeature(String)
	 */
	protected boolean mustCopyFeature(String nameOfFeature)
		throws M4CompilerError
	{
		return true;
	}

	/**
	 * Getter for the parameter &quot;TheConcepts&quot;
	 * @return an array of <code>Concept</code> objects
	 * */
	public Concept[] getTheConcepts() throws M4CompilerError {
		return (Concept[]) this.getParameter("FurtherConcepts");
	}

	// *** Private constants for the supported data modes. ***
	private static final String DATAMODE_SET = "set";
	private static final String DATAMODE_BAG = "multi-set";
	private static final String DATAMODE_DEFAULT = DATAMODE_SET;
	
	/**
	 * Getter for the parameter &quot;DataMode&quot;
	 * @return one of the two <code>String</code> objects
	 * <code>DATAMODE_SET</code> or <code>DATAMODE_BAG</code>
	 * indicating the mode.
	 */
	public String getDataMode() throws M4CompilerError {
		Value dataModeV = (Value) this.getSingleParameter("DataMode");
		if (dataModeV == null || dataModeV.getValue() == null) {
			return DATAMODE_DEFAULT;
		}
		else {
			String dataModeS = dataModeV.getValue();
			if (dataModeS.equalsIgnoreCase(DATAMODE_BAG)) {
				return DATAMODE_BAG;
			}
			else return DATAMODE_SET;
		}
	}

}
/*
 * Historie
 * --------
 *
 * $Log: Union.java,v $
 * Revision 1.4  2006/04/11 14:10:11  euler
 * Updated license text.
 *
 * Revision 1.3  2006/04/06 16:31:11  euler
 * Prepended license remark.
 *
 * Revision 1.2  2006/03/23 11:13:45  euler
 * Improved exception handling.
 *
 * Revision 1.1  2006/01/03 09:54:21  hakenjos
 * Initial version!
 *
 */
