// this code is mostly just blindly ported from
// https://github.com/adamanldo/Cosmo/blob/946af25dcac8a81cb7b49d1a4f83731a5f7c7b46/cogs/utils/fmi_text.py
// since I'm using the same text rendering library, my code is almost one-to-one

use arabic_reshaper::arabic_reshape;
use image::codecs::png::PngEncoder;
use image::{ExtendedColorType, ImageEncoder, RgbaImage};
use imagetext::measure::text_width;
use imagetext::prelude::{BLACK, Outline, TextAlign, WHITE, draw_text_wrapped, scale, stroke};
use imagetext::superfont::SuperFont;
use imagetext::wrap::{WrapStyle, text_wrap};
use unicode_bidi::BidiInfo;

enum TextField {
    Title,
    Artist,
    Album,
}

fn process_text(
    text: &str,
    field: &TextField,
    font: &SuperFont<'static>,
    font_size: f32,
) -> (Vec<String>, i32) {
    let mut text = text.to_string();
    if text
        .chars()
        .any(|x| matches!(x as u32, 0x590..=0x5fe | 0x600..=0x6ff0))
    {
        if text.chars().any(|x| matches!(x as u32, 0x600..=0x6ff0)) {
            text = arabic_reshape(text.as_str());
        }
        let bidi = BidiInfo::new(text.as_str(), None);
        text = bidi
            .paragraphs
            .iter()
            .map(|para| {
                let line = para.range.clone();
                bidi.reorder_line(para, line)
            })
            .collect();
    }
    let (width, allowed_lines) = match field {
        TextField::Title => (350, 2),
        TextField::Artist => (312, 1),
        TextField::Album => (280, 2),
    };
    let mut wrapped_strs = text_wrap(
        text.as_str(),
        width,
        font,
        scale(font_size),
        WrapStyle::Character,
        text_width,
    );
    if wrapped_strs.len() > allowed_lines {
        wrapped_strs.truncate(allowed_lines);
        let text_len = wrapped_strs[allowed_lines - 1].len() - 3;
        wrapped_strs[allowed_lines - 1] = match allowed_lines {
            1..=2 => format!(
                "{}...",
                wrapped_strs[allowed_lines - 1].get(..text_len).unwrap()
            ),
            _ => unreachable!(),
        };
    }
    (wrapped_strs, width)
}

fn draw_info(
    image: &mut RgbaImage,
    text: String,
    text_type: TextField,
    font: &SuperFont<'static>,
    font_size: f32,
    dark_text: bool,
) -> Result<(), String> {
    let (lines, width) = process_text(&text, &text_type, font, font_size);
    let text = lines.join("");
    let y = match text_type {
        TextField::Title => 24.0,
        TextField::Artist => 73.0,
        TextField::Album => 96.0,
    };
    let color = if dark_text { &BLACK } else { &WHITE };
    match draw_text_wrapped(
        image,
        color,
        Outline::Solid {
            stroke: &stroke(0.5),
            fill: color,
        },
        146.0,
        y,
        0.0,
        0.0,
        width as f32,
        scale(font_size),
        font,
        &text,
        1.0,
        TextAlign::Left,
        WrapStyle::Character,
    ) {
        Ok(_) => Ok(()),
        Err(e) => Err(e.to_string()),
    }
}

pub struct TrackInfo {
    pub title: String,
    pub artist: String,
    pub album: String,
}

pub fn fmi_text(
    width: u32,
    height: u32,
    track: TrackInfo,
    fonts: &(SuperFont<'static>, SuperFont<'static>),
    font_size: f32,
    dark_text: bool,
) -> Result<Vec<u8>, String> {
    let mut img = RgbaImage::new(width, height);
    draw_info(
        &mut img,
        track.title,
        TextField::Title,
        &fonts.1.clone(),
        font_size,
        dark_text,
    )?;
    draw_info(
        &mut img,
        track.artist,
        TextField::Artist,
        &fonts.0.clone(),
        font_size,
        dark_text,
    )?;
    draw_info(
        &mut img,
        track.album,
        TextField::Album,
        &fonts.0.clone(),
        font_size,
        dark_text,
    )?;
    let mut blob = Vec::<u8>::new();
    let png_encoder = PngEncoder::new(&mut blob);
    match png_encoder.write_image(&img, width, height, ExtendedColorType::Rgba8) {
        Ok(()) => Ok(blob),
        Err(_) => Err("Failed to encode text png".to_string()),
    }
}
