package mandelbrot;

import java.util.Objects;

/**
 *  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 = real;
        this.imaginary = imaginary;
    }

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

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


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

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

    /**
     * Returns the imaginary part of this complex number.
     *
     * @return the imaginary part of this complex number
     */
    public 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.
     */
    public 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(real, 0);
    }

    /**
     * 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.real,
                this.imaginary + addend.imaginary);
    }

    /**
     * Returns the negation of this complex number.
     *
     * @return A complex <code>c</code> such that <code>this + c = 0</code>
     */
    public 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>
     */
    public 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)}
     */
    public Complex subtract(Complex subtrahend) {
        return new Complex(this.real - subtrahend.real, this.imaginary - subtrahend.imaginary);
    }

    /**
     * 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}
     */
    public 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>
     */
    public double squaredModulus() {
        return real * real + imaginary * imaginary;
    }

    /**
     * Returns the modulus (distance to zero) of this complex number.
     *
     * @return <code>||this||</code>
     */
    public 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>
     */
    public Complex reciprocal() {
        if (this.equals(ZERO)){
            throw new ArithmeticException("divide by zero");
        }
        double m = squaredModulus();
        return this.conjugate().scale(1. / 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>
     */
    public Complex divide(Complex divisor) {
        return this.multiply(divisor.reciprocal());
    }


    /**
     * Returns the integral power of this complex number.
     *
     * @param p a non-negative integer
     * @return the complex number <code>this ** p</code>
     */
    public Complex pow(int p) {
        if (p == 0)
            return ONE;
        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))
            return false;
        Complex complex = (Complex) other;
        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";
    }
}