use font_types::{BigEndian, F2Dot14, Fixed};
use super::Error;
use crate::tables::variations::{ItemVariationData, ItemVariationStore};
const MAX_PRECOMPUTED_SCALARS: usize = 16;
pub struct BlendState<'a> {
    store: ItemVariationStore<'a>,
    coords: &'a [F2Dot14],
    store_index: u16,
    data: Option<ItemVariationData<'a>>,
    region_indices: &'a [BigEndian<u16>],
    scalars: [Fixed; MAX_PRECOMPUTED_SCALARS],
}
impl<'a> BlendState<'a> {
    pub fn new(
        store: ItemVariationStore<'a>,
        coords: &'a [F2Dot14],
        store_index: u16,
    ) -> Result<Self, Error> {
        let mut state = Self {
            store,
            coords,
            store_index,
            data: None,
            region_indices: &[],
            scalars: Default::default(),
        };
        state.update_precomputed_scalars()?;
        Ok(state)
    }
    pub fn set_store_index(&mut self, store_index: u16) -> Result<(), Error> {
        if self.store_index != store_index {
            self.store_index = store_index;
            self.update_precomputed_scalars()?;
        }
        Ok(())
    }
    pub fn region_count(&self) -> Result<usize, Error> {
        Ok(self.region_indices.len())
    }
    pub fn scalars(&self) -> Result<impl Iterator<Item = Result<Fixed, Error>> + '_, Error> {
        let total_count = self.region_indices.len();
        let cached = &self.scalars[..MAX_PRECOMPUTED_SCALARS.min(total_count)];
        let remaining_regions = if total_count > MAX_PRECOMPUTED_SCALARS {
            &self.region_indices[MAX_PRECOMPUTED_SCALARS..]
        } else {
            &[]
        };
        Ok(cached.iter().copied().map(Ok).chain(
            remaining_regions
                .iter()
                .map(|region_ix| self.region_scalar(region_ix.get())),
        ))
    }
    fn update_precomputed_scalars(&mut self) -> Result<(), Error> {
        self.data = None;
        self.region_indices = &[];
        let store = &self.store;
        let varation_datas = store.item_variation_datas();
        let data = varation_datas
            .get(self.store_index as usize)
            .ok_or(Error::InvalidVariationStoreIndex(self.store_index))??;
        let region_indices = data.region_indexes();
        let regions = self.store.variation_region_list()?.variation_regions();
        for (region_ix, scalar) in region_indices
            .iter()
            .take(MAX_PRECOMPUTED_SCALARS)
            .zip(&mut self.scalars)
        {
            let region = regions.get(region_ix.get() as usize)?;
            *scalar = region.compute_scalar(self.coords);
        }
        self.data = Some(data);
        self.region_indices = region_indices;
        Ok(())
    }
    fn region_scalar(&self, index: u16) -> Result<Fixed, Error> {
        Ok(self
            .store
            .variation_region_list()?
            .variation_regions()
            .get(index as usize)
            .map_err(Error::Read)?
            .compute_scalar(self.coords))
    }
}
#[cfg(test)]
mod test {
    use super::*;
    use crate::{FontData, FontRead};
    #[test]
    fn example_blends() {
        example_test(&[-1.0], &[0.0, 1.0]);
        example_test(&[-0.25], &[0.5, 0.0]);
        example_test(&[-0.5], &[1.0, 0.0]);
        example_test(&[-0.75], &[0.5, 0.5]);
        example_test(&[0.0], &[0.0, 0.0]);
        example_test(&[0.5], &[0.0, 0.0]);
        example_test(&[1.0], &[0.0, 0.0]);
    }
    fn example_test(coords: &[f32], expected: &[f64]) {
        let scalars = example_scalars_for_coords(coords);
        let expected: Vec<_> = expected.iter().copied().map(Fixed::from_f64).collect();
        assert_eq!(scalars, expected);
    }
    fn example_scalars_for_coords(coords: &[f32]) -> Vec<Fixed> {
        let ivs = example_ivs();
        let coords: Vec<_> = coords
            .iter()
            .map(|coord| F2Dot14::from_f32(*coord))
            .collect();
        let blender = BlendState::new(ivs, &coords, 0).unwrap();
        blender.scalars().unwrap().map(|res| res.unwrap()).collect()
    }
    fn example_ivs() -> ItemVariationStore<'static> {
        let ivs_data = &font_test_data::cff2::EXAMPLE[18..];
        ItemVariationStore::read(FontData::new(ivs_data)).unwrap()
    }
}