#pragma once

#include <vector>
#include <functional>

#include "c_common.hpp"


typedef double (*EventFunc)(double, double*, char*);

// !!!
// Comment the following 
//typedef EventFunc PyEventMethod;
// Comment this import if working outside of CyRK and you just want the program to compile and run for testing/developing the C++ only code.
// "pysolver_cyhook_api.h" is generated by Cython when building the CyRK project.
// It is based off of the "pysolver_cyhook.pyx" file. 
// Read more about how C++ can call python functions here:
// https://stackoverflow.com/questions/10126668/can-i-override-a-c-virtual-function-within-python-with-cython
// and here: https://github.com/dashesy/pyavfcam/blob/master/src/avf.pyx#L27
#include <Python.h>
#include "pysolver_cyhook_api.h"

struct _object;
typedef _object PyObject;

class Event
{
public:
    std::function<double(Event*, double, double*, char*)> check;

    EventFunc cy_func     = nullptr;
    // By default events are always recorded with no limit and no termination.
    size_t max_allowed    = MAX_SIZET_SIZE;
    size_t current_count  = 0;
    size_t pyevent_index  = MAX_SIZET_SIZE;
    CyrkErrorCodes status = CyrkErrorCodes::UNINITIALIZED_CLASS;
    double last_root      = 0.0;
    int direction         = 0;
    bool is_active        = false;
    bool initialized      = false;
    bool use_pysolver     = false;

    // PySolver Specifics
    PyObject* cython_extension_class_instance = nullptr;  // Pointer to Python instance which holds the diffeq.
    PyEventMethod py_check_method             = nullptr;  // Said python pyevent_check method

    std::vector<double> y_at_root_vec = std::vector<double>();

    Event();
    Event(
        EventFunc event_func,
        size_t max_allowed_ = MAX_SIZET_SIZE,
        int direction_ = 0);
    virtual ~Event();

    CyrkErrorCodes setup(
        EventFunc event_func,
        size_t max_allowed_ = MAX_SIZET_SIZE,
        int direction_ = 0);
    double cy_check(
        double t,
        double* y_ptr,
        char* arg_ptr) noexcept;
    double py_check(
        double t,
        double* y_ptr,
        char* arg_ptr);
    CyrkErrorCodes set_cython_extension_instance(
        PyObject* cython_extension_class_instance,
        PyEventMethod pyevent_method);

protected:
};

typedef std::function<double(Event*, double, double*, char*)> EventFuncWithInst;
