// SPDX-License-Identifier: MPL-2.0
#include "../include/graphic.h"
#include <Uefi.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Library/BaseLib.h>
#include <Protocol/GraphicsOutput.h>
EFI_STATUS bp_graphic_get_mode_count(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop,UINT32 *output) {
  if (gop==NULL || output==NULL) {
    return EFI_INVALID_PARAMETER;
  }
  if (gop->Mode==NULL) {
    return EFI_DEVICE_ERROR;
  }
  *output=gop->Mode->MaxMode;
  return EFI_SUCCESS;
}
EFI_STATUS bp_graphic_get_highest_resolution_index(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop,UINT32 *output) {
  EFI_STATUS status;
  if (gop==NULL || output==NULL) {
    return EFI_INVALID_PARAMETER;
  }
  UINT32 protsnum;
  status=bp_graphic_get_mode_count(gop,&protsnum);
  if (EFI_ERROR(status) || protsnum==0) {
    return EFI_DEVICE_ERROR;
  }
  if (protsnum>1) {
    UINT32 vertical_res;
    UINT32 horizontal_res;
    EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *prot_info;
    UINTN info_size;
    status=gop->QueryMode(gop,0,&info_size,&prot_info);
    if (EFI_ERROR(status)) {
      return EFI_DEVICE_ERROR;
    } else {
      vertical_res=prot_info->VerticalResolution;
      horizontal_res=prot_info->HorizontalResolution;
      *output=0;
    }
    for (INTN i=1;i<protsnum;++i) {
      status=gop->QueryMode(gop,i,&info_size,&prot_info);
      if (EFI_ERROR(status)) {
        return EFI_DEVICE_ERROR;
      }
      UINTN max_pixels=horizontal_res*vertical_res;
      UINTN cur_pixels=prot_info->HorizontalResolution*prot_info->VerticalResolution;
      if (cur_pixels>max_pixels) {
        vertical_res=prot_info->VerticalResolution;
        horizontal_res=prot_info->HorizontalResolution;
        *output=i;
      }
    }
  } else {
    *output=0;
  }
  return EFI_SUCCESS;
}
EFI_STATUS bp_graphic_get_index_by_resolution(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop,UINT64 hor_res,UINT64 vert_res,UINT32 *output) {
  if (gop==NULL || output==NULL || (hor_res==0 && vert_res==0)) {
    return EFI_INVALID_PARAMETER;
  }
  UINT32 mode_count;
  EFI_STATUS status=bp_graphic_get_mode_count(gop,&mode_count);
  if (EFI_ERROR(status)) {
    return EFI_DEVICE_ERROR;
  }
  *output=0xFFFFFFFF;
  for (UINT32 i=0;i<mode_count;i++) {
    EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
    UINTN info_size;
    status=gop->QueryMode(gop,i,&info_size,&info);
    if (EFI_ERROR(status)) {
      return EFI_DEVICE_ERROR;
    }
    if (info->HorizontalResolution==hor_res && info->VerticalResolution==vert_res) {
      *output=i;
      return EFI_SUCCESS;
    }
  }
  return EFI_NOT_FOUND;
}
STATIC UINT32 bp_graphic_get_shift_from_mask (UINT32 mask) {
  UINT32 shift=0;
  while (((mask>>shift)&1)==0 && shift<32) {
    shift++;
  }
  return shift;
}
STATIC UINT32 bp_graphic_get_width_from_mask (UINT32 mask) {
  UINT32 shift=bp_graphic_get_shift_from_mask(mask);
  UINT32 width=0;
  mask>>=shift;
  while ((mask & 1)==1) {
    width++;
    mask>>=1;
  }
  return width;
}
UINT32 bp_graphic_get_pixel_by_color(bp_graphic_COLOR_RGB *color,UINT32 red_bits,UINT32 green_bits,UINT32 blue_bits) {
  UINT32 pixel=0;
  UINT32 r_shift=bp_graphic_get_shift_from_mask(red_bits);
  UINT32 g_shift=bp_graphic_get_shift_from_mask(green_bits);
  UINT32 b_shift=bp_graphic_get_shift_from_mask(blue_bits);
  UINT32 r_width=bp_graphic_get_width_from_mask(red_bits);
  UINT32 g_width=bp_graphic_get_width_from_mask(green_bits);
  UINT32 b_width=bp_graphic_get_width_from_mask(blue_bits);
  UINT32 r=color->r>>(8-r_width);
  UINT32 g=color->g>>(8-g_width);
  UINT32 b=color->b>>(8-b_width);
  pixel|=(r<<r_shift);
  pixel|=(g<<g_shift);
  pixel|=(b<<b_shift);
  return pixel;
}
EFI_STATUS bp_graphic_copy_frame_part(UINT32 *framebuffer_base,UINT32 *from_base,UINTN height_out,UINTN width_out,UINTN x,UINTN y,UINTN height_from,UINTN width_from) {
  if (framebuffer_base==NULL || from_base==NULL) {
    return EFI_INVALID_PARAMETER;
  }
  UINTN copy_width;
  if (x>=width_out) {
    copy_width=0;
  } else {
    UINTN avail=width_out-x;
    copy_width=(width_from<=avail)?width_from:avail;
  }
  UINTN copy_height;
  if (y>=height_out) {
    copy_height=0;
  } else {
    UINTN avail=height_out-y;
    copy_height=(height_from<=avail)?height_from:avail;
  }
  BOOLEAN fully_inside=(copy_width==width_from) && (copy_height==height_from);
  for (UINTN yfor=0;yfor<copy_height;++yfor) {
    UINTN dst_index=(y+yfor)*width_out+x;
    UINTN src_index=yfor*width_from;
    CopyMem(framebuffer_base+dst_index,from_base+src_index,copy_width*sizeof(UINT32));
  }
  if (fully_inside) {
    return EFI_SUCCESS;
  } else {
    return EFI_WARN_BUFFER_TOO_SMALL;
  }
}
