/**
 *   Copyright (c) Rich Hickey. All rights reserved.
 *   The use and distribution terms for this software are covered by the
 *   Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
 *   which can be found in the file epl-v10.html at the root of this distribution.
 *   By using this software in any fashion, you are agreeing to be bound by
 * 	 the terms of this license.
 *   You must not remove this notice, or any other, from this software.
 **/

/* chouser Jun 23, 2010 */

package clojure.lang;

import java.math.BigInteger;
import java.math.BigDecimal;

public final class BigInt extends Number implements IHashEq{

private static final long serialVersionUID = 5097771279236135022L;

final public long lpart;
final public BigInteger bipart;

final public static BigInt ZERO = new BigInt(0,null);
final public static BigInt ONE = new BigInt(1,null);


//must follow Long
public int hashCode(){
	if(bipart == null)
		return (int) (this.lpart ^ (this.lpart >>> 32));
	return bipart.hashCode();
}

public int hasheq(){
	if(bipart == null)
		return Murmur3.hashLong(lpart);
	return bipart.hashCode();

}

public boolean equals(Object obj){
	if(this == obj)
		return true;
	if(obj instanceof BigInt)
		{
		BigInt o = (BigInt) obj;
		if(bipart == null)
			return o.bipart == null && this.lpart == o.lpart;
		return o.bipart != null && this.bipart.equals(o.bipart);
		}
	return false;
}

private BigInt(long lpart, BigInteger bipart){
	this.lpart = lpart;
	this.bipart = bipart;
}

public static BigInt fromBigInteger(BigInteger val){
	if(val.bitLength() < 64)
		return new BigInt(val.longValue(), null);
	else
		return new BigInt(0, val);
}

public static BigInt fromLong(long val){
	return new BigInt(val, null);
}

public BigInteger toBigInteger(){
	if(bipart == null)
		return BigInteger.valueOf(lpart);
	else
		return bipart;
}

public BigDecimal toBigDecimal(){
	if(bipart == null)
		return BigDecimal.valueOf(lpart);
	else
		return new BigDecimal(bipart);
}

///// java.lang.Number:

public int intValue(){
	if(bipart == null)
		return (int) lpart;
	else
		return bipart.intValue();
}

public long longValue(){
	if(bipart == null)
		return lpart;
	else
		return bipart.longValue();
}

public float floatValue(){
	if(bipart == null)
			return lpart;
	else
		return bipart.floatValue();
}

public double doubleValue(){
	if(bipart == null)
		return lpart;
	else
		return bipart.doubleValue();
}

public byte byteValue(){
	if(bipart == null)
		return (byte) lpart;
	else
		return bipart.byteValue();
}

public short shortValue(){
	if(bipart == null)
		return (short) lpart;
	else
		return bipart.shortValue();
}

public static BigInt valueOf(long val){
	return new BigInt(val, null);
}

public String toString(){
	if(bipart == null)
		return String.valueOf(lpart);
	return bipart.toString();
}

public int bitLength(){
	return toBigInteger().bitLength();
}

public BigInt add(BigInt y) {
    if ((bipart == null) && (y.bipart == null)) {
        long ret = lpart + y.lpart;
        if ((ret ^ lpart) >= 0 || (ret ^ y.lpart) >= 0)
            return BigInt.valueOf(ret);
    }
    return BigInt.fromBigInteger(this.toBigInteger().add(y.toBigInteger()));
}

public BigInt multiply(BigInt y) {
    if ((bipart == null) && (y.bipart == null)) {
        long ret = lpart * y.lpart;
            if (y.lpart == 0 ||
                (ret / y.lpart == lpart && lpart != Long.MIN_VALUE))
                return BigInt.valueOf(ret);
        }
    return BigInt.fromBigInteger(this.toBigInteger().multiply(y.toBigInteger()));
}

public BigInt quotient(BigInt y) {
    if ((bipart == null) && (y.bipart == null)) {
        if (lpart == Long.MIN_VALUE && y.lpart == -1)
            return BigInt.fromBigInteger(this.toBigInteger().negate());
        return BigInt.valueOf(lpart / y.lpart);
    }
    return BigInt.fromBigInteger(this.toBigInteger().divide(y.toBigInteger()));
}

public BigInt remainder(BigInt y) {
    if ((bipart == null) && (y.bipart == null)) {
        return BigInt.valueOf(lpart % y.lpart);
    }
    return BigInt.fromBigInteger(this.toBigInteger().remainder(y.toBigInteger()));
}

public boolean lt(BigInt y) {
    if ((bipart == null) && (y.bipart == null)) {
        return lpart < y.lpart;
    }
    return this.toBigInteger().compareTo(y.toBigInteger()) < 0;
}

}
