use std::io::{Read, Seek, SeekFrom};
use byteorder::{ByteOrder, ReadBytesExt};
use linear_map::LinearMap;
use linux_perf_event_reader::PerfEventAttr;
use super::section::PerfFileSection;
use crate::simpleperf::SimplePerfEventType;
use crate::{Error, ReadError};
#[derive(Debug, Clone, Copy)]
pub struct NrCpus {
    pub nr_cpus_available: u32,
    pub nr_cpus_online: u32,
}
impl NrCpus {
    pub const STRUCT_SIZE: usize = 4 + 4;
    pub fn parse<R: Read, T: ByteOrder>(mut reader: R) -> Result<Self, std::io::Error> {
        let nr_cpus_available = reader.read_u32::<T>()?;
        let nr_cpus_online = reader.read_u32::<T>()?;
        Ok(Self {
            nr_cpus_available,
            nr_cpus_online,
        })
    }
}
#[derive(Debug, Clone, Copy)]
pub struct SampleTimeRange {
    pub first_sample_time: u64,
    pub last_sample_time: u64,
}
impl SampleTimeRange {
    pub const STRUCT_SIZE: usize = 8 + 8;
    pub fn parse<R: Read, T: ByteOrder>(mut reader: R) -> Result<Self, std::io::Error> {
        let first_sample_time = reader.read_u64::<T>()?;
        let last_sample_time = reader.read_u64::<T>()?;
        Ok(Self {
            first_sample_time,
            last_sample_time,
        })
    }
}
pub struct HeaderString;
impl HeaderString {
    pub fn parse<R: Read, T: ByteOrder>(mut reader: R) -> Result<Option<String>, std::io::Error> {
        let len = reader.read_u32::<T>()?;
        let mut s = vec![0; len as usize];
        reader.read_exact(&mut s)?;
        let actual_len = memchr::memchr(0, &s).unwrap_or(s.len());
        s.truncate(actual_len);
        Ok(String::from_utf8(s).ok())
    }
}
#[derive(Debug, Clone)]
pub struct AttributeDescription {
    pub attr: PerfEventAttr,
    pub name: Option<String>,
    pub event_ids: Vec<u64>,
}
impl AttributeDescription {
    pub fn parse_event_desc_section<C: Read + Seek, T: ByteOrder>(
        mut cursor: C,
    ) -> Result<Vec<Self>, Error> {
        let nr = cursor.read_u32::<T>()?;
        let mut attributes = Vec::with_capacity(nr as usize);
        let attr_size = cursor.read_u32::<T>()? as u64;
        for _ in 0..nr {
            let attr = Self::parse_single_attr::<_, T>(&mut cursor, attr_size)?;
            let nr_ids = cursor.read_u32::<T>()?;
            let event_string = HeaderString::parse::<_, T>(&mut cursor)?;
            let mut ids = Vec::with_capacity(nr_ids as usize);
            for _ in 0..nr_ids {
                ids.push(cursor.read_u64::<T>()?);
            }
            attributes.push(AttributeDescription {
                attr,
                name: event_string,
                event_ids: ids,
            });
        }
        Ok(attributes)
    }
    pub fn parse_event_types_section<C: Read + Seek, T: ByteOrder>(
        cursor: C,
        event_types_section: &PerfFileSection,
        attr_size: u64,
    ) -> Result<Vec<Self>, Error> {
        Self::parse_sequence_of_attr_and_id_section::<C, T>(
            cursor,
            event_types_section,
            attr_size,
            None,
        )
    }
    pub fn parse_simpleperf_attr_section<C: Read + Seek, T: ByteOrder>(
        cursor: C,
        attr_section: &PerfFileSection,
        attr_size: u64,
        event_types: &[SimplePerfEventType],
    ) -> Result<Vec<Self>, Error> {
        if attr_size < PerfFileSection::STRUCT_SIZE {
            return Err(ReadError::PerfEventAttr.into());
        }
        let attr_size_without_id_section = attr_size - PerfFileSection::STRUCT_SIZE;
        let event_names: Vec<_> = event_types.iter().map(|t| t.name.as_str()).collect();
        Self::parse_sequence_of_attr_and_id_section::<C, T>(
            cursor,
            attr_section,
            attr_size_without_id_section,
            Some(&event_names),
        )
    }
    fn parse_sequence_of_attr_and_id_section<C: Read + Seek, T: ByteOrder>(
        mut cursor: C,
        section: &PerfFileSection,
        attr_size: u64,
        event_names: Option<&[&str]>,
    ) -> Result<Vec<Self>, Error> {
        cursor.seek(SeekFrom::Start(section.offset))?;
        let entry_size = attr_size + PerfFileSection::STRUCT_SIZE;
        let entry_count = section.size / entry_size;
        let mut perf_event_event_type_info = Vec::with_capacity(entry_count as usize);
        for _ in 0..entry_count {
            let attr = Self::parse_single_attr::<_, T>(&mut cursor, attr_size)?;
            let event_ids = PerfFileSection::parse::<_, T>(&mut cursor)?;
            perf_event_event_type_info.push((attr, event_ids));
        }
        let mut attributes = Vec::new();
        for (event_index, (attr, section)) in perf_event_event_type_info.into_iter().enumerate() {
            cursor.seek(SeekFrom::Start(section.offset))?;
            let id_count = section.size / 8;
            let mut event_ids = Vec::with_capacity(id_count as usize);
            for _ in 0..id_count {
                event_ids.push(cursor.read_u64::<T>()?);
            }
            let name = if let Some(names) = event_names {
                names.get(event_index).map(|s| s.to_string())
            } else {
                None
            };
            attributes.push(AttributeDescription {
                attr,
                name,
                event_ids,
            });
        }
        Ok(attributes)
    }
    pub fn parse_attr_section<C: Read + Seek, T: ByteOrder>(
        mut cursor: C,
        attr_section: &PerfFileSection,
        attr_size: u64,
    ) -> Result<Vec<Self>, Error> {
        cursor.seek(SeekFrom::Start(attr_section.offset))?;
        let attr_count = attr_section.size / attr_size;
        let mut attributes = Vec::with_capacity(attr_count as usize);
        for _ in 0..attr_count {
            let attr = Self::parse_single_attr::<_, T>(&mut cursor, attr_size)?;
            attributes.push(AttributeDescription {
                attr,
                name: None,
                event_ids: vec![],
            });
        }
        Ok(attributes)
    }
    fn parse_single_attr<C: Read + Seek, T: ByteOrder>(
        mut cursor: C,
        attr_size: u64,
    ) -> Result<PerfEventAttr, Error> {
        let (attr, size) =
            PerfEventAttr::parse::<_, T>(&mut cursor).map_err(|_| ReadError::PerfEventAttr)?;
        if size > attr_size {
            return Err(Error::InconsistentAttributeSizes(size, attr_size));
        }
        if size < attr_size {
            let remaining_bytes = attr_size - size;
            cursor.seek(SeekFrom::Current(remaining_bytes as i64))?;
        }
        Ok(attr)
    }
    pub fn attributes(&self) -> &PerfEventAttr {
        &self.attr
    }
    pub fn name(&self) -> Option<&str> {
        self.name.as_deref()
    }
    pub fn ids(&self) -> &[u64] {
        &self.event_ids
    }
}
pub struct PmuMappings(pub LinearMap<u32, String>);
impl PmuMappings {
    pub fn parse<R: Read, T: ByteOrder>(mut reader: R) -> Result<Self, std::io::Error> {
        let nr = reader.read_u32::<T>()?;
        let mut vec = Vec::with_capacity(nr as usize);
        for _ in 0..nr {
            let pmu_type = reader.read_u32::<T>()?;
            if let Some(pmu_name) = HeaderString::parse::<_, T>(&mut reader)? {
                vec.push((pmu_type, pmu_name));
            }
        }
        vec.sort_by_key(|item| item.0);
        Ok(Self(vec.into_iter().collect()))
    }
}