/* *****************************************************************************
 * File:      graphics.c
 * Date:      23 Mar 2026
 * Author:    Andrew Levido
 *
 * Copyright 2026 Imbrius Pty Ltd - All Rights Reserved
 *
 ******************************************************************************/

/* Includes ------------------------------------------------------------------*/
#include "graphics.h"

#include "lcd_ili9341.h"
#include "backlight.h"

/* Defines -------------------------------------------------------------------*/
#define GFX_SCREEN_WIDTH        240
#define GFX_SCREEN_HEIGHT       320  
/* Typedefs & Enumerations ---------------------------------------------------*/

/* Global Variables ----------------------------------------------------------*/
static struct {
  uint16_t bg_colour;                 // alpha = 0
  uint16_t fg_alpha_1;                // alpha = 1
  uint16_t fg_alpha_2;                // alpha = 2
  uint16_t fg_colour;                 // alpha = 3
} gfx;

/* Private Functions ---------------------------------------------------------*/
// Create alpha blends when background or foreground colours set
uint16_t gfx_alpha_blend(uint8_t alpha) {
  if (alpha == 0) { return gfx.bg_colour; }
  if (alpha == 3) { return gfx.fg_colour; }

  // Extract color components (R: 5 bits, G: 6 bits, B: 5 bits)
  uint16_t fg_r = (gfx.fg_colour & 0xf8) >> 11;
  uint16_t fg_g = (gfx.fg_colour & 0x7e) >> 5;
  uint16_t fg_b = gfx.fg_colour  & 0x1f;

  uint16_t bg_r = (gfx.bg_colour & 0xf8) >> 11;
  uint16_t bg_g = (gfx.bg_colour & 0x7e) >> 5;
  uint16_t bg_b = gfx.bg_colour  & 0x1f;

  uint16_t out_r = (fg_r * alpha + bg_r * (3 - alpha)) / 3;
  uint16_t out_g = (fg_g * alpha + bg_g * (3 - alpha)) / 3;
  uint16_t out_b = (fg_b * alpha + bg_b * (3 - alpha)) / 3;

  // Recombine into RGB565 format
  return ((out_r & 0x1F) << 11) | ((out_g & 0x3F) << 5) | (out_b & 0x1F);
}


// Draw pixels based on 2-bit alpha values. The variable "max" determines how many pixels are 
// drawn.  
void gfx_process_byte( const uint8_t * data_p, uint8_t max)
{
  uint8_t shift = 8;
  uint8_t alpha;

  if(max > 4) { return; }
  max = 8 - 2 * max;

  while(shift > max) {
    shift -= 2;
    alpha = *data_p & (3 << shift);
    alpha = alpha >> shift;
    switch(alpha) {
      case 0: lcd_write_pixel(gfx.bg_colour); break;
      case 1: lcd_write_pixel(gfx.fg_alpha_2); break; // NOTE Not sure why this is
      case 2: lcd_write_pixel(gfx.fg_alpha_1); break;
      case 3: lcd_write_pixel(gfx.fg_colour); break;
    default: break;
  }
  }
}

/* Get the glyph data for the character - uses cmaps to look up */
const res_glyph_data_t * gfx_get_glyph_data( char c, res_font_t * font)
{
   const res_font_cmap_t * cmap = 0;
   uint8_t i;

  // Find the character map which corresponds to the character
  for(i = 0; i < font->n_maps; i++) {
    if((c>= font->cmaps[i].range_start) && 
      (c <= font->cmaps[i].range_start + font->cmaps[i].range_length)) {
      cmap = &font->cmaps[i];
      break;
    }
  }
  if(cmap == NULL) { return(NULL); }    // char not found

  // Find the glyph. 
  if(cmap->glyph_index_list_sparse == false) {
    if(cmap->glyph_index_list == NULL) {
      return(&font->glyph_data[c - cmap->range_start + cmap->glyph_index_start]);
    }
    else {
      return(&font->glyph_data[cmap->glyph_index_list[c - cmap->range_start] + cmap->glyph_index_start]);
    }
  }
  else {
    // search sparse list for offset
    for(i = 0; i < cmap->list_length; i++) {
      if(cmap->glyph_index_list[i] == c - cmap->range_start) { break; }
    }
    if(i == cmap->list_length) { i = 0; }
    return(&font->glyph_data[cmap->glyph_index_start + i]);
  }
}

// Draw a glyph
uint8_t gfx_draw_glyph(char c, res_font_t * font, int16_t x, int16_t y)
{
  uint16_t whole_bytes;
  uint16_t part_byte;
  uint32_t byte_count;
  const uint8_t * bitmap_p;

  // find glyph data
  const res_glyph_data_t * glyph_data = gfx_get_glyph_data(c, font);
  if(glyph_data == NULL) { return(0); }

  // room for glyph on right?
  if(x + glyph_data->adv_w / 16 > GFX_SCREEN_WIDTH) { return(0); }     
  
  // position for the box
  lcd_start_write(x + glyph_data->ofs_x, y - glyph_data->ofs_y - glyph_data->box_h + 1, glyph_data->box_w, glyph_data->box_h);
 
  
  // Find the glyph data
  bitmap_p = font->glyph_bitmap + glyph_data->bitmap_index;
  whole_bytes = (glyph_data->box_w * glyph_data->box_h) /  4;
  part_byte = (glyph_data->box_w * glyph_data->box_h) %  4;

  for(byte_count = 0; byte_count < whole_bytes; byte_count++) {
    gfx_process_byte(  bitmap_p++, 4);
  }
  if(part_byte != 0) {
    gfx_process_byte(  bitmap_p++, part_byte);
  }
  lcd_stop_write();
  return(glyph_data->adv_w / 16);
}

// get text width
uint8_t gfx_get_text_width(char* string, res_font_t * font)
{
  uint8_t width = 0;
  const res_glyph_data_t * glyph_data ;
  while(*string) {
    glyph_data = gfx_get_glyph_data(*string++, font);
    if(glyph_data != NULL) { 
      width += glyph_data->adv_w / 16;
    }
  }
  return(width);
}
/* Public Functions ----------------------------------------------------------*/
void gfx_init(uint16_t bg_colour, uint16_t fg_colour, uint8_t blt)
{
  gfx.fg_colour = fg_colour;
  gfx.bg_colour = bg_colour;
  gfx.fg_alpha_1 = gfx_alpha_blend(1);
  gfx.fg_alpha_2 = gfx_alpha_blend(2);

  // inialise LCD & backlight
  lcd_init();
  blt_start(20);

  // Write background and turn LCD on
  lcd_write_block(0, 0, GFX_SCREEN_WIDTH, GFX_SCREEN_HEIGHT, bg_colour);
  lcd_on();
}

void gfx_set_bg_colour( uint16_t colour)
{
  gfx.bg_colour = colour;
  gfx.fg_alpha_1 = gfx_alpha_blend(1);
  gfx.fg_alpha_2 = gfx_alpha_blend(2);
}

void gfx_set_fg_colour( uint16_t colour)
{
  gfx.fg_colour = colour;
  gfx.fg_alpha_1 = gfx_alpha_blend(1);
  gfx.fg_alpha_2 = gfx_alpha_blend(2);
}
uint16_t gfx_get_fg_colour(void)
{
  return(gfx.fg_colour);
}

uint16_t gfx_get_bg_colour(void)
{
  return(gfx.bg_colour);
}

void gfx_screen_off()
{
  lcd_off();
}
void gfx_screen_on()
{
  lcd_on();
}


void gfx_draw_image(uint16_t x, uint16_t y, const res_image_t * image)
{
  int16_t line_count;
  const uint8_t * data_p;
  uint16_t whole_bytes;
  uint16_t part_byte;
  uint16_t byte_count;

  if(x >= GFX_SCREEN_WIDTH ) {return; } 
  if(y >= GFX_SCREEN_HEIGHT ) {return; } 
  if(image->h > GFX_SCREEN_HEIGHT - y) {return; }     // not enough height         
  if(image->w > GFX_SCREEN_WIDTH - x) {return; }      // or width

  // Start the write process
  lcd_start_write(x, y, image->w, image->h);

  data_p = image->map;
  whole_bytes = image->w / 4;
  part_byte = image->w % 4;

  // For each line in the image
  for(line_count = 0; line_count < image->h; line_count++) {
    // process lines
    for(byte_count = 0; byte_count < whole_bytes; byte_count++) {
      gfx_process_byte(data_p++, 4);
    }
    if(part_byte != 0) {
      gfx_process_byte(data_p++, part_byte);
    }
  }
  lcd_stop_write();

}

void gfx_draw_image_centred(uint16_t y, const res_image_t * image)
{
  gfx_draw_image((image->w > GFX_SCREEN_WIDTH ? 0 : (GFX_SCREEN_WIDTH - image->w) / 2), y, image);
}

void gfx_draw_block(uint16_t x, uint16_t y,uint16_t w, uint16_t h, uint16_t colour)
{
  lcd_write_block(x, y, w, h, colour);
}


void gfx_draw_text(char* string, uint16_t x, uint16_t y, res_font_t * font)
{
  uint16_t advance;

  if(y < (font->line_height - font->base_line)) {return; }      // too close to top           
  if (y + font->base_line > GFX_SCREEN_HEIGHT) {return; }  // too close to bottom
  
  while(*string) {
    advance = gfx_draw_glyph(*string++, font, x, y);
    x = x + advance;
    if(x > GFX_SCREEN_WIDTH) return;
  }
}

void gfx_draw_text_centred(char* string, uint16_t y, res_font_t * font)
{
  uint16_t w = gfx_get_text_width(string, font);

  gfx_draw_text(string, (w > GFX_SCREEN_WIDTH ? 0 : (GFX_SCREEN_WIDTH - w) / 2), y, font);
}

/* End graphics */