MathComplexCore
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © RicardoSantos

//@version=5

// @description Core functions to handle complex numbers.
library(title='MathComplexCore')

// references:
//      https://en.wikipedia.org/wiki/Complex_number
//      js: https://rosettacode.org/wiki/Fast_Fourier_transform
//      https://github.com/trekhleb/javascript-algorithms/blob/477f30b0bdac6024874d2976de1eca7afe0176dd/src/algorithms/math/complex-number/ComplexNumber.js#L3
//      https://github.com/infusion/Complex.js/
//      https://github.com/mathnet/mathnet-numerics/blob/a50d68d52def605a53d129cc60ea3371a2e97548/src/Numerics/Complex32.cs

//#region   -> Imports:

import RicardoSantos/CommonTypesMath/1 as TMath
import RicardoSantos/DebugConsole/11 as console
logger = console.Console.new().init()
//#endregion



//#region   -> Constructor:

// new () {

// @function Creates a new Complex number.
// @param real      float . Real part of the complex number. default=0.
// @param imaginary float . Imaginary part of the complex number. default=0.
// @returns Complex.
export new (float real=0.0, float imaginary=0.0) =>
    TMath.complex.new(real, imaginary)

// }
// from {

// @function Creates a new Complex number were provided value replaces both real and imaginary parts.
// @param value float . Value of real and imaginary parts.
// @returns Complex.
export from (float value) =>
    new(value, value)

// @function Creates a new Complex number from provided `Vector2` coordinate.
// @param value Vector2 . Value of real and imaginary parts.
// @returns Complex.
export from (TMath.Vector2 value) =>
    new(value.x, value.y)

// @function Creates a new Complex number from provided `Pole` coordinate.
// @param value Pole . Value of real and imaginary parts.
// @returns Complex.
export from (TMath.Pole value) =>
    new(value.radius * math.cos(value.angle), value.radius * math.sin(value.angle))

// @function Creates a new Complex number from provided value.
// @param value string . Value of real and imaginary parts.
// @returns Complex.
export from (string value) =>
    string _clean = str.replace_all(value, ' ', '')
    string _reg = '(?:(?:\\+|-|\\.|\\d){1}(?:\\d)*(?:\\.)?(?:\\d)*){1}(?:(?:(?:E|e){1}(?:\\+|-|\\d)?)+(?:\\d)*(?:\\.)?(?:\\d)*)?'
    string _prefix = str.match(_clean, _reg)
    string _clean1 = str.replace(_clean, _prefix, '', 0)
    string _suffix = str.match(_clean1, _reg)
    if str.length(_suffix) > 0
        new(str.tonumber(_prefix), str.tonumber(_suffix))
    else
        new(nz(str.tonumber(_prefix), 0.0), 0.0)

// string str = input.string('-1.0+30.0e-2i')
// if barstate.islast
//     _c = from(str)
//     label.new(bar_index, 0.0, str.format('real:{0}, imaginary:{1}', _c.re, _c.im))
// }
// from_polar_coordinates () {

// @function Create a complex number from polar coordinates.
// @param magnitude float, default=0.0, The magnitude, which is the distance from the origin (the intersection of the x-axis and the y-axis) to the number.
// @param phase     float, default=0.0, The phase, which is the angle from the line to the horizontal axis, measured in radians.
// @returns Complex.
export from_polar_coordinates (float magnitude=0.0, float phase=0.0) =>
    new(magnitude * math.cos(phase), magnitude * math.sin(phase))

// }
// {

// @function Complex number "0+0i".
// @returns Complex.
export zero () =>
    from(0.0)

// }
// {

// @function Complex number "1+0i".
// @returns Complex.
export one () =>
    new(1.0, 0.0)

// }
// {

// @function Complex number "0+1i".
// @returns Complex.
export imaginary_one () =>
    new(0.0, 1.0)

// }
// {

// @function Complex number with `float(na)` parts.
// @returns Complex.
export nan () =>
    from(float(na))

// }

//#endregion



// is_complex () {

// @function Checks that its a valid complex number.
// @param this Complex Source complex number.
// @returns bool.
export method is_complex (TMath.complex this) =>
    not (na(this.re) or na(this.im)) and this.im != 0.0

// }
// {

// @function Checks that its empty "na" complex number.
// @param this Complex Source complex number.
// @returns bool.
export method is_nan (TMath.complex this) =>
    na(this.re) or na(this.im)

// }
// {

// @function Checks that the complex_number is real.
// @param this Complex Source complex number.
// @returns bool.
export method is_real (TMath.complex this) =>
    // https://github.com/numpy/numpy/blob/v1.24.0/numpy/lib/type_check.py#L210-L244
    this.im == 0.0

// }
// {

// @function Checks that the complex_number is real and not negative.
// @param this Complex Source complex number.
// @returns bool.
export method is_real_non_negative (TMath.complex this) =>
    this.re >= 0.0 and this.im == 0.0

// }
// {

// @function Checks that the complex_number is zero.
// @param this Complex Source complex number.
// @returns bool.
export method is_zero (TMath.complex this) =>
    this.re + this.im == 0.0

// }
// {

// @function Compares two complex numbers: 
// @param this  Complex . Source complex number.
// @param other Complex . Target complex number
// @param eps   float   . Precision value, default=1e-15.
// @returns boolean value representing the equality.
export method equals (TMath.complex this, TMath.complex other, float eps=1e-15) =>
    math.abs(other.re - this.re) <= eps and math.abs(other.im - this.im) <= eps

// }
// {

// @function Converts complex number to a string format, in the form: "a+bi" 
// @param this Complex . Source complex number..
// @param format string, formating to apply.
// @returns string. In "a+bi" format
export method to_string (TMath.complex this, string format='#.##########') =>
    switch
        this.im == 0 => str.format('{0,number,'+format+'}'     , this.re)
        this.re == 0 => str.format('{0,number,'+format+'}i'    , this.im)
        this.im < 0  => str.format('{0,number,'+format+'}-{1,number,'+format+'}i', this.re, -this.im)
        =>              str.format('{0,number,'+format+'}+{1,number,'+format+'}i', this.re, this.im)

// usage:
// console.queue_one(__C, str.format('to_string conversion of complex number "5.2+3.0i": {0}', to_string(new(5.2, 3.0), '#.##'))) // 

// }

if barstate.islastconfirmedhistory
    float _i0 = 1.0
    float _i1 = 2.0
    float _i2 = 3.0
    float _i3 = 4.0
    A = new(0.0, 0.0)
    B = one()
    logger.queue_one(str.format('input parameters: {0}, {1}, {2}, {3}', _i0, _i1, _i2, _i3))
    logger.queue_one(str.format('##   Create      ##', ''))
    logger.queue_one(str.format('new complex number A: {0}', to_string(A, '#.##')))
    logger.queue_one(str.format('new complex number B: {0}', to_string(A, '#.##')))
    logger.queue_one(str.format('new complex number zero(): {0}', to_string(zero(), '#.##')))
    logger.queue_one(str.format('new complex number one(): {0}', to_string(one(), '#.##')))
    logger.queue_one(str.format('new complex number imaginary_one(): {0}', to_string(imaginary_one(), '#.##')))
    logger.queue_one(str.format('new complex number nan(): {0}', to_string(nan(), '#.##')))
    logger.queue_one(str.format('new complex number from_polar_coordinates({0}, {1}): {2}', _i2, _i3, to_string(from_polar_coordinates(_i2, _i3), '#.##')))
    logger.queue_one(str.format('##   Access      ##', ''))
    A.re := _i0 , A.im := _i1
    logger.queue_one(str.format('set real and imaginary parts of A with inputs({0}, {1}): {2}', _i0, _i1, to_string(A, '#.##')))
    logger.queue_one(str.format('set real and imaginary parts of B with inputs({0}, {1}): {2}', _i2, _i3, to_string(B, '#.##')))
    logger.queue_one(str.format('get real and imaginary parts of B:({0}, {1})', B.re, B.im))
    logger.queue_one(str.format('##   Test        ##', ''))
    logger.queue_one(str.format('Test: is A complex number: {0}', is_complex(A)))
    logger.queue_one(str.format('Test: is A NaN: {0}', is_nan(A)))
    logger.queue_one(str.format('Test: is A real: {0}', is_real(A)))
    logger.queue_one(str.format('Test: is A real non negative: {0}', is_real_non_negative(A)))
    logger.queue_one(str.format('Test: is A zero: {0}', is_zero(A)))


// update console
logger.update()
