use crate::communication::{
    AbstractFrame, AbstractFrameTriggering, AbstractPdu, CommunicationDirection, FlexrayPhysicalChannel, Frame,
    FramePort, FrameTriggering, Pdu, PduToFrameMapping, PduTriggering,
};
use crate::{
    abstraction_element, make_unique_name, reflist_iterator, AbstractionElement, ArPackage, AutosarAbstractionError,
    ByteOrder, EcuInstance,
};
use autosar_data::{Element, ElementName, EnumItem};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FlexrayFrame(Element);
abstraction_element!(FlexrayFrame, FlexrayFrame);
impl FlexrayFrame {
    pub(crate) fn new(name: &str, package: &ArPackage, byte_length: u64) -> Result<Self, AutosarAbstractionError> {
        let pkg_elements = package.element().get_or_create_sub_element(ElementName::Elements)?;
        let fr_frame = pkg_elements.create_named_sub_element(ElementName::FlexrayFrame, name)?;
        fr_frame
            .create_sub_element(ElementName::FrameLength)?
            .set_character_data(byte_length.to_string())?;
        Ok(Self(fr_frame))
    }
}
impl AbstractFrame for FlexrayFrame {
    type FrameTriggeringType = FlexrayFrameTriggering;
    fn frame_triggerings(&self) -> impl Iterator<Item = FlexrayFrameTriggering> {
        let model_result = self.element().model();
        let path_result = self.element().path();
        if let (Ok(model), Ok(path)) = (model_result, path_result) {
            let reflist = model.get_references_to(&path);
            FlexrayFrameTriggeringsIterator::new(reflist)
        } else {
            FlexrayFrameTriggeringsIterator::new(vec![])
        }
    }
    fn map_pdu<T: AbstractPdu>(
        &self,
        gen_pdu: &T,
        start_position: u32,
        byte_order: ByteOrder,
        update_bit: Option<u32>,
    ) -> Result<PduToFrameMapping, AutosarAbstractionError> {
        Frame::Flexray(self.clone()).map_pdu(gen_pdu, start_position, byte_order, update_bit)
    }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FlexrayFrameTriggering(Element);
abstraction_element!(FlexrayFrameTriggering, FlexrayFrameTriggering);
impl FlexrayFrameTriggering {
    pub(crate) fn new(
        channel: &FlexrayPhysicalChannel,
        frame: &FlexrayFrame,
        slot_id: u16,
        timing: &FlexrayCommunicationCycle,
    ) -> Result<Self, AutosarAbstractionError> {
        let model = channel.element().model()?;
        let base_path = channel.element().path()?;
        let frame_name = frame
            .name()
            .ok_or(AutosarAbstractionError::InvalidParameter("invalid frame".to_string()))?;
        let ft_name = format!("FT_{frame_name}");
        let ft_name = make_unique_name(&model, &base_path, &ft_name);
        let frame_triggerings = channel
            .element()
            .get_or_create_sub_element(ElementName::FrameTriggerings)?;
        let fr_triggering =
            frame_triggerings.create_named_sub_element(ElementName::FlexrayFrameTriggering, &ft_name)?;
        fr_triggering
            .create_sub_element(ElementName::FrameRef)?
            .set_reference_target(frame.element())?;
        let ft = Self(fr_triggering);
        ft.set_slot(slot_id)?;
        ft.set_timing(timing)?;
        for pdu_mapping in frame.mapped_pdus() {
            if let Some(pdu) = pdu_mapping.pdu() {
                ft.add_pdu_triggering(&pdu)?;
            }
        }
        Ok(ft)
    }
    pub fn set_slot(&self, slot_id: u16) -> Result<(), AutosarAbstractionError> {
        self.element()
            .get_or_create_sub_element(ElementName::AbsolutelyScheduledTimings)?
            .get_or_create_sub_element(ElementName::FlexrayAbsolutelyScheduledTiming)?
            .get_or_create_sub_element(ElementName::SlotId)?
            .set_character_data(slot_id.to_string())?;
        Ok(())
    }
    #[must_use]
    pub fn slot(&self) -> Option<u16> {
        self.element()
            .get_sub_element(ElementName::AbsolutelyScheduledTimings)?
            .get_sub_element(ElementName::FlexrayAbsolutelyScheduledTiming)?
            .get_sub_element(ElementName::SlotId)?
            .character_data()?
            .parse_integer()
    }
    pub fn set_timing(&self, timing: &FlexrayCommunicationCycle) -> Result<(), AutosarAbstractionError> {
        let timings_elem = self
            .element()
            .get_or_create_sub_element(ElementName::AbsolutelyScheduledTimings)?
            .get_or_create_sub_element(ElementName::FlexrayAbsolutelyScheduledTiming)?
            .get_or_create_sub_element(ElementName::CommunicationCycle)?;
        match timing {
            FlexrayCommunicationCycle::Counter { cycle_counter } => {
                let _ = timings_elem.remove_sub_element_kind(ElementName::CycleRepetition);
                timings_elem
                    .get_or_create_sub_element(ElementName::CycleCounter)?
                    .get_or_create_sub_element(ElementName::CycleCounter)?
                    .set_character_data(cycle_counter.to_string())?;
            }
            FlexrayCommunicationCycle::Repetition {
                base_cycle,
                cycle_repetition,
            } => {
                let _ = timings_elem.remove_sub_element_kind(ElementName::CycleCounter);
                let repetition = timings_elem.get_or_create_sub_element(ElementName::CycleRepetition)?;
                repetition
                    .get_or_create_sub_element(ElementName::BaseCycle)?
                    .set_character_data(base_cycle.to_string())?;
                repetition
                    .get_or_create_sub_element(ElementName::CycleRepetition)?
                    .set_character_data::<EnumItem>((*cycle_repetition).into())?;
            }
        }
        Ok(())
    }
    #[must_use]
    pub fn timing(&self) -> Option<FlexrayCommunicationCycle> {
        let timings = self
            .element()
            .get_sub_element(ElementName::AbsolutelyScheduledTimings)?
            .get_sub_element(ElementName::FlexrayAbsolutelyScheduledTiming)?
            .get_sub_element(ElementName::CommunicationCycle)?;
        if let Some(counter_based) = timings.get_sub_element(ElementName::CycleCounter) {
            let cycle_counter = counter_based
                .get_sub_element(ElementName::CycleCounter)?
                .character_data()?
                .parse_integer()?;
            Some(FlexrayCommunicationCycle::Counter { cycle_counter })
        } else if let Some(repetition) = timings.get_sub_element(ElementName::CycleRepetition) {
            let base_cycle = repetition
                .get_sub_element(ElementName::BaseCycle)?
                .character_data()?
                .parse_integer()?;
            let cycle_repetition = repetition
                .get_sub_element(ElementName::CycleRepetition)?
                .character_data()?
                .enum_value()?
                .try_into()
                .ok()?;
            Some(FlexrayCommunicationCycle::Repetition {
                base_cycle,
                cycle_repetition,
            })
        } else {
            None
        }
    }
    pub(crate) fn add_pdu_triggering(&self, pdu: &Pdu) -> Result<PduTriggering, AutosarAbstractionError> {
        FrameTriggering::Flexray(self.clone()).add_pdu_triggering(pdu)
    }
    pub fn connect_to_ecu(
        &self,
        ecu: &EcuInstance,
        direction: CommunicationDirection,
    ) -> Result<FramePort, AutosarAbstractionError> {
        FrameTriggering::Flexray(self.clone()).connect_to_ecu(ecu, direction)
    }
}
impl AbstractFrameTriggering for FlexrayFrameTriggering {
    type FrameType = FlexrayFrame;
}
impl From<FlexrayFrameTriggering> for FrameTriggering {
    fn from(fft: FlexrayFrameTriggering) -> Self {
        FrameTriggering::Flexray(fft)
    }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FlexrayCommunicationCycle {
    Counter {
        cycle_counter: u8,
    },
    Repetition {
        base_cycle: u8,
        cycle_repetition: CycleRepetition,
    },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CycleRepetition {
    C1,
    C2,
    C4,
    C5,
    C8,
    C10,
    C16,
    C20,
    C32,
    C40,
    C50,
    C64,
}
impl TryFrom<EnumItem> for CycleRepetition {
    type Error = AutosarAbstractionError;
    fn try_from(value: EnumItem) -> Result<Self, Self::Error> {
        match value {
            EnumItem::CycleRepetition1 => Ok(Self::C1),
            EnumItem::CycleRepetition2 => Ok(Self::C2),
            EnumItem::CycleRepetition4 => Ok(Self::C4),
            EnumItem::CycleRepetition5 => Ok(Self::C5),
            EnumItem::CycleRepetition8 => Ok(Self::C8),
            EnumItem::CycleRepetition10 => Ok(Self::C10),
            EnumItem::CycleRepetition16 => Ok(Self::C16),
            EnumItem::CycleRepetition20 => Ok(Self::C20),
            EnumItem::CycleRepetition32 => Ok(Self::C32),
            EnumItem::CycleRepetition40 => Ok(Self::C40),
            EnumItem::CycleRepetition50 => Ok(Self::C50),
            EnumItem::CycleRepetition64 => Ok(Self::C64),
            _ => Err(AutosarAbstractionError::ValueConversionError {
                value: value.to_string(),
                dest: "CycleRepetitionType".to_string(),
            }),
        }
    }
}
impl From<CycleRepetition> for EnumItem {
    fn from(value: CycleRepetition) -> Self {
        match value {
            CycleRepetition::C1 => EnumItem::CycleRepetition1,
            CycleRepetition::C2 => EnumItem::CycleRepetition2,
            CycleRepetition::C4 => EnumItem::CycleRepetition4,
            CycleRepetition::C5 => EnumItem::CycleRepetition5,
            CycleRepetition::C8 => EnumItem::CycleRepetition8,
            CycleRepetition::C10 => EnumItem::CycleRepetition10,
            CycleRepetition::C16 => EnumItem::CycleRepetition16,
            CycleRepetition::C20 => EnumItem::CycleRepetition20,
            CycleRepetition::C32 => EnumItem::CycleRepetition32,
            CycleRepetition::C40 => EnumItem::CycleRepetition40,
            CycleRepetition::C50 => EnumItem::CycleRepetition50,
            CycleRepetition::C64 => EnumItem::CycleRepetition64,
        }
    }
}
reflist_iterator!(FlexrayFrameTriggeringsIterator, FlexrayFrameTriggering);
#[cfg(test)]
mod test {
    use std::result;
    use autosar_data::{AutosarModel, AutosarVersion};
    use super::*;
    use crate::{
        communication::{FlexrayChannelName, FlexrayClusterSettings},
        ByteOrder, SystemCategory,
    };
    #[test]
    fn fr_frame() {
        let model = AutosarModel::new();
        let _ = model.create_file("test", AutosarVersion::LATEST).unwrap();
        let package = ArPackage::get_or_create(&model, "/package").unwrap();
        let system = package.create_system("System", SystemCategory::EcuExtract).unwrap();
        let flexray_cluster = system
            .create_flexray_cluster("Cluster", &package, &FlexrayClusterSettings::default())
            .unwrap();
        let channel = flexray_cluster
            .create_physical_channel("Channel", FlexrayChannelName::A)
            .unwrap();
        let ecu_instance = system.create_ecu_instance("ECU", &package).unwrap();
        let can_controller = ecu_instance
            .create_flexray_communication_controller("Controller")
            .unwrap();
        can_controller.connect_physical_channel("connection", &channel).unwrap();
        let pdu1 = system.create_isignal_ipdu("pdu1", &package, 8).unwrap();
        let pdu2 = system.create_isignal_ipdu("pdu2", &package, 8).unwrap();
        let frame1 = system.create_flexray_frame("frame1", &package, 64).unwrap();
        let frame2 = system.create_flexray_frame("frame2", &package, 64).unwrap();
        let mapping = frame1
            .map_pdu(&pdu1, 7, ByteOrder::MostSignificantByteFirst, Some(8))
            .unwrap();
        assert!(frame1.mapped_pdus().count() == 1);
        assert_eq!(frame1.mapped_pdus().next().unwrap(), mapping);
        let frame_triggering1 = channel
            .trigger_frame(
                &frame1,
                1,
                &FlexrayCommunicationCycle::Repetition {
                    base_cycle: 1,
                    cycle_repetition: CycleRepetition::C1,
                },
            )
            .unwrap();
        assert_eq!(frame1.frame_triggerings().count(), 1);
        let frame_triggering2 = channel
            .trigger_frame(&frame2, 2, &FlexrayCommunicationCycle::Counter { cycle_counter: 2 })
            .unwrap();
        assert_eq!(frame2.frame_triggerings().count(), 1);
        assert_eq!(channel.frame_triggerings().count(), 2);
        assert_eq!(frame_triggering1.pdu_triggerings().count(), 1);
        let _ = frame1
            .map_pdu(&pdu2, 71, ByteOrder::MostSignificantByteFirst, None)
            .unwrap();
        assert!(frame1.mapped_pdus().count() == 2);
        assert_eq!(frame_triggering1.pdu_triggerings().count(), 2);
        let frame_port = frame_triggering1
            .connect_to_ecu(&ecu_instance, CommunicationDirection::Out)
            .unwrap();
        assert_eq!(frame_port.ecu().unwrap(), ecu_instance);
        assert_eq!(
            frame_port.communication_direction().unwrap(),
            CommunicationDirection::Out
        );
        assert_eq!(frame_triggering1.frame().unwrap(), frame1);
        assert_eq!(frame_triggering1.slot().unwrap(), 1);
        assert_eq!(
            frame_triggering1.timing().unwrap(),
            FlexrayCommunicationCycle::Repetition {
                base_cycle: 1,
                cycle_repetition: CycleRepetition::C1
            }
        );
        assert_eq!(frame_triggering2.frame().unwrap(), frame2);
        assert_eq!(frame_triggering2.slot().unwrap(), 2);
        assert_eq!(
            frame_triggering2.timing().unwrap(),
            FlexrayCommunicationCycle::Counter { cycle_counter: 2 }
        );
        assert_eq!(mapping.pdu().unwrap(), pdu1.into());
        assert_eq!(mapping.byte_order().unwrap(), ByteOrder::MostSignificantByteFirst);
        assert_eq!(mapping.start_position().unwrap(), 7);
        assert_eq!(mapping.update_bit(), Some(8));
    }
    #[test]
    fn cycle_repetition() {
        assert_eq!(EnumItem::CycleRepetition1, CycleRepetition::C1.into());
        assert_eq!(EnumItem::CycleRepetition2, CycleRepetition::C2.into());
        assert_eq!(EnumItem::CycleRepetition4, CycleRepetition::C4.into());
        assert_eq!(EnumItem::CycleRepetition5, CycleRepetition::C5.into());
        assert_eq!(EnumItem::CycleRepetition8, CycleRepetition::C8.into());
        assert_eq!(EnumItem::CycleRepetition10, CycleRepetition::C10.into());
        assert_eq!(EnumItem::CycleRepetition16, CycleRepetition::C16.into());
        assert_eq!(EnumItem::CycleRepetition20, CycleRepetition::C20.into());
        assert_eq!(EnumItem::CycleRepetition32, CycleRepetition::C32.into());
        assert_eq!(EnumItem::CycleRepetition40, CycleRepetition::C40.into());
        assert_eq!(EnumItem::CycleRepetition50, CycleRepetition::C50.into());
        assert_eq!(EnumItem::CycleRepetition64, CycleRepetition::C64.into());
        assert_eq!(CycleRepetition::C1, EnumItem::CycleRepetition1.try_into().unwrap());
        assert_eq!(CycleRepetition::C2, EnumItem::CycleRepetition2.try_into().unwrap());
        assert_eq!(CycleRepetition::C4, EnumItem::CycleRepetition4.try_into().unwrap());
        assert_eq!(CycleRepetition::C5, EnumItem::CycleRepetition5.try_into().unwrap());
        assert_eq!(CycleRepetition::C8, EnumItem::CycleRepetition8.try_into().unwrap());
        assert_eq!(CycleRepetition::C10, EnumItem::CycleRepetition10.try_into().unwrap());
        assert_eq!(CycleRepetition::C16, EnumItem::CycleRepetition16.try_into().unwrap());
        assert_eq!(CycleRepetition::C20, EnumItem::CycleRepetition20.try_into().unwrap());
        assert_eq!(CycleRepetition::C32, EnumItem::CycleRepetition32.try_into().unwrap());
        assert_eq!(CycleRepetition::C40, EnumItem::CycleRepetition40.try_into().unwrap());
        assert_eq!(CycleRepetition::C50, EnumItem::CycleRepetition50.try_into().unwrap());
        assert_eq!(CycleRepetition::C64, EnumItem::CycleRepetition64.try_into().unwrap());
        let result: result::Result<CycleRepetition, _> = EnumItem::Aa.try_into();
        assert!(result.is_err());
    }
}