# distutils: sources = src/finesse/tinyexpr.c
# distutils: include_dirs = src/finesse/

"""Compiled symbolic expressions used internally via parameters and element workspaces.

This sub-module only exposes C code so can only be used by other Cython
extensions. The symbolic expression struct ``cy_expr`` is used in workspaces
(see :class:`.ElementWorkspace`) and parameter code (see :class:`.Parameter`)
for quick evaluation of changing symbolic expressions.

The ``cy_expr`` struct, and associated functions, are wrappers around the C
based math parsing and evaluation engine, `tinyexpr <https://github.com/codeplea/tinyexpr>`_.
"""

from libc.stdlib cimport malloc, free


cdef cy_expr* cy_expr_new() nogil:
    cdef cy_expr* ce_p = <cy_expr*> malloc(sizeof(cy_expr))
    ce_p.expr = NULL
    ce_p.variables = NULL
    return ce_p

cdef int cy_expr_init(cy_expr* ex, object operation) except -1:
    """Initialise the te_expr and te_variable type fields using the
    operation object (should be an instance of Operation)."""
    cdef str op_str = str(operation)
    cdef list params = operation.parameters() # get the dependent parameter-refs
    cdef int Nparams = len(params)

    ex.variables = <te_variable*> malloc(Nparams * sizeof(te_variable))
    if not ex.variables:
        raise MemoryError()

    cdef Py_ssize_t i
    cdef Parameter p
    for i in range(Nparams):
        pref = params[i] # the ParameterRef object

        # Replace the corresponding parameter name in the operation string with
        # the tinyexpr compatible name version (see ParameterRef.cyexpr_name)
        op_str = op_str.replace(pref.name, pref.cyexpr_name.decode())
        # Also need to replace "quantity**n" with "quantity^n" as te expects powers in this form
        op_str = op_str.replace("**", "^")

        p = pref.parameter
        ex.variables[i] = te_variable(pref.cyexpr_name, &p.__cvalue, 0, NULL)

    cdef int err # position of parsing error in operation expression
    # Make byte str of operation expression, and store it in
    # cy_expr instance for info / debugging purposes later
    byte_op_str = op_str.encode("UTF-8")
    ex.expression = byte_op_str
    ex.expr = te_compile(byte_op_str, ex.variables, Nparams, &err)

    if not ex.expr:
        error_loc_str = "    {m: <{pos}}^".format(m='', pos=str(err - 1))
        raise RuntimeError(
            "Bug encountered! Internal cy_expr parsing error on:\n"
           f"    {op_str}\n" +
            error_loc_str +
           f"\nError near here"
        )

    return 0

cdef void cy_expr_free(cy_expr* ex) nogil:
    if ex != NULL:
        if ex.expr != NULL:
            te_free(ex.expr)
        if ex.variables != NULL:
            free(ex.variables)

        free(ex)
        ex = NULL

cdef double cy_expr_eval(cy_expr* ex) nogil:
    """Evaluate the cythonised symbolic expression."""
    return te_eval(ex.expr)
