/*
 * DualCompositeState.java - This file is part of the Jakstab project.
 * Copyright 2007-2015 Johannes Kinder <jk@jakstab.org>
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, see <http://www.gnu.org/licenses/>.
 */
package org.jakstab.analysis.composite;

import java.util.Collections;
import java.util.Set;

import org.jakstab.analysis.AbstractState;
import org.jakstab.analysis.UnderApproximateState;
import org.jakstab.rtl.expressions.RTLExpression;
import org.jakstab.rtl.expressions.RTLNumber;
import org.jakstab.util.FastSet;
import org.jakstab.util.Logger;
import org.jakstab.util.Tuple;

/**
 * A composite state containing both over- and under-approximate states.
 * 
 * @author Johannes Kinder
 *
 */
public class DualCompositeState extends CompositeState {

	@SuppressWarnings("unused")
	private static final Logger logger = Logger.getLogger(DualCompositeState.class);

	public DualCompositeState(AbstractState[] components) {
		super(components);
	}

	public Set<Tuple<RTLNumber>> projection(RTLExpression... expressions) {

		Set<Tuple<RTLNumber>> result = new FastSet<Tuple<RTLNumber>>();
		
		for (int i=0; i<components.length; i++) {
			if (!(components[i] instanceof UnderApproximateState)) 
				continue;
			// Concretize this component state and then take the union with existing ones
			Set<Tuple<RTLNumber>> concreteTuples = components[i].projectionFromConcretization(expressions);
			//logger.info(concreteTuples);
			/* Return value of null here represents the bottom element, so it has no effect in union */ 
			if (concreteTuples == null) continue;
			/* Else take the union */
			else {
				result.addAll(concreteTuples); 
			}
		}
		//logger.debug("Under-approximation projects " + Arrays.toString(expressions) + " to " + result);
		assert result != null;
		return result;
	}

	@Override
	public Set<Tuple<RTLNumber>> projectionFromConcretization(
			RTLExpression... expressions) {

		Set<Tuple<RTLNumber>> result = null;
		for (int i=0; i<components.length; i++) {
			if (components[i] instanceof UnderApproximateState) 
				continue;
			// Concretize this component state and then intersect it with existing ones
			Set<Tuple<RTLNumber>> concreteTuples = components[i].projectionFromConcretization(expressions);
			//logger.info(concreteTuples);
			/* Return value of null represents the whole set of tuples of numbers (usually b/c the function 
			 * is not implemented), so intersection has no effect */ 
			if (concreteTuples == null) continue;
			/* If we are currently at the whole set, the new tuple set is the intersection */ 
			else if (result == null) result = concreteTuples;
			/* Else intersect */
			else {
				// result.retainAll(concreteTuples);
				// intersect manually b/c of possible null components (wildcards for more tuples)
				//   Note: A tuple with a null component expands to a set of tuples 
				//   with all possible values for that component
				//
				Set<Tuple<RTLNumber>> newResult = new FastSet<Tuple<RTLNumber>>();
				// for all tuples in result
				for (Tuple<RTLNumber> rTuple : result) {
					// for all tuples we got from the current substate 
					cTuplesLoop: for (Tuple<RTLNumber> cTuple : concreteTuples) {
						// array for building new tuple
						RTLNumber[] numbers = new RTLNumber[expressions.length];
						// match components of both tuples against each other
						for (int j=0; j<expressions.length; j++) {
							RTLNumber cNumber = cTuple.get(j);
							RTLNumber rNumber = rTuple.get(j);
							// if the component is no wildcard and not equal, don't match, try next new tuple for match
							if (cNumber != RTLNumber.WILDCARD && rNumber != null && !cNumber.equals(rNumber)) {
								continue cTuplesLoop;
							} else {
								// handle wildcards on both sides:
								if (rNumber == RTLNumber.WILDCARD) numbers[j] = cNumber;
								else numbers[j] = rNumber;
							}
						}
						// We passed all components - so add matching tuple
						newResult.add(Tuple.create(numbers));
						// We still need to continue with the next tuple, since wildcards
						// could allow more possibilities
					}
					// cTuplesLoop finished without finding a match
				}
				result = newResult;
			}
			
		}
		//logger.info("Project " + Arrays.toString(expressions) + " to " + result);
		//assert result != null;
		
		// result can be null when using DummyAnalysis.
		if (result == null) result = Collections.emptySet();
		return result;
	}

}
