use super::names::parse_imb_reference_at;
use super::read::read_u32_le;
use super::types::{
    WinHeaderWord, WinReferenceDeltaFrequency, WinResourceRecordSample, WinResourceReference,
};

pub(super) fn collect_imb_references(bytes: &[u8]) -> Vec<WinResourceReference> {
    let mut references = Vec::new();
    let mut offset = 0usize;
    while offset < bytes.len() {
        if let Some(name) = parse_imb_reference_at(bytes, offset) {
            references.push(WinResourceReference {
                offset,
                offset_hex: format!("0x{offset:04x}"),
                name,
            });
        }
        offset += 1;
    }
    references
}

pub(super) fn build_resource_record_samples(
    bytes: &[u8],
    references: &[WinResourceReference],
) -> Vec<WinResourceRecordSample> {
    let mut samples = Vec::with_capacity(references.len());
    for (index, reference) in references.iter().enumerate() {
        let previous_offset = index
            .checked_sub(1)
            .and_then(|previous| references.get(previous))
            .map(|previous| previous.offset);
        let delta_from_previous = previous_offset.map(|previous| reference.offset - previous);
        let delta_from_previous_hex = delta_from_previous.map(|delta| format!("0x{delta:x}"));

        let prelude_words = if reference.offset >= 16 {
            (0..4)
                .map(|index| {
                    let offset = reference.offset - 16 + index * 4;
                    let value = read_u32_le(bytes, offset).unwrap_or(0);
                    WinHeaderWord {
                        offset,
                        offset_hex: format!("0x{offset:04x}"),
                        value,
                        value_hex: format!("0x{value:08x}"),
                    }
                })
                .collect()
        } else {
            Vec::new()
        };

        let name_end = reference.offset + reference.name.len();
        let post_name_word_0 = read_u32_le(bytes, name_end + 1).unwrap_or(0);
        let post_name_word_1 = read_u32_le(bytes, name_end + 5).unwrap_or(0);
        let post_name_word_0_high_u16 = ((post_name_word_0 >> 16) & 0xffff) as u16;
        let post_name_word_0_low_u16 = (post_name_word_0 & 0xffff) as u16;

        samples.push(WinResourceRecordSample {
            offset: reference.offset,
            offset_hex: reference.offset_hex.clone(),
            name: reference.name.clone(),
            delta_from_previous,
            delta_from_previous_hex,
            prelude_words,
            post_name_word_0,
            post_name_word_0_hex: format!("0x{post_name_word_0:08x}"),
            post_name_word_0_high_u16,
            post_name_word_0_high_u16_hex: format!("0x{post_name_word_0_high_u16:04x}"),
            post_name_word_0_low_u16,
            post_name_word_0_low_u16_hex: format!("0x{post_name_word_0_low_u16:04x}"),
            post_name_word_1,
            post_name_word_1_hex: format!("0x{post_name_word_1:08x}"),
        });
    }
    samples
}

pub(super) fn build_delta_histogram(
    samples: &[WinResourceRecordSample],
) -> Vec<WinReferenceDeltaFrequency> {
    let mut counts = std::collections::BTreeMap::<usize, usize>::new();
    for sample in samples {
        if let Some(delta) = sample.delta_from_previous {
            *counts.entry(delta).or_default() += 1;
        }
    }

    let mut frequencies = counts
        .into_iter()
        .map(|(delta, count)| WinReferenceDeltaFrequency {
            delta,
            delta_hex: format!("0x{delta:x}"),
            count,
        })
        .collect::<Vec<_>>();
    frequencies.sort_by(|left, right| {
        right
            .count
            .cmp(&left.count)
            .then_with(|| left.delta.cmp(&right.delta))
    });
    frequencies.truncate(12);
    frequencies
}

pub(super) fn shared_prelude_prefix_hex(
    samples: &[WinResourceRecordSample],
) -> Option<Vec<String>> {
    let first = samples.first()?;
    if first.prelude_words.len() < 3 {
        return None;
    }
    let prefix = first.prelude_words[..3]
        .iter()
        .map(|word| word.value)
        .collect::<Vec<_>>();
    if samples.iter().all(|sample| {
        sample.prelude_words.len() >= 3
            && sample.prelude_words[..3]
                .iter()
                .map(|word| word.value)
                .collect::<Vec<_>>()
                == prefix
    }) {
        return Some(
            prefix
                .into_iter()
                .map(|value| format!("0x{value:08x}"))
                .collect(),
        );
    }
    None
}
