/*
 * NumberElement.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.explicit;

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

import org.jakstab.analysis.AbstractValue;
import org.jakstab.analysis.LatticeElement;
import org.jakstab.rtl.BitVectorType;
import org.jakstab.rtl.expressions.ExpressionFactory;
import org.jakstab.rtl.expressions.RTLNumber;
import org.jakstab.util.*;

/**
 * @author Johannes Kinder
 */
public class NumberElement implements AbstractValue, BitVectorType {

	@SuppressWarnings("unused")
	private static final Logger logger = Logger.getLogger(NumberElement.class);
	
	public static final NumberElement TRUE = new NumberElement(ExpressionFactory.TRUE);
	public static final NumberElement FALSE = new NumberElement(ExpressionFactory.FALSE);
	
	private static NumberElement[] TOPS = new NumberElement[128];
	static {
		for (int bitWidth = 1; bitWidth <= 128; bitWidth++) {
			TOPS[bitWidth - 1] = new NumberElement(ExpressionFactory.createNumber(bitWidth - 1, bitWidth)); 
		}
	}
	
	public static NumberElement getTop(int bitWidth) {
		return TOPS[bitWidth - 1];
	}

	private final RTLNumber value;
	
	public NumberElement(RTLNumber v) {
		this.value = v;
		assert value != null : "Null number passed into NumberElement constructor";
		assert v.getBitWidth() > 0 : "Cannot create number element for " + v + " with unknown bitwidth!";
	}
	
	public RTLNumber getNumber() {
		assert !isTop() : "Attempting to get value of a TOP NumberElement!";
		return value;
	}
	
	@Override
	public Set<RTLNumber> concretize() {
		if (isTop()) {
			// Enumerate small bitwidths
			if (getBitWidth() == 1) {
				Set<RTLNumber> result = new FastSet<RTLNumber>();
				result.add(ExpressionFactory.TRUE);
				result.add(ExpressionFactory.FALSE);
				return result;
			} else {
				return RTLNumber.ALL_NUMBERS;
			}
		}
		return Collections.singleton(value);
	}

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

	@Override
	public boolean isTop() {
		return this == getTop(getBitWidth());
	}
	
	@Override
	public int getBitWidth() {
		return value.getBitWidth();
	}

	@Override
	public NumberElement join(LatticeElement l) {
		NumberElement other = (NumberElement)l;

		// Can happen for memory cells at same offset but of different size
		//assert other.getBitWidth() == this.getBitWidth() : "Trying to join numberelements of different bitwidth: " + other + " and " + this;
		if (other.getBitWidth() > this.getBitWidth()) return getTop(other.getBitWidth()); 
		if (this.getBitWidth() > other.getBitWidth()) return getTop(this.getBitWidth()); 
		if (l.isTop() || isTop()) return getTop(getBitWidth());
		
		RTLNumber c = other.value;
		if (c.equals(value)) return this;
		else return getTop(getBitWidth());
	}

	@Override
	public boolean lessOrEqual(LatticeElement l) {
		return l.isTop() || equals(l);
	}
	
	@Override
	public boolean equals(Object obj) {
		if (obj == this) return true;
		if (!(obj instanceof NumberElement)) return false;
		NumberElement other = (NumberElement)obj;
		if (getBitWidth() != other.getBitWidth()) return false;
		if (isTop()) return other.isTop();
		return other.value.equals(this.value);
	}

	@Override
	public int hashCode() {
		return value.intValue();
		// intValue is slightly faster than hashCode - and this is a hotspot
		//return value.hashCode();
	}

	@Override
	public String toString() {
		if (isTop()) return Characters.TOP + "<" + getBitWidth() + ">";
		else return value.toString();
	}

	@Override
	public boolean hasUniqueConcretization() {
		return !isTop() && !isBot();
	}
}
