/*
 * 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.HashMap;
import java.util.Iterator;
import java.util.Vector;

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.Column;
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 constructs a new feature by using SQL code (provided by the parameter
 * sql_string) for the column definition for the new feature.
 * 
 * @author Timm Euler
 * @version $Id: GenericFeatureConstruction.java,v 1.5 2006/04/11 14:10:11 euler Exp $
 */
public class GenericFeatureConstruction extends FeatureConstruction
{
	/**
	 * @see edu.udo.cs.miningmart.m4.core.operator.FeatureConstruction#generateSQL(Column)
	 */
	public String generateSQL(Column targetColumn) throws M4CompilerError
	{
		// ignore the targetColumn!
		final String translated = this.getTranslatedSQL_String();
		this.doPrint(Print.OPERATOR, translated);
		return translated;
	}

    private BaseAttribute[] outputBAs = null;
    
    // If a BaseAttribute has no column this is indicated by storing the following String
    // in the HashMap for mapping Pseudo-SQL BAs to SQL-Definitions:
    private static String NO_COLUMN_ATTACHED = "< *** No Column *** >";

	/*
	 * Just checks whether the given base attribute is an output one for this
	 * operator.
	 */
    private boolean isOutputBA(BaseAttribute ba) throws M4CompilerError {
    	if (outputBAs == null) {
    		outputBAs = new BaseAttribute[this.getHighestLoopNr()];
    		for (int i = 0; i < this.getHighestLoopNr(); i++) {
    			outputBAs[i] = (BaseAttribute) this.getSingleParameter("TheOutputAttribute",i);
    		}
    	}
    	for (int i = 0; i < this.getHighestLoopNr(); i++) {
    		if (outputBAs[i].getId() == ba.getId()) return true;
    	}
    	return false;
    }

	/** 
	 * @return the string that the user gives as the parameter &quot;SQL_String&quot;,
	 * but translates BaseAttribute names to Column names
	 * */
	private String getTranslatedSQL_String() throws M4CompilerError {

		HashMap allMappings = this.getBaColumnMapping();
		final String untranslatedSql = this.getSqlString();
		StringBuffer translated = new StringBuffer();

		for (int i=0; i < untranslatedSql.length(); ) {

			int start = nextLetter(untranslatedSql, i);
			if (start > i) {
				translated.append(untranslatedSql.substring(i, start));
			}

			int stop  =	nextNotNameChar(untranslatedSql, start);
			if (stop > start) {
				String token  = untranslatedSql.substring(start, stop);
				String column = (String) allMappings.get(token.toUpperCase());
				if (column == NO_COLUMN_ATTACHED) { // " ==" is ok, because the "static definition" itself is stored
					throw new M4CompilerError("Operator GenericFeatureConstruction: An error occured when trying to " +
					"apply the following Pseudo-SQL statement:\n" + untranslatedSql +
					"\nBaseAttribute " + token + " has no valid column!");
				}
				translated.append(column == null ? token : column);
			}
			i = stop;
		}
		// the brackets seem to be problematic...
		// return "(" + translated.toString() + ")";
		return translated.toString();
	}	


	private HashMap getBaColumnMapping() throws M4CompilerError {
		final HashMap allMappings = new HashMap();

		// resolve MultiColumnFeatures into BaseAttributes;
		// all BaseAttributes are collected in one Vector:		
		final Vector allBaseAttribs = this.getAllInputConceptBAs();
		Iterator it = allBaseAttribs.iterator();

		try {
			while (it.hasNext()) {
				BaseAttribute ba = (BaseAttribute) it.next();
	
				// skip output attributes, because some of them are to be constructed
				// later, and they still don't have their columns !!! 
				if (!this.isOutputBA(ba)) {
					String baName = ba.getName();
					if (allMappings.containsKey(baName)) {
						throw new M4CompilerError(
							"Operator GenericFeatureConstruction: found more than one BaseAttribute '"
								+ baName
								+ "' in TheInputConcept!");
					}
					else {
						edu.udo.cs.miningmart.m4.Column col = ba.getCurrentColumn();
						if (col != null) {
							String colName = col.getSQLDefinition();
							allMappings.put(baName.toUpperCase(), colName);
						}
						else {
							allMappings.put(baName.toUpperCase(), NO_COLUMN_ATTACHED);
						}
					}
				}
			}
		}
   		catch (M4Exception m4e)
   		{   throw new M4CompilerError("M4 interface error in " + this.getName() + ": " + m4e.getMessage());  } 

		return allMappings;
	}


	private static int nextLetter(String s, int start) {
		final int length = s.length();
		for (int i=(start>=0 ? start : 0); i<length; i++) {
			char c = s.charAt(i);
			if (( c >= 'a'  && c <= 'z') || ( c >= 'A' && c<= 'Z')) {
				return i;
			}
			if ((c == '\'') && (i+1 < length)) {
				i = s.indexOf('\'', i+1);
				if (i == -1) {
					i = length;
				}
			}
		}
		return length;
	}

	private static int nextNotNameChar(String s, int start) {
		final int length = s.length();
		for (int i=(start>=0 ? start : 0); i<length; i++) {
			char c = s.charAt(i);
			if (   ( c < 'a'  || c > 'z') && ( c < 'A' || c > 'Z')
			    && ( c < '0' || c > '9')  && (c != '_') )
			{
				return i;
			}
		}
		return length;
	}


	/** @return a Vector containing all BaseAttributes of TheInputConcept. */
	private Vector getAllInputConceptBAs() throws M4CompilerError {
		try {
			final Vector allBaseAttribs = new Vector();
			Collection fs = this.getTheInputConcept().getFeatures();
			Collection theBAs;
			Iterator it = fs.iterator();
			Iterator it2;
			while (it.hasNext()) {
				Feature f = (Feature) it.next();
				if (f instanceof BaseAttribute) {
					allBaseAttribs.add(f);
				}
				else {
					theBAs = ((MultiColumnFeature) f).getBaseAttributes();
					it2 = theBAs.iterator();
					while (it2.hasNext()) {
						allBaseAttribs.add(it2.next());
					}
				}
			}
			return allBaseAttribs;
		}
   		catch (M4Exception m4e)
   		{   throw new M4CompilerError("M4 interface error in " + this.getName() + ": " + m4e.getMessage());  } 
	}
	
	/** @return the parameter &quot;SQL_String&quot; as a String */
	public String getSqlString() throws M4CompilerError {
			Value v = (Value) this.getSingleParameter("SQL_String", this.getCurrentLoopNumber());
			return v.getValue();
	}
	
}
/*
 * Historie
 * --------
 *
 * $Log: GenericFeatureConstruction.java,v $
 * Revision 1.5  2006/04/11 14:10:11  euler
 * Updated license text.
 *
 * Revision 1.4  2006/04/06 16:31:11  euler
 * Prepended license remark.
 *
 * Revision 1.3  2006/03/30 16:07:13  scholz
 * fixed author tags for release
 *
 * Revision 1.2  2006/03/23 11:13:45  euler
 * Improved exception handling.
 *
 * Revision 1.1  2006/01/03 09:54:22  hakenjos
 * Initial version!
 *
 */
