#include "input_axis.h"
#include "debug.h"
#include "input.h"

KeyBind* keybind_new(SDL_Scancode key) {
    KeyBind* self = malloc(sizeof(KeyBind));
    ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for KeyBind instance");
    *self = (KeyBind) {
        .device = NULL,
        .scancode = key,
        .state = 0
    };
    return self;
}

int keybind_is_changed_by(KeyBind* self, SDL_Event event) {
    return self->device->type == InputDevice_KBM
        && (event.type == SDL_KEYUP || event.type == SDL_KEYDOWN)
        && event.key.keysym.scancode == self->scancode
        && event.key.repeat == 0;
}

InputEvent keybind_evaluate(KeyBind* self, SDL_Event event) {
    return (InputEvent) {
        .type = InputEvent_Bool,
        .as_bool = self->device->keyboard.state[self->scancode]
    };
}

void keybind_set_device(KeyBind* self, InputDevice* device) {
    self->device = device;
}

ControllerAxis* controlleraxis_new(int axis) {
    ControllerAxis* self = malloc(sizeof(ControllerAxis));
    ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for ControllerAxis instance");
    *self = (ControllerAxis){
        .axis = axis,
        .device = NULL
    };
    return self;
}

int controlleraxis_is_changed_by(ControllerAxis* self, SDL_Event event) {
    int r = self->device->type == InputDevice_Gamepad
        && event.type == SDL_CONTROLLERAXISMOTION
        && event.caxis.which == self->device->gamepad.id
        && event.caxis.axis == self->axis;
    return r;
}

InputEvent controlleraxis_evaluate(ControllerAxis* self, SDL_Event event) {
    float result = (float)event.caxis.value / 32767.0;
    return (InputEvent) {
        .type = InputEvent_Float,
        .as_float = result
    };
}

void controlleraxis_set_device(ControllerAxis* self, InputDevice* device) {
    self->device = device;
}

ControllerButton* controllerbutton_new(int button) {
    ControllerButton* self = malloc(sizeof(ControllerButton));
    ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for ControllerButton instance");
    *self = (ControllerButton) {
        .button = button,
        .device = NULL
    };
    return self;
}

int controllerbutton_is_changed_by(ControllerButton* self, SDL_Event event) {
    return self->device->type == InputDevice_Gamepad
        && event.cbutton.which == self->device->gamepad.id
        && (event.type == SDL_CONTROLLERBUTTONUP || event.type == SDL_CONTROLLERBUTTONDOWN)
        && event.cbutton.button == self->button;
}

InputEvent controllerbutton_evaluate(ControllerButton* self, SDL_Event event) {
    return (InputEvent) {
        .type = InputEvent_Bool,
        .as_bool = event.cbutton.state
    };
}

void controllerbutton_set_device(ControllerButton* self, InputDevice* device) {
    self->device = device;
}

CompositeAxis1D* compositeaxis1d_new(InputAxis left, InputAxis right, InputEventType type) {
    CompositeAxis1D* self = malloc(sizeof(CompositeAxis1D));
    ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for CompositeAxis1D instance");
    *self = (CompositeAxis1D) {
        .left = left,
        .right = right,
        .type = type,
    };
    return self;
}

int compositeaxis1d_is_changed_by(CompositeAxis1D* self, SDL_Event event) {
    return self->left.tc->is_changed_by(self->left.data, event)
        || self->right.tc->is_changed_by(self->right.data, event);
}

static inline
InputEvent _internal_event_to_type(InputEventType type, InputEvent event) {
    if(type == InputEvent_Vector && event.type == InputEvent_Vector)
        return event;

    float as_float = 0;
    switch(event.type) {
    case InputEvent_Vector:
        LOG_ERROR("No (1)");
        break;
    case InputEvent_Bool:
        as_float = event.as_bool;
        break;
    case InputEvent_Float:
        as_float = event.as_float;
        break;
    case InputEvent_Int:
        as_float = event.as_int;
        break;
    }
    event.type = type;
    switch(type) {
    case InputEvent_Int:
        event.as_int = round(as_float);
        return event;
    case InputEvent_Float:
        event.as_float = as_float;
        return event;
    case InputEvent_Bool:
        event.as_bool = as_float != 0;
        return event;
    case InputEvent_Vector:
        LOG_ERROR("No (2)");
        return event;
    }
    return event;
}

InputEvent compositeaxis1d_evaluate(CompositeAxis1D* self, SDL_Event event) {
    InputEvent left_result = self->left.tc->evaluate(self->left.data, event);
    InputEvent right_result = self->right.tc->evaluate(self->right.data, event);

    ASSERT_RETURN(self->type != InputEvent_Vector
                || (left_result.type == InputEvent_Vector && right_result.type == InputEvent_Vector),
                ((InputEvent){.type = 0, .as_bool = 0}), "Composite axis can only output vector if both composite elements output vector.");

    InputEvent final = {
        .type = self->type
    };
    // if both outputs are booleans, they will be combined to an int
    if(final.type == InputEvent_Bool)
        final.type = InputEvent_Int;

    left_result = _internal_event_to_type(final.type, left_result);
    right_result = _internal_event_to_type(final.type, right_result);

    switch(final.type) {
    default:
        LOG_ERROR("Invalid composite input result");
        final.type = InputEvent_Bool;
        final.as_bool = 0;
        return final;
    case InputEvent_Int:
        final.as_int = right_result.as_int - left_result.as_int;
        return final;
    case InputEvent_Float:
        final.as_float = right_result.as_float - left_result.as_float;
        return final;
    case InputEvent_Vector:
        final.as_vector = vsubf(right_result.as_vector, left_result.as_vector);
        return final;
    }
}

void compositeaxis1d_set_device(CompositeAxis1D* self, InputDevice* device) {
    self->left.tc->set_device(self->left.data, device);
    self->right.tc->set_device(self->right.data, device);
}

void compositeaxis1d_drop(CompositeAxis1D* self) {
    self->left.drop->drop(self->left.data);
    self->right.drop->drop(self->right.data);
    free(self);
}
