#ifndef _fencer_input_axis_h
#define _fencer_input_axis_h

#include "typeclass_helpers.h"
#include "vmath.h"
#include "drop.h"
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_keyboard.h>
#include <SDL2/SDL_gamecontroller.h>

struct InputDevice;

typedef enum InputEventType {
    InputEvent_Vector = 0,
    InputEvent_Float = 1,
    InputEvent_Int = 2,
    InputEvent_Bool = 3,
} InputEventType;

typedef struct InputEvent {
    InputEventType type;
    union {
        Vector as_vector;
        float as_float;
        int as_int;
        int as_bool;
    };
} InputEvent;

typedef struct {
    int (*const is_changed_by)(void*, SDL_Event);
    struct InputEvent (*const evaluate)(void*, SDL_Event);
    void (*const set_device)(void*, struct InputDevice*);
} IInputAxis;

typedef struct {
    void* data;
    IInputAxis const* tc;
    IDrop const* drop;
} InputAxis;

#define impl_InputAxis_for(T, is_changed_by_f, evaluate_f, set_device_f)\
static inline InputAxis T##_as_InputAxis(T* x) {\
    TC_FN_TYPECHECK(int, is_changed_by_f, T*, SDL_Event);\
    TC_FN_TYPECHECK(struct InputEvent, evaluate_f, T*, SDL_Event);\
    TC_FN_TYPECHECK(void, set_device_f, T*, struct InputDevice*);\
    static IInputAxis const tc = {\
        .is_changed_by = (int(*const)(void*,SDL_Event))         is_changed_by_f,\
        .evaluate = (struct InputEvent(*const)(void*,SDL_Event))            evaluate_f,\
        .set_device = (void(*const)(void*,struct InputDevice*)) set_device_f\
    };\
    IDrop const* drop = T##_as_Drop(x).tc;\
    return (InputAxis){.data=x, .tc=&tc, .drop = drop};\
}

typedef struct KeyBind {
    int state;
    SDL_Scancode scancode;
    struct InputDevice* device;
} KeyBind;

extern KeyBind* keybind_new(SDL_Scancode bind);
extern int keybind_is_changed_by(KeyBind* self, SDL_Event event);
extern struct InputEvent keybind_evaluate(KeyBind* self, SDL_Event);
extern void keybind_set_device(KeyBind* self, struct InputDevice* device);

impl_default_Drop_for(KeyBind)
impl_InputAxis_for(KeyBind,
    keybind_is_changed_by,
    keybind_evaluate,
    keybind_set_device
)

typedef struct ControllerAxis {
    struct InputDevice* device;
    int axis;
} ControllerAxis;

extern ControllerAxis* controlleraxis_new(int axis);
extern int controlleraxis_is_changed_by(ControllerAxis* self, SDL_Event event);
extern struct InputEvent controlleraxis_evaluate(ControllerAxis* self, SDL_Event event);
extern void controlleraxis_set_device(ControllerAxis* self, struct InputDevice* device);

impl_default_Drop_for(ControllerAxis)
impl_InputAxis_for(ControllerAxis,
    controlleraxis_is_changed_by,
    controlleraxis_evaluate,
    controlleraxis_set_device
)

typedef struct ControllerButton {
    struct InputDevice* device;
    int button;
} ControllerButton;

extern ControllerButton* controllerbutton_new(int button);
extern int controllerbutton_is_changed_by(ControllerButton* self, SDL_Event event);
extern struct InputEvent controllerbutton_evaluate(ControllerButton* self, SDL_Event event);
extern void controllerbutton_set_device(ControllerButton* self, struct InputDevice* device);

impl_default_Drop_for(ControllerButton)
impl_InputAxis_for(ControllerButton,
    controllerbutton_is_changed_by,
    controllerbutton_evaluate,
    controllerbutton_set_device
)

typedef struct CompositeAxis1D {
    InputAxis left;
    InputAxis right;
    InputEventType type;
} CompositeAxis1D;

extern CompositeAxis1D* compositeaxis1d_new(InputAxis left, InputAxis right, InputEventType type);
extern int compositeaxis1d_is_changed_by(CompositeAxis1D* self, SDL_Event event);
extern struct InputEvent compositeaxis1d_evaluate(CompositeAxis1D* self, SDL_Event event);
extern void compositeaxis1d_set_device(CompositeAxis1D* self, struct InputDevice* device);
extern void compositeaxis1d_drop(CompositeAxis1D* self);

impl_Drop_for(CompositeAxis1D,
    compositeaxis1d_drop
)
impl_InputAxis_for(CompositeAxis1D,
    compositeaxis1d_is_changed_by,
    compositeaxis1d_evaluate,
    compositeaxis1d_set_device
)

#endif // !_fencer_input_axis_h