Documentation
use psdk_father::args::{ArgContext, PsdkArgBase, PsdkEasyArg};
use psdk_father::command::CommandClause;
use psdk_father::errors::{PsdkFatherError, PsdkFatherResult};
use std::sync::{Arc, Mutex};

use crate::marks::{Font, Rotation};

use super::{CImageBytes, CImageBytesOptions};

#[derive(Clone, Debug)]
pub struct CTextCanvasOptions {
  pub x: i32,
  pub y: i32,
  pub image_bytes: Vec<u8>,
  pub compress: bool,
}

#[derive(Clone, Debug)]
pub struct CTextCanvasImageOptions {
  pub content: String,
  pub font: Font,
  pub rotation: Rotation,
  pub bold: Option<bool>,
  pub alpha: i32,
}

#[derive(Clone, Debug)]
pub struct CTextCanvas {
  image_bytes: CImageBytes,
}

impl CTextCanvas {
  pub fn new(options: CTextCanvasOptions) -> PsdkFatherResult<Self> {
    Ok(Self {
      image_bytes: CImageBytes::new(CImageBytesOptions {
        x: options.x,
        y: options.y,
        bytes: options.image_bytes,
        compress: options.compress,
        reverse: false,
        gray_threshold: None,
        smooth_weight: 0,
        edge_dither_mode: None,
        edge_dither_depth: Default::default(),
      })?,
    })
  }

  pub fn generate_image_bytes(options: CTextCanvasImageOptions) -> PsdkFatherResult<Vec<u8>> {
    let alpha = options.alpha.clamp(0, 255) as u8;
    let font_height = options.font.get_height().max(7);
    let scale = (font_height / 7).max(1);
    let char_width = 5 * scale;
    let char_height = 7 * scale;
    let char_gap = scale;
    let padding = 2u32;
    let char_count = options.content.chars().count().max(1) as u32;
    let width = padding * 2 + char_count * char_width + char_count.saturating_sub(1) * char_gap;
    let height = padding * 2 + char_height;
    let mut canvas = RgbaCanvas::new(width, height);

    for (index, character) in options.content.chars().enumerate() {
      let pattern = glyph_pattern(character);
      let x = padding + index as u32 * (char_width + char_gap);
      draw_glyph(
        &mut canvas,
        Point { x, y: padding },
        scale,
        pattern,
        options.bold.unwrap_or(false),
        alpha,
      );
    }

    let canvas = rotate_rgba(canvas, &options.rotation);
    encode_png_rgba(canvas.width, canvas.height, &canvas.rgba)
  }
}

struct RgbaCanvas {
  width: u32,
  height: u32,
  rgba: Vec<u8>,
}

impl RgbaCanvas {
  fn new(width: u32, height: u32) -> Self {
    Self {
      width,
      height,
      rgba: vec![255u8; (width * height * 4) as usize],
    }
  }
}

#[derive(Clone, Copy)]
struct Point {
  x: u32,
  y: u32,
}

fn draw_glyph(
  canvas: &mut RgbaCanvas,
  origin: Point,
  scale: u32,
  pattern: [u8; 7],
  bold: bool,
  alpha: u8,
) {
  for (row, bits) in pattern.iter().enumerate() {
    for column in 0..5 {
      if bits & (1 << (4 - column)) == 0 {
        continue;
      }
      let px = origin.x + column * scale;
      let py = origin.y + row as u32 * scale;
      fill_rect(
        canvas,
        Point { x: px, y: py },
        scale + u32::from(bold),
        scale,
        alpha,
      );
    }
  }
}

fn fill_rect(canvas: &mut RgbaCanvas, origin: Point, rect_width: u32, rect_height: u32, alpha: u8) {
  for py in origin.y..(origin.y + rect_height).min(canvas.height) {
    for px in origin.x..(origin.x + rect_width).min(canvas.width) {
      let offset = ((py * canvas.width + px) * 4) as usize;
      canvas.rgba[offset] = 0;
      canvas.rgba[offset + 1] = 0;
      canvas.rgba[offset + 2] = 0;
      canvas.rgba[offset + 3] = alpha;
    }
  }
}

fn rotate_rgba(canvas: RgbaCanvas, rotation: &Rotation) -> RgbaCanvas {
  match rotation {
    Rotation::Rotation0 => canvas,
    Rotation::Rotation90 => {
      let mut rotated = RgbaCanvas::new(canvas.height, canvas.width);
      for y in 0..canvas.height {
        for x in 0..canvas.width {
          copy_pixel(
            &canvas,
            &mut rotated,
            Point { x, y },
            Point {
              x: canvas.height - 1 - y,
              y: x,
            },
          );
        }
      }
      rotated
    }
    Rotation::Rotation180 => {
      let mut rotated = RgbaCanvas::new(canvas.width, canvas.height);
      for y in 0..canvas.height {
        for x in 0..canvas.width {
          copy_pixel(
            &canvas,
            &mut rotated,
            Point { x, y },
            Point {
              x: canvas.width - 1 - x,
              y: canvas.height - 1 - y,
            },
          );
        }
      }
      rotated
    }
    Rotation::Rotation270 => {
      let mut rotated = RgbaCanvas::new(canvas.height, canvas.width);
      for y in 0..canvas.height {
        for x in 0..canvas.width {
          copy_pixel(
            &canvas,
            &mut rotated,
            Point { x, y },
            Point {
              x: y,
              y: canvas.width - 1 - x,
            },
          );
        }
      }
      rotated
    }
  }
}

fn copy_pixel(
  source: &RgbaCanvas,
  target: &mut RgbaCanvas,
  source_point: Point,
  target_point: Point,
) {
  let source_offset = ((source_point.y * source.width + source_point.x) * 4) as usize;
  let target_offset = ((target_point.y * target.width + target_point.x) * 4) as usize;
  target.rgba[target_offset..target_offset + 4]
    .copy_from_slice(&source.rgba[source_offset..source_offset + 4]);
}

fn encode_png_rgba(width: u32, height: u32, rgba: &[u8]) -> PsdkFatherResult<Vec<u8>> {
  let mut png_bytes = Vec::new();
  {
    let mut encoder = png::Encoder::new(&mut png_bytes, width, height);
    encoder.set_color(png::ColorType::Rgba);
    encoder.set_depth(png::BitDepth::Eight);
    let mut writer = encoder
      .write_header()
      .map_err(|error| PsdkFatherError::Image(format!("{:?}", error)))?;
    writer
      .write_image_data(rgba)
      .map_err(|error| PsdkFatherError::Image(format!("{:?}", error)))?;
  }
  Ok(png_bytes)
}

fn glyph_pattern(character: char) -> [u8; 7] {
  match character.to_ascii_uppercase() {
    'A' => [
      0b01110, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b10001,
    ],
    'B' => [
      0b11110, 0b10001, 0b10001, 0b11110, 0b10001, 0b10001, 0b11110,
    ],
    'C' => [
      0b01111, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b01111,
    ],
    'D' => [
      0b11110, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b11110,
    ],
    'E' => [
      0b11111, 0b10000, 0b10000, 0b11110, 0b10000, 0b10000, 0b11111,
    ],
    'F' => [
      0b11111, 0b10000, 0b10000, 0b11110, 0b10000, 0b10000, 0b10000,
    ],
    'G' => [
      0b01111, 0b10000, 0b10000, 0b10111, 0b10001, 0b10001, 0b01111,
    ],
    'H' => [
      0b10001, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b10001,
    ],
    'I' => [
      0b11111, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b11111,
    ],
    'J' => [
      0b00111, 0b00010, 0b00010, 0b00010, 0b10010, 0b10010, 0b01100,
    ],
    'K' => [
      0b10001, 0b10010, 0b10100, 0b11000, 0b10100, 0b10010, 0b10001,
    ],
    'L' => [
      0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b11111,
    ],
    'M' => [
      0b10001, 0b11011, 0b10101, 0b10101, 0b10001, 0b10001, 0b10001,
    ],
    'N' => [
      0b10001, 0b11001, 0b10101, 0b10011, 0b10001, 0b10001, 0b10001,
    ],
    'O' => [
      0b01110, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01110,
    ],
    'P' => [
      0b11110, 0b10001, 0b10001, 0b11110, 0b10000, 0b10000, 0b10000,
    ],
    'Q' => [
      0b01110, 0b10001, 0b10001, 0b10001, 0b10101, 0b10010, 0b01101,
    ],
    'R' => [
      0b11110, 0b10001, 0b10001, 0b11110, 0b10100, 0b10010, 0b10001,
    ],
    'S' => [
      0b01111, 0b10000, 0b10000, 0b01110, 0b00001, 0b00001, 0b11110,
    ],
    'T' => [
      0b11111, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100,
    ],
    'U' => [
      0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01110,
    ],
    'V' => [
      0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01010, 0b00100,
    ],
    'W' => [
      0b10001, 0b10001, 0b10001, 0b10101, 0b10101, 0b10101, 0b01010,
    ],
    'X' => [
      0b10001, 0b10001, 0b01010, 0b00100, 0b01010, 0b10001, 0b10001,
    ],
    'Y' => [
      0b10001, 0b10001, 0b01010, 0b00100, 0b00100, 0b00100, 0b00100,
    ],
    'Z' => [
      0b11111, 0b00001, 0b00010, 0b00100, 0b01000, 0b10000, 0b11111,
    ],
    '0' => [
      0b01110, 0b10001, 0b10011, 0b10101, 0b11001, 0b10001, 0b01110,
    ],
    '1' => [
      0b00100, 0b01100, 0b00100, 0b00100, 0b00100, 0b00100, 0b01110,
    ],
    '2' => [
      0b01110, 0b10001, 0b00001, 0b00010, 0b00100, 0b01000, 0b11111,
    ],
    '3' => [
      0b11110, 0b00001, 0b00001, 0b01110, 0b00001, 0b00001, 0b11110,
    ],
    '4' => [
      0b00010, 0b00110, 0b01010, 0b10010, 0b11111, 0b00010, 0b00010,
    ],
    '5' => [
      0b11111, 0b10000, 0b10000, 0b11110, 0b00001, 0b00001, 0b11110,
    ],
    '6' => [
      0b01110, 0b10000, 0b10000, 0b11110, 0b10001, 0b10001, 0b01110,
    ],
    '7' => [
      0b11111, 0b00001, 0b00010, 0b00100, 0b01000, 0b01000, 0b01000,
    ],
    '8' => [
      0b01110, 0b10001, 0b10001, 0b01110, 0b10001, 0b10001, 0b01110,
    ],
    '9' => [
      0b01110, 0b10001, 0b10001, 0b01111, 0b00001, 0b00001, 0b01110,
    ],
    ' ' => [0, 0, 0, 0, 0, 0, 0],
    _ => [0b11111, 0b10001, 0b00001, 0b00110, 0b00100, 0, 0b00100],
  }
}

impl TryFrom<(i32, i32, Vec<u8>)> for CTextCanvas {
  type Error = psdk_father::errors::PsdkFatherError;

  fn try_from((x, y, image_bytes): (i32, i32, Vec<u8>)) -> Result<Self, Self::Error> {
    Self::new(CTextCanvasOptions {
      x,
      y,
      image_bytes,
      compress: false,
    })
  }
}

impl PsdkEasyArg<String> for CTextCanvas {
  fn context(&self) -> Arc<Mutex<ArgContext>> {
    self.image_bytes.context()
  }
}

impl PsdkArgBase<String> for CTextCanvas {
  fn header(&self) -> String {
    self.image_bytes.header()
  }

  fn clause(&self) -> PsdkFatherResult<CommandClause> {
    self.image_bytes.clause()
  }
}

psdk_father::impl_psdk_arg_to_easy_arg!(String, CTextCanvas, true);