/// Decodes an encoded image and extracts text from the fixed-grid encrypted image format.
use base64::{
    alphabet,
    engine::{self, general_purpose},
    Engine as _,
};
use crate::char_mappings::maps::mappings::numbers_to_letter;
use image::io::Reader as ImageReader;
use image::imageops;

const IMAGE_WIDTH: u32 = 350;
const LENGTH_PREFIX_DIGITS: usize = 4;
const ANCHOR_ROWS: [u32; 11] = [0, 35, 70, 105, 140, 175, 210, 245, 280, 315, 349];
const CUSTOM_ENGINE: engine::GeneralPurpose =
    engine::GeneralPurpose::new(&alphabet::STANDARD, general_purpose::PAD);

pub fn decode_image_and_extract_text(encoded_image: &str) -> Option<String> {
    let styles = ["h", "h2", "v", "v2"];
    for &style in &styles {
        if let Some(test) = match style {
            "h" => test_and_return(encoded_image),
            "h2" | "v" | "v2" => process_image(encoded_image, style),
            _ => None,
        } {
            return Some(test);
        }
    }
    None
}

fn process_image(encoded_image: &str, style: &str) -> Option<String> {
    let image_data = CUSTOM_ENGINE.decode(encoded_image).ok()?;
    let img = match image::load(std::io::Cursor::new(&image_data), image::ImageFormat::Png) {
        Ok(img) => img,
        Err(e) => return Some(format!("Error loading image: {}", e)),
    };
    let new_img = match style {
        "h2" => imageops::flip_vertical(&img),
        "v" => imageops::rotate270(&img),
        "v2" => {
            let rotated_img = imageops::rotate90(&img);
            imageops::flip_vertical(&rotated_img)
        }
        _ => return None,
    };
    let mut buffer = Vec::new();
    if let Err(e) = image::codecs::png::PngEncoder::new(&mut buffer).encode(
        &new_img,
        new_img.width(),
        new_img.height(),
        image::ColorType::Rgba8,
    ) {
        return Some(format!("Error encoding image: {}", e));
    }
    let encoded_image = CUSTOM_ENGINE.encode(&buffer);
    test_and_return(&encoded_image)
}

fn test_and_return(encoded_image: &str) -> Option<String> {
    let image_data = CUSTOM_ENGINE.decode(encoded_image).ok()?;
    let img = ImageReader::new(std::io::Cursor::new(image_data))
        .with_guessed_format()
        .ok()?
        .decode()
        .ok()?
        .to_rgba8();

    if img.width() != IMAGE_WIDTH {
        return None;
    }

    let mut extracted_text = String::with_capacity(ANCHOR_ROWS.len() * IMAGE_WIDTH as usize);
    for &row in &ANCHOR_ROWS {
        for x in 0..IMAGE_WIDTH {
            let pixel = img.get_pixel(x, row);
            let [r, g, b, _] = pixel.0;
            let c = numbers_to_letter(r, g, b)?;
            extracted_text.push(c);
        }
    }

    let payload_length: usize = extracted_text
        .get(..LENGTH_PREFIX_DIGITS)?
        .parse()
        .ok()?;
    let start = LENGTH_PREFIX_DIGITS;
    let end = start + payload_length;
    extracted_text.get(start..end).map(|s| s.to_string())
}
