/*
 * 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 abstract class LispComplex extends LispNumber {

	/**
	 * 
	 * @param r
	 * @param i
	 * @return
	 */
	public static LispComplex newComplex(LispReal r, LispReal i) {
		if(r.isNaN() || i.isNaN()) {
			return LispDouble.NaN;
//		} else if(i.signum() == 0) {
		} else if(i.isExact() && i.signum() == 0) {
			return r;
		} else if(r.isExact() && i.isExact()) {
			return new LispComplexImpl(r, i);
		} else {
			return new LispComplexImpl(r.toInexact(), i.toInexact());
		}
	}

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

	/**
	 * 
	 * @param r
	 * @param i
	 * @return
	 */
	public static LispComplex newPolar(double r, double a) {
		double x;

		if(Double.isNaN(r) ||
				Double.isNaN(a) || Double.isInfinite(a)) {
			return LispDouble.NaN;
		} else if(r == 0.0) {
			return LispDouble.ZERO;
		} else {
			x = Math.IEEEremainder(a, 2 * Math.PI);
			x = (x > Math.PI) ? x - 2 * Math.PI : x;
			return LispComplex.newComplex(
					new LispDouble(r), new LispDouble(x));
		}
	}

	/*
	 * (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#isNaN()
	 */
	public boolean isNaN() {
		return getReal().isNaN() || getImag().isNaN();
	}

	/*
	 * (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#isOne()
	 */
	public boolean isOne() {
		return getReal().isOne() && getImag().isZero();
	}

	/*
	 * (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.LispQuaternion#uminus()
	 */
	public abstract LispComplex uminus();

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#toExact()
	 */
	public abstract LispComplex toExact();

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#toInexact()
	 */
	public abstract LispComplex toInexact();

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#conjugate()
	 */
	public abstract LispComplex conjugate();

	/**
	 * 
	 * @return
	 */
	public abstract LispReal angle();

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#getImags()
	 */
	@Override
	public LispReal[] getImags() {
		return new LispReal[] { getImag() };
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#getImagsDouble()
	 */
	@Override
	public double[] getImagsDouble() {
		return new double[] { getImagDouble() };
	}

	/*
	 * (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(getImag().signum() < 0 || getImag().isInfinity()) {
			return new LispString(
					LispUtils.toWrite(getReal()) +
					LispUtils.toWrite(getImag()) + "i");
		} else {
			return new LispString(
					LispUtils.toWrite(getReal()) + "+" +
					LispUtils.toWrite(getImag()) + "i");
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.lisp.LispNumber#toLispString(int, int)
	 */
	@Override
	public LispString toLispString(int radix, int precision) {
		String rs, is;

		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");
		}

		rs = getReal().toLispString(radix, precision).getString();
		is = getImag().toLispString(radix, precision).getString();
		if(getImag().signum() < 0 && !getImag().isInfinity()) {
			return new LispString(rs + is + "i");
		} else {
			return new LispString(rs + "+" + is + "i");
		}
	}

	/*
	 * (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 ((getReal().isEqualTo(c.getReal())) &&
					(getImag().isEqualTo(c.getImag())));
		} else {
			return false;
		}
		//throw new IllegalArgumentException(x.toString());
	}

	/* (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 LispComplex.newComplex(
					(LispReal)getReal().add(c.getReal()),
					(LispReal)getImag().add(c.getImag()));
		} else if(x instanceof LispReal) {
			return newComplex((LispReal)getReal().add(x.getReal()),
					getImag());
		}
		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 LispComplex.newComplex(
					(LispReal)getReal().sub(c.getReal()),
					(LispReal)getImag().sub(c.getImag()));
		} else if(x instanceof LispReal) {
			return newComplex((LispReal)getReal().sub(x.getReal()),
					getImag());
		}
		throw new IllegalArgumentException(x.toString());
	}

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

			if(xr.signum() == 0) {
				return newComplex(
						(LispReal)getImag().uminus().mul(xi),
						(LispReal)getReal().mul(xi));
			} else {
				return newComplex(
						(LispReal)getReal().mul(xr).sub(getImag().mul(xi)),
						(LispReal)getImag().mul(xr).add(getReal().mul(xi)));
			}
		} else if(x instanceof LispReal) {
			LispReal r = x.getReal();

			return newComplex(
					(LispReal)getReal().mul(r),
					(LispReal)getImag().mul(r));
		}
		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) {
			LispReal xr = ((LispComplex)x).getReal();
			LispReal xi = ((LispComplex)x).getImag();

			if(xr.signum() == 0) {
				return newComplex(
						(LispReal)getImag().div(xi),
						(LispReal)getReal().uminus().div(xi));
			} else {
				LispReal nr = (LispReal)xr.mul(xr).add(xi.mul(xi));
				LispReal x1 =
						(LispReal)getReal().mul(xr).add(getImag().mul(xi));
				LispReal x2 =
						(LispReal)getImag().mul(xr).sub(getReal().mul(xi));

				return newComplex((LispReal)x1.div(nr),
						(LispReal)x2.div(nr));
			}
		} else if(x instanceof LispReal) {
			LispReal r = x.getReal();

			return newComplex((LispReal)getReal().div(r),
					(LispReal)getImag().div(r));
		}
		throw new IllegalArgumentException(x.toString());
	}

}
