/*
 * 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;

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

import net.morilib.lisp.sos.LispType;

/**
 * 
 *
 *
 * @author MORIGUCHI, Yuichiro 2009
 */
public final class LispComplex extends LispNumber {

	/**
	 * 
	 */
	public static final LispComplex I = new LispComplex(0, 1.0);

	/**
	 * 
	 */
	public static final LispComplex MINUS_I = new LispComplex(0, -1.0);

	//
	private double real;
	private double imag;

	//
	private LispComplex(double r, double i) {
		real = r;
		imag = i;
	}

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

	/*
	 * (non-Javadoc)
	 * @see net.morilib.lisp.Datum#getReal()
	 */
	public LispReal getReal() {
		return new LispDouble(real);
	}

	/*
	 * (non-Javadoc)
	 * @see net.morilib.lisp.Datum#getImagDouble()
	 */
	public double getImagDouble() {
		return imag;
	}

	/*
	 * (non-Javadoc)
	 * @see net.morilib.lisp.Datum#getImag()
	 */
	public LispReal getImag() {
		return new LispDouble(imag);
	}

	/**
	 * 
	 * @param r
	 * @param i
	 * @return
	 */
	public static LispNumber newComplex(double r, double i) {
		if(i == 0.0) {
			return new LispDouble(r);
		} else if(Double.isNaN(r) || Double.isNaN(i)) {
			return LispDouble.NaN;
		} else {
			return new LispComplex(r, i);
		}
	}

	/*
	 * (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#add(net.morilib.lisp.LispNumber)
	 */
	@Override
	public LispNumber add(LispNumber x) {
		if(x instanceof LispComplex) {
			LispComplex c = (LispComplex)x;

			return newComplex(real + c.real, imag + c.imag);
		} else if(x instanceof LispReal) {
			return newComplex(real + x.getRealDouble(), imag);
		}
		throw new IllegalArgumentException(x.toString());
	}

	/*
	 * (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#div(net.morilib.lisp.LispNumber)
	 */
	@Override
	public LispNumber div(LispNumber x) {
		if(x instanceof LispComplex) {
			double xr = ((LispComplex)x).real;
			double xi = ((LispComplex)x).imag;

			if(xr == 0.0) {
				return newComplex(imag / xi, -real / xi);
			} else {
				return newComplex(
						(real * xr + imag * xi) / (xr * xr + xi * xi),
						(imag * xr - real * xi) / (xr * xr + xi * xi));
			}
		} else if(x instanceof LispReal) {
			double r = x.getRealDouble();

			return newComplex(real / r, imag / r);
		}
		throw new IllegalArgumentException(x.toString());
	}

	/*
	 * (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#isEqualTo(net.morilib.lisp.LispNumber)
	 */
	@Override
	public boolean isEqualTo(LispNumber x) {
		if(x instanceof LispComplex) {
			LispComplex c = (LispComplex)x;

			return (real == c.real) && (imag == c.imag);
		} else if(x instanceof LispReal) {
			return false;
		}
		throw new IllegalArgumentException(x.toString());
	}

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

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

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

	/*
	 * (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#mul(net.morilib.lisp.LispNumber)
	 */
	@Override
	public LispNumber mul(LispNumber x) {
		if(x instanceof LispComplex) {
			double xr = ((LispComplex)x).real;
			double xi = ((LispComplex)x).imag;

			if(xr == 0.0) {
				return newComplex(-imag * xi, real * xi);
			} else {
				return newComplex(
						(real * xr - imag * xi),
						(imag * xr + real * xi));
			}
		} else if(x instanceof LispReal) {
			double r = x.getRealDouble();

			return newComplex(real * r, imag * r);
		}
		throw new IllegalArgumentException(x.toString());
	}

	/*
	 * (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#sub(net.morilib.lisp.LispNumber)
	 */
	@Override
	public LispNumber sub(LispNumber x) {
		if(x instanceof LispComplex) {
			LispComplex c = (LispComplex)x;

			return newComplex(real - c.real, imag - c.imag);
		} else if(x instanceof LispReal) {
			return newComplex(real - x.getRealDouble(), imag);
		}
		throw new IllegalArgumentException(x.toString());
	}

	/*
	 * (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#uminus()
	 */
	@Override
	public LispNumber uminus() {
		return newComplex(-real, -imag);
	}

	/*
	 * (non-Javadoc)
	 * @see net.morilib.lisp.Atom#getResult()
	 */
	@Override
	public String getResult() {
		if(imag > 0 && imag != Double.POSITIVE_INFINITY) {
			return (LispNumber.disp(real) + "+" +
					LispNumber.disp(imag) + "i");
		} else {
			return (LispNumber.disp(real) +
					LispNumber.disp(imag) + "i");
		}
	}

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

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

	/*
	 * (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#toExact()
	 */
	public LispNumber toExact() {
		throw new LispNotSupportedException(
				"err.notsupported.exactcomplex");
	}

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

	/*
	 * (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(imag > 0 && imag != Double.POSITIVE_INFINITY) {
			return new LispString(
					LispNumber.disp(real) + "+" +
					LispNumber.disp(imag) + "i");
		} else {
			return new LispString(
					LispNumber.disp(real) +
					LispNumber.disp(imag) + "i");
		}
	}

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

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

	/*
	 * (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#getBigInteger()
	 */
	@Override
	public BigInteger getBigInteger() {
		throw new UnsupportedOperationException();
	}

	/*
	 * (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#getDenominator()
	 */
	@Override
	public BigInteger getDenominator() {
		throw new UnsupportedOperationException();
	}

	/*
	 * (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#getInt()
	 */
	@Override
	public int getInt() {
		throw new UnsupportedOperationException();
	}

	/*
	 * (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#getLong()
	 */
	@Override
	public long getLong() {
		throw new UnsupportedOperationException();
	}

	/*
	 * (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#getBigDecimal()
	 */
	public BigDecimal getBigDecimal() {
		throw new UnsupportedOperationException();
	}

	/*
	 * (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#getNumerator()
	 */
	@Override
	public BigInteger getNumerator() {
		throw new UnsupportedOperationException();
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.Datum#getType()
	 */
	@Override
	public LispType getType() {
		return LispType.COMPLEX;
	}

	/*
	 * (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	public boolean equals(Object x) {
		if(x instanceof LispComplex) {
			LispComplex c = (LispComplex)x;

			return (real == c.real) && (imag == c.imag);
		}
		return false;
	}

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

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

	/*
	 * (non-Javadoc)
	 * @see net.morilib.lisp.Datum#toString()
	 */
	public String toString() {
		return getResult();
	}

}
