package ti.lang;
import java.util.StringTokenizer;

// Complex number immutable class (rectangular)
// Kaushik Datta and Dan Bonachea {kdatta,bonachea}@cs.berkeley.edu

public immutable class Complex {
    public double re;
    public double im;

    private static final double NaN = Double.NaN;
    private static final Complex i = new Complex(0, 1);

    public inline Complex() {
	re = 0.0;
	im = 0.0;
    }

    public inline Complex(double re, double im) {
	if (Double.isNaN(re+im)) {
	    this.re = NaN;
	    this.im = 0.0;
	}
	else {
	    this.re = re;
	    this.im = im;
	}
    }

    public String toString() {
	String s = new String("");
	if ((re == 0) || (im == 0)) {
	    if (im == 0) {
		s = s + re;
	    }
	    else {
		s = s + im + "i";
	    }
	}
	else {
	    if (im > 0) {
		s = s + re + " + " + im + "i";
	    }
	    else {
		s = s + re + " - " + Math.abs(im) + "i";
	    }
	}
	return s;
    }

    /* String s must be in the form: a, bi, or a+bi, where a and b can be parsed into doubles */
    public static Complex parseComplex(String s) throws NullPointerException, NumberFormatException {
	String token1, token2, token3, token4, token5;
	double firstNumber, real, imag;

	real = 0.0;
	imag = 0.0;

	// remove all spaces from the string
	StringTokenizer st1 = new StringTokenizer(s, " ", false);
	String s1 = "";
	while (st1.hasMoreTokens()) {
	    s1 = s1 + st1.nextToken();
	}

	// now parse the string s1
	StringTokenizer st2 = new StringTokenizer(s1, "+-i", true);

	if (st2.hasMoreTokens()) {
	    token1 = st2.nextToken();
	}
	else {
	    throw new NumberFormatException();
	}
	if (token1.equals("+")) {
	    if (st2.hasMoreTokens()) {
		token2 = st2.nextToken();
	    }
	    else {
		throw new NumberFormatException();
	    }
	    firstNumber = Double.valueOf(token2).doubleValue();
	}
	else if (token1.equals("-")) {
	    if (st2.hasMoreTokens()) {
		token2 = st2.nextToken();
	    }
	    else {
		throw new NumberFormatException();
	    }
	    firstNumber = -Double.valueOf(token2).doubleValue();
	}
	else {
	    firstNumber = Double.valueOf(token1).doubleValue();
	}
	if (st2.hasMoreTokens()) {
	    token3 = st2.nextToken();
	    if (token3.equals("i")) {
		imag = firstNumber;
	    }
	    else {
		real = firstNumber;
		if (st2.hasMoreTokens()) {
		    token4 = st2.nextToken();
		}
		else {
		    throw new NumberFormatException();
		}
		if (token3.equals("+")) {
		    imag = Double.valueOf(token4).doubleValue();
		}
		else if (token3.equals("-")) {
		    imag = -Double.valueOf(token4).doubleValue();
		}
		if (st2.hasMoreTokens()) {
		    token5 = st2.nextToken();
		}
		else {
		    throw new NumberFormatException();
		}
		if (!token5.equals("i")) {
		    throw new NumberFormatException();
		}
	    }
	    if (st2.hasMoreTokens()) {
		throw new NumberFormatException();
	    }
	    else {
		return new Complex(real, imag);
	    }
	}
	else {
	    return new Complex(firstNumber, 0.0);
	}
    }

    /* Operator overloading methods */

    // negative
    public inline Complex op-() {
	return new Complex(-re, -im);
    }

    public inline Complex op+(double d1) {
	return new Complex(re + d1, im);
    }
	
    public inline Complex op+(Complex c1) {
	return new Complex(re + c1.re, im + c1.im);
    }

    public inline Complex op-(double d1) {
	return new Complex(re - d1, im);
    }

    public inline Complex op-(Complex c1) {
	return new Complex(re - c1.re, im - c1.im);
    }

    public inline Complex op*(double d1) {
	return new Complex(re * d1, im * d1);
    }

    public inline Complex op*(Complex c1) {
	return new Complex(re * c1.re - im * c1.im, im * c1.re + re * c1.im);
    }

    public inline Complex op/(double d1) {
	return new Complex(re / d1, im / d1);
    }

    public Complex op/(Complex c1) {
	double magnitudeC1Squared = c1.re * c1.re + c1.im * c1.im;
	return new Complex((re * c1.re + im * c1.im) / magnitudeC1Squared, (im * c1.re - re * c1.im) / magnitudeC1Squared);
    }

    public inline boolean op==(double d1) {
	return ((re == d1) && (im == 0.0));
    }

    public inline boolean op==(Complex c1) {
	return ((re == c1.re) && (im == c1.im));
    }

    public inline boolean op!=(double d1) {
	return ((re != d1) || (im != 0.0));
    }

    public inline boolean op!=(Complex c1) {
	return ((re != c1.re) || (im != c1.im));
    }

    // complex conjugate
    public inline Complex op~() {
	return new Complex(re, -im);
    }

    // complex number raised to double power
    public Complex op^(double d1) {
	if (abs() > 0.0) {
	    return (log() * d1).exp();
	}
	else {
	    if (d1 != 0.0) {
		return this;
	    }
	    else {
		return new Complex(NaN, 0.0);
	    }
	}
    }

    // complex number raised to complex power
    public Complex op^(Complex c1) {
	if (abs() > 0.0) {
	    return (log() * c1).exp();
	}
	else {
	    if (c1.abs() > 0.0) {
		return this;
	    }
	    else {
		return new Complex(NaN, 0.0);
	    }
	}
    }

    /* non-operator overloading methods */

    public inline Complex neg() {
	return (-this);
    }

    public inline Complex add(double d1) {
	return (this + d1);
    }

    public inline Complex add(Complex c1) {
	return (this + c1);
    }

    public static inline Complex add(Complex c1, double d1) {
	return (c1 + d1);
    }

    /* this add is for (d1 + c1), which is undefined
       using operator overloading */
    public static inline Complex add(double d1, Complex c1) {
	return (c1 + d1);
    }

    public inline Complex sub(double d1) {
	return (this - d1);
    }

    public inline Complex sub(Complex c1) {
	return (this - c1);
    }

    public static inline Complex sub(Complex c1, double d1) {
	return (c1 - d1);
    }

    /* this subtract is for (d1 - c1), which is undefined
       using operator overloading */
    public static inline Complex sub(double d1, Complex c1) {
	return new Complex(d1 - c1.re, -c1.im);
    }

    public inline Complex mult(double d1) {
	return (this * d1);
    }

    public inline Complex mult(Complex c1) {
	return (this * c1);
    }

    public static inline Complex mult(Complex c1, double d1) {
	return (c1 * d1);
    }

    /* this multiply is for (d1 * c1), which is undefined
       using operator overloading */
    public static inline Complex mult(double d1, Complex c1) {
	return (c1 * d1);
    }

    public inline Complex div(double d1) {
	return (this / d1);
    }

    public inline Complex div(Complex c1) {
	return (this / c1);
    }

    public static inline Complex div(Complex c1, double d1) {
	return (c1 / d1);
    }

    /* this divide is for (d1 / c1), which is undefined
       using operator overloading */
    public static Complex div(double d1, Complex c1) {
	double magnitudeC1Squared = c1.re * c1.re + c1.im * c1.im;
	return new Complex((d1 * c1.re)/magnitudeC1Squared, -(d1 * c1.im)/magnitudeC1Squared);
    }

    public inline boolean eq(double d1) {
	return (this == d1);
    }

    public inline boolean eq(Complex c1) {
	return (this == c1);
    }

    public inline boolean neq(double d1) {
	return (this != d1);
    }

    public inline boolean neq(Complex c1) {
	return (this != c1);
    }

    // returns the complex conjugate
    public inline Complex conj() {
	return (~this);
    }

    public inline Complex pow(double d1) {
	return this^d1;
    }

    public inline Complex pow(Complex c1) {
	return this^c1;
    }

    public inline Complex exp() {
	double magnitude = Math.exp(re);
	return new Complex(magnitude * Math.cos(im), magnitude * Math.sin(im));
    }

    public inline Complex log() {
	return new Complex(Math.log(abs()), arg());
    }

    public inline Complex sqrt() {
	return (log()/2.0).exp();
    }

    public Complex sin() {
	double sinRe = Math.sin(re);
	double cosRe = Math.cos(re);
	double expIm = Math.exp(im);
	double expMinusIm = 1.0/expIm;

	return new Complex(sinRe * (expMinusIm + expIm)/2.0, cosRe * (expIm - expMinusIm)/2.0);
    }

    public Complex cos() {
	double sinRe = Math.sin(re);
	double cosRe = Math.cos(re);
	double expIm = Math.exp(im);
	double expMinusIm = 1.0/expIm;

	return new Complex(cosRe * (expMinusIm + expIm)/2.0, sinRe * (expMinusIm - expIm)/2.0);
    }

    public Complex tan() {
	double sinRe = Math.sin(re);
	double cosRe = Math.cos(re);
	double expIm = Math.exp(im);
	double expMinusIm = 1.0/expIm;
	double sum = expIm + expMinusIm;
	double diff = expIm - expMinusIm;
	double x1 = sinRe * sum;
	double y1 = cosRe * diff;
	double x2 = cosRe * sum;
	double y2 = sinRe * diff;
	double magnitudeSquared = x2 * x2 + y2 * y2;

	return new Complex((x1 * x2 - y1 * y2)/magnitudeSquared, (x2 * y1 + x1 * y2)/magnitudeSquared);
    }

    public Complex asin() {
	return (-i * (i * this + (-this * this + 1.0).sqrt()).log());
    }

    public Complex acos() {
	return (-i * (this + i * (-this * this + 1.0).sqrt()).log());
    }

    public Complex atan() {
	return (i/2.0) * ((i + this)/(i - this)).log();
    }

    public Complex sinh() {
	double sinIm = Math.sin(im);
	double cosIm = Math.cos(im);
	double expRe = Math.exp(re);
	double expMinusRe = 1.0/expRe;

	return new Complex(cosIm * (expRe - expMinusRe)/2.0, sinIm * (expRe + expMinusRe)/2.0);
    }

    public Complex cosh() {
	double sinIm = Math.sin(im);
	double cosIm = Math.cos(im);
	double expRe = Math.exp(re);
	double expMinusRe = 1.0/expRe;

	return new Complex(cosIm * (expRe + expMinusRe)/2.0, sinIm * (expRe - expMinusRe)/2.0);
    }


    public Complex tanh() {
	double sinIm = Math.sin(im);
	double cosIm = Math.cos(im);
	double expRe = Math.exp(re);
	double expMinusRe = 1.0/expRe;
	double sum = expRe + expMinusRe;
	double diff = expRe - expMinusRe;
	double x1 = cosIm * diff;
	double y1 = sinIm * sum;
	double x2 = cosIm * sum;
	double y2 = sinIm * diff;
	double magnitudeSquared = x2 * x2 + y2 * y2;

	return new Complex((x1 * x2 + y1 * y2)/magnitudeSquared, (x2 * y1 - x1 * y2)/magnitudeSquared);
    }

    public Complex asinh() {
	return (this + ((this * this) + 1.0).sqrt()).log();
    }

    // this function has extra code to match its result with other common math libraries
    public Complex acosh() {
	Complex result = (this + ((this * this) - 1.0).sqrt()).log();
	
	if (this.re >= 0.0) {
	    return result;
	}
	else if ((this.re > -1.0) || (this.im != 0.0)) {
	    return -result;
	}
	else {
	    return new Complex(-result.re, result.im);
	}
    }

    public Complex atanh() {
	return ((this + 1.0)/(-this + 1.0)).log() / 2.0;
    }

    /* Methods for converting to polar */

    // returns the absolute value (magnitude)
    public inline double abs() {
	return Math.sqrt(re * re + im * im);
    }

    // returns abs()^2.  This is used to compare magnitudes without Math.sqrt()
    public inline double absSquared() {
	return (re * re + im * im);
    }

    /* returns the argument (phase angle) in radians.
       The result must be between -pi and pi */
    public inline double arg() {
	return Math.atan2(im, re);
    }

    /* returns a ComplexPolar object from the magnitude
       and angle (in radians) of the complex number */
    public inline ComplexPolar toPolar() {
	return new ComplexPolar(abs(), arg());
    }
}