// SPDX-License-Identifier: MPL-2.0
#include "../include/font.h"
#include <Uefi.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
const UINT8 fbm_sign[3]={'F','B','M'};
EFI_STATUS bp_font_get_header(bp_disk_FILE *fbm_file,bp_font_FBM_HEADER **header) {
  if (fbm_file==NULL) {
    return EFI_INVALID_PARAMETER;
  }
  *header=AllocatePool(sizeof(bp_font_FBM_HEADER));
  if (!*header) {
    return EFI_OUT_OF_RESOURCES;
  }
  if (fbm_file->file_size_bytes<10) {
    return EFI_COMPROMISED_DATA;
  }
  if (CompareMem(fbm_file->content,fbm_sign,3)!=0) {
    return EFI_UNSUPPORTED;
  }
  CopyMem(&(*header)->encoding,fbm_file->content+3,1);
  CopyMem(&(*header)->entries_count,fbm_file->content+4,4);
  CopyMem(&(*header)->width,fbm_file->content+8,1);
  CopyMem(&(*header)->height,fbm_file->content+9,1);
  return EFI_SUCCESS;
}
EFI_STATUS bp_font_load_font(bp_disk_FILE *fbm_file,bp_font_FBM_FONT **loaded_font) {
  EFI_STATUS status;
  if (fbm_file==NULL) {
    return EFI_INVALID_PARAMETER;
  }
  bp_font_FBM_HEADER *header;
  status=bp_font_get_header(fbm_file,&header);
  if (EFI_ERROR(status)) {
    return status;
  }
  UINTN size_glyph_bytes=header->height*header->width;
  if (fbm_file->file_size_bytes!=10+(size_glyph_bytes+4)*header->entries_count) {
    FreePool(header);
    return EFI_COMPROMISED_DATA;
  }
  if (size_glyph_bytes==0 || header->entries_count==0) {
    FreePool(header);
    return EFI_UNSUPPORTED;
  }
  bp_font_FBM_FONT *font=AllocatePool(sizeof(bp_font_FBM_FONT));
  if (!font) {
    FreePool(header);
    return EFI_OUT_OF_RESOURCES;
  }
  font->entries_count=header->entries_count;
  font->chars=AllocatePool(sizeof(bp_font_FBM_CHAR)*header->entries_count);
  if (!font->chars) {
    FreePool(font);
    FreePool(header);
    return EFI_OUT_OF_RESOURCES;
  }
  UINT8 *cursor=fbm_file->content+10;
  for (UINT32 i=0;i<header->entries_count;++i) {
    CopyMem(&(font->chars[i].codepoint),cursor,4);
    cursor+=4;
    font->chars[i].data=AllocatePool(size_glyph_bytes);
    if (!font->chars[i].data) {
      for (UINT32 j=0;j<i;++j) {
        if (font->chars[j].data) {
          FreePool(font->chars[j].data);
        }
      }
      FreePool(font->chars);
      FreePool(font);
      FreePool(header);
      return EFI_OUT_OF_RESOURCES;
    }
    CopyMem(font->chars[i].data,cursor,size_glyph_bytes);
    cursor+=size_glyph_bytes;
  }
  FreePool(header);
  *loaded_font=font;
  return EFI_SUCCESS;
}
EFI_STATUS bp_font_check_font(bp_font_FBM_FONT *font,bp_font_FBM_HEADER *header) {
  if (font==NULL || header==NULL) {
    return EFI_INVALID_PARAMETER;
  }
  UINTN size_glyph_bytes=header->height*header->width;
  if (size_glyph_bytes==0 || header->entries_count==0 || font->entries_count==0 || header->entries_count!=font->entries_count) {
    FreePool(header);
    return EFI_UNSUPPORTED;
  }
  for (UINT32 i=0;i<font->entries_count;++i) {
    BOOLEAN check1=(BOOLEAN)(((font->chars[i].codepoint>>24)>>1) & 1);
    BOOLEAN check2=(BOOLEAN)(((font->chars[i].codepoint>>16)>>3) & 1);
    BOOLEAN check3=(BOOLEAN)(((font->chars[i].codepoint>>8)>>5) & 1);
    for (UINTN y=0;y<size_glyph_bytes;++y) {
      BOOLEAN c1=(BOOLEAN)((font->chars[i].data[y]>>6)&0x01);
      BOOLEAN c2=(BOOLEAN)((font->chars[i].data[y]>>4)&0x01);
      BOOLEAN c3=(BOOLEAN)((font->chars[i].data[y]>>2)&0x01);
      if (c1!=check1 || c2!=check2 || c3!=check3) {
        return EFI_VOLUME_CORRUPTED;
      }
    }
    if (header->encoding==1) {
      UINT8 codepoint=(UINT8)((font->chars[i].codepoint>>24) & 0xFF);
      if (codepoint>0x7F && codepoint<0xFF) {
        return EFI_UNSUPPORTED;
      }
    }
  }
  return EFI_SUCCESS;
}
EFI_STATUS bp_font_parse_font(bp_font_FBM_FONT *font,bp_font_FBM_HEADER *header,bp_font_FBM_FONT_BUFFER **parsed_font) {
  if (font==NULL || header==NULL) {
    return EFI_INVALID_PARAMETER;
  }
  UINTN size_glyph_bytes=header->height*header->width;
  if (size_glyph_bytes==0 || header->entries_count==0 || font->entries_count==0 || header->entries_count!=font->entries_count) {
    return EFI_UNSUPPORTED;
  }
  bp_font_FBM_FONT_BUFFER *font_buffer=AllocatePool(sizeof(bp_font_FBM_FONT_BUFFER));
  if (!font_buffer) {
    return EFI_OUT_OF_RESOURCES;
  }
  font_buffer->entries_count=header->entries_count;
  font_buffer->chars=AllocatePool(sizeof(bp_font_FBM_CHAR_BUFFER)*header->entries_count);
  if (!font_buffer->chars) {
    FreePool(font_buffer);
    return EFI_OUT_OF_RESOURCES;
  }
  for (UINT32 i=0;i<font->entries_count;++i) {
    bp_font_FBM_CHAR_BUFFER *char_buf=&font_buffer->chars[i];
    char_buf->data=AllocatePool(sizeof(bp_font_FBM_PIXEL)*header->height*header->width);
    if (!char_buf->data) {
      for (INTN y=0;y<i;++y) {
        FreePool(font_buffer->chars[y].data);
      }
      FreePool(font_buffer->chars);
      FreePool(font_buffer);
      return EFI_OUT_OF_RESOURCES;
    }
    char_buf->codepoint=font->chars[i].codepoint;
    for (INTN p=0;p<header->height*header->width;p++) {
      bp_font_FBM_PIXEL *pixel=&char_buf->data[p];
      UINT8 r=font->chars[i].data[p];
      pixel->enabled=(BOOLEAN)(r & 0x01);
      UINT8 shading=(((r>>7)&1)<<3)|(((r>>5)&1)<<2)|(((r>>3)&1)<<1)|(((r>>1)&1)<<0);
      pixel->shading=shading;
    }
  }
  for (UINT32 i=0;i<font->entries_count;++i) {
    FreePool(font->chars[i].data);
  }
  FreePool(font->chars);
  FreePool(font);
  *parsed_font=font_buffer;
  return EFI_SUCCESS;
}
EFI_STATUS bp_font_render_font(bp_font_FBM_FONT_BUFFER *font_buffer,bp_font_FBM_HEADER *header,bp_font_FBM_RENDERER_SETTING settings,bp_font_FBM_FONT_RENDER **rendered_font) {
  if (font_buffer==NULL || header==NULL) {
    return EFI_INVALID_PARAMETER;
  }
  if (settings.red_bits==0 || settings.green_bits==0 || settings.blue_bits==0) {
    return EFI_INVALID_PARAMETER;
  }
  bp_graphic_COLOR_RGB gradient[16];
  for (int i=0;i<16;++i) {
    gradient[i].r=(UINT8)(settings.background_color.r+i*(settings.text_color.r-settings.background_color.r)/15.0f);
    gradient[i].g=(UINT8)(settings.background_color.g+i*(settings.text_color.g-settings.background_color.g)/15.0f);
    gradient[i].b=(UINT8)(settings.background_color.b+i*(settings.text_color.b-settings.background_color.b)/15.0f);
  }
  UINTN size_glyph_bytes=header->height*header->width;
  if (size_glyph_bytes==0 || header->entries_count==0 || font_buffer->entries_count==0 || header->entries_count!=font_buffer->entries_count) {
    return EFI_UNSUPPORTED;
  }
  bp_font_FBM_FONT_RENDER *renderer=AllocatePool(sizeof(bp_font_FBM_FONT_RENDER));
  if (!renderer) {
    return EFI_OUT_OF_RESOURCES;
  }
  renderer->entries_count=font_buffer->entries_count;
  renderer->chars=AllocatePool(sizeof(bp_font_FBM_CHAR_RENDER)*font_buffer->entries_count);
  if (!renderer->chars) {
    FreePool(renderer);
    return EFI_OUT_OF_RESOURCES;
  }
  for (UINT32 i=0;i<font_buffer->entries_count;++i) {
    bp_font_FBM_CHAR_BUFFER *char_buffer=&font_buffer->chars[i];
    bp_font_FBM_CHAR_RENDER *char_render=&renderer->chars[i];
    char_render->data=AllocatePool(sizeof(UINT32)*header->width*header->height);
    if (!char_render->data) {
      for (INTN y=0;y<i;++y) {
        FreePool(renderer->chars[y].data);
      }
      FreePool(renderer->chars);
      FreePool(renderer);
      return EFI_OUT_OF_RESOURCES;
    }
    char_render->red_bits=settings.red_bits;
    char_render->blue_bits=settings.blue_bits;
    char_render->green_bits=settings.green_bits;
    char_render->codepoint=char_buffer->codepoint;
    char_render->height=header->height;
    char_render->width=header->width;
    for (UINTN p=0;p<header->width*header->height;++p) {
      bp_font_FBM_PIXEL *pixel=&char_buffer->data[p];
      if (pixel->shading>15) {
        for (INTN y=0;y<i;++y) {
          FreePool(renderer->chars[y].data);
        }
        FreePool(renderer->chars);
        FreePool(renderer);
        return EFI_COMPROMISED_DATA;
      }
      bp_graphic_COLOR_RGB color;
      if (!pixel->enabled) {
        color.r=settings.background_color.r;
        color.g=settings.background_color.g;
        color.b=settings.background_color.b;
      } else {
        color.r=gradient[pixel->shading].r;
        color.g=gradient[pixel->shading].g;
        color.b=gradient[pixel->shading].b;
      }
      UINT32 pixel_value=bp_graphic_get_pixel_by_color(&color,settings.red_bits,settings.green_bits,settings.blue_bits);
      char_render->data[p]=pixel_value;
    }
  }
  for (UINT32 i=0;i<font_buffer->entries_count;++i) {
    FreePool(font_buffer->chars[i].data);
  }
  FreePool(font_buffer->chars);
  FreePool(font_buffer);
  *rendered_font=renderer;
  return EFI_SUCCESS;
}
