<?php

namespace LWVendor\Complex;

use InvalidArgumentException;
class Functions
{
    /**
     * Returns the absolute value (modulus) of a complex number.
     * Also known as the rho of the complex number, i.e. the distance/radius
     *   from the centrepoint to the representation of the number in polar coordinates.
     *
     * This function is a synonym for rho()
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    float            The absolute (or rho) value of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     *
     * @see    rho
     *
     */
    public static function abs($complex) : float
    {
        return self::rho($complex);
    }
    /**
     * Returns the inverse cosine of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The inverse cosine of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     */
    public static function acos($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        $invsqrt = self::sqrt(Operations::subtract(1, Operations::multiply($complex, $complex)));
        $adjust = new Complex($complex->getReal() - $invsqrt->getImaginary(), $complex->getImaginary() + $invsqrt->getReal());
        $log = self::ln($adjust);
        return new Complex($log->getImaginary(), -1 * $log->getReal());
    }
    /**
     * Returns the inverse hyperbolic cosine of a complex number.
     *
     * Formula from Wolfram Alpha:
     *   cosh^(-1)z = ln(z + sqrt(z + 1) sqrt(z - 1)).
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The inverse hyperbolic cosine of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     */
    public static function acosh($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        if ($complex->isReal() && $complex->getReal() > 1) {
            return new Complex(\acosh($complex->getReal()));
        }
        $acosh = self::ln(Operations::add($complex, Operations::multiply(self::sqrt(Operations::add($complex, 1)), self::sqrt(Operations::subtract($complex, 1)))));
        return $acosh;
    }
    /**
     * Returns the inverse cotangent of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The inverse cotangent of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     * @throws    \InvalidArgumentException    If function would result in a division by zero
     */
    public static function acot($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        return self::atan(self::inverse($complex));
    }
    /**
     * Returns the inverse hyperbolic cotangent of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The inverse hyperbolic cotangent of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     * @throws    \InvalidArgumentException    If function would result in a division by zero
     */
    public static function acoth($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        return self::atanh(self::inverse($complex));
    }
    /**
     * Returns the inverse cosecant of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The inverse cosecant of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     * @throws    \InvalidArgumentException    If function would result in a division by zero
     */
    public static function acsc($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
            return new Complex(\INF);
        }
        return self::asin(self::inverse($complex));
    }
    /**
     * Returns the inverse hyperbolic cosecant of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The inverse hyperbolic cosecant of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     * @throws    \InvalidArgumentException    If function would result in a division by zero
     */
    public static function acsch($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
            return new Complex(\INF);
        }
        return self::asinh(self::inverse($complex));
    }
    /**
     * Returns the argument of a complex number.
     * Also known as the theta of the complex number, i.e. the angle in radians
     *   from the real axis to the representation of the number in polar coordinates.
     *
     * This function is a synonym for theta()
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    float            The argument (or theta) value of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     *
     * @see    theta
     */
    public static function argument($complex) : float
    {
        return self::theta($complex);
    }
    /**
     * Returns the inverse secant of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The inverse secant of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     * @throws    \InvalidArgumentException    If function would result in a division by zero
     */
    public static function asec($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
            return new Complex(\INF);
        }
        return self::acos(self::inverse($complex));
    }
    /**
     * Returns the inverse hyperbolic secant of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The inverse hyperbolic secant of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     * @throws    \InvalidArgumentException    If function would result in a division by zero
     */
    public static function asech($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
            return new Complex(\INF);
        }
        return self::acosh(self::inverse($complex));
    }
    /**
     * Returns the inverse sine of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The inverse sine of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     */
    public static function asin($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        $invsqrt = self::sqrt(Operations::subtract(1, Operations::multiply($complex, $complex)));
        $adjust = new Complex($invsqrt->getReal() - $complex->getImaginary(), $invsqrt->getImaginary() + $complex->getReal());
        $log = self::ln($adjust);
        return new Complex($log->getImaginary(), -1 * $log->getReal());
    }
    /**
     * Returns the inverse hyperbolic sine of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The inverse hyperbolic sine of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     */
    public static function asinh($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        if ($complex->isReal() && $complex->getReal() > 1) {
            return new Complex(\asinh($complex->getReal()));
        }
        $asinh = clone $complex;
        $asinh = $asinh->reverse()->invertReal();
        $asinh = self::asin($asinh);
        return $asinh->reverse()->invertImaginary();
    }
    /**
     * Returns the inverse tangent of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The inverse tangent of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     * @throws    \InvalidArgumentException    If function would result in a division by zero
     */
    public static function atan($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        if ($complex->isReal()) {
            return new Complex(\atan($complex->getReal()));
        }
        $t1Value = new Complex(-1 * $complex->getImaginary(), $complex->getReal());
        $uValue = new Complex(1, 0);
        $d1Value = clone $uValue;
        $d1Value = Operations::subtract($d1Value, $t1Value);
        $d2Value = Operations::add($t1Value, $uValue);
        $uResult = $d1Value->divideBy($d2Value);
        $uResult = self::ln($uResult);
        $realMultiplier = -0.5;
        $imaginaryMultiplier = 0.5;
        if (\abs($uResult->getImaginary()) === \M_PI) {
            // If we have an imaginary value at the max or min (PI or -PI), then we need to ensure
            //    that the primary is assigned for the correct quadrant.
            $realMultiplier = $uResult->getImaginary() === \M_PI && $uResult->getReal() > 0.0 || $uResult->getImaginary() === -\M_PI && $uResult->getReal() < 0.0 ? 0.5 : -0.5;
        }
        return new Complex($uResult->getImaginary() * $realMultiplier, $uResult->getReal() * $imaginaryMultiplier, $complex->getSuffix());
    }
    /**
     * Returns the inverse hyperbolic tangent of a complex number.
     *
     * Formula from Wolfram Alpha:
     *  tanh^(-1)z = 1/2 [ln(1 + z) - ln(1 - z)].
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The inverse hyperbolic tangent of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     */
    public static function atanh($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        if ($complex->isReal()) {
            $real = $complex->getReal();
            if ($real >= -1.0 && $real <= 1.0) {
                return new Complex(\atanh($real));
            } else {
                return new Complex(\atanh(1 / $real), $real < 0.0 ? \M_PI_2 : -1 * \M_PI_2);
            }
        }
        $atanh = Operations::multiply(Operations::subtract(self::ln(Operations::add(1.0, $complex)), self::ln(Operations::subtract(1.0, $complex))), 0.5);
        return $atanh;
    }
    /**
     * Returns the complex conjugate of a complex number
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The conjugate of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     */
    public static function conjugate($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        return new Complex($complex->getReal(), -1 * $complex->getImaginary(), $complex->getSuffix());
    }
    /**
     * Returns the cosine of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The cosine of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     */
    public static function cos($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        if ($complex->isReal()) {
            return new Complex(\cos($complex->getReal()));
        }
        return self::conjugate(new Complex(\cos($complex->getReal()) * \cosh($complex->getImaginary()), \sin($complex->getReal()) * \sinh($complex->getImaginary()), $complex->getSuffix()));
    }
    /**
     * Returns the hyperbolic cosine of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The hyperbolic cosine of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     */
    public static function cosh($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        if ($complex->isReal()) {
            return new Complex(\cosh($complex->getReal()));
        }
        return new Complex(\cosh($complex->getReal()) * \cos($complex->getImaginary()), \sinh($complex->getReal()) * \sin($complex->getImaginary()), $complex->getSuffix());
    }
    /**
     * Returns the cotangent of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The cotangent of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     * @throws    \InvalidArgumentException    If function would result in a division by zero
     */
    public static function cot($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
            return new Complex(\INF);
        }
        return self::inverse(self::tan($complex));
    }
    /**
     * Returns the hyperbolic cotangent of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The hyperbolic cotangent of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     * @throws    \InvalidArgumentException    If function would result in a division by zero
     */
    public static function coth($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        return self::inverse(self::tanh($complex));
    }
    /**
     * Returns the cosecant of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The cosecant of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     * @throws    \InvalidArgumentException    If function would result in a division by zero
     */
    public static function csc($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
            return new Complex(\INF);
        }
        return self::inverse(self::sin($complex));
    }
    /**
     * Returns the hyperbolic cosecant of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The hyperbolic cosecant of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     * @throws    \InvalidArgumentException    If function would result in a division by zero
     */
    public static function csch($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
            return new Complex(\INF);
        }
        return self::inverse(self::sinh($complex));
    }
    /**
     * Returns the exponential of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The exponential of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     */
    public static function exp($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        if ($complex->getReal() == 0.0 && \abs($complex->getImaginary()) == \M_PI) {
            return new Complex(-1.0, 0.0);
        }
        $rho = \exp($complex->getReal());
        return new Complex($rho * \cos($complex->getImaginary()), $rho * \sin($complex->getImaginary()), $complex->getSuffix());
    }
    /**
     * Returns the inverse of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The inverse of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     * @throws    InvalidArgumentException    If function would result in a division by zero
     */
    public static function inverse($complex) : Complex
    {
        $complex = clone Complex::validateComplexArgument($complex);
        if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
            throw new InvalidArgumentException('Division by zero');
        }
        return $complex->divideInto(1.0);
    }
    /**
     * Returns the natural logarithm of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The natural logarithm of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     * @throws    InvalidArgumentException  If the real and the imaginary parts are both zero
     */
    public static function ln($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
            throw new InvalidArgumentException();
        }
        return new Complex(\log(self::rho($complex)), self::theta($complex), $complex->getSuffix());
    }
    /**
     * Returns the base-2 logarithm of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The base-2 logarithm of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     * @throws    InvalidArgumentException  If the real and the imaginary parts are both zero
     */
    public static function log2($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
            throw new InvalidArgumentException();
        } elseif ($complex->getReal() > 0.0 && $complex->getImaginary() == 0.0) {
            return new Complex(\log($complex->getReal(), 2), 0.0, $complex->getSuffix());
        }
        return self::ln($complex)->multiply(\log(Complex::EULER, 2));
    }
    /**
     * Returns the common logarithm (base 10) of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The common logarithm (base 10) of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     * @throws    InvalidArgumentException  If the real and the imaginary parts are both zero
     */
    public static function log10($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
            throw new InvalidArgumentException();
        } elseif ($complex->getReal() > 0.0 && $complex->getImaginary() == 0.0) {
            return new Complex(\log10($complex->getReal()), 0.0, $complex->getSuffix());
        }
        return self::ln($complex)->multiply(\log10(Complex::EULER));
    }
    /**
     * Returns the negative of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The negative value of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     *
     * @see    rho
     *
     */
    public static function negative($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        return new Complex(-1 * $complex->getReal(), -1 * $complex->getImaginary(), $complex->getSuffix());
    }
    /**
     * Returns a complex number raised to a power.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @param     float|integer    $power      The power to raise this value to
     * @return    Complex          The complex argument raised to the real power.
     * @throws    Exception        If the power argument isn't a valid real
     */
    public static function pow($complex, $power) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        if (!\is_numeric($power)) {
            throw new Exception('Power argument must be a real number');
        }
        if ($complex->getImaginary() == 0.0 && $complex->getReal() >= 0.0) {
            return new Complex(\pow($complex->getReal(), $power));
        }
        $rValue = \sqrt($complex->getReal() * $complex->getReal() + $complex->getImaginary() * $complex->getImaginary());
        $rPower = \pow($rValue, $power);
        $theta = $complex->argument() * $power;
        if ($theta == 0) {
            return new Complex(1);
        }
        return new Complex($rPower * \cos($theta), $rPower * \sin($theta), $complex->getSuffix());
    }
    /**
     * Returns the rho of a complex number.
     * This is the distance/radius from the centrepoint to the representation of the number in polar coordinates.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    float            The rho value of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     */
    public static function rho($complex) : float
    {
        $complex = Complex::validateComplexArgument($complex);
        return \sqrt($complex->getReal() * $complex->getReal() + $complex->getImaginary() * $complex->getImaginary());
    }
    /**
     * Returns the secant of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The secant of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     * @throws    \InvalidArgumentException    If function would result in a division by zero
     */
    public static function sec($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        return self::inverse(self::cos($complex));
    }
    /**
     * Returns the hyperbolic secant of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The hyperbolic secant of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     * @throws    \InvalidArgumentException    If function would result in a division by zero
     */
    public static function sech($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        return self::inverse(self::cosh($complex));
    }
    /**
     * Returns the sine of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The sine of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     */
    public static function sin($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        if ($complex->isReal()) {
            return new Complex(\sin($complex->getReal()));
        }
        return new Complex(\sin($complex->getReal()) * \cosh($complex->getImaginary()), \cos($complex->getReal()) * \sinh($complex->getImaginary()), $complex->getSuffix());
    }
    /**
     * Returns the hyperbolic sine of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The hyperbolic sine of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     */
    public static function sinh($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        if ($complex->isReal()) {
            return new Complex(\sinh($complex->getReal()));
        }
        return new Complex(\sinh($complex->getReal()) * \cos($complex->getImaginary()), \cosh($complex->getReal()) * \sin($complex->getImaginary()), $complex->getSuffix());
    }
    /**
     * Returns the square root of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The Square root of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     */
    public static function sqrt($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        $theta = self::theta($complex);
        $delta1 = \cos($theta / 2);
        $delta2 = \sin($theta / 2);
        $rho = \sqrt(self::rho($complex));
        return new Complex($delta1 * $rho, $delta2 * $rho, $complex->getSuffix());
    }
    /**
     * Returns the tangent of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The tangent of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     * @throws    InvalidArgumentException    If function would result in a division by zero
     */
    public static function tan($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        if ($complex->isReal()) {
            return new Complex(\tan($complex->getReal()));
        }
        $real = $complex->getReal();
        $imaginary = $complex->getImaginary();
        $divisor = 1 + \pow(\tan($real), 2) * \pow(\tanh($imaginary), 2);
        if ($divisor == 0.0) {
            throw new InvalidArgumentException('Division by zero');
        }
        return new Complex(\pow(self::sech($imaginary)->getReal(), 2) * \tan($real) / $divisor, \pow(self::sec($real)->getReal(), 2) * \tanh($imaginary) / $divisor, $complex->getSuffix());
    }
    /**
     * Returns the hyperbolic tangent of a complex number.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    Complex          The hyperbolic tangent of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     * @throws    \InvalidArgumentException    If function would result in a division by zero
     */
    public static function tanh($complex) : Complex
    {
        $complex = Complex::validateComplexArgument($complex);
        $real = $complex->getReal();
        $imaginary = $complex->getImaginary();
        $divisor = \cos($imaginary) * \cos($imaginary) + \sinh($real) * \sinh($real);
        if ($divisor == 0.0) {
            throw new InvalidArgumentException('Division by zero');
        }
        return new Complex(\sinh($real) * \cosh($real) / $divisor, 0.5 * \sin(2 * $imaginary) / $divisor, $complex->getSuffix());
    }
    /**
     * Returns the theta of a complex number.
     *   This is the angle in radians from the real axis to the representation of the number in polar coordinates.
     *
     * @param     Complex|mixed    $complex    Complex number or a numeric value.
     * @return    float            The theta value of the complex argument.
     * @throws    Exception        If argument isn't a valid real or complex number.
     */
    public static function theta($complex) : float
    {
        $complex = Complex::validateComplexArgument($complex);
        if ($complex->getReal() == 0.0) {
            if ($complex->isReal()) {
                return 0.0;
            } elseif ($complex->getImaginary() < 0.0) {
                return \M_PI / -2;
            }
            return \M_PI / 2;
        } elseif ($complex->getReal() > 0.0) {
            return \atan($complex->getImaginary() / $complex->getReal());
        } elseif ($complex->getImaginary() < 0.0) {
            return -(\M_PI - \atan(\abs($complex->getImaginary()) / \abs($complex->getReal())));
        }
        return \M_PI - \atan($complex->getImaginary() / \abs($complex->getReal()));
    }
}
