package org.mandarax.util.resultsetfilters;

/*
 * Copyright (C) 1999-2004 <a href="mailto:mandarax@jbdietrich.com">Jens Dietrich</a>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

import java.util.*;
import org.mandarax.kernel.*;
import org.mandarax.util.*;

/**
 * Filter for result sets. Results having the same substitutions for 
 * a list of query variables (not necessary all of them) but different proofs are identified.
 * Similar to SQLs GROUP BY functionality.
 * A Group By filter is created using a result set and an array of aggregation funtions. 
 * This array can contain null slots, indicating that the respective variable is used
 * in the group by list. The array defines aggregation functions accourding to the 
 * order of the variables in the original result set.
 * @author <A HREF="mailto:mandarax@jbdietrich.com">Jens Dietrich</A>
 * @version 3.3.1 <9 August 2004>
 * @since 3.0
 */

public class GroupByFilter extends CachedResultSet {

	private ResultSet original = null;
	private Map aggregations = new Hashtable();
	private VariableTerm[] groupByTerms = null;
	private AggregationFunction[] aggregationFunctions = null;

	/**
	 * Constructor.
	 * @param original a not yet initialized result set (the cursor has not yet been moved)
	 * @param aggregationFunctions an array of aggregation functions
	 * @param groupByTerms an array of group by terms
	 */
	public GroupByFilter(
		ResultSet org,
		AggregationFunction[] aggregationFunctions,
		VariableTerm[] groupByTerms)
		throws InferenceException {
		super();
		this.aggregationFunctions = aggregationFunctions;
		this.groupByTerms = groupByTerms;
		this.original = org;

		// initialize result list and the aggregation
		results = new ArrayList();
		aggregate();
	}
	/**
	 * Aggregate the values and build the groups.
	 */
	private void aggregate() throws InferenceException {


			 
		//	aggregate values
		while (original.next()) {
			// build key (to create or find aggregations)
			List key = new ArrayList();
			for (int i = 0; i < groupByTerms.length; i++) {
				Object value = original.getResult(groupByTerms[i]);
				// compute key
				key.add(value);
			}
			// check whether aggregations exit for this key
			AggregationFunction[] ag = (AggregationFunction[]) aggregations.get(key);
			if (ag == null) {
				ag = new AggregationFunction[aggregationFunctions.length];
				for (int i = 0; i < aggregationFunctions.length; i++) {
					ag[i] = aggregationFunctions[i].copy();
				}
				aggregations.put(key, ag);
			}
			// aggregate values
			for (int i = 0; i < ag.length; i++) {
				ag[i].add(original);
			}
		}
		// build the result set as list
		results = new ArrayList();
		LogicFactory lfactory = LogicFactory.getDefaultFactory();
		for (Iterator iter = aggregations.entrySet().iterator();iter.hasNext();){ 
			Map.Entry nextEntry = (Map.Entry)iter.next();
			List nextKey = (List)nextEntry.getKey();
			AggregationFunction[] nextValue = (AggregationFunction[])nextEntry.getValue();
			Replacement[] replacements = new Replacement[groupByTerms.length + aggregationFunctions.length];
			// replacements for "group by" terms
			for (int i=0;i<groupByTerms.length;i++) {
				replacements[i] = new Replacement(groupByTerms[i],lfactory.createConstantTerm(nextKey.get(i),groupByTerms[i].getType()));
			}
			// replacements for aggregated terms
			for (int i=0;i<nextValue.length;i++) {
				replacements[groupByTerms.length+i] = new Replacement(
					nextValue[i].getVariableInResult(),
					lfactory.createConstantTerm(
						nextValue[i].getAggregatedValue(),
						nextValue[i].getResultType()
					)
				);
			}	
			// add result
			results.add(new ResultImpl(replacements));		
			
			// initialize variable list
			vars = new ArrayList();
			// add "group by" terms
			for (int i=0;i<groupByTerms.length;i++) vars.add(groupByTerms[i]);
			// add aggregated terms 
			for (int i=0;i<aggregationFunctions.length;i++) vars.add(aggregationFunctions[i].getVariableInResult()); // do this after values have been set .. then the type is initialized
			
		}		
	}

}
