/*
 * fontconfig/fc-fontations/instance_enumerate.rs
 *
 * Copyright 2025 Google LLC.
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of the author(s) not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  The authors make no
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.
 *
 * THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

use crate::InstanceMode::{self, Named};
use read_fonts::{
    FileRef::{self, Collection, Font},
    FontRef,
};
use skrifa::MetadataProvider;

pub fn fonts_and_indices(
    file_ref: Option<FileRef>,
) -> impl Iterator<Item = (FontRef, Option<i32>)> {
    let (iter_one, iter_two) = match file_ref {
        Some(Font(font)) => (Some((Ok(font.clone()), None)), None),
        Some(Collection(collection)) => (
            None,
            Some(
                collection
                    .iter()
                    .enumerate()
                    .map(|entry| (entry.1, Some(entry.0 as i32))),
            ),
        ),
        None => (None, None),
    };
    iter_two
        .into_iter()
        .flatten()
        .chain(iter_one)
        .filter_map(|(font_result, index)| {
            if let Ok(font) = font_result {
                return Some((font, index));
            }
            None
        })
}

// Produces an iterator over the named instances of the font.
#[allow(unused)]
pub fn enumerate_named_instances<'a>(f: &'a FontRef) -> impl Iterator<Item = InstanceMode> + 'a {
    let default_coords = f.axes().iter().map(|axis| axis.default_value());
    f.named_instances()
        .iter()
        .enumerate()
        .filter_map(move |(i, instance)| {
            let user_coords = instance.user_coords();
            if user_coords.eq(default_coords.clone()) {
                None
            } else {
                Some(Named(i as i32))
            }
        })
}

/// Used for producing the expected set of patterns out of variable fonts.
/// Produces an iterator over
/// * the default instance,
/// * the named instances, and the
/// * variable instance.
#[allow(unused)]
pub fn all_instances<'a>(f: &'a FontRef) -> Box<dyn Iterator<Item = InstanceMode> + 'a> {
    if f.axes().is_empty() {
        Box::new(std::iter::once(InstanceMode::Default))
    } else {
        Box::new(
            std::iter::once(InstanceMode::Default)
                .chain(enumerate_named_instances(f))
                .chain(std::iter::once(InstanceMode::Variable)),
        )
    }
}

#[cfg(test)]
mod test {
    use crate::{
        instance_enumerate::{all_instances, fonts_and_indices},
        InstanceMode,
    };

    use read_fonts::FileRef;
    use std::path::PathBuf;

    fn testfontdir() -> PathBuf {
        let default_build_dir = std::env::current_dir().unwrap().join("build");
        let build_dir = std::env::var("builddir").unwrap_or_else(|_| {
            println!("builddir env var not set, using current directory + \"build/\"");
            default_build_dir.to_string_lossy().to_string()
        });
        PathBuf::from(build_dir)
            .join("testfonts")
            .join("roboto-flex-fonts")
            .join("fonts")
            .join("variable")
    }

    fn variable_collection_testfont() -> PathBuf {
        testfontdir().join(
            "RobotoFlex[GRAD,XOPQ,XTRA,YOPQ,YTAS,YTDE,YTFI,YTLC,YTUC,opsz,slnt,wdth,wght].ttf",
        )
    }

    const EXPECTED_INSTANCES: [InstanceMode; 21] = [
        InstanceMode::Default,
        InstanceMode::Named(0),
        InstanceMode::Named(1),
        InstanceMode::Named(2),
        InstanceMode::Named(4),
        InstanceMode::Named(5),
        InstanceMode::Named(6),
        InstanceMode::Named(7),
        InstanceMode::Named(8),
        InstanceMode::Named(9),
        InstanceMode::Named(10),
        InstanceMode::Named(11),
        InstanceMode::Named(12),
        InstanceMode::Named(13),
        InstanceMode::Named(14),
        InstanceMode::Named(15),
        InstanceMode::Named(16),
        InstanceMode::Named(17),
        InstanceMode::Named(18),
        InstanceMode::Named(19),
        InstanceMode::Variable,
    ];

    #[test]
    #[cfg_attr(skip_external_fonts, ignore)]
    fn test_instance_iterator() {
        let bytes = std::fs::read(variable_collection_testfont())
            .expect("Could not read test font file");
        let fileref = FileRef::new(&bytes).ok();
        let fonts = fonts_and_indices(fileref);
        for (font, _ttc_index) in fonts {
            all_instances(&font)
                .zip(EXPECTED_INSTANCES.iter())
                .for_each(|(instance, expected_instance)| {
                    assert_eq!(instance, *expected_instance);
                })
        }
    }
}
