mod strings;

use crate::types::Arr;
use iced_x86::{CpuidFeature, Decoder, DecoderOptions, Instruction};
use std::{
    cmp::Ordering,
    fmt,
    fs::File,
    io::{BufRead, BufReader, Result, Seek, SeekFrom},
};

const OPTIONS: u32 = DecoderOptions::NO_INVALID_CHECK;

pub trait Feature {
    fn new(id: CpuidFeature) -> Self;
    fn add(&mut self, instruction: Instruction);
    fn finish(&mut self);
    fn found(&self) -> bool;
    fn need_endln() -> bool;
}

pub struct FSimple {
    id: CpuidFeature,
    found: bool,
}

impl Feature for FSimple {
    fn new(id: CpuidFeature) -> Self {
        Self { id, found: false }
    }

    fn add(&mut self, _: Instruction) {
        self.found = true;
    }

    fn finish(&mut self) {}

    fn found(&self) -> bool {
        self.found
    }

    fn need_endln() -> bool {
        true
    }
}

impl fmt::Display for FSimple {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:?} ", self.id)
    }
}

pub struct FDetail {
    id: CpuidFeature,
    mnemonics: Vec<usize>,
}

impl FDetail {
    fn mnemonic_str_ord(a: &usize, b: &usize) -> Ordering {
        strings::MNEMONIC[*a].cmp(strings::MNEMONIC[*b])
    }

    fn sort(&mut self) {
        self.mnemonics.sort_unstable_by(Self::mnemonic_str_ord);
    }
}

impl Feature for FDetail {
    fn new(id: CpuidFeature) -> Self {
        Self {
            id,
            mnemonics: Vec::new(),
        }
    }

    fn add(&mut self, instruction: Instruction) {
        let mnemonic = instruction.mnemonic() as usize;
        if !self.mnemonics.contains(&mnemonic) {
            self.mnemonics.push(mnemonic);
        }
    }

    fn finish(&mut self) {
        self.sort();
    }

    fn found(&self) -> bool {
        !self.mnemonics.is_empty()
    }

    fn need_endln() -> bool {
        false
    }
}

impl fmt::Display for FDetail {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:?} : ", self.id)?;

        for mnemonic in &self.mnemonics {
            write!(f, "{} ", strings::MNEMONIC[*mnemonic])?;
        }

        writeln!(f)
    }
}

pub struct Task<T: Feature> {
    bitness: u32,
    features: Arr<T>,
}

impl<T: Feature> Task<T> {
    pub fn new(bitness: u32) -> Self {
        Self {
            bitness,
            features: CpuidFeature::values().map(T::new).collect(),
        }
    }

    fn add(&mut self, instruction: Instruction) {
        if instruction.is_invalid() {
            return;
        }

        for id in instruction.cpuid_features() {
            if let Some(feature) = self.features.get_mut(*id as usize) {
                feature.add(instruction);
            }
        }
    }

    pub fn read(&mut self, file: &mut File, offset: u64, size: u64) -> Result<()> {
        file.seek(SeekFrom::Start(offset))?;
        let mut reader = BufReader::with_capacity(size as usize, file);
        let decoder = Decoder::new(self.bitness, reader.fill_buf()?, OPTIONS);

        for instruction in decoder {
            self.add(instruction);
        }

        Ok(())
    }

    pub fn finish(&mut self) {
        for feature in &mut self.features {
            feature.finish();
        }
    }

    pub fn features(&self) -> &[T] {
        &self.features
    }

    pub fn has_cpuid(&self) -> bool {
        self.features
            .get(CpuidFeature::CPUID as usize)
            .map(|i| i.found())
            .unwrap_or_default()
    }
}
