use std::fmt::Display;

use crate::pal::AbstractProcessor;
use crate::pal::windows::{ProcessorGroupIndex, ProcessorIndexInGroup};
use crate::{EfficiencyClass, MemoryRegionId, ProcessorId};

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(crate) struct ProcessorImpl {
    pub(crate) group_index: ProcessorGroupIndex,
    pub(crate) index_in_group: ProcessorIndexInGroup,

    // Cumulative index when counting across all groups.
    pub(crate) id: ProcessorId,

    pub(crate) memory_region_id: MemoryRegionId,

    pub(crate) efficiency_class: EfficiencyClass,
}

impl ProcessorImpl {
    pub(crate) fn new(
        group_index: ProcessorGroupIndex,
        index_in_group: ProcessorIndexInGroup,
        id: ProcessorId,
        memory_region_id: MemoryRegionId,
        efficiency_class: EfficiencyClass,
    ) -> Self {
        Self {
            group_index,
            index_in_group,
            id,
            memory_region_id,
            efficiency_class,
        }
    }
}

impl Display for ProcessorImpl {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "processor {} [{}.{}]",
            self.id, self.group_index, self.index_in_group
        )
    }
}

impl AbstractProcessor for ProcessorImpl {
    fn id(&self) -> ProcessorId {
        self.id
    }

    fn memory_region_id(&self) -> MemoryRegionId {
        self.memory_region_id
    }

    fn efficiency_class(&self) -> EfficiencyClass {
        self.efficiency_class
    }
}

impl AsRef<Self> for ProcessorImpl {
    fn as_ref(&self) -> &Self {
        self
    }
}

impl PartialOrd for ProcessorImpl {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for ProcessorImpl {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.id.cmp(&other.id)
    }
}

#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
    use super::*;

    #[test]
    fn smoke_test() {
        let processor = ProcessorImpl::new(0, 1, 2, 3, EfficiencyClass::Performance);

        assert_eq!(processor.id(), 2);
        assert_eq!(processor.memory_region_id(), 3);
        assert_eq!(processor.efficiency_class(), EfficiencyClass::Performance);

        let processor2 = ProcessorImpl::new(0, 1, 2, 3, EfficiencyClass::Performance);
        assert_eq!(processor, processor2);

        let processor3 = ProcessorImpl::new(0, 1, 4, 3, EfficiencyClass::Performance);
        assert_ne!(processor, processor3);
        assert!(processor < processor3);
        assert!(processor3 > processor);
    }

    #[test]
    fn display_shows_processor_id_and_group_info() {
        // group_index=1, index_in_group=3, id=7
        let processor = ProcessorImpl::new(1, 3, 7, 0, EfficiencyClass::Performance);

        let display_output = processor.to_string();

        assert!(
            display_output.contains("processor 7"),
            "display should contain processor ID: {display_output}"
        );
        assert!(
            display_output.contains("[1.3]"),
            "display should contain group.index format: {display_output}"
        );
    }

    #[test]
    fn as_ref_returns_self() {
        let processor = ProcessorImpl::new(0, 2, 5, 1, EfficiencyClass::Efficiency);

        let processor_ref: &ProcessorImpl = processor.as_ref();

        assert_eq!(processor_ref.id, processor.id);
        assert_eq!(processor_ref.group_index, processor.group_index);
        assert_eq!(processor_ref.index_in_group, processor.index_in_group);
    }
}
