package ec.gp.semantic.inverters;

import ec.EvolutionState;
import ec.gp.GPTree;
import ec.gp.semantic.BooleanDesiredSemantics;
import ec.gp.semantic.DesiredSemanticsBase;
import ec.gp.semantic.ISemantics;
import ec.gp.semantic.SemanticsBase;
import ec.gp.semantic.func.SimpleNodeBase;

import java.util.NavigableSet;

public final class BooleanSemanticInverter extends SemanticInverter<Boolean> {
	private static final BooleanDesiredSemantics[] EMPTY_ARRAY = new BooleanDesiredSemantics[0];
	
	public BooleanSemanticInverter(EvolutionState state) {
		super(state);
	}
	
	@Override
	public void invertTree(final GPTree tree, ISemantics _desired, ISemantics... _forbidden) {
		BooleanDesiredSemantics desired = new BooleanDesiredSemantics((SemanticsBase<Boolean>) _desired);
		BooleanDesiredSemantics[] forbidden = _forbidden.length == 0 ? EMPTY_ARRAY : new BooleanDesiredSemantics[_forbidden.length];
		for (int f = 0; f < _forbidden.length; ++f) {
			forbidden[f] = new BooleanDesiredSemantics((SemanticsBase<Boolean>) _forbidden[f]);
		}
		this.desiredSemantics.put(tree.child, new Descriptor(desired, forbidden));
		this.invertRecursive((SimpleNodeBase<?>) tree.child, desired, forbidden);
	}
	
	@Override
	public DesiredSemanticsBase<Boolean> convert(ISemantics semantics) {
		return new BooleanDesiredSemantics(semantics);
	}
	
	@Override
	protected DesiredSemanticsBase<Boolean> invertInstruction(final SimpleNodeBase<?> node, final int forChild,
	                                                          final DesiredSemanticsBase<Boolean> desiredSemantics,
	                                                          final ISemantics[] childrenSemantics) {
		
		final DesiredSemanticsBase<Boolean> desiredSemanticsForChild = new BooleanDesiredSemantics(desiredSemantics.size());
		
		Boolean[] args = null;
		if (childrenSemantics.length != 0)
			args = new Boolean[childrenSemantics.length];
		
		for (int fc = 0; fc < desiredSemantics.size(); ++fc) {
			
			NavigableSet<Boolean> desiredValues = desiredSemantics.getValuesFor(fc);
			
			if (desiredValues.isEmpty()) {
				// don't care value
				continue;
			} else if (desiredValues.first() == null) {
				// inconsistent value
				// rewrite to child semantics
				desiredSemanticsForChild.markInconsistent(fc);
				continue;
			}
			
			// prepare arguments
			for (int j = 0; j < childrenSemantics.length; ++j) {
				args[j] = (Boolean) childrenSemantics[j].getValue(fc);
			}
			
			// execute inversion
			for (final Boolean value : desiredValues) {
				Boolean[] inversions = ((SimpleNodeBase<Boolean>) node).invert(value, forChild, args);
				desiredSemanticsForChild.addValues(fc, inversions);
			}
			
			NavigableSet<Boolean> childValues = desiredSemanticsForChild.getValuesFor(fc);
			//if it is 0 then `don't care', if 1, then it is either inconsistent, true or false,
			// otherwise we have to check the contents
			switch (childValues.size()) {
				case 2: // three possible cases: true & false, true & null, false & null
					assert (childValues.contains(true) && childValues.contains(false)) ||
							(childValues.contains(true) && childValues.contains(null)) ||
							(childValues.contains(false) && childValues.contains(null));
					
					if (childValues.first() == null) {
						childValues.remove(null); // remove inconsistent value
					} else {
						assert childValues.contains(false);
						assert childValues.contains(true);
						childValues.clear(); // mark `don't care'
					}
					break;
				case 3: // only one possible case: false & true & null
					assert childValues.contains(false);
					assert childValues.contains(true);
					assert childValues.contains(null);
					// mark `don't care'
					childValues.clear();
					break;
			}
		}
		
		desiredSemanticsForChild.compact();
		
		return desiredSemanticsForChild;
	}
	
}
