// SPDX-License-Identifier: MPL-2.0
#include "devs/input/kbd.h"
struct sh_kbd_DRIVER_HANDLE {
  sh_uint8 id;
  sh_uint32 generation;
};
struct sh_kbd_CONSUMER_HANDLE {
  sh_uint8 id;
  sh_uint32 generation;
  sh_uint32 read_index;
};
typedef struct {
  sh_kbd_DEVICE by_id[256];
  sh_uint32 id_generation[256];
  sh_uint64 bitmap[4];
  sh_uint64 keyboard_count;
  sh_SPIN_LOCK id_manipulation_spinlock;
  sh_bool ps2_registered;
  sh_uint16 events_queue_capacity;
  sh_bool is_kbd_devs_initialized;
} kbd_devs_template;
kbd_devs_template kbd_devs={.id_manipulation_spinlock=SH_LOCK()};
SH_STATUS sh_kbd_init_devs() {
  sh_spin_lock(&kbd_devs.id_manipulation_spinlock);
  if (kbd_devs.is_kbd_devs_initialized) {
    sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
    return SH_STATUS_ALREADY_INITIALIZED;
  }
  sh_mem_set_8((sh_uint8*)kbd_devs.by_id,0x0,sizeof(sh_kbd_DEVICE)*256);
  for (sh_iter64 i=0;i<256;i++) kbd_devs.by_id[i].spinlock=SH_LOCK_LOCAL();
  sh_mem_set_8((sh_uint8*)kbd_devs.id_generation,0x0,sizeof(sh_uint16)*256);
  sh_mem_set_8((sh_uint8*)kbd_devs.bitmap,0x0,sizeof(sh_uint64)*4);
  kbd_devs.keyboard_count=0;
  kbd_devs.is_kbd_devs_initialized=SH_TRUE;
  sh_devs_RESULT res;
  SH_STATUS status=sh_devs_query("$kernel/conf/kbd-events-queue-capacity",&res);
  if (sh_status_error(status)) {
    sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
    return status;
  }
  kbd_devs.events_queue_capacity=(sh_uint16)res.value;
  sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_kbd_register_ps2(sh_bool is_present,sh_kbd_DRIVER_HANDLE **handle) {
  if (handle==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  sh_spin_lock(&kbd_devs.id_manipulation_spinlock);
  if (!kbd_devs.is_kbd_devs_initialized) {
    sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
    return SH_STATUS_DEVS_NOT_INITIALIZED;
  }
  if (kbd_devs.ps2_registered) {
    sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
    return SH_STATUS_ALREADY_INITIALIZED;
  }
  sh_uint8 id=0;
  kbd_devs.bitmap[0]|=1ULL;
  kbd_devs.id_generation[id]++;
  sh_kbd_DEVICE *dev=&kbd_devs.by_id[id];
  dev->kbd_id=id;
  dev->present=is_present;
  dev->ver_maj=SH_FALSE;
  dev->ver_num=SH_FALSE;
  dev->shift=SH_FALSE;
  dev->alt=SH_FALSE;
  dev->ctrl=SH_FALSE;
  dev->win=SH_FALSE;
  dev->spinlock=SH_LOCK_LOCAL();
  sh_queue_KBD_EVENT *dev_queue=sh_malloc(sizeof(sh_queue_KBD_EVENT));
  if (!dev_queue) {
    sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
    return SH_STATUS_ERROR_NULLPTR_RETURNED;
  }
  SH_STATUS status=sh_queue_event_init(dev_queue,kbd_devs.events_queue_capacity);
  if (sh_status_error(status)) {
    sh_free(dev_queue);
    sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
    return status;
  }
  dev->events_queue=dev_queue;
  kbd_devs.keyboard_count++;
  kbd_devs.ps2_registered=SH_TRUE;
  sh_kbd_DRIVER_HANDLE *driver_handle=sh_malloc(sizeof(sh_kbd_DRIVER_HANDLE));
  if (!dev_queue) {
    status=sh_queue_event_destroy(dev_queue);
    if (sh_status_error(status)) return status;
    sh_free(dev_queue);
    sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
    return SH_STATUS_ERROR_NULLPTR_RETURNED;
  }
  driver_handle->id=id;
  driver_handle->generation=kbd_devs.id_generation[id];
  *handle=driver_handle;
  sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_kbd_register(sh_bool is_present,sh_kbd_DRIVER_HANDLE **handle) {
  if (handle==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  sh_spin_lock(&kbd_devs.id_manipulation_spinlock);
  if (!kbd_devs.is_kbd_devs_initialized) {
    sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
    return SH_STATUS_DEVS_NOT_INITIALIZED;
  }
  sh_uint8 id=0;
  sh_bool found=SH_FALSE;
  for (sh_uint32 i=1;i<256;i++) {
    sh_uint32 idx=i/64;
    sh_uint32 bit=i%64;
    if (!(kbd_devs.bitmap[idx] & (1ULL<<bit))) {
      kbd_devs.bitmap[idx]|=(1ULL<<bit);
      id=(sh_uint8)i;
      found=SH_TRUE;
      break;
    }
  }
  if (!found) {
    sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
    return SH_STATUS_NOT_FOUND;
  }
  sh_kbd_DEVICE *dev=&kbd_devs.by_id[id];
  sh_mem_set_8((sh_uint8*)dev,0,sizeof(sh_kbd_DEVICE));
  dev->kbd_id=id;
  dev->present=is_present;
  dev->ver_maj=SH_FALSE;
  dev->ver_num=SH_FALSE;
  dev->shift=SH_FALSE;
  dev->alt=SH_FALSE;
  dev->ctrl=SH_FALSE;
  dev->win=SH_FALSE;
  dev->spinlock=SH_LOCK_LOCAL();
  kbd_devs.id_generation[id]++;
  sh_queue_KBD_EVENT *dev_queue=sh_malloc(sizeof(sh_queue_KBD_EVENT));
  if (!dev_queue) {
    kbd_devs.bitmap[id/64]&= ~(1ULL<<(id%64));
    sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
    return SH_STATUS_ERROR_NULLPTR_RETURNED;
  }
  SH_STATUS status=sh_queue_event_init(dev_queue,kbd_devs.events_queue_capacity);
  if (sh_status_error(status)) {
    sh_free(dev_queue);
    kbd_devs.bitmap[id/64]&= ~(1ULL<<(id%64));
    sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
    return status;
  }
  dev->events_queue=dev_queue;
  kbd_devs.keyboard_count++;
  sh_kbd_DRIVER_HANDLE *driver_handle=sh_malloc(sizeof(sh_kbd_DRIVER_HANDLE));
  if (!dev_queue) {
    status=sh_queue_event_destroy(dev_queue);
    if (sh_status_error(status)) return status;
    sh_free(dev_queue);
    sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
    return SH_STATUS_ERROR_NULLPTR_RETURNED;
  }
  driver_handle->id=id;
  driver_handle->generation=kbd_devs.id_generation[id];
  *handle=driver_handle;
  sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_kbd_set_flag(sh_kbd_DRIVER_HANDLE *handle,sh_uint8 flag,sh_bool value) {
  if (handle==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  sh_spin_lock(&kbd_devs.id_manipulation_spinlock);
  if (!kbd_devs.is_kbd_devs_initialized) {
    sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
    return SH_STATUS_DEVS_NOT_INITIALIZED;
  }
  sh_uint8 id=handle->id;
  if (kbd_devs.id_generation[id]!=handle->generation) {
    sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
    return SH_STATUS_INVALID_HANDLE;
  }
  sh_uint32 idx=id/64;
  sh_uint32 bit=id%64;
  if (!(kbd_devs.bitmap[idx] & (1ULL<<bit))) {
    sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
    return SH_STATUS_INVALID_HANDLE;
  }
  sh_kbd_DEVICE *dev=&kbd_devs.by_id[id];
  sh_spin_lock(&dev->spinlock);
  sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
  switch (flag) {
    case SH_KBD_FLAG_PRESENT:
      dev->present=value;
      break;
    case SH_KBD_FLAG_VER_MAJ:
      dev->ver_maj=value;
      break;
    case SH_KBD_FLAG_VER_NUM:
      dev->ver_num=value;
      break;
    case SH_KBD_FLAG_SHIFT:
      dev->shift=value;
      break;
    case SH_KBD_FLAG_ALT:
      dev->alt=value;
      break;
    case SH_KBD_FLAG_CTRL:
      dev->ctrl=value;
      break;
    case SH_KBD_FLAG_WIN:
      dev->win=value;
      break;
    default:
      sh_spin_unlock(&dev->spinlock);
      return SH_STATUS_INVALID_PARAMETER;
  }
  sh_spin_unlock(&dev->spinlock);
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_kbd_push_events(sh_kbd_DRIVER_HANDLE *handle,sh_kbd_EVENT *event) {
  if (handle==SH_NULLPTR || event==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  sh_uint8 id=handle->id;
  if (kbd_devs.id_generation[id]!=handle->generation) return SH_STATUS_INVALID_HANDLE;
  if (!(kbd_devs.bitmap[id/64] & (1ULL<<(id%64)))) return SH_STATUS_INVALID_HANDLE;
  sh_kbd_DEVICE *dev=&kbd_devs.by_id[id];
  sh_spin_lock(&dev->spinlock);
  if (!dev->present) {
    sh_spin_unlock(&dev->spinlock);
    return SH_STATUS_DEVICE_NOT_PRESENT;
  }
  sh_queue_KBD_EVENT *queue=(sh_queue_KBD_EVENT*)dev->events_queue;
  if (queue==SH_NULLPTR) {
    sh_spin_unlock(&dev->spinlock);
    return SH_STATUS_INVALID_STATE;
  }
  SH_STATUS status=sh_queue_event_push(queue,*event);
  if (sh_status_error(status)) {
    sh_spin_unlock(&dev->spinlock);
    return status;
  }
  sh_spin_unlock(&dev->spinlock);
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_kbd_enumerate_devices(sh_kbd_ENUMERATION *enumeration) {
  if (enumeration==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  sh_spin_lock(&kbd_devs.id_manipulation_spinlock);
  if (!kbd_devs.is_kbd_devs_initialized) {
    sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
    return SH_STATUS_DEVS_NOT_INITIALIZED;
  }
  enumeration->bitmap[0]=kbd_devs.bitmap[0];
  enumeration->bitmap[1]=kbd_devs.bitmap[1];
  enumeration->bitmap[2]=kbd_devs.bitmap[2];
  enumeration->bitmap[3]=kbd_devs.bitmap[3];
  sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_kbd_get_handle(sh_uint8 kbd_id,sh_kbd_CONSUMER_HANDLE **handle) {
  if (handle==SH_NULLPTR)return SH_STATUS_INVALID_PARAMETER;
  sh_spin_lock(&kbd_devs.id_manipulation_spinlock);
  if (!kbd_devs.is_kbd_devs_initialized) {
    sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
    return SH_STATUS_DEVS_NOT_INITIALIZED;
  }
  sh_uint32 idx=kbd_id>>6;
  sh_uint32 bit=kbd_id&63;
  if (!(kbd_devs.bitmap[idx] & (1ULL<<bit))) {
    sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
    return SH_STATUS_NOT_FOUND;
  }
  sh_uint32 generation=kbd_devs.id_generation[kbd_id];
  sh_kbd_CONSUMER_HANDLE *h=sh_malloc(sizeof(sh_kbd_CONSUMER_HANDLE));
  if (h==SH_NULLPTR) {
    sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
    return SH_STATUS_ERROR_NULLPTR_RETURNED;
  }
  h->id=kbd_id;
  h->generation=generation;
  h->read_index=kbd_devs.by_id[kbd_id].events_queue->write_index;
  *handle=h;
  sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_kbd_destroy_handle(sh_kbd_CONSUMER_HANDLE **handle) {
  if (handle==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  if (*handle==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  sh_free(*handle);
  *handle=SH_NULLPTR;
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_kbd_read_flag(sh_kbd_DRIVER_HANDLE *d_handle,sh_kbd_CONSUMER_HANDLE *c_handle,sh_uint8 flag,sh_bool *value) {
  if (value==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  if ((d_handle==SH_NULLPTR && c_handle==SH_NULLPTR) || (d_handle!=SH_NULLPTR && c_handle!=SH_NULLPTR)) return SH_STATUS_INVALID_PARAMETER;
  sh_uint8 id;
  sh_uint32 generation;
  if (d_handle!=SH_NULLPTR) {
    id=d_handle->id;
    generation=d_handle->generation;
  } else {
    id=c_handle->id;
    generation=c_handle->generation;
  }
  sh_spin_lock(&kbd_devs.id_manipulation_spinlock);
  if (!kbd_devs.is_kbd_devs_initialized) {
    sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
    return SH_STATUS_DEVS_NOT_INITIALIZED;
  }
  if (kbd_devs.id_generation[id]!=generation) {
    sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
    return SH_STATUS_INVALID_HANDLE;
  }
  sh_uint32 idx=id>>6;
  sh_uint32 bit=id&63;
  if (!(kbd_devs.bitmap[idx] & (1ULL<<bit))) {
    sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
    return SH_STATUS_INVALID_HANDLE;
  }
  sh_kbd_DEVICE *dev=&kbd_devs.by_id[id];
  sh_spin_lock(&dev->spinlock);
  sh_spin_unlock(&kbd_devs.id_manipulation_spinlock);
  switch (flag) {
    case SH_KBD_FLAG_PRESENT:
      *value=dev->present;
      break;
    case SH_KBD_FLAG_VER_MAJ:
      *value=dev->ver_maj;
      break;
    case SH_KBD_FLAG_VER_NUM:
      *value=dev->ver_num;
      break;
    case SH_KBD_FLAG_SHIFT:
      *value=dev->shift;
      break;
    case SH_KBD_FLAG_ALT:
      *value=dev->alt;
      break;
    case SH_KBD_FLAG_CTRL:
      *value=dev->ctrl;
      break;
    case SH_KBD_FLAG_WIN:
      *value=dev->win;
      break;
    default:
      sh_spin_unlock(&dev->spinlock);
      return SH_STATUS_INVALID_PARAMETER;
  }
  sh_spin_unlock(&dev->spinlock);
  return SH_STATUS_SUCCESS;
}
