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

static List _devices;

static inline
void _internal_open_keyboard() {
    InputDevice* keyboard = malloc(sizeof(InputDevice));
    ASSERT_RETURN(keyboard != NULL, , "Failed to allocate space for keyboard input device");
    *keyboard = (InputDevice){
        .listeners = NULL,
        .type = InputDevice_KBM,
        .id = -1,
        .keyboard = {
            .state = SDL_GetKeyboardState(NULL)
        }
    };
    list_add(&_devices, &keyboard);
    LOG_INFO("registered keyboard %d", keyboard->id);
}

static inline
void _internal_open_controller(int id) {
    InputDevice* device = malloc(sizeof(InputDevice));
    ASSERT_RETURN(device != NULL, , "Failed to allocate space for gamecontroller input device");
    *device = (InputDevice) {
        .listeners = NULL,
        .type = InputDevice_Gamepad,
        .id = id,
        .gamepad = {
            .id = id,
            .controller = SDL_GameControllerOpen(id)
        }
    };

    list_add(&_devices, &device);
    LOG_INFO("registered game controller %d", device->id);
}

void input_init() {
    _devices = list_from_type(InputDevice*);
    // open the keyboard by default
    _internal_open_keyboard();

    // open any controllers already available
    const int joystick_count = SDL_NumJoysticks();
    for(int i = 0; i < joystick_count; ++i) {
       _internal_open_controller(i);
    }
}

void input_clean() {
    list_foreach(InputDevice**, _device, &_devices) {
        InputDevice* device = *_device;
        if(device->type == InputDevice_Gamepad)
            SDL_GameControllerClose(device->gamepad.controller);
        free(device);
    }
    list_empty(&_devices);
}

void input_handle_event(SDL_Event event) {
    list_foreach(InputDevice**, _device, &_devices) {
        InputDevice* device = *_device;

        if(device->listeners != NULL) {
            list_foreach(InputListener*, listener, device->listeners) {
                InputAxis axis = listener->axis;

                if(axis.tc->is_changed_by(axis.data, event)) {
                    InputEvent value = axis.tc->evaluate(axis.data, event);
                    listener->fn(listener->self, value);
                }
            }
        }
    }
}

void input_device_set_listeners(InputDevice* self, List* listeners) {
    if(self->listeners != NULL) {
        list_foreach(InputListener*, listener, self->listeners) {
            listener->axis.tc->set_device(listener->axis.data,  NULL);
        }
    }

    self->listeners = listeners;

    if(listeners == NULL)
        return;

    list_foreach(InputListener*, listener, listeners)
        listener->axis.tc->set_device(listener->axis.data, self);
    LOG_INFO("Set listeners for device %d to %p", self->id, listeners);
}

InputDevice* input_get_device_by_id(int id) {
    list_foreach(InputDevice**, _device, &_devices) {
        InputDevice* device = *_device;
        if(device->id == id) {
            return device;
        }
    }
    return NULL;
}
