// SPDX-License-Identifier: MPL-2.0
#include "devs/input/ps2.h"
#include "devs/input/kbd.h"
#include "irq/irq.h"
enum ps2_scancode_state {
  PS2_SCANCODE_STATE_NORMAL,
  PS2_SCANCODE_STATE_E0,
  PS2_SCANCODE_STATE_E1
};
enum ps2_command_state {
  PS2_COMMAND_STATE_IDLE,
  PS2_COMMAND_STATE_WAIT_ACK,
  PS2_COMMAND_STATE_WAIT_LED_ACK_1,
  PS2_COMMAND_STATE_WAIT_LED_ACK_2,
  PS2_COMMAND_STATE_WAIT_SC_SET_ACK_1,
  PS2_COMMAND_STATE_WAIT_SC_SET_ACK_2,
  PS2_COMMAND_STATE_WAIT_IDENTIFY_ACK,
  PS2_COMMAND_STATE_WAIT_IDENTIFY_BYTE_1,
  PS2_COMMAND_STATE_WAIT_IDENTIFY_BYTE_2,
  PS2_COMMAND_STATE_WAIT_ECHO,
  PS2_COMMAND_STATE_WAIT_RESET_ACK,
  PS2_COMMAND_STATE_WAIT_RESET_RESULT
};
typedef struct {
  enum ps2_scancode_state scancode_state;
  enum ps2_command_state command_state;
  sh_ps2_KBD_COMMAND current_command;
  sh_uint8 last_byte;
  sh_uint8 e1_bytes_left;
  sh_uint8 e1_bytes_idx;
  sh_uint8 e1_bytes_array[5];
  sh_bool command_in_progress;
  sh_uint8 keyboard_id[2];
  sh_uint8 keyboard_id_len;
  sh_bool scanning_enabled;
  sh_bool shift_state;
  sh_bool l_alt_state;
  sh_bool r_alt_state;
  sh_bool l_ctrl_state;
  sh_bool r_ctrl_state;
  sh_bool win_state;
  sh_bool capslock_state;
  sh_bool numlock_state;
  sh_kbd_DRIVER_HANDLE *d_handle;
} ps2_state_template;
ps2_state_template ps2_state;
static void sh_ps2_send_keyboard_command(sh_uint8 cmd,sh_uint8 data,sh_bool has_data) {
  ps2_state.current_command.command=cmd;
  ps2_state.current_command.data=data;
  ps2_state.current_command.has_data=has_data;
  ps2_state.command_in_progress=SH_TRUE;
  while (sh_ps2_cant_write());
  sh_asm_outb(SH_PS2_PORT_DATA,cmd);
}
SH_STATUS sh_ps2_send_keyboard_command_wait(sh_uint8 cmd,sh_uint8 data,sh_bool has_data) {
  sh_ps2_send_keyboard_command(cmd,data,has_data);
  while (ps2_state.command_in_progress) {
    asm volatile("hlt");
  }
  return SH_STATUS_SUCCESS;
}
// Because we don't have a scheduler or kernel threads/worker, we can't properly update these leds for the moment
__attribute__((__unused__)) static void sh_ps2_update_leds() {
  sh_uint8 led_byte=0;
  if (ps2_state.numlock_state) {
    led_byte|=SH_PS2_KBD_LEDS_NUM_LOCK;
  }
  if (ps2_state.capslock_state) {
    led_byte|=SH_PS2_KBD_LEDS_CAPS_LOCK;
  }
  ps2_state.command_state=PS2_COMMAND_STATE_WAIT_LED_ACK_1;
  sh_ps2_send_keyboard_command_wait(SH_PS2_KBD_COMMAND_SET_LEDS,led_byte,SH_TRUE);
  return;
}
static SH_STATUS sh_ps2_handle_command_response(sh_uint8 data) {
  if (ps2_state.command_state==PS2_COMMAND_STATE_WAIT_ACK) {
    if (data==SH_PS2_KBD_ACK) {
      if (ps2_state.current_command.command==SH_PS2_KBD_COMMAND_DISABLE_SCANNING) {
        ps2_state.scanning_enabled=SH_FALSE;
      } else if (ps2_state.current_command.command==SH_PS2_KBD_COMMAND_ENABLE_SCANNING) {
        ps2_state.scanning_enabled=SH_TRUE;
      }
      ps2_state.command_state=PS2_COMMAND_STATE_IDLE;
      ps2_state.current_command.command=0;
      ps2_state.current_command.data=0;
      ps2_state.current_command.has_data=SH_FALSE;
      ps2_state.command_in_progress=SH_FALSE;
      return SH_STATUS_SUCCESS;
    } else if (data==SH_PS2_KBD_RESEND) {
      sh_ps2_send_keyboard_command(ps2_state.current_command.command,ps2_state.current_command.data,ps2_state.current_command.has_data);
      return SH_STATUS_SUCCESS;
    }
  } else if (ps2_state.command_state==PS2_COMMAND_STATE_WAIT_ECHO) {
    if (data==SH_PS2_KBD_COMMAND_ECHO) {
      ps2_state.command_state=PS2_COMMAND_STATE_IDLE;
      ps2_state.command_in_progress=SH_FALSE;
      return SH_STATUS_SUCCESS;
    } else {
      sh_iprint(SH_DEBUG,"wrong echo\n");
      return SH_STATUS_SUCCESS;
    }
  } else if (ps2_state.command_state==PS2_COMMAND_STATE_WAIT_IDENTIFY_ACK) {
    if (data==SH_PS2_KBD_ACK) {
      ps2_state.keyboard_id_len=0;
      ps2_state.command_state=PS2_COMMAND_STATE_WAIT_IDENTIFY_BYTE_1;
      return SH_STATUS_SUCCESS;
    } else if (data==SH_PS2_KBD_RESEND) {
      sh_ps2_send_keyboard_command(SH_PS2_KBD_COMMAND_IDENTIFY,0,SH_FALSE);
      ps2_state.command_state=PS2_COMMAND_STATE_WAIT_IDENTIFY_ACK;
      return SH_STATUS_SUCCESS;
    }
  } else if (ps2_state.command_state==PS2_COMMAND_STATE_WAIT_IDENTIFY_BYTE_1) {
    ps2_state.keyboard_id[0]=data;
    ps2_state.keyboard_id_len=1;
    ps2_state.command_state=PS2_COMMAND_STATE_WAIT_IDENTIFY_BYTE_2;
    return SH_STATUS_SUCCESS;
  } else if (ps2_state.command_state==PS2_COMMAND_STATE_WAIT_IDENTIFY_BYTE_2) {
    ps2_state.keyboard_id[1]=data;
    ps2_state.keyboard_id_len=2;
    ps2_state.command_state=PS2_COMMAND_STATE_IDLE;
    ps2_state.command_in_progress=SH_FALSE;
    return SH_STATUS_SUCCESS;
  } else if (ps2_state.command_state==PS2_COMMAND_STATE_WAIT_LED_ACK_1) {
    if (data==SH_PS2_KBD_ACK) {
      while (sh_ps2_cant_write());
      sh_asm_outb(SH_PS2_PORT_DATA,ps2_state.current_command.data);
      ps2_state.command_state=PS2_COMMAND_STATE_WAIT_LED_ACK_2;
      return SH_STATUS_SUCCESS;
    } else if (data==SH_PS2_KBD_RESEND) {
      sh_ps2_send_keyboard_command(SH_PS2_KBD_COMMAND_SET_LEDS,ps2_state.current_command.data,SH_TRUE);
      ps2_state.command_state=PS2_COMMAND_STATE_WAIT_LED_ACK_1;
      return SH_STATUS_SUCCESS;
    }
  } else if (ps2_state.command_state==PS2_COMMAND_STATE_WAIT_LED_ACK_2) {
    if (data==SH_PS2_KBD_ACK) {
      ps2_state.command_state=PS2_COMMAND_STATE_IDLE;
      ps2_state.command_in_progress=SH_FALSE;
      return SH_STATUS_SUCCESS;
    } else if (data==SH_PS2_KBD_RESEND) {
      while (sh_ps2_cant_write());
      sh_asm_outb(SH_PS2_PORT_DATA,ps2_state.current_command.data);
      return SH_STATUS_SUCCESS;
    }
  } else if (ps2_state.command_state==PS2_COMMAND_STATE_WAIT_RESET_ACK) {
    if (data==SH_PS2_KBD_ACK) {
      ps2_state.command_state=PS2_COMMAND_STATE_WAIT_RESET_RESULT;
      return SH_STATUS_SUCCESS;
    } else if (data==SH_PS2_KBD_RESEND) {
      sh_ps2_send_keyboard_command(SH_PS2_KBD_COMMAND_RESET,0,SH_FALSE);
      ps2_state.command_state=PS2_COMMAND_STATE_WAIT_RESET_ACK;
      return SH_STATUS_SUCCESS;
    }
  } else if (ps2_state.command_state==PS2_COMMAND_STATE_WAIT_RESET_RESULT) {
    if (data==0xAA || data==0xFC) {
      ps2_state.command_state=PS2_COMMAND_STATE_IDLE;
      ps2_state.command_in_progress=SH_FALSE;
      return SH_STATUS_SUCCESS;
    } else {
      sh_iprintf(SH_DEBUG,"wrong reset\n");
    }
  } else if (ps2_state.command_state==PS2_COMMAND_STATE_WAIT_SC_SET_ACK_1) {
    if (data==SH_PS2_KBD_ACK) {
      while (sh_ps2_cant_write());
      sh_asm_outb(SH_PS2_PORT_DATA,ps2_state.current_command.data);
      ps2_state.command_state=PS2_COMMAND_STATE_WAIT_SC_SET_ACK_2;
      return SH_STATUS_SUCCESS;
    } else if (data==SH_PS2_KBD_RESEND) {
      sh_ps2_send_keyboard_command(SH_PS2_KBD_COMMAND_SC_SET,ps2_state.current_command.data,SH_TRUE);
      ps2_state.command_state=PS2_COMMAND_STATE_WAIT_SC_SET_ACK_1;
      return SH_STATUS_SUCCESS;
    }
  } else if (ps2_state.command_state==PS2_COMMAND_STATE_WAIT_SC_SET_ACK_2) {
    if (data==SH_PS2_KBD_ACK) {
      ps2_state.command_state=PS2_COMMAND_STATE_IDLE;
      ps2_state.command_in_progress=SH_FALSE;
      return SH_STATUS_SUCCESS;
    } else if (data==SH_PS2_KBD_RESEND) {
      while (sh_ps2_cant_write());
      sh_asm_outb(SH_PS2_PORT_DATA,ps2_state.current_command.data);
      return SH_STATUS_SUCCESS;
    }
  }
  return SH_STATUS_INVALID_STATE;
}
static inline sh_bool sh_ps2_shift() {
  return ps2_state.shift_state;
}
static inline sh_bool sh_ps2_alt() {
  return ps2_state.l_alt_state || ps2_state.r_alt_state;
}
static inline sh_bool sh_ps2_ctrl() {
  return ps2_state.l_ctrl_state || ps2_state.r_ctrl_state;
}
static void sh_ps2_handle_scancodes(sh_uint8 data) {
  if (data==0xE0 && ps2_state.scancode_state==PS2_SCANCODE_STATE_NORMAL) {
    ps2_state.scancode_state=PS2_SCANCODE_STATE_E0;
    return;
  } else if (data==0xE1 && ps2_state.scancode_state==PS2_SCANCODE_STATE_NORMAL) {
    ps2_state.scancode_state=PS2_SCANCODE_STATE_E1;
    ps2_state.e1_bytes_left=5;
    ps2_state.e1_bytes_idx=0;
    return;
  }
  if (ps2_state.scancode_state!=PS2_SCANCODE_STATE_E1) {
    switch (data) {
      case 0x1D: {
        // ctrl pressed
        if (ps2_state.scancode_state==PS2_SCANCODE_STATE_NORMAL) {
          sh_kbd_EVENT ev={
            .scancode=SH_KBD_CONTEXT_LCTRL,
            .pressed=SH_TRUE,
            .context_shift_win=(ps2_state.win_state<<1)|(sh_ps2_shift()),
            .context_alt=(ps2_state.l_alt_state<<1)|(ps2_state.r_alt_state),
            .context_ctrl=(ps2_state.l_ctrl_state<<1)|(ps2_state.r_ctrl_state),
            .context_lock_status=(ps2_state.capslock_state<<1)|(ps2_state.numlock_state),
            .event_type=SH_EVENT_TYPE_KBD_CONTEXT,
            .timestamp=sh_tsc_get_kernel_current_tsc()
          };
          SH_STATUS status=sh_kbd_push_events(ps2_state.d_handle,&ev);
          if (sh_status_error(status)) {
            sh_iprintf(SH_WARNING,"Error pushing events: status=%8s\n",status);
          }
          ps2_state.l_ctrl_state=SH_TRUE;
          status=sh_kbd_set_flag(ps2_state.d_handle,SH_KBD_FLAG_CTRL,sh_ps2_ctrl());
          if (sh_status_error(status)) {
            sh_iprintf(SH_WARNING,"Error settings ctrl flags: status=%8s\n",status);
          }
          return;
        } else if (ps2_state.scancode_state==PS2_SCANCODE_STATE_E0) {
          sh_kbd_EVENT ev={
            .scancode=SH_KBD_CONTEXT_RCTRL,
            .pressed=SH_TRUE,
            .context_shift_win=(ps2_state.win_state<<1)|(sh_ps2_shift()),
            .context_alt=(ps2_state.l_alt_state<<1)|(ps2_state.r_alt_state),
            .context_ctrl=(ps2_state.l_ctrl_state<<1)|(ps2_state.r_ctrl_state),
            .context_lock_status=(ps2_state.capslock_state<<1)|(ps2_state.numlock_state),
            .event_type=SH_EVENT_TYPE_KBD_CONTEXT,
            .timestamp=sh_tsc_get_kernel_current_tsc()
          };
          SH_STATUS status=sh_kbd_push_events(ps2_state.d_handle,&ev);
          if (sh_status_error(status)) {
            sh_iprintf(SH_WARNING,"Error pushing events: status=%8s\n",status);
          }
          ps2_state.r_ctrl_state=SH_TRUE;
          status=sh_kbd_set_flag(ps2_state.d_handle,SH_KBD_FLAG_CTRL,sh_ps2_ctrl());
          if (sh_status_error(status)) {
            sh_iprintf(SH_WARNING,"Error settings ctrl flags: status=%8s\n",status);
          }
          ps2_state.scancode_state=PS2_SCANCODE_STATE_NORMAL;
          return;
        }
        break;
      }
      case 0x9D: {
        // ctrl released
        if (ps2_state.scancode_state==PS2_SCANCODE_STATE_NORMAL) {
          sh_kbd_EVENT ev={
            .scancode=SH_KBD_CONTEXT_LCTRL,
            .pressed=SH_FALSE,
            .context_shift_win=(ps2_state.win_state<<1)|(sh_ps2_shift()),
            .context_alt=(ps2_state.l_alt_state<<1)|(ps2_state.r_alt_state),
            .context_ctrl=(ps2_state.l_ctrl_state<<1)|(ps2_state.r_ctrl_state),
            .context_lock_status=(ps2_state.capslock_state<<1)|(ps2_state.numlock_state),
            .event_type=SH_EVENT_TYPE_KBD_CONTEXT,
            .timestamp=sh_tsc_get_kernel_current_tsc()
          };
          SH_STATUS status=sh_kbd_push_events(ps2_state.d_handle,&ev);
          if (sh_status_error(status)) {
            sh_iprintf(SH_WARNING,"Error pushing events: status=%8s\n",status);
          }
          ps2_state.l_ctrl_state=SH_FALSE;
          status=sh_kbd_set_flag(ps2_state.d_handle,SH_KBD_FLAG_CTRL,sh_ps2_ctrl());
          if (sh_status_error(status)) {
            sh_iprintf(SH_WARNING,"Error settings ctrl flags: status=%8s\n",status);
          }
          return;
        } else if (ps2_state.scancode_state==PS2_SCANCODE_STATE_E0) {
          sh_kbd_EVENT ev={
            .scancode=SH_KBD_CONTEXT_RCTRL,
            .pressed=SH_FALSE,
            .context_shift_win=(ps2_state.win_state<<1)|(sh_ps2_shift()),
            .context_alt=(ps2_state.l_alt_state<<1)|(ps2_state.r_alt_state),
            .context_ctrl=(ps2_state.l_ctrl_state<<1)|(ps2_state.r_ctrl_state),
            .context_lock_status=(ps2_state.capslock_state<<1)|(ps2_state.numlock_state),
            .event_type=SH_EVENT_TYPE_KBD_CONTEXT,
            .timestamp=sh_tsc_get_kernel_current_tsc()
          };
          SH_STATUS status=sh_kbd_push_events(ps2_state.d_handle,&ev);
          if (sh_status_error(status)) {
            sh_iprintf(SH_WARNING,"Error pushing events: status=%8s\n",status);
          }
          ps2_state.r_ctrl_state=SH_FALSE;
          status=sh_kbd_set_flag(ps2_state.d_handle,SH_KBD_FLAG_CTRL,sh_ps2_ctrl());
          if (sh_status_error(status)) {
            sh_iprintf(SH_WARNING,"Error settings ctrl flags: status=%8s\n",status);
          }
          ps2_state.scancode_state=PS2_SCANCODE_STATE_NORMAL;
          return;
        }
        break;
      }
      case 0x36: {
        // right shift pressed
        sh_kbd_EVENT ev={
          .scancode=SH_KBD_CONTEXT_RSHIFT,
          .pressed=SH_TRUE,
          .context_shift_win=(ps2_state.win_state<<1)|(sh_ps2_shift()),
          .context_alt=(ps2_state.l_alt_state<<1)|(ps2_state.r_alt_state),
          .context_ctrl=(ps2_state.l_ctrl_state<<1)|(ps2_state.r_ctrl_state),
          .context_lock_status=(ps2_state.capslock_state<<1)|(ps2_state.numlock_state),
          .event_type=SH_EVENT_TYPE_KBD_CONTEXT,
          .timestamp=sh_tsc_get_kernel_current_tsc()
        };
        SH_STATUS status=sh_kbd_push_events(ps2_state.d_handle,&ev);
        if (sh_status_error(status)) {
          sh_iprintf(SH_WARNING,"Error pushing events: status=%8s\n",status);
        }
        ps2_state.shift_state=SH_TRUE;
        status=sh_kbd_set_flag(ps2_state.d_handle,SH_KBD_FLAG_SHIFT,sh_ps2_shift());
        if (sh_status_error(status)) {
          sh_iprintf(SH_WARNING,"Error settings shift flags: status=%8s\n",status);
        }
        return;
      }
      case 0x2A: {
        // left shift pressed
        sh_kbd_EVENT ev={
          .scancode=SH_KBD_CONTEXT_LSHIFT,
          .pressed=SH_TRUE,
          .context_shift_win=(ps2_state.win_state<<1)|(sh_ps2_shift()),
          .context_alt=(ps2_state.l_alt_state<<1)|(ps2_state.r_alt_state),
          .context_ctrl=(ps2_state.l_ctrl_state<<1)|(ps2_state.r_ctrl_state),
          .context_lock_status=(ps2_state.capslock_state<<1)|(ps2_state.numlock_state),
          .event_type=SH_EVENT_TYPE_KBD_CONTEXT,
          .timestamp=sh_tsc_get_kernel_current_tsc()
        };
        SH_STATUS status=sh_kbd_push_events(ps2_state.d_handle,&ev);
        if (sh_status_error(status)) {
          sh_iprintf(SH_WARNING,"Error pushing events: status=%8s\n",status);
        }
        ps2_state.shift_state=SH_TRUE;
        status=sh_kbd_set_flag(ps2_state.d_handle,SH_KBD_FLAG_SHIFT,sh_ps2_shift());
        if (sh_status_error(status)) {
          sh_iprintf(SH_WARNING,"Error settings shift flags: status=%8s\n",status);
        }
        return;
      }
      case 0xAA: {
        // left shift released
        sh_kbd_EVENT ev={
          .scancode=SH_KBD_CONTEXT_LSHIFT,
          .pressed=SH_FALSE,
          .context_shift_win=(ps2_state.win_state<<1)|(sh_ps2_shift()),
          .context_alt=(ps2_state.l_alt_state<<1)|(ps2_state.r_alt_state),
          .context_ctrl=(ps2_state.l_ctrl_state<<1)|(ps2_state.r_ctrl_state),
          .context_lock_status=(ps2_state.capslock_state<<1)|(ps2_state.numlock_state),
          .event_type=SH_EVENT_TYPE_KBD_CONTEXT,
          .timestamp=sh_tsc_get_kernel_current_tsc()
        };
        SH_STATUS status=sh_kbd_push_events(ps2_state.d_handle,&ev);
        if (sh_status_error(status)) {
          sh_iprintf(SH_WARNING,"Error pushing events: status=%8s\n",status);
        }
        ps2_state.shift_state=SH_FALSE;
        status=sh_kbd_set_flag(ps2_state.d_handle,SH_KBD_FLAG_SHIFT,sh_ps2_shift());
        if (sh_status_error(status)) {
          sh_iprintf(SH_WARNING,"Error settings shift flags: status=%8s\n",status);
        }
        return;
      }
      case 0xB6: {
        // right shift released
        sh_kbd_EVENT ev={
          .scancode=SH_KBD_CONTEXT_RSHIFT,
          .pressed=SH_FALSE,
          .context_shift_win=(ps2_state.win_state<<1)|(sh_ps2_shift()),
          .context_alt=(ps2_state.l_alt_state<<1)|(ps2_state.r_alt_state),
          .context_ctrl=(ps2_state.l_ctrl_state<<1)|(ps2_state.r_ctrl_state),
          .context_lock_status=(ps2_state.capslock_state<<1)|(ps2_state.numlock_state),
          .event_type=SH_EVENT_TYPE_KBD_CONTEXT,
          .timestamp=sh_tsc_get_kernel_current_tsc()
        };
        SH_STATUS status=sh_kbd_push_events(ps2_state.d_handle,&ev);
        if (sh_status_error(status)) {
          sh_iprintf(SH_WARNING,"Error pushing events: status=%8s\n",status);
        }
        ps2_state.shift_state=SH_FALSE;
        status=sh_kbd_set_flag(ps2_state.d_handle,SH_KBD_FLAG_SHIFT,sh_ps2_shift());
        if (sh_status_error(status)) {
          sh_iprintf(SH_WARNING,"Error settings shift flags: status=%8s\n",status);
        }
        return;
      }
      case 0x38: {
        // alt pressed
        if (ps2_state.scancode_state==PS2_SCANCODE_STATE_NORMAL) {
          sh_kbd_EVENT ev={
            .scancode=SH_KBD_CONTEXT_LALT,
            .pressed=SH_TRUE,
            .context_shift_win=(ps2_state.win_state<<1)|(sh_ps2_shift()),
            .context_alt=(ps2_state.l_alt_state<<1)|(ps2_state.r_alt_state),
            .context_ctrl=(ps2_state.l_ctrl_state<<1)|(ps2_state.r_ctrl_state),
            .context_lock_status=(ps2_state.capslock_state<<1)|(ps2_state.numlock_state),
            .event_type=SH_EVENT_TYPE_KBD_CONTEXT,
            .timestamp=sh_tsc_get_kernel_current_tsc()
          };
          SH_STATUS status=sh_kbd_push_events(ps2_state.d_handle,&ev);
          if (sh_status_error(status)) {
            sh_iprintf(SH_WARNING,"Error pushing events: status=%8s\n",status);
          }
          ps2_state.l_alt_state=SH_TRUE;
          status=sh_kbd_set_flag(ps2_state.d_handle,SH_KBD_FLAG_ALT,sh_ps2_alt());
          if (sh_status_error(status)) {
            sh_iprintf(SH_WARNING,"Error settings alt flags: status=%8s\n",status);
          }
          return;
        } else if (ps2_state.scancode_state==PS2_SCANCODE_STATE_E0) {
          sh_kbd_EVENT ev={
            .scancode=SH_KBD_CONTEXT_RALT,
            .pressed=SH_TRUE,
            .context_shift_win=(ps2_state.win_state<<1)|(sh_ps2_shift()),
            .context_alt=(ps2_state.l_alt_state<<1)|(ps2_state.r_alt_state),
            .context_ctrl=(ps2_state.l_ctrl_state<<1)|(ps2_state.r_ctrl_state),
            .context_lock_status=(ps2_state.capslock_state<<1)|(ps2_state.numlock_state),
            .event_type=SH_EVENT_TYPE_KBD_CONTEXT,
            .timestamp=sh_tsc_get_kernel_current_tsc()
          };
          SH_STATUS status=sh_kbd_push_events(ps2_state.d_handle,&ev);
          if (sh_status_error(status)) {
            sh_iprintf(SH_WARNING,"Error pushing events: status=%8s\n",status);
          }
          ps2_state.r_alt_state=SH_TRUE;
          status=sh_kbd_set_flag(ps2_state.d_handle,SH_KBD_FLAG_ALT,sh_ps2_alt());
          if (sh_status_error(status)) {
            sh_iprintf(SH_WARNING,"Error settings alt flags: status=%8s\n",status);
          }
          ps2_state.scancode_state=PS2_SCANCODE_STATE_NORMAL;
          return;
        }
        break;
      }
      case 0xB8: {
        // alt released
        if (ps2_state.scancode_state==PS2_SCANCODE_STATE_NORMAL) {
          sh_kbd_EVENT ev={
            .scancode=SH_KBD_CONTEXT_LALT,
            .pressed=SH_FALSE,
            .context_shift_win=(ps2_state.win_state<<1)|(sh_ps2_shift()),
            .context_alt=(ps2_state.l_alt_state<<1)|(ps2_state.r_alt_state),
            .context_ctrl=(ps2_state.l_ctrl_state<<1)|(ps2_state.r_ctrl_state),
            .context_lock_status=(ps2_state.capslock_state<<1)|(ps2_state.numlock_state),
            .event_type=SH_EVENT_TYPE_KBD_CONTEXT,
            .timestamp=sh_tsc_get_kernel_current_tsc()
          };
          SH_STATUS status=sh_kbd_push_events(ps2_state.d_handle,&ev);
          if (sh_status_error(status)) {
            sh_iprintf(SH_WARNING,"Error pushing events: status=%8s\n",status);
          }
          ps2_state.l_alt_state=SH_FALSE;
          status=sh_kbd_set_flag(ps2_state.d_handle,SH_KBD_FLAG_ALT,sh_ps2_alt());
          if (sh_status_error(status)) {
            sh_iprintf(SH_WARNING,"Error settings alt flags: status=%8s\n",status);
          }
          return;
        } else if (ps2_state.scancode_state==PS2_SCANCODE_STATE_E0) {
          sh_kbd_EVENT ev={
            .scancode=SH_KBD_CONTEXT_RALT,
            .pressed=SH_FALSE,
            .context_shift_win=(ps2_state.win_state<<1)|(sh_ps2_shift()),
            .context_alt=(ps2_state.l_alt_state<<1)|(ps2_state.r_alt_state),
            .context_ctrl=(ps2_state.l_ctrl_state<<1)|(ps2_state.r_ctrl_state),
            .context_lock_status=(ps2_state.capslock_state<<1)|(ps2_state.numlock_state),
            .event_type=SH_EVENT_TYPE_KBD_CONTEXT,
            .timestamp=sh_tsc_get_kernel_current_tsc()
          };
          SH_STATUS status=sh_kbd_push_events(ps2_state.d_handle,&ev);
          if (sh_status_error(status)) {
            sh_iprintf(SH_WARNING,"Error pushing events: status=%8s\n",status);
          }
          ps2_state.r_alt_state=SH_FALSE;
          status=sh_kbd_set_flag(ps2_state.d_handle,SH_KBD_FLAG_ALT,sh_ps2_alt());
          if (sh_status_error(status)) {
            sh_iprintf(SH_WARNING,"Error settings alt flags: status=%8s\n",status);
          }
          ps2_state.scancode_state=PS2_SCANCODE_STATE_NORMAL;
          return;
        }
        break;
      }
      case 0xC5: {
        // numlock released
        sh_kbd_EVENT ev={
          .scancode=SH_KBD_CONTEXT_NUMLOCK,
          .pressed=SH_FALSE,
          .context_shift_win=(ps2_state.win_state<<1)|(sh_ps2_shift()),
          .context_alt=(ps2_state.l_alt_state<<1)|(ps2_state.r_alt_state),
          .context_ctrl=(ps2_state.l_ctrl_state<<1)|(ps2_state.r_ctrl_state),
          .context_lock_status=(ps2_state.capslock_state<<1)|(ps2_state.numlock_state),
          .event_type=SH_EVENT_TYPE_KBD_CONTEXT,
          .timestamp=sh_tsc_get_kernel_current_tsc()
        };
        SH_STATUS status=sh_kbd_push_events(ps2_state.d_handle,&ev);
        if (sh_status_error(status)) {
          sh_iprintf(SH_WARNING,"Error pushing events: status=%8s\n",status);
        }
        ps2_state.numlock_state=ps2_state.numlock_state?SH_FALSE:SH_TRUE;
        status=sh_kbd_set_flag(ps2_state.d_handle,SH_KBD_FLAG_VER_NUM,ps2_state.numlock_state);
        if (sh_status_error(status)) {
          sh_iprintf(SH_WARNING,"Error settings numlock flags: status=%8s\n",status);
        }
        return;
      }
      case 0xBA: {
        // capslock released
        sh_kbd_EVENT ev={
          .scancode=SH_KBD_CONTEXT_CAPSLOCK,
          .pressed=SH_FALSE,
          .context_shift_win=(ps2_state.win_state<<1)|(sh_ps2_shift()),
          .context_alt=(ps2_state.l_alt_state<<1)|(ps2_state.r_alt_state),
          .context_ctrl=(ps2_state.l_ctrl_state<<1)|(ps2_state.r_ctrl_state),
          .context_lock_status=(ps2_state.capslock_state<<1)|(ps2_state.numlock_state),
          .event_type=SH_EVENT_TYPE_KBD_CONTEXT,
          .timestamp=sh_tsc_get_kernel_current_tsc()
        };
        SH_STATUS status=sh_kbd_push_events(ps2_state.d_handle,&ev);
        if (sh_status_error(status)) {
          sh_iprintf(SH_WARNING,"Error pushing events: status=%8s\n",status);
        }
        ps2_state.capslock_state=ps2_state.capslock_state?SH_FALSE:SH_TRUE;
        status=sh_kbd_set_flag(ps2_state.d_handle,SH_KBD_FLAG_VER_MAJ,ps2_state.capslock_state);
        if (sh_status_error(status)) {
          sh_iprintf(SH_WARNING,"Error settings capslock flags: status=%8s\n",status);
        }
        return;
      }
      case 0x45: {
        // numlock pressed
        sh_kbd_EVENT ev={
          .scancode=SH_KBD_CONTEXT_NUMLOCK,
          .pressed=SH_TRUE,
          .context_shift_win=(ps2_state.win_state<<1)|(sh_ps2_shift()),
          .context_alt=(ps2_state.l_alt_state<<1)|(ps2_state.r_alt_state),
          .context_ctrl=(ps2_state.l_ctrl_state<<1)|(ps2_state.r_ctrl_state),
          .context_lock_status=(ps2_state.capslock_state<<1)|(ps2_state.numlock_state),
          .event_type=SH_EVENT_TYPE_KBD_CONTEXT,
          .timestamp=sh_tsc_get_kernel_current_tsc()
        };
        SH_STATUS status=sh_kbd_push_events(ps2_state.d_handle,&ev);
        if (sh_status_error(status)) {
          sh_iprintf(SH_WARNING,"Error pushing events: status=%8s\n",status);
        }
        return;
      }
      case 0x3A: {
        // capslock pressed
        sh_kbd_EVENT ev={
          .scancode=SH_KBD_CONTEXT_CAPSLOCK,
          .pressed=SH_TRUE,
          .context_shift_win=(ps2_state.win_state<<1)|(sh_ps2_shift()),
          .context_alt=(ps2_state.l_alt_state<<1)|(ps2_state.r_alt_state),
          .context_ctrl=(ps2_state.l_ctrl_state<<1)|(ps2_state.r_ctrl_state),
          .context_lock_status=(ps2_state.capslock_state<<1)|(ps2_state.numlock_state),
          .event_type=SH_EVENT_TYPE_KBD_CONTEXT,
          .timestamp=sh_tsc_get_kernel_current_tsc()
        };
        SH_STATUS status=sh_kbd_push_events(ps2_state.d_handle,&ev);
        if (sh_status_error(status)) {
          sh_iprintf(SH_WARNING,"Error pushing events: status=%8s\n",status);
        }
        return;
      }
      default: {
        break;
      }
    }
  }
  if (ps2_state.scancode_state==PS2_SCANCODE_STATE_NORMAL) {
    if (data & (1u<<7)) {
      sh_kbd_EVENT ev={
        .scancode=0x0000|data,
        .pressed=SH_FALSE,
        .context_shift_win=(ps2_state.win_state<<1)|(sh_ps2_shift()),
        .context_alt=(ps2_state.l_alt_state<<1)|(ps2_state.r_alt_state),
        .context_ctrl=(ps2_state.l_ctrl_state<<1)|(ps2_state.r_ctrl_state),
        .context_lock_status=(ps2_state.capslock_state<<1)|(ps2_state.numlock_state),
        .event_type=SH_EVENT_TYPE_PS2_NORMAL,
        .timestamp=sh_tsc_get_kernel_current_tsc()
      };
      SH_STATUS status=sh_kbd_push_events(ps2_state.d_handle,&ev);
      if (sh_status_error(status)) {
        sh_iprintf(SH_WARNING,"Error pushing events: status=%8s\n",status);
      }
      return;
    } else {
      sh_kbd_EVENT ev={
        .scancode=0x0000|data,
        .pressed=SH_TRUE,
        .context_shift_win=(ps2_state.win_state<<1)|(sh_ps2_shift()),
        .context_alt=(ps2_state.l_alt_state<<1)|(ps2_state.r_alt_state),
        .context_ctrl=(ps2_state.l_ctrl_state<<1)|(ps2_state.r_ctrl_state),
        .context_lock_status=(ps2_state.capslock_state<<1)|(ps2_state.numlock_state),
        .event_type=SH_EVENT_TYPE_PS2_NORMAL,
        .timestamp=sh_tsc_get_kernel_current_tsc()
      };
      SH_STATUS status=sh_kbd_push_events(ps2_state.d_handle,&ev);
      if (sh_status_error(status)) {
        sh_iprintf(SH_WARNING,"Error pushing events: status=%8s\n",status);
      }
      return;
    }
  } else if (ps2_state.scancode_state==PS2_SCANCODE_STATE_E0) {
    if (data & (1u<<7)) {
      sh_kbd_EVENT ev={
        .scancode=0xE000|data,
        .pressed=SH_FALSE,
        .context_shift_win=(ps2_state.win_state<<1)|(sh_ps2_shift()),
        .context_alt=(ps2_state.l_alt_state<<1)|(ps2_state.r_alt_state),
        .context_ctrl=(ps2_state.l_ctrl_state<<1)|(ps2_state.r_ctrl_state),
        .context_lock_status=(ps2_state.capslock_state<<1)|(ps2_state.numlock_state),
        .event_type=SH_EVENT_TYPE_PS2_E0,
        .timestamp=sh_tsc_get_kernel_current_tsc()
      };
      SH_STATUS status=sh_kbd_push_events(ps2_state.d_handle,&ev);
      if (sh_status_error(status)) {
        sh_iprintf(SH_WARNING,"Error pushing events: status=%8s\n",status);
      }
      return;
    } else {
      sh_kbd_EVENT ev={
        .scancode=0xE000|data,
        .pressed=SH_TRUE,
        .context_shift_win=(ps2_state.win_state<<1)|(sh_ps2_shift()),
        .context_alt=(ps2_state.l_alt_state<<1)|(ps2_state.r_alt_state),
        .context_ctrl=(ps2_state.l_ctrl_state<<1)|(ps2_state.r_ctrl_state),
        .context_lock_status=(ps2_state.capslock_state<<1)|(ps2_state.numlock_state),
        .event_type=SH_EVENT_TYPE_PS2_E0,
        .timestamp=sh_tsc_get_kernel_current_tsc()
      };
      SH_STATUS status=sh_kbd_push_events(ps2_state.d_handle,&ev);
      if (sh_status_error(status)) {
        sh_iprintf(SH_WARNING,"Error pushing events: status=%8s\n",status);
      }
      return;
    }
  } else if (ps2_state.scancode_state==PS2_SCANCODE_STATE_E1) {
    ps2_state.e1_bytes_array[ps2_state.e1_bytes_idx]=data;
    ps2_state.e1_bytes_left--;
    ps2_state.e1_bytes_idx++;
    if (ps2_state.e1_bytes_idx==5 && ps2_state.e1_bytes_left==0) ps2_state.scancode_state=PS2_SCANCODE_STATE_NORMAL;
    sh_kbd_EVENT ev={
      .scancode=0,
      .pressed=SH_TRUE,
      .context_shift_win=(ps2_state.win_state<<1)|(sh_ps2_shift()),
      .context_alt=(ps2_state.l_alt_state<<1)|(ps2_state.r_alt_state),
      .context_ctrl=(ps2_state.l_ctrl_state<<1)|(ps2_state.r_ctrl_state),
      .context_lock_status=(ps2_state.capslock_state<<1)|(ps2_state.numlock_state),
      .event_type=SH_EVENT_TYPE_KBD_PAUSE,
      .timestamp=sh_tsc_get_kernel_current_tsc()
    };
    SH_STATUS status=sh_kbd_push_events(ps2_state.d_handle,&ev);
    if (sh_status_error(status)) {
      sh_iprintf(SH_WARNING,"Error pushing events: status=%8s\n",status);
    }
    return;
  }
}
void sh_ps2_irq1_handler() {
  sh_uint8 data;
  if (!sh_ps2_output_ready()) return;
  data=sh_asm_inb(SH_PS2_PORT_DATA);
  ps2_state.last_byte=data;
  if (ps2_state.command_in_progress) {
    sh_ps2_handle_command_response(data);
    return;
  }
  if (ps2_state.scanning_enabled) {
    sh_ps2_handle_scancodes(data);
  }
  return;
}
SH_STATUS sh_ps2_driver_init() {
  sh_asm_cli();
  // flush output buffer
  while (sh_ps2_output_ready()) {
    sh_asm_inb(SH_PS2_PORT_DATA);
  }
  // debug log status and config byte
  sh_printf(SH_DEBUG,"PS2 status byte initial value: 0x%1U\n",sh_ps2_read_status());
  sh_printf(SH_DEBUG,"PS2 config byte initial value: 0x%1U\n",sh_ps2_read_config());
  // disable ps2 ports
  while (sh_ps2_cant_write());
  sh_asm_outb(SH_PS2_PORT_COMMAND,SH_PS2_COMMAND_PORT1_DISABLE);
  while (sh_ps2_cant_write());
  sh_asm_outb(SH_PS2_PORT_COMMAND,SH_PS2_COMMAND_PORT2_DISABLE);
  // reflush output buffer
  while (sh_ps2_output_ready()) {
    sh_asm_inb(SH_PS2_PORT_DATA);
  }
  // disable IRQs
  sh_uint8 cfg=sh_ps2_read_config();
  cfg&= (sh_uint8)~SH_PS2_CONFIG_PORT1_IRQ;
  cfg&= (sh_uint8)~SH_PS2_CONFIG_PORT2_IRQ;
  sh_ps2_write_config(cfg);
  // ps2 self test
  while (sh_ps2_cant_write());
  sh_asm_outb(SH_PS2_PORT_COMMAND,SH_PS2_COMMAND_PS2_SELF_TEST);
  while (!sh_ps2_output_ready());
  sh_uint8 result=sh_asm_inb(SH_PS2_PORT_DATA);
  if (result!=SH_PS2_SELF_TEST_SUCCESS) return SH_STATUS_TEST_FAILED;
  // port1 test
  while (sh_ps2_cant_write());
  sh_asm_outb(SH_PS2_PORT_COMMAND,SH_PS2_COMMAND_PORT1_TEST);
  while (!sh_ps2_output_ready());
  result=sh_asm_inb(SH_PS2_PORT_DATA);
  if (result!=SH_PS2_PORTS_SELF_TEST_SUCCESS) return SH_STATUS_TEST_FAILED;
  // enable port1
  while (sh_ps2_cant_write());
  sh_asm_outb(SH_PS2_PORT_COMMAND,SH_PS2_COMMAND_PORT1_ENABLE);
  // enable translation
  cfg=sh_ps2_read_config();
  cfg|=SH_PS2_CONFIG_TRANSLATION;
  sh_ps2_write_config(cfg);
  // initialize ps2_state struct
  ps2_state.scancode_state=PS2_SCANCODE_STATE_NORMAL;
  ps2_state.command_state=PS2_COMMAND_STATE_IDLE;
  ps2_state.current_command.command=0;
  ps2_state.current_command.data=0;
  ps2_state.current_command.has_data=SH_FALSE;
  ps2_state.last_byte=0;
  ps2_state.e1_bytes_left=0;
  ps2_state.command_in_progress=SH_FALSE;
  ps2_state.keyboard_id[0]=0;
  ps2_state.keyboard_id[1]=0;
  ps2_state.keyboard_id_len=0;
  ps2_state.shift_state=SH_FALSE;
  ps2_state.l_alt_state=SH_FALSE;
  ps2_state.r_alt_state=SH_FALSE;
  ps2_state.l_ctrl_state=SH_FALSE;
  ps2_state.r_ctrl_state=SH_FALSE;
  ps2_state.win_state=SH_FALSE;
  ps2_state.capslock_state=SH_FALSE;
  ps2_state.numlock_state=SH_FALSE;
  // enabling IRQ1
  cfg=sh_ps2_read_config();
  cfg|=(sh_uint8)SH_PS2_CONFIG_PORT1_IRQ;
  sh_ps2_write_config(cfg);
  SH_STATUS status=sh_irq_legacy_register_handler(1,&sh_ps2_irq1_handler);
  if (sh_status_error(status)) return status;
  sh_asm_sti();
  // disabling scanning
  ps2_state.command_state=PS2_COMMAND_STATE_WAIT_ACK;
  sh_ps2_send_keyboard_command_wait(SH_PS2_KBD_COMMAND_DISABLE_SCANNING,0,SH_FALSE);
  // set leds
  ps2_state.command_state=PS2_COMMAND_STATE_WAIT_LED_ACK_1;
  sh_ps2_send_keyboard_command_wait(SH_PS2_KBD_COMMAND_SET_LEDS,0,SH_TRUE);
  // identify keyboard
  ps2_state.command_state=PS2_COMMAND_STATE_WAIT_IDENTIFY_ACK;
  sh_ps2_send_keyboard_command_wait(SH_PS2_KBD_COMMAND_IDENTIFY,0,SH_FALSE);
  sh_printf(SH_DEBUG,"PS2 identifier value : 0x%1U 0x%1U\n",ps2_state.keyboard_id[0],ps2_state.keyboard_id[1]);
  // register ps2 keyboard
  status=sh_kbd_register_ps2(SH_TRUE,&ps2_state.d_handle);
  if (sh_status_error(status)) return status;
  // enable scanning
  ps2_state.command_state=PS2_COMMAND_STATE_WAIT_ACK;
  sh_ps2_send_keyboard_command_wait(SH_PS2_KBD_COMMAND_ENABLE_SCANNING,0,SH_FALSE);
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_ps2_disable_scanning() {
  ps2_state.command_state=PS2_COMMAND_STATE_WAIT_ACK;
  sh_ps2_send_keyboard_command_wait(SH_PS2_KBD_COMMAND_DISABLE_SCANNING,0,SH_FALSE);
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_ps2_enable_scanning() {
  ps2_state.command_state=PS2_COMMAND_STATE_WAIT_ACK;
  sh_ps2_send_keyboard_command_wait(SH_PS2_KBD_COMMAND_ENABLE_SCANNING,0,SH_FALSE);
  return SH_STATUS_SUCCESS;
}
