// SPDX-License-Identifier: MPL-2.0
#include "../include/ui.h"
#include "../include/graphic.h"
#include "../include/debug.h"
#include <Uefi.h>
#include <Library/PrintLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Library/BaseLib.h>
#include <Protocol/SerialIo.h>
const UINT8 bootanim_header[8]={'B','o','o','t','A','n','i','m'};
EFI_STATUS bp_ui_draw_text_ascii(UINT32 *fb_base,bp_font_FBM_FONT_RENDER *font,INTN x,INTN y,UINTN width,UINTN heigth,CHAR8 *text,bp_ui_TEXT_BOX **handle) {
  if (fb_base==NULL || font==NULL || text==NULL) {
    return EFI_INVALID_PARAMETER;
  }
  if (width==0 || heigth==0) {
    *handle=NULL;
    return EFI_INVALID_PARAMETER;
  }
  if (x==-1) {
    x=(width/2)-((font->chars[0].width*AsciiStrLen(text))/2);
  }
  if (y==-1) {
    y=(heigth/2)-(font->chars[0].height/2);
  }
  *handle=AllocatePool(sizeof(bp_ui_TEXT_BOX));
  if (!*handle) {
    *handle=NULL;
    return EFI_OUT_OF_RESOURCES;
  }
  (*handle)->x=x;
  (*handle)->y=y;
  (*handle)->width=font->chars[0].width*AsciiStrLen(text);
  (*handle)->height=font->chars[0].height;
  UINTN *index_array=AllocatePool(AsciiStrLen(text)*sizeof(UINTN));
  if (!index_array) {
    FreePool(*handle);
    *handle=NULL;
    return EFI_OUT_OF_RESOURCES;
  }
  UINT32 codepoint=0xFF000000;
  UINTN not_found_index=0;
  BOOLEAN found=FALSE;
  for (UINTN y=0;y<font->entries_count;++y) {
    if (font->chars[y].codepoint==codepoint) {
      not_found_index=y;
      found=TRUE;
      break;
    }
  }
  if (!found) {
    FreePool(*handle);
    FreePool(index_array);
    *handle=NULL;
    return EFI_NOT_FOUND;
  }
  for (UINTN i=0;i<AsciiStrLen(text);++i) {
    UINT32 codepoint=((UINT32)text[i])<<24;
    UINTN index=0;
    BOOLEAN found=FALSE;
    for (UINTN j=0;j<font->entries_count;++j) {
      if (font->chars[j].codepoint==codepoint) {
        index=j;
        found=TRUE;
        break;
      }
    }
    if (!found) {
      index_array[i]=not_found_index;
    } else {
      index_array[i]=index;
    }
  }
  UINT32 *space_buf=AllocateZeroPool(font->chars[0].width*font->chars[0].height*sizeof(UINT32));
  for (UINTN i=0;i<AsciiStrLen(text);++i) {
    UINTN x_write=x+i*font->chars[0].width;
    if (x_write<width) {
      if (text[i]!=' ') {
        bp_graphic_copy_frame_part(fb_base,font->chars[index_array[i]].data,heigth,width,x_write,y,font->chars[index_array[i]].height,font->chars[index_array[i]].width);
      } else {
        bp_graphic_copy_frame_part(fb_base,space_buf,heigth,width,x_write,y,font->chars[index_array[i]].height,font->chars[index_array[i]].width);
      }
    }
  }
  FreePool(index_array);
  return EFI_SUCCESS;
}
EFI_STATUS bp_ui_render_boot_animation_4frames(bp_disk_FILE *boot_animation_file,UINTN screen_width,UINTN screen_height,UINTN frame_index,bp_ui_BOOT_ANIMATION **rendered_boot_animation) {
  if (!boot_animation_file || !rendered_boot_animation) {
    return EFI_INVALID_PARAMETER;
  }
  UINT64 w=0,h=0,frames_count=0,pixels_per_frame=0;
  CopyMem(&w,boot_animation_file->content+8,8);
  CopyMem(&h,boot_animation_file->content+16,8);
  CopyMem(&frames_count,boot_animation_file->content+24,8);
  CopyMem(&pixels_per_frame,boot_animation_file->content+32,8);
  if (frame_index>=frames_count) {
    return EFI_INVALID_PARAMETER;
  }
  if (*rendered_boot_animation==NULL) {
    *rendered_boot_animation=AllocatePool(sizeof(bp_ui_BOOT_ANIMATION));
    if (!*rendered_boot_animation) {
      return EFI_OUT_OF_RESOURCES;
    }
    (*rendered_boot_animation)->frames_count=4;
    (*rendered_boot_animation)->frames=AllocatePool(sizeof(bp_ui_BOOT_ANIMATION_FRAME)*4);
    if (!(*rendered_boot_animation)->frames) {
      FreePool(*rendered_boot_animation);
      *rendered_boot_animation=NULL;
      return EFI_OUT_OF_RESOURCES;
    }
  }
  double scale_x=(double)screen_width/(double)w;
  double scale_y=(double)screen_height/(double)h;
  UINT16 *frame0=AllocatePool(sizeof(UINT16)*2*pixels_per_frame);
  UINT16 *frame1=AllocatePool(sizeof(UINT16)*2*pixels_per_frame);
  if (!frame0 || !frame1) {
    return EFI_OUT_OF_RESOURCES;
  }
  UINT8 *cursor=boot_animation_file->content+40+frame_index*sizeof(UINT16)*2*pixels_per_frame;
  CopyMem(frame0,cursor,sizeof(UINT16)*2*pixels_per_frame);
  cursor+=sizeof(UINT16)*2*pixels_per_frame;
  CopyMem(frame1,cursor,sizeof(UINT16)*2*pixels_per_frame);
  for (UINTN f=0;f<4;f++) {
    bp_ui_BOOT_ANIMATION_FRAME *dst=&(*rendered_boot_animation)->frames[f];
    dst->pixels_count=screen_width*screen_height;
    dst->pixels=AllocatePool(sizeof(UINT32)*screen_width*screen_height);
    if (!dst->pixels) {
      for (UINTN j=0;j<f;j++) {
        FreePool((*rendered_boot_animation)->frames[j].pixels);
      }
      FreePool(frame0);
      FreePool(frame1);
      FreePool((*rendered_boot_animation)->frames);
      FreePool(*rendered_boot_animation);
      *rendered_boot_animation=NULL;
      return EFI_OUT_OF_RESOURCES;
    }
    SetMem32(dst->pixels,sizeof(UINT32)*screen_width*screen_height,0);
    double t;
    if (f!=0) {
      t=(f+1)/4.0;
    } else {
      t=0.0;
    }
    for (UINTN p=0;p<pixels_per_frame;p++) {
      UINT16 x0=frame0[p*2];
      UINT16 y0=frame0[p*2+1];
      UINT16 x1=frame1[p*2];
      UINT16 y1=frame1[p*2+1];
      UINTN x_interp=(UINTN)((x0*(1.0-t)+x1*t)*scale_x);
      UINTN y_interp=(UINTN)((y0*(1.0-t)+y1*t)*scale_y);
      if (x_interp>=screen_width)x_interp=screen_width-1;
      if (y_interp>=screen_height)y_interp=screen_height-1;
      dst->pixels[y_interp*screen_width+x_interp]=0xFFFFFFFF;
    }
  }
  FreePool(frame0);
  FreePool(frame1);
  return EFI_SUCCESS;
}
EFI_STATUS bp_ui_render_boot_animation_last_frame(bp_disk_FILE *boot_animation_file,UINTN screen_width,UINTN screen_height,bp_ui_BOOT_ANIMATION_FRAME **rendered_frame) {
  if (!boot_animation_file || !rendered_frame) {
    return EFI_INVALID_PARAMETER;
  }
  UINT64 w=0,h=0,frames_count=0,pixels_per_frame=0;
  CopyMem(&w,boot_animation_file->content+8,8);
  CopyMem(&h,boot_animation_file->content+16,8);
  CopyMem(&frames_count,boot_animation_file->content+24,8);
  CopyMem(&pixels_per_frame,boot_animation_file->content+32,8);
  if (*rendered_frame==NULL) {
    *rendered_frame=AllocateZeroPool(sizeof(bp_ui_BOOT_ANIMATION_FRAME));
    if (!*rendered_frame) {
      return EFI_OUT_OF_RESOURCES;
    }
    (*rendered_frame)->pixels=AllocateZeroPool(screen_height*screen_width*sizeof(UINT32));
    if (!(*rendered_frame)->pixels) {
      FreePool(*rendered_frame);
    }
  }
  double scale_x=(double)screen_width/(double)w;
  double scale_y=(double)screen_height/(double)h;
  UINT16 *frame=AllocateZeroPool(sizeof(UINT16)*2*pixels_per_frame);
  if (!frame) {
    FreePool(*rendered_frame);
    FreePool((*rendered_frame)->pixels);
    return EFI_OUT_OF_RESOURCES;
  }
  UINTN size=+40+frames_count*sizeof(UINT16)*2*pixels_per_frame;
  UINT8 *cursor=boot_animation_file->content+size-sizeof(UINT16)*2*pixels_per_frame;
  CopyMem(frame,cursor,sizeof(UINT16)*2*pixels_per_frame);
  for (INTN i=0;i<pixels_per_frame;++i) {
    UINT16 x=frame[i*2]*scale_x;
    UINT16 y=frame[i*2+1]*scale_y;
    if (x>=screen_width)x=screen_width-1;
    if (y>=screen_height)y=screen_height-1;
    (*rendered_frame)->pixels[y*screen_width+x]=0xFFFFFFFF;
    (*rendered_frame)->pixels[y*screen_width+x+1]=0xFFFFFFFF;
    (*rendered_frame)->pixels[(y+1)*screen_width+x]=0xFFFFFFFF;
    (*rendered_frame)->pixels[(y+1)*screen_width+x+1]=0xFFFFFFFF;
    (*rendered_frame)->pixels[y*screen_width+x-1]=0xFFFFFFFF;
    (*rendered_frame)->pixels[(y-1)*screen_width+x]=0xFFFFFFFF;
    (*rendered_frame)->pixels[(y-1)*screen_width+x-1]=0xFFFFFFFF;
    (*rendered_frame)->pixels[(y-1)*screen_width+x+1]=0xFFFFFFFF;
    (*rendered_frame)->pixels[(y+1)*screen_width+x-1]=0xFFFFFFFF;
  }
  FreePool(frame);
  return EFI_SUCCESS;
}
EFI_STATUS bp_ui_render_boot_animation_last_frame_color(bp_disk_FILE *boot_animation_file,UINTN screen_width,UINTN screen_height,UINT32 color,bp_ui_BOOT_ANIMATION_FRAME **rendered_frame) {
  if (!boot_animation_file || !rendered_frame) {
    return EFI_INVALID_PARAMETER;
  }
  UINT64 w=0,h=0,frames_count=0,pixels_per_frame=0;
  CopyMem(&w,boot_animation_file->content+8,8);
  CopyMem(&h,boot_animation_file->content+16,8);
  CopyMem(&frames_count,boot_animation_file->content+24,8);
  CopyMem(&pixels_per_frame,boot_animation_file->content+32,8);
  if (*rendered_frame==NULL) {
    *rendered_frame=AllocateZeroPool(sizeof(bp_ui_BOOT_ANIMATION_FRAME));
    if (!*rendered_frame) {
      return EFI_OUT_OF_RESOURCES;
    }
    (*rendered_frame)->pixels=AllocateZeroPool(screen_height*screen_width*sizeof(UINT32));
    if (!(*rendered_frame)->pixels) {
      FreePool(*rendered_frame);
    }
  }
  double scale_x=(double)screen_width/(double)w;
  double scale_y=(double)screen_height/(double)h;
  UINT16 *frame=AllocateZeroPool(sizeof(UINT16)*2*pixels_per_frame);
  if (!frame) {
    FreePool(*rendered_frame);
    FreePool((*rendered_frame)->pixels);
    return EFI_OUT_OF_RESOURCES;
  }
  UINTN size=+40+frames_count*sizeof(UINT16)*2*pixels_per_frame;
  UINT8 *cursor=boot_animation_file->content+size-sizeof(UINT16)*2*pixels_per_frame;
  CopyMem(frame,cursor,sizeof(UINT16)*2*pixels_per_frame);
  for (INTN i=0;i<pixels_per_frame;++i) {
    UINT16 x=frame[i*2]*scale_x;
    UINT16 y=frame[i*2+1]*scale_y;
    if (x>=screen_width)x=screen_width-1;
    if (y>=screen_height)y=screen_height-1;
    (*rendered_frame)->pixels[y*screen_width+x]=color;
    (*rendered_frame)->pixels[y*screen_width+x+1]=color;
    (*rendered_frame)->pixels[(y+1)*screen_width+x]=color;
    (*rendered_frame)->pixels[(y+1)*screen_width+x+1]=color;
    (*rendered_frame)->pixels[y*screen_width+x-1]=color;
    (*rendered_frame)->pixels[(y-1)*screen_width+x]=color;
    (*rendered_frame)->pixels[(y-1)*screen_width+x-1]=color;
    (*rendered_frame)->pixels[(y-1)*screen_width+x+1]=color;
    (*rendered_frame)->pixels[(y+1)*screen_width+x-1]=color;
  }
  FreePool(frame);
  (*rendered_frame)->pixels_count=screen_width*screen_height;
  return EFI_SUCCESS;
}
EFI_STATUS bp_ui_show_error_screen(EFI_SERIAL_IO_PROTOCOL *serial,CHAR8 *error_code,UINT32* fb_base,bp_font_FBM_FONT_RENDER *font,UINTN horizontal_resolution,UINTN vertical_resoltion,bp_ui_BOOT_ANIMATION_FRAME *background_logo,BOOLEAN allow_erroring) {
  EFI_STATUS status;
  CHAR8 temp1[256];
  CHAR8 temp2[256];
  if (fb_base==NULL || font==NULL || background_logo==NULL || error_code==NULL) {
    AsciiSPrint(temp1,256,"[BlastProof:BlastUi] Error: Couldn't display error screen. Given error code: %s",error_code);
    bp_debug_write_line(serial,temp1,allow_erroring);
    bp_debug_write_line(serial,"[BlastProof:BlastUi] Hint for why this error happen: bad pointer.",allow_erroring);
    return EFI_INVALID_PARAMETER;
  }
  if (AsciiStrnLenS(error_code,128)==0) {
    AsciiSPrint(temp1,256,"[BlastProof:BlastUi]: Couldn't display error screen.",error_code);
    bp_debug_write_line(serial,temp1,allow_erroring);
    bp_debug_write_line(serial,"[BlastProof:BlastUi] Hint for why this error happen: error code empty.",allow_erroring);
    return EFI_INVALID_PARAMETER;
  }
  SetMem32(fb_base,horizontal_resolution*vertical_resoltion,0x00000000);
  for (UINTN i=0;i<vertical_resoltion;i++) {
    CopyMem(fb_base+i*horizontal_resolution,background_logo->pixels+i*horizontal_resolution,horizontal_resolution*sizeof(UINT32));
  }
  AsciiSPrint(temp1,256,"An error happened. Please reboot your computer.");
  bp_ui_TEXT_BOX *handle_1;
  status=bp_ui_draw_text_ascii(fb_base,font,-1,vertical_resoltion*0.85,horizontal_resolution,vertical_resoltion,temp1,&handle_1);
  if (EFI_ERROR(status)) {
    AsciiSPrint(temp1,256,"[BlastProof:BlastUi]: Couldn't display error screen. Given error code: %s",error_code);
    bp_debug_write_line(serial,temp1,allow_erroring);
    bp_debug_write_line(serial,"[BlastProof:BlastUi] Hint for why this error happen: couldn't display first line.",allow_erroring);
    return status;
  }
  AsciiSPrint(temp2,256,"Error code: %a",error_code);
  bp_ui_TEXT_BOX *handle_2;
  status=bp_ui_draw_text_ascii(fb_base,font,-1,vertical_resoltion*0.85+16,horizontal_resolution,vertical_resoltion,temp2,&handle_2);
  if (EFI_ERROR(status)) {
    AsciiSPrint(temp1,256,"[BlastProof:BlastUi]: Couldn't display error screen. Given error code: %s",error_code);
    bp_debug_write_line(serial,temp1,allow_erroring);
    bp_debug_write_line(serial,"[BlastProof:BlastUi] Hint for why this error happen: couldn't display second line.",allow_erroring);
    return status;
  }
  return EFI_SUCCESS;
}
EFI_STATUS bp_ui_erase_text(UINT32 *fb_base,UINTN screen_width,UINTN screen_heigth,bp_ui_TEXT_BOX *text_box) {
  if (fb_base==NULL || text_box==NULL) {
    return EFI_INVALID_PARAMETER;
  }
  if (text_box->x+text_box->width>screen_width || text_box->y+text_box->height>screen_heigth) {
    return EFI_INVALID_PARAMETER;
  }
  for (UINTN y=0;y<text_box->height;y++) {
    SetMem32(fb_base+(text_box->y+y)*screen_width+text_box->x,text_box->width*sizeof(UINT32),0x00000000);
  }
  return EFI_SUCCESS;
}
