hiraku-engine 0.1.0

Hiraku visual-novel engine
use hiraku_script::hson::HsonMap;

use super::{VfsError, invalid_settings_data};

#[derive(Debug, Default)]
pub(super) struct BootSection {
    pub(super) startup: Option<String>,
}

#[derive(Debug, Default)]
pub(super) struct SettingsFile {
    pub(super) startup: Option<String>,
    pub(super) fonts: FontsSettings,
    pub(super) backgrounds_dir: Option<String>,
    pub(super) soundeffects_dir: Option<String>,
    pub(super) bgm_dir: Option<String>,
    pub(super) voice_dir: Option<String>,
    pub(super) movies_dir: Option<String>,
    pub(super) characters_dir: Option<String>,
    pub(super) textures_dir: Option<String>,
    pub(super) res_root: Option<String>,
    pub(super) boot: BootSection,
}

#[derive(Debug, Default)]
pub(super) struct FontsSettings {
    pub(super) path: Option<String>,
}

pub(super) fn settings_from_data(mut data: HsonMap, path: &str) -> Result<SettingsFile, VfsError> {
    let startup = take_data_string(&mut data, "startup", path)?;
    let backgrounds_dir = take_data_string(&mut data, "backgroundsDir", path)?;
    let soundeffects_dir = take_data_string(&mut data, "soundeffectsDir", path)?;
    let bgm_dir = take_data_string(&mut data, "bgmDir", path)?;
    let voice_dir = take_data_string(&mut data, "voiceDir", path)?;
    let movies_dir = take_data_string(&mut data, "moviesDir", path)?;
    let characters_dir = take_data_string(&mut data, "charactersDir", path)?;
    let textures_dir = take_data_string(&mut data, "texturesDir", path)?;
    let res_root = take_data_string(&mut data, "resRoot", path)?;

    let fonts = if let Some(mut fonts) = take_data_map(&mut data, "fonts", path)? {
        let path_value = take_data_string(&mut fonts, "path", path)?;
        ensure_empty_data_map(fonts, path, "fonts")?;
        FontsSettings { path: path_value }
    } else {
        FontsSettings::default()
    };

    let boot = if let Some(mut boot) = take_data_map(&mut data, "boot", path)? {
        let startup = take_data_string(&mut boot, "startup", path)?;
        ensure_empty_data_map(boot, path, "boot")?;
        BootSection { startup }
    } else {
        BootSection::default()
    };

    ensure_empty_data_map(data, path, "settings")?;
    Ok(SettingsFile {
        startup,
        fonts,
        backgrounds_dir,
        soundeffects_dir,
        bgm_dir,
        voice_dir,
        movies_dir,
        characters_dir,
        textures_dir,
        res_root,
        boot,
    })
}

pub(super) fn take_data_string(
    data: &mut HsonMap,
    key: &str,
    path: &str,
) -> Result<Option<String>, VfsError> {
    data.remove(key)
        .map(|value| {
            value
                .as_str()
                .map(ToOwned::to_owned)
                .ok_or_else(|| invalid_settings_data(path, format!("`{key}` must be a string")))
        })
        .transpose()
}

fn take_data_map(data: &mut HsonMap, key: &str, path: &str) -> Result<Option<HsonMap>, VfsError> {
    data.remove(key)
        .map(|value| {
            value
                .as_map()
                .cloned()
                .ok_or_else(|| invalid_settings_data(path, format!("`{key}` must be a map")))
        })
        .transpose()
}

pub(super) fn ensure_empty_data_map(
    data: HsonMap,
    path: &str,
    section: &str,
) -> Result<(), VfsError> {
    if data.is_empty() {
        return Ok(());
    }
    let keys = data.keys().cloned().collect::<Vec<_>>().join(", ");
    Err(invalid_settings_data(
        path,
        format!("unknown {section} setting(s): {keys}"),
    ))
}