use write_fonts::tables::{
    gdef::CaretValue as RawCaretValue,
    gpos::{AnchorTable, ValueFormat},
    layout::{Device, DeviceOrVariationIndex, PendingVariationIndex},
    variations::{ivs_builder::VariationStoreBuilder, VariationRegion},
};
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ValueRecord {
        pub(crate) x_advance: Option<Metric>,
        pub(crate) y_advance: Option<Metric>,
        pub(crate) x_placement: Option<Metric>,
        pub(crate) y_placement: Option<Metric>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Anchor {
        pub(crate) x: Metric,
        pub(crate) y: Metric,
                pub(crate) contourpoint: Option<u16>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[allow(missing_docs)]
pub enum DeviceOrDeltas {
    Device(Device),
    Deltas(Vec<(VariationRegion, i16)>),
    #[default]
    None,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub(crate) struct Metric {
        pub default: i16,
        pub device_or_deltas: DeviceOrDeltas,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CaretValue {
        Coordinate {
                default: i16,
                deltas: DeviceOrDeltas,
    },
                PointIndex(u16),
}
impl ValueRecord {
        pub fn new() -> Self {
        Default::default()
    }
                    pub fn make_rtl_compatible(&mut self) {
        if self.x_placement.is_none() {
            self.x_placement.clone_from(&self.x_advance);
        }
    }
            pub fn with_x_placement(mut self, val: i16) -> Self {
        self.x_placement
            .get_or_insert_with(Default::default)
            .default = val;
        self
    }
        pub fn with_y_placement(mut self, val: i16) -> Self {
        self.y_placement
            .get_or_insert_with(Default::default)
            .default = val;
        self
    }
        pub fn with_x_advance(mut self, val: i16) -> Self {
        self.x_advance.get_or_insert_with(Default::default).default = val;
        self
    }
        pub fn with_y_advance(mut self, val: i16) -> Self {
        self.y_advance.get_or_insert_with(Default::default).default = val;
        self
    }
                pub fn with_x_placement_device(mut self, val: impl Into<DeviceOrDeltas>) -> Self {
        self.x_placement
            .get_or_insert_with(Default::default)
            .device_or_deltas = val.into();
        self
    }
                pub fn with_y_placement_device(mut self, val: impl Into<DeviceOrDeltas>) -> Self {
        self.y_placement
            .get_or_insert_with(Default::default)
            .device_or_deltas = val.into();
        self
    }
                pub fn with_x_advance_device(mut self, val: impl Into<DeviceOrDeltas>) -> Self {
        self.x_advance
            .get_or_insert_with(Default::default)
            .device_or_deltas = val.into();
        self
    }
                pub fn with_y_advance_device(mut self, val: impl Into<DeviceOrDeltas>) -> Self {
        self.y_advance
            .get_or_insert_with(Default::default)
            .device_or_deltas = val.into();
        self
    }
    pub(crate) fn clear_zeros(mut self) -> Self {
        self.x_advance = self.x_advance.filter(|m| !m.is_zero());
        self.y_advance = self.y_advance.filter(|m| !m.is_zero());
        self.x_placement = self.x_placement.filter(|m| !m.is_zero());
        self.y_placement = self.y_placement.filter(|m| !m.is_zero());
        self
    }
    pub(crate) fn format(&self) -> ValueFormat {
        const EMPTY: ValueFormat = ValueFormat::empty();
        use ValueFormat as VF;
        let get_flags = |field: &Option<Metric>, def_flag, dev_flag| {
            let field = field.as_ref();
            let def_flag = if field.is_some() { def_flag } else { EMPTY };
            let dev_flag = field
                .and_then(|fld| (!fld.device_or_deltas.is_none()).then_some(dev_flag))
                .unwrap_or(EMPTY);
            (def_flag, dev_flag)
        };
        let (x_adv, x_adv_dev) = get_flags(&self.x_advance, VF::X_ADVANCE, VF::X_ADVANCE_DEVICE);
        let (y_adv, y_adv_dev) = get_flags(&self.y_advance, VF::Y_ADVANCE, VF::Y_ADVANCE_DEVICE);
        let (x_place, x_place_dev) =
            get_flags(&self.x_placement, VF::X_PLACEMENT, VF::X_PLACEMENT_DEVICE);
        let (y_place, y_place_dev) =
            get_flags(&self.y_placement, VF::Y_PLACEMENT, VF::Y_PLACEMENT_DEVICE);
        x_adv | y_adv | x_place | y_place | x_adv_dev | y_adv_dev | x_place_dev | y_place_dev
    }
        pub fn is_all_zeros(&self) -> bool {
        let device_mask = ValueFormat::X_PLACEMENT_DEVICE
            | ValueFormat::Y_PLACEMENT_DEVICE
            | ValueFormat::X_ADVANCE_DEVICE
            | ValueFormat::Y_ADVANCE_DEVICE;
        let format = self.format();
        if format.is_empty() || format.intersects(device_mask) {
            return false;
        }
        let all_values = [
            &self.x_placement,
            &self.y_placement,
            &self.x_advance,
            &self.y_advance,
        ];
        all_values
            .iter()
            .all(|v| v.as_ref().map(|v| v.is_zero()).unwrap_or(true))
    }
                        pub(crate) fn for_pair_pos(self, in_vert_feature: bool) -> Self {
        if !self.is_all_zeros() {
            return self.clear_zeros();
        }
        let mut out = self.clear_zeros();
        if in_vert_feature {
            out.y_advance = Some(0.into());
        } else {
            out.x_advance = Some(0.into());
        }
        out
    }
    pub(crate) fn build(
        self,
        var_store: &mut VariationStoreBuilder,
    ) -> write_fonts::tables::gpos::ValueRecord {
        let mut result = write_fonts::tables::gpos::ValueRecord::new();
        result.x_advance = self.x_advance.as_ref().map(|val| val.default);
        result.y_advance = self.y_advance.as_ref().map(|val| val.default);
        result.x_placement = self.x_placement.as_ref().map(|val| val.default);
        result.y_placement = self.y_placement.as_ref().map(|val| val.default);
        result.x_advance_device = self
            .x_advance
            .and_then(|val| val.device_or_deltas.build(var_store))
            .into();
        result.y_advance_device = self
            .y_advance
            .and_then(|val| val.device_or_deltas.build(var_store))
            .into();
        result.x_placement_device = self
            .x_placement
            .and_then(|val| val.device_or_deltas.build(var_store))
            .into();
        result.y_placement_device = self
            .y_placement
            .and_then(|val| val.device_or_deltas.build(var_store))
            .into();
        result
    }
}
impl Anchor {
        pub fn new(x: i16, y: i16) -> Self {
        Anchor {
            x: x.into(),
            y: y.into(),
            contourpoint: None,
        }
    }
                pub fn with_x_device(mut self, val: impl Into<DeviceOrDeltas>) -> Self {
        self.x.device_or_deltas = val.into();
        self
    }
                pub fn with_y_device(mut self, val: impl Into<DeviceOrDeltas>) -> Self {
        self.y.device_or_deltas = val.into();
        self
    }
                    pub fn with_contourpoint(mut self, idx: u16) -> Self {
        self.contourpoint = Some(idx);
        self
    }
    pub(crate) fn build(self, var_store: &mut VariationStoreBuilder) -> AnchorTable {
        let x = self.x.default;
        let y = self.y.default;
        let x_dev = self.x.device_or_deltas.build(var_store);
        let y_dev = self.y.device_or_deltas.build(var_store);
        if x_dev.is_some() || y_dev.is_some() {
            AnchorTable::format_3(x, y, x_dev, y_dev)
        } else if let Some(point) = self.contourpoint {
            AnchorTable::format_2(x, y, point)
        } else {
            AnchorTable::format_1(x, y)
        }
    }
}
impl CaretValue {
                pub(crate) fn build(self, var_store: &mut VariationStoreBuilder) -> RawCaretValue {
        match self {
            CaretValue::Coordinate { default, deltas } => match deltas.build(var_store) {
                Some(deltas) => RawCaretValue::format_3(default, deltas),
                None => RawCaretValue::format_1(default),
            },
            CaretValue::PointIndex(index) => RawCaretValue::format_2(index),
        }
    }
}
impl Metric {
    fn is_zero(&self) -> bool {
        self.default == 0 && !self.has_device_or_deltas()
    }
        pub fn has_device_or_deltas(&self) -> bool {
        !self.device_or_deltas.is_none()
    }
}
impl DeviceOrDeltas {
    fn is_none(&self) -> bool {
        *self == DeviceOrDeltas::None
    }
    fn build(self, var_store: &mut VariationStoreBuilder) -> Option<DeviceOrVariationIndex> {
        match self {
            DeviceOrDeltas::Device(dev) => Some(DeviceOrVariationIndex::Device(dev)),
            DeviceOrDeltas::Deltas(deltas) => {
                let temp_id = var_store.add_deltas(deltas);
                Some(DeviceOrVariationIndex::PendingVariationIndex(
                    PendingVariationIndex::new(temp_id),
                ))
            }
            DeviceOrDeltas::None => None,
        }
    }
}
impl From<i16> for Metric {
    fn from(src: i16) -> Metric {
        Metric {
            default: src,
            device_or_deltas: DeviceOrDeltas::None,
        }
    }
}
impl From<Option<Device>> for DeviceOrDeltas {
    fn from(src: Option<Device>) -> DeviceOrDeltas {
        src.map(DeviceOrDeltas::Device).unwrap_or_default()
    }
}
impl From<Device> for DeviceOrDeltas {
    fn from(value: Device) -> Self {
        DeviceOrDeltas::Device(value)
    }
}
impl From<Vec<(VariationRegion, i16)>> for DeviceOrDeltas {
    fn from(src: Vec<(VariationRegion, i16)>) -> DeviceOrDeltas {
        if src.is_empty() {
            DeviceOrDeltas::None
        } else {
            DeviceOrDeltas::Deltas(src)
        }
    }
}