//! This provides auto spacing for the default DFI fonts.
//!
//! It creates a string with up to two 1px spaces (U+200A "Hair Space") between all chars that would otherwise touch each other using the given font ids.
//!
//! Available are all fonts with user_id 5747312 and sub_ids 614201xxx.
//! Other fonts will return an unmodified string.
//!
//! ## Example
//!
//! ```
//! use lotus_extra::fonts::AutoSpacing;
//! use lotus_script::content::ContentId;
//!
//! let auto_spacing = AutoSpacing::default();
//!
//! let string = "Nice Test String";
//! let font = ContentId { user_id: 5747312, sub_id: 614201007 };
//!
//! let spaced_string = auto_spacing.get_string(&font, string);
//!
//! assert_eq!("N  i c e Te s t St r i n g".to_string(), spaced_string);
//! ```

use std::{collections::HashMap, path::PathBuf, str::FromStr};

use lotus_script::content::ContentId;
use rust_embed::Embed;
use serde::Deserialize;

#[derive(Embed)]
#[folder = "files/auto_spacing"]
#[include = "*.toml"]
struct AutoSpacingFile;

pub struct AutoSpacing {
    data: HashMap<String, AutoSpacingData>,
}

impl Default for AutoSpacing {
    /// Initializes AutoSpacing with the required font data.
    fn default() -> Self {
        let mut data = HashMap::new();
        for file_path in AutoSpacingFile::iter() {
            // get name:
            let Ok(path) = PathBuf::from_str(&file_path);
            let Some(name) = path.file_stem() else {
                continue;
            };
            let Some(id) = name.to_str() else {
                continue;
            };
            // get data:
            let Some(file) = AutoSpacingFile::get(&file_path) else {
                continue;
            };
            let Ok(as_data) = toml::from_slice(&file.data) else {
                continue;
            };
            // insert:
            data.insert(id.to_string(), as_data);
        }
        Self { data }
    }
}

impl AutoSpacing {
    /// Creates a string with up to two 1px spaces (U+200A) between all chars that would otherwise touch each other using the given font.
    pub fn get_string(&self, font: &ContentId, string: &str) -> String {
        let id = format!("{}_{}", font.user_id, font.sub_id);
        let Some(data) = self.data.get(&id) else {
            return string.to_string();
        };
        let mut string_iter = string.chars();
        let mut result = String::new();
        if let Some(mut last) = string_iter.next() {
            result.push(last);
            for current in string_iter {
                result.push_str(&" ".repeat(data.check_spacing(&last, &current)));
                result.push(current);
                last = current;
            }
        }
        result
    }
}

#[derive(Debug, Deserialize)]
struct AutoSpacingData(HashMap<String, AutoSpacingCharData>);

impl AutoSpacingData {
    fn check_spacing(&self, char_0: &char, char_1: &char) -> usize {
        let Self(data) = self;
        match data.get(&char_0.to_string()) {
            Some(char) => char.get_spacing(char_1),
            None => 1,
        }
    }
}

#[derive(Debug, Deserialize)]
struct AutoSpacingCharData(HashMap<String, String>);

impl AutoSpacingCharData {
    fn get_spacing(&self, char_1: &char) -> usize {
        let Self(data) = self;
        for spacing in data.iter() {
            if spacing.1.contains(*char_1) {
                return spacing.0.parse().unwrap_or(1);
            }
        }
        1
    }
}
