/*
 * CompositeState.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.Arrays;
import java.util.Set;

import org.jakstab.analysis.*;
import org.jakstab.analysis.location.LocationState;
import org.jakstab.cfa.Location;
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 cartesian product state that consists of a vector of substates where the 
 * first substate is guaranteed to be a location state.
 * 
 * @author Johannes Kinder
 */
public class CompositeState implements AbstractState {

	@SuppressWarnings("unused")
	private static final Logger logger = Logger.getLogger(CompositeState.class);
		
	protected final AbstractState[] components;
	
	/**
	 * @param components
	 */
	public CompositeState(AbstractState[] components) {
		super();
		this.components = components;
	}
	
	@Override
	public String getIdentifier() {
		StringBuilder id = new StringBuilder();
		id.append("ID");
		for (AbstractState c : components)
			id.append(c.getIdentifier()).append(":");
		return id.toString();
	}

	@Override
	public Location getLocation() {
		return ((LocationState)components[0]).getLocation();
	}
	
	public AbstractState getComponent(int index) {
		return components[index];
	}
	
	public int numComponents() {
		return components.length;
	}

	@Override
	public AbstractState join(LatticeElement l) {
		CompositeState other = (CompositeState)l;
		AbstractState[] jComponents = new AbstractState[components.length];
		for (int i=0; i<components.length; i++) {
			jComponents[i] = components[i].join(other.components[i]);
		}
		return new CompositeState(jComponents);
	}

	@Override
	public Set<Tuple<RTLNumber>> projectionFromConcretization(
			RTLExpression... expressions) {
		Set<Tuple<RTLNumber>> result = null;
		for (int i=0; i<components.length; i++) {
			// 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;
		return result;
	}

	@Override
	public boolean isBot() {
		return false;
	}

	@Override
	public boolean isTop() {
		return false;
	}

	@Override
	public boolean lessOrEqual(LatticeElement l) {
		CompositeState other = (CompositeState)l;
		for (int i=0; i<components.length; i++) {
			if (!(components[i].lessOrEqual(other.components[i])))
				return false;
		}
		return true;
	}
	
	@Override
	public String toString() {
		StringBuilder res = new StringBuilder();
		res.append('(');
		for (int i=0; i<components.length - 1; i++) {
			res.append(components[i].toString());
			res.append(',');
		}
		res.append(components[components.length - 1].toString());
		res.append(')');
		return res.toString();
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + Arrays.hashCode(components);
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == null)
			return false;
		CompositeState other = (CompositeState) obj;
		if (!Arrays.equals(components, other.components))
			return false;
		return true;
	}
}
