package math.number;

import math.Maths;

/**
 * Data type for complex number &isin;&#x2102; which defines complex arithmetic and
 * mathematical functions.
 * @author Daniel Weschke
 */
public final class NumberComplex extends Number{

	/**
	 * real part &real;
	 */
	private double ℜ;
	
	/**
	 * imaginary part &image;
	 */
	private double ℑ;

	/**
	 * Constructs the complex number z = a + bi, a,b=0
	 */
	public NumberComplex(){
	}
	
	/**
	 * Constructs the complex number z = a + bi, b=0
	 * @param a real part
	 */
	public NumberComplex(double a){
		ℜ = a;
	}
	
	/**
	 * Constructs the complex number z = ℜ(z) + ℑ(z)i
	 * @param ℜ real part
	 * @param ℑ imaginary part
	 */
	public NumberComplex(double ℜ, double ℑ){
		this.ℜ = ℜ;
		this.ℑ = ℑ;
	}
	
	/**
	 * Constructs the complex number z = a + bi
	 * @param z complex number
	 */
	public NumberComplex(NumberComplex z){
		this(z.ℜ(), z.ℑ());
	}
	
	/**
	 * Constructs the complex number z = a + bi
	 * @param z complex number
	 * @return complex number
	 */
	public NumberComplex assign(NumberComplex z){
		ℜ = z.ℜ();
		ℑ = z.ℑ();
		return this;
	}
	
	/**
	 * Real part of this complex number
	 * (the x-coordinate in rectangular coordinates).
	 * @return Re(z) where z is this complex number
	 */
	public double ℜ(){
		return ℜ;
	}
	
	/**
	 * Imaginary part of this complex number
	 * (the y-coordinate in rectangular coordinates).
	 * @return ℑ(z) where z is this complex number
	 */
	public double ℑ(){
		return ℑ;
	}
	
	/**
	 * Addition with real number (doesn't change this complex number).<br />
	 * (a+bi) + c = (a+c)+bi.
	 * @param c real number
	 * @return (a+bi) + c
	 * @see #plus(NumberComplex)
	 * @see #plusAssign(NumberComplex)
	 */
	public NumberComplex plus(double c){
		return new NumberComplex(ℜ()+c,ℑ());
	}
	
	/**
	 * Addition with complex number (doesn't change this complex number).<br />
	 * (a+bi) + (c+di) = (a+c)+(b+d)i.
	 * @param z complex number
	 * @return (a+bi) + (c+di)
	 * @see #plus(double)
	 * @see #plusAssign(NumberComplex)
	 */
	public NumberComplex plus(NumberComplex z){
		return new NumberComplex(ℜ()+z.ℜ(),ℑ()+z.ℑ());
	}
	
	/**
	 * Addition with complex number (does change this complex number).<br />
	 * (a+bi) + (c+di) = (a+c)+(b+d)i.
	 * @param z complex number
	 * @return y = y + z
	 * @see #plus(double)
	 * @see #plus(NumberComplex)
	 */
	public NumberComplex plusAssign(NumberComplex z){
		ℜ += z.ℜ();
		ℑ += z.ℑ();
		return this; 
	}
	
	/**
	 * Subtraction of Complex numbers (doesn't change this Complex number).<br />
	 * (a+bi) - c = (a-c)+bi.
	 * @param c real number
	 * @return (a+bi) - c
	 * @see #minus(NumberComplex)
	 */
	public NumberComplex minus(double c){
		return plus(-c);
	}
	
	/**
	 * Subtraction of Complex numbers (doesn't change this Complex number).<br />
	 * (a+bi) - (c+di) = (a-c)+(b-d)i.
	 * @param z complex number
	 * @return (a+bi) - (c+di)
	 * @see NumberComplex#minus(double)
	 */
	public NumberComplex minus(NumberComplex z){
		return new NumberComplex(ℜ()-z.ℜ(),ℑ()-z.ℑ());
	}
	
	/**
	 * Scalar multiplication (doesn't change this Complex number).
	 * @param s scalar
	 * @return s(a+bi)
	 * @see #times(NumberComplex)
	 */
	public NumberComplex times(double s){
		return new NumberComplex(ℜ()*s,ℑ()*s);
	}
	
	/**
	 * Complex multiplication (doesn't change this Complex number).
	 * @param z complex number
	 * @return (a+bi)(c+di) = ac-bd + (ad+bc)i
	 * @see #times(double)
	 */
	public NumberComplex times(NumberComplex z){
		// local r and i is needed because re and im depends on re and im
		double r = ℜ()*z.ℜ() - ℑ()*z.ℑ();
		double i = ℜ()*z.ℑ() + ℑ()*z.ℜ();
		return new NumberComplex(r, i);
	}
	
	/**
	 * Division of Complex numbers (doesn't change this Complex number).<br />
	 * (a+bi)/(c+di) = (ac+bd)/(cc+dd) + (bc-ad)/(cc+dd) i
	 * @param z complex number
	 * @return (a+bi)/(c+di) = (ac+bd)/(cc+dd) + (bc-ad)/(cc+dd) i
	 */
	public NumberComplex over(NumberComplex z){
        return times(z.reciprocal());
	}

    /**
     * @return a new complex object whose value is the reciprocal of this
     */
    public NumberComplex reciprocal() {
        double scale = ℜ()*ℜ() + ℑ()*ℑ();
        return new NumberComplex(ℜ() / scale, -ℑ() / scale);
    }
    
    /**
     * Modulus or absolute value or length of this complex number
     * (the distance from the origin in polar coordinates).
     * @return |z| where z is this Complex number.
     */
    public double mod(){
		return Maths.hypot(ℜ(), ℑ());
    }
	
	/**
	 * Argument or phase of this Complex number 
	 * (the angle in radians with the real/x-axis in polar coordinates).
	 * @return arg(z) in radians where z is this Complex number
	 */
	public double arg(){
		return Maths.atan(ℑ(), ℜ());
	}
	
	/**
	 * Argument of this complex number 
	 * (the angle &ang; in radians with the real/x-axis in polar coordinates).
	 * @return arg(z) in deg where z is this complex number
	 */
	public double argd(){
		return Maths.atand(ℑ(), ℜ());
	}
    
    /**
     * Complex conjugate of this complex number
     * (the conjugate of x+i*y is x-i*y).
     * @return z-bar where z is this complex number.
    */
    public NumberComplex conj() {
        return new NumberComplex(ℜ(),-ℑ());
    }
    
    /**
     * Complex square root (doesn't change this complex number).
     * Computes the principal branch of the square root, which 
     * is the value with 0 <= arg < pi.
     * @return sqrt(z) where z is this Complex number.
     */
    public NumberComplex sqrt(){
        double r=Math.sqrt(this.mod());
        double ϑ=this.arg()/2;
        return new NumberComplex(r*Math.cos(ϑ),r*Math.sin(ϑ));
    }

    /**
     * Complex exponential (doesn't change this Complex number).
     * @return exp(z) where z is this Complex number.
     */
    public NumberComplex exp() {
        return new NumberComplex(Math.exp(ℜ())*Math.cos(ℑ()),
        		Math.exp(ℜ())*Math.sin(ℑ()));
    }
    
    /**
     * Principal branch of the complex logarithm of this complex number 
     * (doesn't change this complex number).
     * The principal branch is the branch with -π < arg <= π.
     * @return log(z) where z is this Complex number.
     */
    public NumberComplex log() {
        return new NumberComplex(Math.log(this.mod()),this.arg());
    }
    
    /**
     * Cosine of this complex number (doesn't change this complex number).<br />
     * cos(z) = (e<sup>iz</sup>+e<sup>-iz</sup>)/2.
     * @return cos(z) where z is this Complex number.
     */
    public NumberComplex cos(){
        return new NumberComplex(cosh(ℑ())*Math.cos(ℜ()),-sinh(ℑ())*Math.sin(ℜ()));
    }
    
    /**
     * Sine of this complex number (doesn't change this complex number).<br />
     * sin(z) = (e<sup>iz</sup>-e<sup>-iz</sup>)/(2i).
     * @return sin(z) where z is this Complex number.
     */
    public NumberComplex sin() {
        return new NumberComplex(cosh(ℑ())*Math.sin(ℜ()),sinh(ℑ())*Math.cos(ℜ()));
    }
    
    /**
     * Tangent of this complex number (doesn't change this complex number).<br />
     * tan(z) = sin(z)/cos(z).
        @return tan(z) where z is this Complex number.
    */
    public NumberComplex tan() {
        return sin().over(cos());
    }
    
    /**
     * Real cosh function (used to compute complex trig functions)
     * @param ϑ argument
     * @return (e<sup>ϑ</sup>+e<sup>-ϑ</sup>)/2
     */
    private double cosh(double ϑ){
        return (Math.exp(ϑ)+Math.exp(-ϑ))/2;
    }
    
    /**
     * Hyperbolic cosine of this complex number (doesn't change this complex number).<br />
     * cosh(z) = (e<sup>z</sup> + e<sup>-z</sup>)/2.
     * @return cosh(z) where z is this Complex number.
     */
    public NumberComplex cosh() {
        return new NumberComplex(cosh(ℜ())*Math.cos(ℑ()),sinh(ℜ())*Math.sin(ℑ()));
    }
    
    /**
     * Real sinh function (used to compute complex trig functions)
     * @param ϑ argumant
     * @return (e<sup>ϑ</sup>-e<sup>-ϑ</sup>)/2
     */
    private double sinh(double ϑ){
        return (Math.exp(ϑ)-Math.exp(-ϑ))/2;
    }
    
    /**
     * Hyperbolic sine of this complex number (doesn't change this complex number).<br />
     * sinh(z) = (e<sup>z</sup>-e<sup>-z</sup>)/2.
     * @return sinh(z) where z is this Complex number.
     */
    public NumberComplex sinh(){
        return new NumberComplex(sinh(ℜ())*Math.cos(ℑ()),cosh(ℜ())*Math.sin(ℑ()));
    }

    /**
     * Negative of this complex number (chs stands for change sign). 
     * This produces a new Complex number and doesn't change this Complex number.<br />
     * -(x+i*y) = -x-i*y.
     * @return -z where z is this Complex number.
     */
    public NumberComplex chs() {
        return new NumberComplex(-ℜ(),-ℑ());
    }
	
    /**
     * A string representation of the complex object.
     */
	@Override
	public String toString(){
		if(ℑ() == 0) return ""+ℜ();
		if(ℜ() == 0) return ℑ()+"i";
		if(ℑ() > 0)  return ""+ℜ()+" + "+ℑ()+"i";
		if(ℑ() < 0)  return ""+ℜ()+" - "+(-ℑ())+"i";
		else return ℜ()+" + i*"+ℑ(); // shouldn't get here (unless Inf or NaN)
	}
	
	public void printRe(){
		System.out.println("ℜ(z) = "+ℜ());
	}
	
	public void printIm(){
		System.out.println("ℑ(z) = "+ℑ());
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		NumberComplex cn1 = new NumberComplex(4,1);
		System.out.println("a = "+cn1);
		NumberComplex cn2 = new NumberComplex(4,-1);
		System.out.println("b = "+cn2);
		NumberComplex cn = cn1.times(cn2);
		System.out.println("a * b = "+cn);
		System.out.println("mod(): "+(new NumberComplex(4,3).mod()==5));
		System.out.println(cn.argd());
		

        NumberComplex a = new NumberComplex(5.0, 6.0);
        NumberComplex b = new NumberComplex(-3.0, 4.0);

        System.out.println("a            = " + a);
        System.out.println("b            = " + b);
        System.out.println("ℜ(a)         = " + a.ℜ());
        System.out.println("ℑ(a)         = " + a.ℑ());
        System.out.println("b + a        = " + b.plus(a));
        System.out.println("a - b        = " + a.minus(b));
        System.out.println("a * b        = " + a.times(b));
        System.out.println("b * a        = " + b.times(a));
        System.out.println("a / b        = " + a.over(b));
        System.out.println("(a / b) * b  = " + a.over(b).times(b));
        System.out.println("conj(a)      = " + a.conj());
        System.out.println("|a|          = " + a.mod());
        System.out.println("tan(a)       = " + a.tan());
	}

}
