// SPDX-License-Identifier: MPL-2.0
#include "../include/console.h"
#include "../include/layout.h"
#include <Uefi.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Library/BaseLib.h>
#include <Library/PrintLib.h>
#include <Protocol/SimpleTextIn.h>
#include <Guid/GlobalVariable.h>
VOID bp_console_clear_screen() {
  gST->ConOut->ClearScreen(gST->ConOut);
  return;
}
VOID bp_console_set_console_mode() {
  UINTN current_mode=gST->ConOut->Mode->Mode;
  UINTN max_cols;
  UINTN max_rows;
  UINTN new_mode=current_mode;
  EFI_STATUS status=gST->ConOut->QueryMode(gST->ConOut,current_mode,&max_cols,&max_rows);
  if (EFI_ERROR(status)) {
    Print(L"[BlastConsole] Error: couldn't identify current mode.\n");
    while (TRUE) {};
  }
  for (INT32 mode=0;mode<gST->ConOut->Mode->MaxMode;mode++) {
    UINTN cols;
    UINTN rows;
    if (EFI_ERROR(gST->ConOut->QueryMode(gST->ConOut,mode,&cols,&rows))) {
      continue;
    }
    if ((cols>max_cols && !(rows<max_rows)) || (rows>max_rows && !(cols<max_cols))) {
      max_cols=cols;
      max_rows=rows;
      new_mode=mode;
    }
  }
  status=gST->ConOut->SetMode(gST->ConOut,new_mode);
  if (!EFI_ERROR(status)) {
    bp_console_clear_screen();
  }
  return;
}
EFI_STATUS bp_console_clear_row(UINTN row) {
  UINTN current_mode=gST->ConOut->Mode->Mode;
  UINTN cols;
  UINTN rows;
  EFI_STATUS status=gST->ConOut->QueryMode(gST->ConOut,current_mode,&cols,&rows);
  if (EFI_ERROR(status)) {
    Print(L"[BlastConsole] Error: couldn't identify current mode.\n");
    while (TRUE) {};
  }
  if (row>=rows) return EFI_INVALID_PARAMETER;
  CHAR16 *buf=AllocatePool(sizeof(CHAR16)*cols+2);
  if (buf==NULL) return EFI_OUT_OF_RESOURCES;
  for (int i=0;i<cols;i++) {
    buf[i]=L' ';
  }
  buf[cols]=L'\0';
  gST->ConOut->SetCursorPosition(gST->ConOut,0,row);
  Print(buf);
  FreePool(buf);
  return EFI_SUCCESS;
}
EFI_STATUS bp_console_write_centered(CHAR16 *text,UINTN row) {
  if (text==NULL) return EFI_INVALID_PARAMETER;
  UINTN current_mode=gST->ConOut->Mode->Mode;
  UINTN cols;
  UINTN rows;
  EFI_STATUS status=gST->ConOut->QueryMode(gST->ConOut,current_mode,&cols,&rows);
  if (EFI_ERROR(status)) {
    Print(L"[BlastConsole] Error: couldn't identify current mode.\n");
    while (TRUE) {};
  }
  if (row>=rows) return EFI_INVALID_PARAMETER;
  UINTN text_len=StrnLenS(text,cols);
  UINTN starting_x=(cols/2)-(text_len/2);
  bp_console_clear_row(row);
  gST->ConOut->SetCursorPosition(gST->ConOut,starting_x,row);
  Print(L"%s",text);
  return EFI_SUCCESS;
}
VOID bp_console_print_file(bp_disk_FILE *file) {
  for (UINTN i=0;i<file->file_size_bytes;i++) {
    CHAR8 c=file->content[i];
    if (c=='\n') {
      Print(L"\r\n");
    } else {
      Print(L"%c",(CHAR16)c);
    }
  }
}
EFI_STATUS bp_console_read_password(CHAR16 *prompt,bp_console_PASSWORD **pwd) {
  EFI_STATUS status;
  if (prompt==NULL || pwd==NULL) {
    return EFI_INVALID_PARAMETER;
  }
  UINTN index=BP_CONSOLE_DEFAULT_LAYOUT_INDEX;
  UINTN layout_count=bp_layout_get_layout_count();
  if (layout_count==0) return EFI_NOT_FOUND;
  CHAR16 **layouts=bp_layout_get_layout_list();
  UINTN current_mode=gST->ConOut->Mode->Mode;
  UINTN cols;
  UINTN rows;
  status=gST->ConOut->QueryMode(gST->ConOut,current_mode,&cols,&rows);
  if (EFI_ERROR(status)) {
    Print(L"[BlastConsole] Error: couldn't identify current mode.\n");
    while (TRUE) {};
  }
  bp_console_write_centered(prompt,rows/2-3);
  gST->ConOut->EnableCursor(gST->ConOut,FALSE);
  EFI_INPUT_KEY key;
  UINTN buf_size=16;
  UINTN length=0;
  CHAR16 *buffer=AllocatePool(buf_size*sizeof(CHAR16));
  if (buffer==NULL) {
    return EFI_OUT_OF_RESOURCES;
  }
  bp_console_write_centered(L"[No input detected yet]",rows/2-1);
  bp_console_write_centered(L"Current keyboard layout:",rows/2+1);
  CHAR16 layout_buf[128];
  UnicodeSPrint(layout_buf,sizeof(layout_buf),L"<- %s (%d/%d) ->",layouts[index],index+1,layout_count);
  bp_console_write_centered(layout_buf,rows/2+2);
  bp_console_write_centered(L"[F1] Shutdown device",rows/2+4);
  bp_console_write_centered(L"[F2] Reboot device  ",rows/2+5);
  bp_console_write_centered(L"[F3] UEFI firmware  ",rows/2+6);
  while (TRUE) {
    status=gST->ConIn->ReadKeyStroke(gST->ConIn,&key);
    if (status==EFI_NOT_READY) {
      continue;
    } else if (EFI_ERROR(status)) {
      FreePool(buffer);
      return status;
    }
    if (key.UnicodeChar==CHAR_CARRIAGE_RETURN) {
      break;
    } else if (key.ScanCode==SCAN_RIGHT) {
      index=(index+1)%layout_count;
      UnicodeSPrint(layout_buf,sizeof(layout_buf),L"<- %s (%d/%d) ->",layouts[index],index+1,layout_count);
      bp_console_write_centered(layout_buf,rows/2+2);
    } else if (key.ScanCode==SCAN_LEFT) {
      index=(index+layout_count-1)%layout_count;
      UnicodeSPrint(layout_buf,sizeof(layout_buf),L"<- %s (%d/%d) ->",layouts[index],index+1,layout_count);
      bp_console_write_centered(layout_buf,rows/2+2);
    } else if (key.ScanCode==SCAN_F1) {
      gRT->ResetSystem(EfiResetShutdown,EFI_SUCCESS,0,NULL);
    } else if (key.ScanCode==SCAN_F2) {
      gRT->ResetSystem(EfiResetCold,EFI_SUCCESS,0,NULL);
    } else if (key.ScanCode==SCAN_F3) {
      UINT64 os_indications;
      UINTN size=sizeof(os_indications);
      status=gRT->GetVariable(L"OsIndications",&gEfiGlobalVariableGuid,NULL,&size,&os_indications);
      if (EFI_ERROR(status)) os_indications=0;
      os_indications|=EFI_OS_INDICATIONS_BOOT_TO_FW_UI;
      status=gRT->SetVariable(L"OsIndications",&gEfiGlobalVariableGuid,EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,sizeof(os_indications),&os_indications);
      if (!EFI_ERROR(status)) {
        gRT->ResetSystem(EfiResetCold,EFI_SUCCESS,0,NULL);
      } else {
        bp_console_write_centered(L"Reboot to firmware failed. Please reboot manually.",rows/2+8);
      }
    } else if (key.UnicodeChar==CHAR_BACKSPACE) {
      if (length>0) {
        length--;
        buffer[length]='\0';
        if (length==0) {
          bp_console_write_centered(L"[No input detected yet]",rows/2-1);
        } else {
          bp_console_write_centered(L"[Hidden input active]",rows/2-1);
        }
      }
    } else if (key.UnicodeChar>=L' ' && key.UnicodeChar<=0x7E) {
      if (length+1>=buf_size) {
        CHAR16 *new_buf=AllocatePool(buf_size*2*sizeof(CHAR16));
        if (new_buf==NULL) {
          FreePool(buffer);
          return EFI_OUT_OF_RESOURCES;
        }
        CopyMem(new_buf,buffer,(length+1)*sizeof(CHAR16));
        FreePool(buffer);
        buffer=new_buf;
        buf_size*=2;
      }
      buffer[length]=bp_layout_convert_char(key.UnicodeChar,index);
      if (length==0) {
        bp_console_write_centered(L"[Hidden input active]",rows/2-1);
      }
      length++;
    }
  }
  buffer[length]=L'\0';
  *pwd=AllocatePool(sizeof(bp_console_PASSWORD));
  if (*pwd==NULL) {
    FreePool(buffer);
    return EFI_OUT_OF_RESOURCES;
  }
  (*pwd)->password=buffer;
  (*pwd)->size=length;
  bp_console_clear_screen();
  gST->ConOut->SetCursorPosition(gST->ConOut,0,0);
  return EFI_SUCCESS;
}
