MathComplexTrignometry
// 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 Methods for complex number trignometry operations.
library(title='MathComplexTrignometry')

// reference:
//      https://github.com/mathnet/mathnet-numerics/blob/a50d68d52def605a53d129cc60ea3371a2e97548/src/Numerics/Trigonometry.cs
//      test values : https://www.mathsisfun.com/numbers/complex-number-calculator.html

//#region   -> Imports:

import RicardoSantos/DebugConsole/11 as console
logger = console.Console.new().init(30)
import RicardoSantos/MathTrigonometry/1 as trig
import RicardoSantos/MathConstants/1 as constants
import RicardoSantos/CommonTypesMath/1 as TMath
import RicardoSantos/MathComplexCore/3 as complex
import RicardoSantos/MathComplexOperator/2 as complex_op
import RicardoSantos/MathComplexExtension/2 as complex_ex

//#endregion

string        strof_a = input.string('3.1416+1.618i', 'A:')
TMath.complex input_a = complex.from(strof_a)
// preview of functions parameters:
bool show_1 = input.bool(title='Show Trig functions:', defval=true)
bool show_2 = input.bool(title='Show hyper Trig functions:', defval=true)
bool show_3 = input.bool(title='Show arc Trig functions:', defval=true)
bool show_4 = input.bool(title='Show hyper arc Trig functions:', defval=true)



//#region   -> Methods:

// sinh  () {

// @function Hyperbolic Sine of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method sinh (TMath.complex this) =>
    if complex.is_real(this)
        complex.new(trig.sinh(this.re), 0.0)
    else
        float _absre = math.abs(this.re)
        if _absre >= 22.0
            float _h = math.exp(_absre) * 0.5
            complex.new(math.sign(this.re) * _h * math.cos(this.im), _h * math.sin(this.im))
        else
            complex.new(trig.sinh(this.re) * math.cos(this.im), trig.cosh(this.re) * math.sin(this.im))

// usage:
if show_2
    logger.queue_one(str.format('sinh({0}): {1}', strof_a, complex.to_string(sinh(input_a), '#.##')))

// }
// cosh  () {

// @function Hyperbolic cosine of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method cosh (TMath.complex this) =>
    if complex.is_real(this)
        complex.new(trig.cosh(this.re), 0.0)
    else
        float _absre = math.abs(this.re)
        if _absre >= 22.0
            float _h = math.exp(_absre) * 0.5
            complex.new(_h * math.cos(this.im), math.sign(this.re) * _h * math.sin(this.im))
        else
            complex.new(trig.cosh(this.re) * math.cos(this.im), trig.sinh(this.re) * math.sin(this.im))

// usage:
if show_2
    logger.queue_one(str.format('cosh({0}): {1}', strof_a, complex.to_string(cosh(input_a), '#.##')))

// }
// tanh  () {

// @function Hyperbolic tangent of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method tanh (TMath.complex this) =>
    if complex.is_real(this)
        complex.new(trig.tanh(this.re), 0.0)
    else
        if math.abs(this.re) >= 22.0
            float _e = math.exp(-math.abs(this.re))
            if _e == 0.0
                complex.new(math.sign(this.re), 0.0)
            else
                complex.new(math.sign(this.re), 4.0 * math.cos(this.im) * math.sin(this.im) * math.pow(_e, 2.0))
        else
            float POSINF = 1.7976931348623157e+308 // positive float infinity, credit to Xel_Arjona.
            float _tani  = math.tan(this.im)
            float _sinhr = trig.sinh(this.re)
            float _coshr = trig.cosh(this.re)
            if math.abs(_tani) >= POSINF
                complex.new(_coshr / _sinhr, 0.0)
            else
                float _beta = 1.0 + math.pow(_tani, 2.0)
                float _denom = 1.0 + _beta * math.pow(_sinhr, 2.0)
                complex.new(_beta * _coshr * _sinhr / _denom, _tani / _denom)

// usage:
if show_2
    logger.queue_one(str.format('tanh({0}): {1}', strof_a, complex.to_string(tanh(input_a), '#.##')))

// }
// coth  () {

// @function Hyperbolic cotangent of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method coth (TMath.complex this) =>
    if complex.is_real(this)
        complex.new(trig.coth(this.re), 0.0)
    else
        complex_op.divide(complex.one(), tanh(this))

// usage:
if show_2
    logger.queue_one(str.format('coth({0}): {1}', strof_a, complex.to_string(coth(input_a), '#.##')))

// }
// sech  () {

// @function Hyperbolic Secant of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method sech (TMath.complex this) =>
    if complex.is_real(this)
        complex.new(trig.sech(this.re), 0.0)
    else
        float _absre = math.abs(this.re)
        float _tani  = math.tan(this.im)
        float _cosi  = math.cos(this.im)
        float _sinhr = trig.sinh(this.re)
        float _coshr = trig.cosh(this.re)
        if _absre >= 22.0
            float _e = math.exp(-_absre)
            if _e == 0.0
                complex.zero()
            else
                float _e2 = math.pow(_e, 2.0)
                complex.new(4.0 * _coshr * _cosi * _e2, -4.0 * _sinhr * _tani * _cosi * _e2)
        else
            float POSINF = 1.7976931348623157e+308 // positive float infinity, credit to Xel_Arjona.
            if math.abs(_tani) >= POSINF
                complex.new(0.0, -math.sign(_tani) / _sinhr)
            else
                float _beta = 1.0 + math.pow(_tani, 2.0)
                float _denom = 1.0 +_beta * math.pow(_sinhr, 2.0)
                complex.new(_coshr / _cosi / _denom, -_sinhr * _tani / _cosi / _denom)

// usage:
if show_2
    logger.queue_one(
     str.format('sech({0}): {1}', strof_a, complex.to_string(sech(input_a), '#.##')))

// }
// csch  () {

// @function Hyperbolic Cosecant of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method csch (TMath.complex this) =>
    if complex.is_real(this)
        complex.new(trig.csch(this.re), 0.0)
    else
        float _absre = math.abs(this.re)
        float _coti = trig.cot(this.im)
        float _sini = math.sin(this.im)
        float _sinhr = trig.sinh(this.re)
        float _coshr = trig.cosh(this.re)
        if _absre >= 22.0
            float _e = math.exp(-_absre)
            if _e == 0.0
                complex.zero()
            else
                float _e2 = math.pow(_e, 2.0)
                complex.new(4.0 * _sinhr * _coti * _sini * _e2, -4.0 * _coshr * _sini * _e2)
        else
            float POSINF = 1.7976931348623157e+308 // positive float infinity, credit to Xel_Arjona.
            if math.abs(_coti) >= POSINF
                complex.new(math.sign(_coti) / _sinhr, 0.0)
            else
                float _beta = 1.0 + math.pow(_coti, 2.0)
                float _denom = 1.0 +_beta * math.pow(_sinhr, 2.0)
                complex.new(_sinhr * _coti / _sini / _denom, -_coshr / _sini / _denom)

// usage:
if show_2
    logger.queue_one(str.format('csch({0}): {1}', strof_a, complex.to_string(csch(input_a), '#.##')))

// }
// sin   () {

// @function Trigonometric Sine of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method sin (TMath.complex this) =>
    if complex.is_real(this)
        complex.new(math.sin(this.re), 0.0)
    else
        complex.new(math.sin(this.re) * trig.cosh(this.im), math.cos(this.re) * trig.sinh(this.im))

// usage:
if show_1
    logger.queue_one(str.format('sin({0}): {1}', strof_a, complex.to_string(sin(input_a), '#.##')))

// }
// cos   () {

// @function Trigonometric cosine of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method cos (TMath.complex this) =>
    if complex.is_real(this)
        complex.new(math.cos(this.re), 0.0)
    else
        complex.new(math.cos(this.re) * trig.cosh(this.im), -math.sin(this.re) * trig.sinh(this.im))

// usage:
if show_1
    logger.queue_one(str.format('cos({0}): {1}', strof_a, complex.to_string(cos(input_a), '#.##')))

// }
// tan   () {

// @function Trigonometric tangent of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method tan (TMath.complex this) =>
    if complex.is_real(this)
        complex.new(math.tan(this.re), 0.0)
    else
        TMath.complex _z = tanh(complex.new(-this.im, this.re))
        complex.new(_z.im, -_z.re)

// usage:
if show_1
    logger.queue_one(str.format('tan({0}): {1}', strof_a, complex.to_string(tan(input_a), '#.##')))

// }
// cot   () {

// @function Trigonometric cotangent of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method cot (TMath.complex this) =>
    if complex.is_real(this)
        complex.new(trig.cot(this.re), 0.0)
    else
        TMath.complex _z = coth(complex.new(this.im, -this.re))
        complex.new(_z.im, -_z.re)

// usage:
if show_1
    logger.queue_one(str.format('cot({0}): {1}', strof_a, complex.to_string(cot(input_a), '#.##')))

// }
// sec   () {

// @function Trigonometric Secant of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method sec (TMath.complex this) =>
    if complex.is_real(this)
        complex.new(trig.sec(this.re), 0.0)
    else
        float _cosr = math.cos(this.re)
        float _sinhi = trig.sinh(this.im)
        float _denom = math.pow(_cosr, 2) + math.pow(_sinhi, 2)
        complex.new(_cosr * trig.cosh(this.im) / _denom, math.sin(this.re) * _sinhi / _denom)

// usage:
if show_1
    logger.queue_one(str.format('sec({0}): {1}', strof_a, complex.to_string(sec(input_a), '#.##')))

// }
// csc   () {

// @function Trigonometric Cosecant of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method csc (TMath.complex this) =>
    if complex.is_real(this)
        complex.new(trig.csc(this.re), 0.0)
    else
        float _sinr = math.sin(this.re)
        float _sinhi = trig.sinh(this.im)
        float _denom = math.pow(_sinr, 2) + math.pow(_sinhi, 2)
        complex.new(_sinr * trig.cosh(this.im) / _denom, -math.cos(this.re) * _sinhi / _denom)

// usage:
if show_1
    logger.queue_one(str.format('csc({0}): {1}', strof_a, complex.to_string(csc(input_a), '#.##')))

// }
// asin  () {

// @function Trigonometric Arc Sine of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method asin (TMath.complex this) =>
    TMath.complex _imo = complex.imaginary_one()
    TMath.complex _nimo = complex_op.negative(_imo)
    TMath.complex _one = complex.one()
    if this.im > 0.0 or (this.im == 0.0 and this.re < 0.0)
        TMath.complex _ncomplex = complex_op.negative(this)
        TMath.complex _sq = complex_ex.square_root(complex_op.subtract(_one, complex_ex.square(_ncomplex)))
        TMath.complex _ln = complex_ex.natural_logarithm(complex_op.add(_sq, complex_op.multiply(_imo, _ncomplex)))
        complex_op.negative(complex_op.multiply(_nimo, _ln))
    else
        TMath.complex _sq = complex_ex.square_root(complex_op.subtract(_one, complex_ex.square(this)))
        TMath.complex _ln = complex_ex.natural_logarithm(complex_op.add(_sq, complex_op.multiply(_imo, this)))
        complex_op.multiply(_nimo, _ln)

// usage:
if show_3
    logger.queue_one(str.format('asin({0}): {1}', strof_a, complex.to_string(asin(input_a), '#.##')))

// }
// acos  () {

// @function Trigonometric Arc Cosine of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method acos (TMath.complex this) =>
    TMath.complex _imo = complex.imaginary_one()
    TMath.complex _nimo = complex_op.negative(complex.imaginary_one())
    TMath.complex _one = complex.one()
    if this.im < 0.0 or (this.im == 0.0 and this.re > 0.0)
        TMath.complex _ncomplex = complex_op.negative(this)
        TMath.complex _1_sqrt = complex_ex.square_root(complex_op.subtract(_one, complex_ex.square(_ncomplex)))
        complex_op.subtract(complex.new(math.pi, 0.0), complex_op.multiply(_nimo, complex_ex.natural_logarithm(complex_op.add(_ncomplex, complex_op.multiply(_imo, _1_sqrt)))))
    else
        TMath.complex _1_sqrt = complex_ex.square_root(complex_op.subtract(_one, complex_ex.square(this)))
        complex_op.multiply(_nimo, complex_ex.natural_logarithm(complex_op.add(this, complex_op.multiply(_imo, _1_sqrt))))

// usage:
if show_3
    logger.queue_one(str.format('acos({0}): {1}', strof_a, complex.to_string(acos(input_a), '#.##')))

// }
// atan  () {

// @function Trigonometric Arc Tangent of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method atan (TMath.complex this) =>
    TMath.complex _iz = complex.new(-this.im, this.re)
    TMath.complex _one = complex.one()
    complex_op.multiply(complex.new(0.0, 0.5), complex_op.subtract(complex_ex.natural_logarithm(complex_op.subtract(_one, _iz)), complex_ex.natural_logarithm(complex_op.add(_one, _iz))))

// usage:
if show_3
    logger.queue_one(str.format('atan({0}): {1}', strof_a, complex.to_string(atan(input_a), '#.##')))

// }
// acot  () {

// @function Trigonometric Arc Cotangent of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method acot (TMath.complex this) =>
    if complex.is_zero(this)
        complex.new(constants.PiOver2(), 0.0)
    else
        TMath.complex _one = complex.one()
        TMath.complex _half = complex.new(0.5, 0.0)
        TMath.complex _imo = complex.imaginary_one()
        TMath.complex _inv = complex_op.divide(_imo, this)
        complex_op.multiply(complex_op.multiply(_imo, _half), complex_op.subtract(complex_ex.natural_logarithm(complex_op.subtract(_one, _inv)), complex_ex.natural_logarithm(complex_op.add(_one, _inv))))

// usage:
if show_3
    logger.queue_one(str.format('acot({0}): {1}', strof_a, complex.to_string(acot(input_a), '#.##')))

// }
// asec  () {

// @function Trigonometric Arc Secant of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method asec (TMath.complex this) =>
    TMath.complex _one = complex.one()
    TMath.complex _imo = complex.imaginary_one()
    TMath.complex _inv = complex_op.divide(_one, this)
    TMath.complex _nimo = complex_op.negative(_imo)
    TMath.complex _imosqrt = complex_op.multiply(_imo, complex_ex.square_root(complex_op.subtract(_one, complex_ex.square(_inv))))
    complex_op.multiply(_nimo, complex_ex.natural_logarithm(complex_op.add(_inv, _imosqrt)))

// usage:
if show_3
    logger.queue_one(str.format('asec({0}): {1}', strof_a, complex.to_string(asec(input_a), '#.##')))

// }
// acsc  () {

// @function Trigonometric Arc Cosecant of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method acsc (TMath.complex this) =>
    TMath.complex _one = complex.one()
    TMath.complex _imo = complex.imaginary_one()
    TMath.complex _inv = complex_op.divide(_one, this)
    TMath.complex _nimo = complex_op.negative(_imo)
    TMath.complex _sqrt = complex_ex.square_root(complex_op.subtract(_one, complex_ex.square(_inv)))
    complex_op.multiply(_nimo, complex_ex.natural_logarithm(complex_op.add(complex_op.multiply(_imo, _inv), _sqrt)))

// usage:
if show_3
    logger.queue_one(str.format('acsc({0}): {1}', strof_a, complex.to_string(acsc(input_a), '#.##')))

// }
// asinh () {

// @function Hyperbolic Arc Sine of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method asinh (TMath.complex this) =>
    TMath.complex _one = complex.one()
    TMath.complex _sq = complex_ex.square_root(complex_op.add(complex_ex.square(this), _one))
    complex_ex.natural_logarithm(complex_op.add(this, _sq))

// usage:
if show_4
    logger.queue_one(str.format('asinh({0}): {1}', strof_a, complex.to_string(asinh(input_a), '#.##')))

// }
// acosh () {

// @function Hyperbolic Arc Cosine of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method acosh (TMath.complex this) =>
    TMath.complex _one = complex.one()
    TMath.complex _sqn = complex_ex.square_root(complex_op.subtract(this, _one))
    TMath.complex _sqp = complex_ex.square_root(complex_op.add(this, _one))
    TMath.complex _sq = complex_op.multiply(_sqn, _sqp)
    complex_ex.natural_logarithm(complex_op.add(this, _sq))

// usage:
if show_4
    logger.queue_one(str.format('acosh({0}): {1}', strof_a, complex.to_string(acosh(input_a), '#.##')))

// }
// atanh () {

// @function Hyperbolic Arc Tangent of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method atanh (TMath.complex this) =>
    TMath.complex _one = complex.one()
    TMath.complex _half = complex.new(0.5, 0.0)
    TMath.complex _lnn = complex_ex.natural_logarithm(complex_op.subtract(_one, this))
    TMath.complex _lnp = complex_ex.natural_logarithm(complex_op.add(_one, this))
    TMath.complex _ln = complex_op.subtract(_lnp, _lnn)
    complex_op.multiply(_half, _ln)

// usage:
if show_4
    logger.queue_one(str.format('atanh({0}): {1}', strof_a, complex.to_string(atanh(input_a), '#.##')))

// }
// acoth () {

// @function Hyperbolic Arc Cotangent of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method acoth (TMath.complex this) =>
    TMath.complex _one = complex.one()
    TMath.complex _half = complex.new(0.5, 0.0)
    TMath.complex _inv = complex_op.divide(_one, this)
    TMath.complex _lnn = complex_ex.natural_logarithm(complex_op.subtract(_one, _inv))
    TMath.complex _lnp = complex_ex.natural_logarithm(complex_op.add(_one, _inv))
    TMath.complex _ln = complex_op.subtract(_lnp, _lnn)
    complex_op.multiply(_half, _ln)

// usage:
if show_4
    logger.queue_one(str.format('acoth({0}): {1}', strof_a, complex.to_string(acoth(input_a), '#.##')))

// }
// asech () {

// @function Hyperbolic Arc Secant of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method asech (TMath.complex this) =>
    TMath.complex _one = complex.one()
    TMath.complex _inv = complex_op.divide(_one, this)
    TMath.complex _sqn = complex_ex.square_root(complex_op.subtract(_inv, _one))
    TMath.complex _sqp = complex_ex.square_root(complex_op.add(_inv, _one))
    TMath.complex _sq = complex_op.multiply(_sqn, _sqp)
    complex_ex.natural_logarithm(complex_op.add(_inv, _sq))

// usage:
if show_4
    logger.queue_one(str.format('asech({0}): {1}', strof_a, complex.to_string(asech(input_a), '#.##')))

// }
// acsch () {

// @function Hyperbolic Arc Cosecant of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method acsch (TMath.complex this) =>
    TMath.complex _one = complex.one()
    TMath.complex _inv = complex_op.divide(_one, this)
    TMath.complex _sq = complex_ex.square_root(complex_op.add(complex_ex.square(_inv), _one))
    complex_ex.natural_logarithm(complex_op.add(_inv, _sq))

// usage:
if show_4
    logger.queue_one(str.format('acsch({0}): {1}', strof_a, complex.to_string(acsch(input_a), '#.##')))
// remarks:
// }

//#endregion

logger.update()