import textwrap
# Functions for the automatic logging of stuff
# https://stackoverflow.com/questions/41954269/create-c-function-pointers-structure-in-python
# https://stackabuse.com/enhancing-python-with-custom-c-extensions/ Read
# https://stackoverflow.com/questions/49941617/runtime-generation-and-compilation-of-cython-functions
# https://realpython.com/cpython-source-code-guide/
# https://docs.python.org/3.6/c-api/index.html
# https://stackoverflow.com/questions/6626167/build-a-pyobject-from-a-c-function
# https://docs.python.org/3.6/extending/newtypes_tutorial.html?highlight=pointer
# https://realpython.com/cpython-source-code-guide/
# https://diseraluca.github.io/blog/2019/03/21/wetting-feet-with-python-c-api
# https://docs.python.org/3/c-api/function.html


# See example_perl.pm autologging
def autogen_C_logging_code(logging_dict):
    """
    Function that autogenerates PRINTF statements for binaryc

    Input:
        dictionary where the key is the header of that logging line and items which are lists of parameters that will be put in that logging line

        example: {'MY_STELLAR_DATA': 
        [
            'model.time',
            'star[0].mass',
            'model.probability',
            'model.dt'
        ']}
    """

    # Check if the input is of the correct form 
    if not type(logging_dict)==dict:
        print("Error: please use a dictionary as input")
        return None

    code = ''
    # Loop over dict keys
    for key in logging_dict:
        logging_dict_entry = logging_dict[key]

        # Check if item is of correct type:
        if type(logging_dict_entry)==list:

            # Construct print statement
            code += 'PRINTF("{}'.format(key)
            code += ' {}'.format('%g '*len(logging_dict_entry))
            code = code.strip()
            code += '\n"'

            # Add format keys
            for param in logging_dict_entry:
                code += ',((double)stardata->{})'.format(param)
            code += ');\n'

        else:
            print('Error: please use a list for the list of parameters that you want to have logged')
    code = code.strip()
    # print("MADE AUTO CODE\n\n{}\n\n{}\n\n{}\n".format('*'*60, repr(code), '*'*60))

    return code


autogen_C_logging_code(
    {
        'MY_STELLAR_DATA': ['model.time', 'star[0].mass'], 
        'my_sss2': ['model.time', 'star[1].mass']
    }
)

####################################################################################

# see example_perl.pm binary_c_log_code

def binary_c_log_code(code):
    """
    Function to construct the code to construct the custom logging function
    """
    custom_logging_function_string = """
        #pragma push_macro(\"MAX\")
        #pragma push_macro(\"MIN\")
        #undef MAX
        #undef MIN
        #include \"binary_c.h\"

        void custom_output_function(SV * x);
        SV * custom_output_function_pointer(void);

        SV * custom_output_function_pointer()
        {{
            /*
             * use PTR2UV to convert the function pointer 
             * &custom_output_function to an unsigned int,
             * which is then converted to a Perl SV
             */
            return (SV*)newSVuv(PTR2UV(custom_output_function));
        }}

        void custom_output_function(SV * x)
        {{
            struct stardata_t * stardata = (struct stardata_t *)x;
            {code};
        }}

        #undef MAX 
        #undef MIN
        #pragma pop_macro(\"MIN\")
        #pragma pop_macro(\"MAX\")
    """.format(code=code)

    print(textwrap.dedent(custom_logging_function_string))
    # return custom_logging_function_string


code = autogen_C_logging_code(
    {
        'MY_STELLAR_DATA': ['model.time', 'star[0].mass'], 
        'my_sss2': ['model.time', 'star[1].mass']
    }
)

binary_c_log_code(code)