/*
 * Copyright 2009 Yuichiro Moriguchi
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.morilib.lisp.nano;

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

/**
 * 
 *
 *
 * @author MORIGUCHI, Yuichiro 2009
 */
public class LispDouble extends LispInexactReal
implements java.io.Serializable {

	//
	private static final long serialVersionUID = -3604865349391960731L;

	/**
	 * 
	 */
	public static final LispDouble ZERO = new LispDouble(0.0);

	/**
	 * 
	 */
	public static final LispDouble ONE = new LispDouble(1.0);

	/**
	 * 
	 */
	public static final LispDouble POSITIVE_INFINITY =
		new LispDouble(Double.POSITIVE_INFINITY);

	/**
	 * 
	 */
	public static final LispDouble NEGATIVE_INFINITY =
		new LispDouble(Double.NEGATIVE_INFINITY);

	/**
	 * 
	 */
	public static final LispDouble NaN = new LispDouble(Double.NaN);

	//
	/*package*/ double number;

	/**
	 * 
	 * @param x
	 */
	public LispDouble(double x) {
		this.number = x;
	}

	/**
	 * 
	 * @param val
	 * @return
	 */
	public static LispExactReal toExact(double val) {
		if(Double.isInfinite(val) || Double.isNaN(val)) {
			throw new LispNotSupportedException(
					"err.notsupported.exactinfinity");
		}

		BigDecimal v = BigDecimal.valueOf(val);
		return LispUtils.bigDecimalToRational(v);
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#getNumerator()
	 */
	public BigInteger getNumerator() {
		LispReal r = LispDouble.toExact(number);

		return r.getNumerator();
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#getDenominator()
	 */
	public BigInteger getDenominator() {
		LispReal r = LispDouble.toExact(number);

		return r.getDenominator();
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#add(net.morilib.lisp.LispNumber)
	 */
	public LispNumber add(LispNumber x) {
		if(x instanceof LispReal) {
			return new LispDouble(number + x.getRealDouble());
		} else if(x instanceof LispComplex) {
			LispComplex c = (LispComplex)x;

			return LispComplex.newComplex(
					(LispReal)add(c.getReal()),
					(LispReal)c.getImag());
		}
		throw new IllegalArgumentException(x.toString());
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#sub(net.morilib.lisp.LispNumber)
	 */
	public LispNumber sub(LispNumber x) {
		if(x instanceof LispReal) {
			return new LispDouble(number - x.getRealDouble());
		} else if(x instanceof LispComplex) {
			LispComplex c = (LispComplex)x;

			return LispComplex.newComplex(
					(LispReal)sub(c.getReal()),
					c.getImag().uminus());
		}
		throw new IllegalArgumentException(x.toString());
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#mul(net.morilib.lisp.LispNumber)
	 */
	public LispNumber mul(LispNumber x) {
		if(x instanceof LispReal) {
			return new LispDouble(number * x.getRealDouble());
		} else if(x instanceof LispComplex) {
			LispComplex c = (LispComplex)x;

			return LispComplex.newComplex(
					(LispReal)mul(c.getReal()),
					(LispReal)mul(c.getImag()));
		}
		throw new IllegalArgumentException(x.toString());
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#div(net.morilib.lisp.LispNumber)
	 */
	public LispNumber div(LispNumber x) {
		if(x instanceof LispReal) {
			return new LispDouble(number / x.getRealDouble());
		} else if(x instanceof LispComplex) {
			LispReal xr = x.getReal();
			LispReal xi = x.getImag();
			LispReal xn = (LispReal)xr.mul(xr).add(xi.mul(xi));

			return LispComplex.newComplex(
					(LispReal)mul(xr).div(xn),
					(LispReal)mul(xi).uminus().div(xn));
		}
		throw new IllegalArgumentException(x.toString());
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#uminus()
	 */
	public LispDouble uminus() {
		return new LispDouble(-number);
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#isEqualTo(net.morilib.lisp.LispNumber)
	 */
	public boolean isEqualTo(LispNumber x) {
		if(x instanceof LispReal) {
			return number == x.getRealDouble();
		}
		return false;
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispReal#isLessThan(net.morilib.lisp.LispReal)
	 */
	public boolean isLessThan(LispReal x) {
		return number < x.getRealDouble();
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispReal#isMoreThan(net.morilib.lisp.LispReal)
	 */
	public boolean isMoreThan(LispReal x) {
		return number > x.getRealDouble();
	}

	/**
	 * 
	 * @return
	 */
	public double doubleValue() {
		return number;
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispReal#signum()
	 */
	public int signum() {
		return (number > 0) ? 1 : ((number < 0) ? -1 : 0);
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispReal#toExact()
	 */
	public LispExactReal toExact() {
		return LispDouble.toExact(number);
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispReal#toInexact()
	 */
	public LispReal toInexact() {
		return this;
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.Atom#print()
	 */
	public String toDisplay() {
		return disp(number);
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.Atom#getResult()
	 */
	public String toWrite() {
		return disp(number);
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#isInteger()
	 */
	@Override
	public boolean isInteger() {
		return LispUtils.toIntegerExact(number) != null;
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#isRational()
	 */
	@Override
	public boolean isRational() {
		return !(Double.isInfinite(number) || Double.isNaN(number));
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#isReal()
	 */
	@Override
	public boolean isReal() {
		return true;
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#isExact()
	 */
	public boolean isExact() {
		return false;
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#toLispString(int)
	 */
	public LispString toLispString(int radix) {
		if(radix < 2 || radix > 36) {
			throw new IndexOutOfBoundsException(
					"radix is out of range");
		} else if(radix != 10) {
			throw new IllegalArgumentException(
					"radix except 10 is not supported");
		}

		if(Double.isNaN(number)) {
			return new LispString("+nan.0");
		} else if(number == Double.POSITIVE_INFINITY) {
			return new LispString("+inf.0");
		} else if(number == Double.NEGATIVE_INFINITY) {
			return new LispString("-inf.0");
		} else {
			return new LispString(Double.toString(number));
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#toLispString(int, int)
	 */
	@Override
	public LispString toLispString(int radix, int precision) {
		if(radix < 2 || radix > 36) {
			throw new IndexOutOfBoundsException(
					"radix is out of range");
		} else if(radix != 10) {
			throw new IllegalArgumentException(
					"radix except 10 is not supported");
		} else if(precision < 0) {
			throw new IllegalArgumentException(
					"precision must not be negative");
		}

		if(Double.isNaN(number)) {
			return new LispString("+nan.0");
		} else if(number == Double.POSITIVE_INFINITY) {
			return new LispString("+inf.0");
		} else if(number == Double.NEGATIVE_INFINITY) {
			return new LispString("-inf.0");
		} else {
			return new LispString(String.format(
					"%" + precision + "d", number));
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#isNaN()
	 */
	public boolean isNaN() {
		return Double.isNaN(number);
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#isOne()
	 */
	public boolean isOne() {
		return number == 1.0;
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#getBigInteger()
	 */
	@Override
	public BigInteger getBigInteger() {
		return getBigDecimal().toBigInteger();
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#getInt()
	 */
	@Override
	public int getInt() {
		return getBigInteger().intValue();
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#getLong()
	 */
	@Override
	public long getLong() {
		return getBigInteger().longValue();
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#getBigDecimal()
	 */
	public BigDecimal getBigDecimal() {
		if(Double.isInfinite(number) || Double.isNaN(number)) {
			throw new NumberFormatException(
					"Infinities or NaNs is not supported");
		}

		return BigDecimal.valueOf(number);
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#getRealDouble()
	 */
	@Override
	public double getRealDouble() {
		return number;
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispReal#isInfinity()
	 */
	@Override
	public boolean isInfinity() {
		return Double.isInfinite(number);
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispReal#floor()
	 */
	@Override
	public LispReal floor() {
		return new LispDouble(Math.floor(number));
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispReal#ceil()
	 */
	@Override
	public LispReal ceil() {
		return new LispDouble(Math.ceil(number));
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#isFinite()
	 */
	@Override
	public boolean isFinite() {
		return !Double.isInfinite(number);
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispComplex#angle()
	 */
	@Override
	public LispReal angle() {
		return new LispDouble(number < 0 ? Math.PI : 0.0);
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	public boolean equals(Object x) {
		if(x instanceof LispDouble) {
			return Double.compare(number, ((LispDouble)x).number) == 0;
		}
		return false;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	public int hashCode() {
		int l = 17;
		long lr = Double.doubleToLongBits(number);

		l = 37 * l + (int)(lr ^ (lr >>> 32));
		return l;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		return Double.toString(number);
	}

}
