/*
 * SetOfVariables.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.rtl.expressions;

import java.io.Serializable;
import java.util.*;

import org.jakstab.util.Logger;

/**
 * A specialized set for variables, uses bitvectors to speed up set operations.
 * 
 * @author Johannes Kinder
 */
public class SetOfVariables extends AbstractSet<RTLVariable> implements Set<RTLVariable>, Serializable {
	
	public static final SetOfVariables EMPTY_SET = new SetOfVariables();

	@SuppressWarnings("unused")
	private final static Logger logger = Logger.getLogger(SetOfVariables.class);
	private static final long serialVersionUID = -5505393011046912608L;

	private final BitSet bitSet;

	public SetOfVariables() {
		bitSet = new BitSet(ExpressionFactory.DEFAULT_VARIABLE_COUNT);
	}
	
	public SetOfVariables(Collection<RTLVariable> collection) {
		this();
		addAll(collection);
	}

	public SetOfVariables(SetOfVariables proto) {
		this.bitSet = (BitSet)proto.bitSet.clone();
	}

	@Override
	public final boolean add(RTLVariable e) {
		if (bitSet.get(e.getIndex()))
			return false;
		bitSet.set(e.getIndex());
		return true;
	}

	@Override
	public final boolean addAll(Collection<? extends RTLVariable> c) {
		boolean changed = false;
		for(RTLVariable element : c) {
			changed |= add(element);
		}
		return changed;
	}
	
	/**
	 * Adds all elements of an array to this Set.
	 * 
	 * @param array the array to take the elements from
	 * @return true if this set was changed, false otherwise
	 */
	public final boolean addAll(RTLVariable[] array) {
		if (array == null) return false;
		boolean changed = false;
		for (int i=0; i<array.length; i++)
			changed |= this.add(array[i]);
		return changed;
	}

	public final void addAll(SetOfVariables vset) {
		bitSet.or(vset.bitSet);
	}

	@Override
	public final void clear() {
		bitSet.clear();
	}

	@Override
	public final boolean contains(Object o) {
		if (o instanceof RTLVariable) {
			return bitSet.get(((RTLVariable)o).getIndex());
		} else return false;
	}

	/*
	 * @see java.util.AbstractCollection#containsAll(java.util.Collection)
	 */
	@Override
	public final boolean containsAll(Collection<?> c) {
		if (c instanceof SetOfVariables) {
			SetOfVariables vset = (SetOfVariables)c;
			BitSet bitSetCopy = (BitSet)bitSet.clone();
			bitSetCopy.and(vset.bitSet);
			return bitSetCopy.equals(bitSet);
		} else {
			return super.containsAll(c);
		}
	}

	@Override
	public boolean equals(Object o) {
		if (o instanceof SetOfVariables)
			return equals((SetOfVariables)o);
		else
			return super.equals(o);
	}

	public boolean equals(SetOfVariables vset) {
		return vset != null && bitSet.equals(vset.bitSet);
	}

	@Override
	public int hashCode() {
		return bitSet.hashCode();
	}
	
	@Override
	public final boolean isEmpty() {
		return bitSet.isEmpty();
	}
	
	@Override
	public final Iterator<RTLVariable> iterator() {
		return new Iterator<RTLVariable>() {
			private int index = 0;
			@Override
			public boolean hasNext() {
				return (bitSet.nextSetBit(index) >= 0);
			}
			@Override
			public RTLVariable next() {
				int nextSet = bitSet.nextSetBit(index);
				index = nextSet + 1;
				return ExpressionFactory.getVariable(nextSet);
			}
			@Override
			public void remove() {
				bitSet.clear(index - 1);
			}
		};
	}

	@Override
	public final boolean remove(Object o) {
		if (o instanceof RTLVariable) {
			RTLVariable var = (RTLVariable)o;
			if (!bitSet.get(var.getIndex()))
				return false;
			else {
				bitSet.clear(var.getIndex());
				return true;
			}
		} else return false;
	}
	
	@Override
	public final boolean removeAll(Collection<?> c) {
		if (c instanceof SetOfVariables) {
			SetOfVariables vset = (SetOfVariables)c;
			if (bitSet.intersects(vset.bitSet)) {
				bitSet.andNot(vset.bitSet);
				return true;
			} else return false;
		} else {
			return super.removeAll(c);
		}
	}

	public final void removeAll(SetOfVariables vset) {
		bitSet.andNot(vset.bitSet);
	}
	
	/*
	 * @see java.util.AbstractCollection#retainAll(java.util.Collection)
	 */
	@Override
	public final boolean retainAll(Collection<?> c) {
		if (c instanceof SetOfVariables) {
			SetOfVariables vset = (SetOfVariables)c;
			if (!bitSet.equals(vset.bitSet)) {
				bitSet.and(vset.bitSet);
				return true;
			} else return false;
		} else {
			return super.retainAll(c);
		}
	}
	
	public final void retainAll(SetOfVariables vset) {
		bitSet.and(vset.bitSet);
	}

	@Override
	public final int size() {
		return bitSet.cardinality();
	}
	
}
