package speiger.src.coreengine.utils.collections.collection;

import speiger.src.collections.ints.collections.IntCollection;
import speiger.src.coreengine.math.MathUtils;

public class FixedBitSet {
	final long[] data;
	final int capacity;
	int storedBits = -1;
	int wordsInUse = 0;
	
	public FixedBitSet(int maxBits) {
		capacity = maxBits;
		data = new long[MathUtils.ceil(maxBits / 8D)];
	}
	
	public FixedBitSet(long[] data) {
		this(data.length * 8, data);
	}
	
	public FixedBitSet(int maxBits, long[] data) {
		this.data = data;
		capacity = maxBits;
		wordsInUse = data.length - 1;
		validateWords();
	}
	
	public void set(boolean value) {
		if(value) set();
		else clear();
	}
	
	public void set(int index, boolean value) {
		if(value) set(index);
		else clear(index);
	}
	
	public void set(int fromIndex, int toIndex, boolean value) {
		if(value) set(fromIndex, toIndex);
		else clear(fromIndex, toIndex);
	}
	
	public boolean ifSet(int index, boolean value) {
		return value ? ifSet(index) : ifClear(index);
	}
	
	public void clear() {
		for(int i = 0;i < wordsInUse;i++) {
			data[wordsInUse] = 0;
		}
		wordsInUse = 0;
		storedBits = 0;
	}
	
	public void clear(int index) {
		if(index >= 0 && index < capacity) {
			data[index >> 6] &= ~(1L << index);
			validateWords();
		}
	}
	
	public void clear(int fromIndex, int toIndex) {
		if(fromIndex < 0 || toIndex < 0 || fromIndex >= capacity || toIndex >= capacity || fromIndex >= toIndex) return;
		int startWord = fromIndex >> 6;
		if(startWord >= wordsInUse) return;
		int endWord = (toIndex - 1) >> 6;
		if(endWord >= wordsInUse) {
			toIndex = length();
			endWord = wordsInUse - 1;
		}
		long startIndex = -1 << fromIndex;
		long endIndex = -1 >>> -toIndex;
		if(startWord == endWord) data[startWord] &= ~(startIndex & endIndex);
		else {
			data[startWord] &= ~startIndex;
			for(int i = startWord + 1;i < endWord;i++) {
				data[i] = 0;
			}
			data[endWord] &= ~endIndex;
		}
		validateWords();
	}
	
	public boolean ifClear(int index) {
		if(index >= 0 && index < capacity) {
			long bit = 1L << index;
			int wordIndex = index >> 6;
			if((data[wordIndex] & bit) != 0) {
				data[wordIndex] &= ~bit;
				validateWords();
				return true;
			}
		}
		return false;
	}
	
	public void set() {
		for(int i = 0;i < data.length;i++) {
			data[i] = -1;
		}
		wordsInUse = data.length - 1;
		storedBits = capacity;
	}
	
	public void set(int index) {
		if(index >= 0 && index < capacity) {
			data[index >> 6] |= 1L << index;
			updateWords(index >> 6);
		}
	}
	
	public void set(int fromIndex, int toIndex) {
		if(fromIndex < 0 || toIndex < 0 || fromIndex >= capacity || toIndex >= capacity || fromIndex >= toIndex) { return; }
		int startWord = fromIndex >> 6;
		int endWord = (toIndex - 1) >> 6;
		long startIndex = -1 << fromIndex;
		long endIndex = -1 >>> -toIndex;
		if(startWord == endWord) data[startWord] |= (startIndex & endIndex);
		else {
			data[startWord] |= startIndex;
			for(int i = startWord + 1;i < endWord;i++) {
				data[i] = -1;
			}
			data[endWord] |= endIndex;
		}
		updateWords(endWord);
	}
	
	public boolean ifSet(int index) {
		if(index >= 0 && index < capacity) {
			long bit = 1L << index;
			int wordIndex = index >> 6;
			if((data[wordIndex] & bit) == 0) {
				data[wordIndex] |= bit;
				updateWords(wordIndex);
				return true;
			}
		}
		return false;
	}
	
	public boolean get(int index) {
		return (data[index >> 6] & (1L << index)) != 0;
	}
	
	protected void validateWords() {
		for(;wordsInUse >= 0;wordsInUse--) {
			if(data[wordsInUse] == 0) wordsInUse--;
		}
		wordsInUse++;
		storedBits = -1;
	}
	
	protected void updateWords(int check) {
		if(wordsInUse < check) wordsInUse = check;
		storedBits = -1;
	}
	
	public boolean isEmpty() { return wordsInUse == 0; }
	public boolean isFull() { return cardinality() == capacity; }
	
	public int cardinality() {
		if(storedBits != -1) { return storedBits; }
		int sum = 0;
		for(int i = 0;i < wordsInUse;i++) {
			sum += Long.bitCount(data[i]);
		}
		storedBits = sum;
		return sum;
	}
	
	public int length() {
		return wordsInUse == 0 ? 0 : 64 * (wordsInUse - 1) + (64 - Long.numberOfLeadingZeros(data[wordsInUse - 1]));
	}
	
	public long[] getData() { return data.clone(); }
	
	public FixedBitSet trim() {
		if(wordsInUse == 0) return new FixedBitSet(1);
		FixedBitSet set = new FixedBitSet(length());
		System.arraycopy(data, 0, set.data, 0, set.data.length);
		set.wordsInUse = wordsInUse;
		return set;
	}
	
	public void pourIndexes(IntCollection target) {
		if(wordsInUse == 0) { return; }
		for(int i = 0;i < wordsInUse;i++) {
			long word = data[i];
			int wordBits = i << 6;
			while(word != 0) {
				long t = word & -word;
				target.add(wordBits + Long.bitCount(t - 1));
				word ^= t;
			}
		}
	}
}
