MathComplexEvaluate
// 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 TODO: add library description here
library(title='MathComplexEvaluate')

//#region   -> Imports:

import RicardoSantos/DebugConsole/11 as console
logger = console.Console.new().init()

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
import RicardoSantos/MathComplexArray/2 as complex_ar

import RicardoSantos/StringEvaluation/1 as str_eval

//#endregion



//#region   -> Helper Constant and Functions:

string RGXop          = '[-+*/%^]?'
string RGXfloat       = '((\\.\\d+)|(\\d*\\.\\d+)|(\\d+\\.?))'
string RGXreal        = '([-+]?'+RGXfloat+'(?:[eE^][-+]?'+RGXfloat+')?)'
string RGXrealcomplex = '('+RGXreal+'[ijIJ]?)'
string RGXcomplex     = RGXop+'('+'('+RGXreal+'[-+]'+RGXreal+'[ijIJ])'+'|'+RGXrealcomplex+')'

// is_op () {

// @function Check if char is a operator.
// @param char string, 1 character string.
// @returns bool.
is_op (string char) =>
    switch char
        '+' => true
        '-' => true
        '*' => true
        '/' => true
        '^' => true
        '%' => true
        => false

// }
// operator () {

// @function operation between left and right values.
// @param op string, operator string character.
// @param left float, left value of operation.
// @param right float, right value of operation.
operator (string op, TMath.complex left, TMath.complex right) =>
    switch op
        '+' => complex_op.add(left, right)
        '-' => complex_op.subtract(left, right)
        '*' => complex_op.multiply(left, right)
        '/' => complex_op.divide(left, right)
        '^' => complex_ex.power(left, right)
        '%' => complex_op.divide(left, right) // modulo of complex number is sqrt(a^2 + b^2) or abs(a+bi) 
        => complex.nan()

// }
// operator_precedence () {

// @function level of precedence of operator.
// @param op string, operator 1 char string.
// @returns int.
operator_precedence (string op) =>
    switch op
        '+' => 2
        '-' => 2
        '*' => 3
        '/' => 3
        '^' => 4
        '%' => 3
        => 0

// }

//#endregion

// eval () {
// reference:
//     https://rosettacode.org/wiki/Parsing/RPN_calculator_algorithm

// @function evaluate a string with references to a array of arguments.
//| @param expression string, arithmetic operations with references to indices in arguments, ex:"0+1*0+2*2+3" arguments[1, 2, 3]
//| @param arguments  float array, arguments.
//| @returns Complex Number, solution.
export eval (string expression, array<TMath.complex> arguments) =>
    int _size_a = array.size(id=arguments)
    if _size_a < 1
        runtime.error('MathComplexEvaluate -> eval(): "arguments" is empty.')
        complex.nan()
    else
        string[] _tokens = str_eval.generate_rpn(str_eval.cleanup(expression))
        array<TMath.complex> _args = array.copy(arguments)
        int _size_t = array.size(id=_tokens)
        if _size_t < 1
            runtime.error('MathComplexEvaluate -> eval(): "expression" is empty.')
            complex.nan()
        else
            // calculate solution:
            TMath.complex _solution = complex.nan()
            _stack = array.new_int()
            for _i = 0 to _size_t-1
                string _token = array.get(id=_tokens, index=_i)
                float _number = str.tonumber(_token)
                if not na(_number)//
                    array.push(id=_stack, value=int(_number))
                else
                    if array.size(id=_stack) > 1
                    //     runtime.error('StringEvaluation -> generate_rpn(): insufficient operands!!.\n' + str.tostring(_stack))
                    // else
                        o2 = array.pop(id=_stack)
                        o1 = array.pop(id=_stack)
                        if is_op(_token)
                            TMath.complex _op = operator(_token, _args.get(o1), _args.get(o2))
                            _args.push(_op)
                            array.push(id=_stack, value=int(array.size(_args)/2)-1)
                        else
                            runtime.error('MathComplexEvaluate -> eval(): unrecognized operator: (' + _token + ' ).')
            _solution := _args.pop()
            _solution

// usage:
array<TMath.complex> args = array.new<TMath.complex>(1, complex.from(input.string('3.141+1.618i')))
for _i = 0 to 5
    args.push(complex.new(math.random(), math.random()))
string ev1 = input.string('((0+ 0 *2/1-3)%0)^4')
string ev2 = input.string('((0+ 0 *2/1-3)+0)^4')
logger.queue_one(str.format('arguments: {0}', complex_ar.to_string(args, '#.##')))
logger.queue_one(str.format('expression using idx: {0} = {1}', ev1, complex.to_string(eval(ev1, args), '#.##')))
logger.queue_one(str.format('expression using idx: {0} = {1}', ev2, complex.to_string(eval(ev2, args), '#.##')))

// }
// parse () {

// @function Parse a string expression that may contain numbers and complex numbers.
// @param expression string . Expression to evaluate, ex:. `"2i*(3+2-3J)-2.0e3+1.0e-15i"`.
// @returns Complex number. Solution.
export parse (string expression) =>
    string _exp = str.replace_all(expression, ' ', '')
    array<string> _tokens = array.new<string>()
    array<string> _unique = array.new<string>()
    for _i = 0 to 20
        string _t = str.match(_exp, RGXcomplex)
        if _t == 'na' or _t == ''
            break
        else
            // find the tokens that match a number or complex number expression:
            string _prefix = str.substring(_t, 0, 1)
            string _op = is_op(_prefix) ? _prefix : ''
            string _suffix = is_op(_prefix) ? str.substring(_t, 1, str.length(_t)) : str.substring(_t, 0, str.length(_t))
            _tokens.push(_suffix)
            _exp := str.replace(_exp, _t, _op+'na', 0)
            // populate unique id list of tokens:
            int _idx = array.indexof(_unique, _suffix)
            if _idx < 0
                _unique.push(_suffix)
    // replace `na` placeholders with token array indices,
    // and convert tokens to complex numbers:
    array<TMath.complex> _args = array.new<TMath.complex>()
    for _token in _tokens
        int _idx = array.indexof(_unique, _token)
        _exp := str.replace(_exp, 'na', str.tostring(_idx, '#'), 0)
        _args.push(complex.from(_token))
    eval(_exp, _args)

string str = input.text_area('1i*2+2i/2 + 2-1.0+3.0i')
logger.queue_one(complex.to_string(parse(str)))

// }

logger.update()

// // formula:
// 1.0+1.0 1.0e1+1.0e1 .0+.0
// +1+1-2+1-1+2-2+1+1+1+1+1*1*1*/1/1+1*2-1/1-+*
// -1*1e1i/1.0+1i+1.0*1-.2e-1i
// 1i*2+2i/2+2-1.0+3.0i
// ..0 1..0 1...0...0.0.000.0...0...00.....
// . ++-*
// real         : 0  -0  .0  -.0  -0.0  0e0  -0e0  -0e-0  0.0e0.0  -0.0e-0.0  00  -00  00e00  -00e-00  00.00e00.00  -00.00e-00.00
// real complex : 0i -0i .0i -.0i -0.0i 0e0i -0e0i -0e-0i 0.0e0.0i -0.0e-0.0i 00i -00i 00e00i -00e-00i 00.00e00.00i -00.00e-00.00i
// complex      : 0+0i -0-0i .0+.0i -.0-.0i -0.0-0.0i 0e0+0e0i -0e0-0e0i -0e-0-0e-0i 0.0e0.0+0.0e0.0i -0.0e-0.0-0.0e-0.0i 
//                00+00i -00-00i 00e00+00e00i -00e-00-00e-00i 00.00e00.00+00.00e00.00i -00.00e-00.00-00.00e-00.00i

// last = '(([-+]?((\.\d+)|(\d*\.\d+)|(\d+\.?))(?:[eE^][-+]?((\.\d+)|(\d*\.\d+)|(\d+\.?)))?)[-+]([-+]?((\.\d+)|(\d*\.\d+)|(\d+\.?))(?:[eE^][-+]?((\.\d+)|(\d*\.\d+)|(\d+\.?)))?)[i])|([-+]?((\.\d+)|(\d*\.\d+)|(\d+\.?))(?:[eE^][-+]?((\.\d+)|(\d*\.\d+)|(\d+\.?)))?[i]?)'
// wOPs = '[-+*/%^]?((([-+]?((\.\d+)|(\d*\.\d+)|(\d+\.?))(?:[eE^][-+]?((\.\d+)|(\d*\.\d+)|(\d+\.?)))?)[-+]([-+]?((\.\d+)|(\d*\.\d+)|(\d+\.?))(?:[eE^][-+]?((\.\d+)|(\d*\.\d+)|(\d+\.?)))?)[ijIJ])|([-+]?((\.\d+)|(\d*\.\d+)|(\d+\.?))(?:[eE^][-+]?((\.\d+)|(\d*\.\d+)|(\d+\.?)))?[ijIJ]?))'
