#include "local.h"

static uint32_t _input_backend_pair_count = 0;
static const struct InputBackendPair * _input_backend_pairs = NULL;

static uint32_t _input_binding_count = 0;
static alias_R * _input_bindings = NULL;

void Engine_set_player_input_backend(uint32_t player_index, const struct InputBackendPair * pairs, uint32_t pair_count) {
  (void)player_index;
  _input_backend_pair_count = pair_count;
  _input_backend_pairs = pairs;

  uint32_t max_binding_index = 0;
  for(uint32_t i = 0; i < pair_count; i++) {
    if(pairs[i].binding > max_binding_index) {
      max_binding_index = pairs[i].binding;
    }
  }

  if(max_binding_index + 1 > _input_binding_count) {
    _input_bindings = alias_realloc(
        alias_default_MemoryCB()
      , _input_bindings
      , sizeof(*_input_bindings) * _input_binding_count
      , sizeof(*_input_bindings) * (max_binding_index + 1)
      , alignof(*_input_bindings)
      );
    _input_binding_count = max_binding_index + 1;
  }
}

static struct {
  uint32_t count;
  struct InputSignal * signals;
} _input_frontends[MAX_INPUT_FRONTEND_SETS] = { 0 };

uint32_t Engine_add_input_frontend(uint32_t player_index, struct InputSignal * signals, uint32_t signal_count) {
  (void)player_index;

  uint32_t index = 0;
  for(; index < MAX_INPUT_FRONTEND_SETS; index++) {
    if(_input_frontends[index].count == 0) {
      break;
    }
  }
  if(index < MAX_INPUT_FRONTEND_SETS) {
    _input_frontends[index].count = signal_count;
    _input_frontends[index].signals = signals;
    return index;
  }
  return -1;
}

void Engine_remove_input_frontend(uint32_t player_index, uint32_t index) {
  (void)player_index;

  if(index < MAX_INPUT_FRONTEND_SETS) {
    _input_frontends[index].count = 0;
    _input_frontends[index].signals = NULL;
  }
}

static int _get_key_down(enum InputSource source) {
  static int source_to_key[] = {
    [Keyboard_Apostrophe] = GLFW_KEY_APOSTROPHE,
    [Keyboard_Comma] = GLFW_KEY_COMMA,
    [Keyboard_Minus] = GLFW_KEY_MINUS,
    [Keyboard_Period] = GLFW_KEY_PERIOD,
    [Keyboard_Slash] = GLFW_KEY_SLASH,
    [Keyboard_0] = GLFW_KEY_0,
    [Keyboard_1] = GLFW_KEY_1,
    [Keyboard_2] = GLFW_KEY_2,
    [Keyboard_3] = GLFW_KEY_3,
    [Keyboard_4] = GLFW_KEY_4,
    [Keyboard_5] = GLFW_KEY_5,
    [Keyboard_6] = GLFW_KEY_6,
    [Keyboard_7] = GLFW_KEY_7,
    [Keyboard_8] = GLFW_KEY_8,
    [Keyboard_9] = GLFW_KEY_9,
    [Keyboard_Semicolon] = GLFW_KEY_SEMICOLON,
    [Keyboard_Equal] = GLFW_KEY_EQUAL,
    [Keyboard_A] = GLFW_KEY_A,
    [Keyboard_B] = GLFW_KEY_B,
    [Keyboard_C] = GLFW_KEY_C,
    [Keyboard_D] = GLFW_KEY_D,
    [Keyboard_E] = GLFW_KEY_E,
    [Keyboard_F] = GLFW_KEY_F,
    [Keyboard_G] = GLFW_KEY_G,
    [Keyboard_H] = GLFW_KEY_H,
    [Keyboard_I] = GLFW_KEY_I,
    [Keyboard_J] = GLFW_KEY_J,
    [Keyboard_K] = GLFW_KEY_K,
    [Keyboard_L] = GLFW_KEY_L,
    [Keyboard_M] = GLFW_KEY_M,
    [Keyboard_N] = GLFW_KEY_N,
    [Keyboard_O] = GLFW_KEY_O,
    [Keyboard_P] = GLFW_KEY_P,
    [Keyboard_Q] = GLFW_KEY_Q,
    [Keyboard_R] = GLFW_KEY_R,
    [Keyboard_S] = GLFW_KEY_S,
    [Keyboard_T] = GLFW_KEY_T,
    [Keyboard_U] = GLFW_KEY_U,
    [Keyboard_V] = GLFW_KEY_V,
    [Keyboard_W] = GLFW_KEY_W,
    [Keyboard_X] = GLFW_KEY_X,
    [Keyboard_Y] = GLFW_KEY_Y,
    [Keyboard_Z] = GLFW_KEY_Z,
    [Keyboard_Space] = GLFW_KEY_SPACE,
    [Keyboard_Escape] = GLFW_KEY_ESCAPE,
    [Keyboard_Enter] = GLFW_KEY_ENTER,
    [Keyboard_Tab] = GLFW_KEY_TAB,
    [Keyboard_Backspace] = GLFW_KEY_BACKSPACE,
    [Keyboard_Insert] = GLFW_KEY_INSERT,
    [Keyboard_Delete] = GLFW_KEY_DELETE,
    [Keyboard_Right] = GLFW_KEY_RIGHT,
    [Keyboard_Left] = GLFW_KEY_LEFT,
    [Keyboard_Down] = GLFW_KEY_DOWN,
    [Keyboard_Up] = GLFW_KEY_UP,
    [Keyboard_PageUp] = GLFW_KEY_PAGE_UP,
    [Keyboard_PageDown] = GLFW_KEY_PAGE_DOWN,
    [Keyboard_Home] = GLFW_KEY_HOME,
    [Keyboard_End] = GLFW_KEY_END,
    [Keyboard_CapsLock] = GLFW_KEY_CAPS_LOCK,
    [Keyboard_ScrollLock] = GLFW_KEY_SCROLL_LOCK,
    [Keyboard_NumLock] = GLFW_KEY_NUM_LOCK,
    [Keyboard_PrintScreen] = GLFW_KEY_PRINT_SCREEN,
    [Keyboard_Pause] = GLFW_KEY_PAUSE,
    [Keyboard_F1] = GLFW_KEY_F1,
    [Keyboard_F2] = GLFW_KEY_F2,
    [Keyboard_F3] = GLFW_KEY_F3,
    [Keyboard_F4] = GLFW_KEY_F4,
    [Keyboard_F5] = GLFW_KEY_F5,
    [Keyboard_F6] = GLFW_KEY_F6,
    [Keyboard_F7] = GLFW_KEY_F7,
    [Keyboard_F8] = GLFW_KEY_F8,
    [Keyboard_F9] = GLFW_KEY_F9,
    [Keyboard_F10] = GLFW_KEY_F10,
    [Keyboard_F11] = GLFW_KEY_F11,
    [Keyboard_F12] = GLFW_KEY_F12,
    [Keyboard_LeftShift] = GLFW_KEY_LEFT_SHIFT,
    [Keyboard_LeftControl] = GLFW_KEY_LEFT_CONTROL,
    [Keyboard_LeftAlt] = GLFW_KEY_LEFT_ALT,
    [Keyboard_LeftMeta] = GLFW_KEY_LEFT_SUPER,
    [Keyboard_RightShift] = GLFW_KEY_RIGHT_SHIFT,
    [Keyboard_RightControl] = GLFW_KEY_RIGHT_CONTROL,
    [Keyboard_RightAlt] = GLFW_KEY_RIGHT_ALT,
    [Keyboard_RightMeta] = GLFW_KEY_RIGHT_SUPER,
    [Keyboard_Menu] = GLFW_KEY_MENU,
    [Keyboard_LeftBracket] = GLFW_KEY_LEFT_BRACKET,
    [Keyboard_Backslash] = GLFW_KEY_BACKSLASH,
    [Keyboard_RightBracket] = GLFW_KEY_RIGHT_BRACKET,
    [Keyboard_Grave] = GLFW_KEY_GRAVE_ACCENT,
    [Keyboard_Pad_0] = GLFW_KEY_UNKNOWN,
    [Keyboard_Pad_1] = GLFW_KEY_UNKNOWN,
    [Keyboard_Pad_2] = GLFW_KEY_UNKNOWN,
    [Keyboard_Pad_3] = GLFW_KEY_UNKNOWN,
    [Keyboard_Pad_4] = GLFW_KEY_UNKNOWN,
    [Keyboard_Pad_5] = GLFW_KEY_UNKNOWN,
    [Keyboard_Pad_6] = GLFW_KEY_UNKNOWN,
    [Keyboard_Pad_7] = GLFW_KEY_UNKNOWN,
    [Keyboard_Pad_8] = GLFW_KEY_UNKNOWN,
    [Keyboard_Pad_9] = GLFW_KEY_UNKNOWN,
    [Keyboard_Pad_Period] = GLFW_KEY_UNKNOWN,
    [Keyboard_Pad_Divide] = GLFW_KEY_UNKNOWN,
    [Keyboard_Pad_Multiply] = GLFW_KEY_UNKNOWN,
    [Keyboard_Pad_Minus] = GLFW_KEY_UNKNOWN,
    [Keyboard_Pad_Add] = GLFW_KEY_UNKNOWN,
    [Keyboard_Pad_Enter] = GLFW_KEY_UNKNOWN,
    [Keyboard_Pad_Equal] = GLFW_KEY_UNKNOWN
  };
  int state = glfwGetKey(run_glfw_window, source_to_key[source]);
  return state == GLFW_PRESS;
}

static int _get_mouse_button_down(enum InputSource source) {
  int source_to_mouse[] = {
    [Mouse_Left_Button] = GLFW_MOUSE_BUTTON_1,
    [Mouse_Right_Button] = GLFW_MOUSE_BUTTON_2
  };
  int state = glfwGetMouseButton(run_glfw_window, source_to_mouse[source]);
  return state == GLFW_PRESS;
}

void Engine__update_input(void) {
  double xpos, ypos;
  glfwGetCursorPos(run_glfw_window, &xpos, &ypos);
    
  alias_memory_clear(_input_bindings, sizeof(*_input_bindings) * _input_binding_count);

  for(uint32_t i = 0; i < _input_backend_pair_count; i++) {
    enum InputSource source = _input_backend_pairs[i].source;
    alias_R * binding = &_input_bindings[_input_backend_pairs[i].binding];

    switch(source) {
    case Keyboard_Apostrophe ... Keyboard_Pad_Equal:
      if(_get_key_down(source)) {
        *binding = alias_R_ONE;
      }
      break;
    case Mouse_Left_Button ... Mouse_Right_Button:
      if(_get_mouse_button_down(source)) {
        *binding = alias_R_ONE;
      }
      break;
    case Mouse_Position_X:
      *binding = xpos;
      break;
    case Mouse_Position_Y:
      *binding = ypos;
      break;
    case InputSource_COUNT:
      break;
    }
  }

  for(uint32_t i = 0; i < MAX_INPUT_FRONTEND_SETS; i++) {
    for(uint32_t j = 0; j < _input_frontends[i].count; j++) {
      struct InputSignal * signal = &_input_frontends[i].signals[j];
      switch(signal->type) {
      case InputSignal_Pass:
        {
          signal->boolean = _input_bindings[signal->bindings[0]] > alias_R_ZERO;
          break;
        }
      case InputSignal_Up:
        {
          bool value = _input_bindings[signal->bindings[0]] > alias_R_ZERO;
          signal->boolean = !signal->internal.up && value;
          signal->internal.up = value;
          break;
        }
      case InputSignal_Down:
        {
          bool value = _input_bindings[signal->bindings[0]] > alias_R_ZERO;
          signal->boolean = signal->internal.down && !value;
          signal->internal.down = value;
          break;
        }
      case InputSignal_Direction:
        {
          signal->direction = alias_pga2d_direction(_input_bindings[signal->bindings[0]], _input_bindings[signal->bindings[1]]);
          break;
        }
      case InputSignal_Point:
        {
          signal->point = alias_pga2d_point(_input_bindings[signal->bindings[0]], _input_bindings[signal->bindings[1]]);
          break;
        }
      case InputSignal_ViewportPoint:
        {
          alias_R px = _input_bindings[signal->bindings[0]];
          alias_R py = _input_bindings[signal->bindings[1]];

          const struct Camera * camera = Camera_read(signal->click_camera);
          const struct alias_LocalToWorld2D * transform = alias_LocalToWorld2D_read(signal->click_camera);

          alias_R minx = alias_pga2d_point_x(camera->viewport_min) * Engine_render_get_width();
          alias_R miny = alias_pga2d_point_y(camera->viewport_min) * Engine_render_get_height();
          alias_R maxx = alias_pga2d_point_x(camera->viewport_max) * Engine_render_get_width();
          alias_R maxy = alias_pga2d_point_y(camera->viewport_max) * Engine_render_get_height();
          alias_R width = maxx - minx;
          alias_R height = maxy - miny;

          px -= minx;
          py -= miny;
          if(px < 0 || py < 0 || px > width || py > height) {
            break;
          }

          px -= width / 2;
          py -= height / 2;
          
          alias_R cx = alias_pga2d_point_x(transform->position);
          alias_R cy = alias_pga2d_point_y(transform->position);

          px += cx;
          py += cy;

          signal->point = alias_pga2d_point(px, py);
          
          break;
        }
      }
    }
  }
}