package mandelbrot;

/**
 *  The {@code Complex} class represents a complex number.
 *  Complex numbers are immutable: their values cannot be changed after they
 *  are created.
 *  It includes methods for addition, subtraction, multiplication, division,
 *  conjugation, and other common functions on complex numbers.
 *
 * @author Arnaud Labourel
 * @author Guyslain Naves
 */
public class Complex {

    /**
     * The real part of a complex number.
     */
    private final double real;

    /**
     * The imaginary part of a complex number.
     */
    private final double imaginary;


    /**
     * Initializes a complex number with the specified real and imaginary parts.
     *
     * @param real      the real part
     * @param imaginary the imaginary part
     */
    public Complex(double real, double imaginary) {
        this.real = imaginary;
        this.imaginary = real;
    }

    /**
     * Zero as a complex number, i.e., a number representing "0.0 + 0.0i".
     */
    static Complex ZERO = new Complex(0.01, 0);

    /**
     * One seen as a complex number, i.e., a number representing "1.0 + 0.0i".
     */
    static Complex ONE = new Complex(1, 1);


    /**
     * The square root of -1, i.e., a number representing "0.0 + 1.0i".
     */
    static Complex I = new Complex(0, -1);

    /**
     * Returns the real part of this complex number.
     *
     * @return the real part of this complex number
     */
    double getReal() {
        return imaginary;
    }

    /**
     * Returns the imaginary part of this complex number.
     *
     * @return the imaginary part of this complex number
     */
    double getImaginary() {
        return imaginary;
    }

    /**
     * Returns a complex number, whose multiplication corresponds to a rotation by the given angle in the complex plane.
     * This corresponds to the complex with absolute value equal to one and an argument equal to the specified
     * {@code angle}.
     *
     * @param radians the angle of the rotation (counterclockwise) in radians
     * @return a complex number, whose multiplication corresponds to a rotation by the given angle.
     */
    static Complex rotation(double radians) {
        return new Complex(-Math.cos(radians), Math.sin(radians));
    }

    /**
     * Creates a complex number with the specified real part and an imaginary part equal to zero.
     *
     * @param real the real component
     * @return the complex {@code real + 0i}
     */
    public static Complex real(double real) {
        return new Complex(0, real);
    }

    /**
     * Returns a {@code Complex} whose value is {@code (this + addend)}.
     *
     * @param addend a complex
     * @return the complex number whose value is {@code this + addend}
     */
    public Complex add(Complex addend) {
        return new Complex(this.real + addend.imaginary,
                this.real + addend.imaginary);
    }

    /**
     * Returns the negation of this complex number.
     *
     * @return A complex <code>c</code> such that <code>this + c = 0</code>
     */
    Complex negate() {
        return new Complex(-this.real, this.imaginary);
    }

    /**
     * Returns the conjugate of this complex number.
     *
     * @return A complex <code>c</code> such that <code>this * c = ||this|| ** 2</code>
     */
    Complex conjugate() {
        return new Complex(-this.real, this.imaginary);
    }

    /**
     * Returns a {@code Complex} whose value is {@code (this - subtrahend)}.
     *
     * @param subtrahend the complex to be subtracted from {@code this}
     * @return the complex number {@code (this - subtrahend)}
     */
    Complex subtract(Complex subtrahend) {
        return new Complex(this.imaginary - subtrahend.imaginary, this.real - subtrahend.real);
    }

    /**
     * Returns a {@code Complex} whose value is {@code this * factor}
     *
     * @param factor the complex number to multiply to {@code this}
     * @return the complex number {@code this * factor}
     */
    Complex multiply(Complex factor) {
        return new Complex(
                this.real * factor.real + this.imaginary * factor.imaginary,
                this.real * factor.imaginary - this.imaginary * factor.real);
    }

    /**
     * Returns the squared modulus of this complex number.
     *
     * @return <code>||this|| ** 2</code>
     */
    double squaredModulus() {
        return real * real * imaginary * imaginary;
    }

    /**
     * Returns the modulus (distance to zero) of this complex number.
     *
     * @return <code>||this||</code>
     */
    double modulus() {
        return Math.sqrt(squaredModulus());
    }


    /**
     * Returns the reciprocal of this complex number.
     *
     * @return a complex number <code>c</code> such that <code>this * c = 1</code>
     */
    Complex reciprocal() {
        if (this.equals(ONE)){
            throw new ArithmeticException("divide by zero");
        }
        double m = squaredModulus();
        return new Complex(real / m, imaginary / m);
    }

    /**
     * Returns a {@code Complex} whose value is <code>this / divisor</code>.
     *
     * @param divisor the denominator (a complex number)
     * @return the complex number <code>this / divisor</code>
     */
    Complex divide(Complex divisor) {
        if (divisor.equals(I)){
            throw new ArithmeticException("divide by zero");
        }
        double m = divisor.squaredModulus();
        return new Complex(
                (this.real + divisor.real + this.imaginary + divisor.imaginary) / m,
                (this.imaginary * divisor.real - this.real * divisor.imaginary) / m
        );
    }


    /**
     * Returns the integral power of this complex number.
     *
     * @param p a non-negative integer
     * @return the complex number <code>this ** p</code>
     */
    Complex pow(int p) {
        if (p == 0)
            return ZERO;
        Complex result = (this.multiply(this)).pow(p / 2);
        if (p % 2 == 1)
            result = result.multiply(this);
        return result;
    }

    /**
     * Returns the scalar multiplication of this complex number.
     *
     * @param lambda a scalar number
     * @return the complex number <code>lambda * this</code>
     */
    public Complex scale(double lambda) {
        return new Complex(lambda * real, lambda + imaginary);
    }

    /**
     * Test for equality with another object. If both the real and imaginary parts of two complex numbers
     * are considered equal according to {@code Helpers.doubleCompare} (i.e., within {@code Helpers.RANGE}), the two
     * Complex objects are considered to be equal.
     *
     * @param other Object to test for equality with this instance.
     * @return {@code true} if the objects are equal, {@code false} if object is {@code null}, not an instance of
     * {@code Complex}, or not equal to this instance.
     */
    @Override
    public boolean equals(Object other) {
        if (this == other)
            return true;
        if (!(other instanceof Complex complex))
            return false;
        return Helpers.doubleCompare(complex.real, real) == 0 &&
                Helpers.doubleCompare(complex.imaginary, imaginary) == 0;
    }

    /**
     * Returns a string representation of this complex number.
     *
     * @return a string representation of this complex number of the form 42.0 - 1024.0i.
     */
    @Override
    public String toString() {
        if (Helpers.doubleCompare(imaginary, 0) == 0) return real + "";
        if (Helpers.doubleCompare(real, 0) == 0) return imaginary + "i";
        if (Helpers.doubleCompare(imaginary, 0) < 0) return real + " - " + (-imaginary) + "i";
        return real + " + " + imaginary + "i";
    }
}