use alloc::vec::Vec;

use crate::read::{Bytes, ReadError, ReadRef, Result, StringTable};
use crate::{elf, endian};

use super::FileHeader;

/// A version index.
#[derive(Debug, Default, Clone, Copy)]
pub struct VersionIndex(pub u16);

impl VersionIndex {
    /// Return the version index.
    pub fn index(&self) -> u16 {
        self.0 & elf::VERSYM_VERSION
    }

    /// Return true if it is the local index.
    pub fn is_local(&self) -> bool {
        self.index() == elf::VER_NDX_LOCAL
    }

    /// Return true if it is the global index.
    pub fn is_global(&self) -> bool {
        self.index() == elf::VER_NDX_GLOBAL
    }

    /// Return the hidden flag.
    pub fn is_hidden(&self) -> bool {
        self.0 & elf::VERSYM_HIDDEN != 0
    }
}

/// A version definition or requirement.
///
/// This is derived from entries in the `SHT_GNU_verdef` and `SHT_GNU_verneed` sections.
#[derive(Debug, Default, Clone, Copy)]
pub struct Version<'data> {
    name: &'data [u8],
    hash: u32,
    // Used to keep track of valid indices in `VersionTable`.
    valid: bool,
}

impl<'data> Version<'data> {
    /// Return the version name.
    pub fn name(&self) -> &'data [u8] {
        self.name
    }

    /// Return hash of the version name.
    pub fn hash(&self) -> u32 {
        self.hash
    }
}

/// A table of version definitions and requirements.
///
/// It allows looking up the version information for a given symbol index.
///
/// This is derived from entries in the `SHT_GNU_versym`, `SHT_GNU_verdef` and `SHT_GNU_verneed` sections.
#[derive(Debug, Clone)]
pub struct VersionTable<'data, Elf: FileHeader> {
    symbols: &'data [elf::Versym<Elf::Endian>],
    versions: Vec<Version<'data>>,
}

impl<'data, Elf: FileHeader> Default for VersionTable<'data, Elf> {
    fn default() -> Self {
        VersionTable {
            symbols: &[],
            versions: Vec::new(),
        }
    }
}

impl<'data, Elf: FileHeader> VersionTable<'data, Elf> {
    /// Parse the version sections.
    pub fn parse<R: ReadRef<'data>>(
        endian: Elf::Endian,
        versyms: &'data [elf::Versym<Elf::Endian>],
        verdefs: Option<VerdefIterator<'data, Elf>>,
        verneeds: Option<VerneedIterator<'data, Elf>>,
        strings: StringTable<'data, R>,
    ) -> Result<Self> {
        let mut max_index = 0;
        if let Some(mut verdefs) = verdefs.clone() {
            while let Some((verdef, _)) = verdefs.next()? {
                if verdef.vd_flags.get(endian) & elf::VER_FLG_BASE != 0 {
                    continue;
                }
                let index = verdef.vd_ndx.get(endian) & elf::VERSYM_VERSION;
                if max_index < index {
                    max_index = index;
                }
            }
        }
        if let Some(mut verneeds) = verneeds.clone() {
            while let Some((_, mut vernauxs)) = verneeds.next()? {
                while let Some(vernaux) = vernauxs.next()? {
                    let index = vernaux.vna_other.get(endian) & elf::VERSYM_VERSION;
                    if max_index < index {
                        max_index = index;
                    }
                }
            }
        }

        // Indices should be sequential, but this could be up to
        // 32k * size_of::<Version>() if max_index is bad.
        let mut versions = vec![Version::default(); max_index as usize + 1];

        if let Some(mut verdefs) = verdefs {
            while let Some((verdef, mut verdauxs)) = verdefs.next()? {
                if verdef.vd_flags.get(endian) & elf::VER_FLG_BASE != 0 {
                    continue;
                }
                let index = verdef.vd_ndx.get(endian) & elf::VERSYM_VERSION;
                if index <= elf::VER_NDX_GLOBAL {
                    // TODO: return error?
                    continue;
                }
                if let Some(verdaux) = verdauxs.next()? {
                    versions[usize::from(index)] = Version {
                        name: verdaux.name(endian, strings)?,
                        hash: verdef.vd_hash.get(endian),
                        valid: true,
                    };
                }
            }
        }
        if let Some(mut verneeds) = verneeds {
            while let Some((_, mut vernauxs)) = verneeds.next()? {
                while let Some(vernaux) = vernauxs.next()? {
                    let index = vernaux.vna_other.get(endian) & elf::VERSYM_VERSION;
                    if index <= elf::VER_NDX_GLOBAL {
                        // TODO: return error?
                        continue;
                    }
                    versions[usize::from(index)] = Version {
                        name: vernaux.name(endian, strings)?,
                        hash: vernaux.vna_hash.get(endian),
                        valid: true,
                    };
                }
            }
        }

        Ok(VersionTable {
            symbols: versyms,
            versions,
        })
    }

    /// Return true if the version table is empty.
    pub fn is_empty(&self) -> bool {
        self.symbols.is_empty()
    }

    /// Return version index for a given symbol index.
    pub fn version_index(&self, endian: Elf::Endian, index: usize) -> VersionIndex {
        let version_index = match self.symbols.get(index) {
            Some(x) => x.0.get(endian),
            // Ideally this would be VER_NDX_LOCAL for undefined symbols,
            // but currently there are no checks that need this distinction.
            None => elf::VER_NDX_GLOBAL,
        };
        VersionIndex(version_index)
    }

    /// Return version information for a given symbol version index.
    ///
    /// Returns `Ok(None)` for local and global versions.
    /// Returns `Err(_)` if index is invalid.
    pub fn version(&self, index: VersionIndex) -> Result<Option<&Version<'data>>> {
        if index.index() <= elf::VER_NDX_GLOBAL {
            return Ok(None);
        }
        self.versions
            .get(usize::from(index.index()))
            .filter(|version| version.valid)
            .read_error("Invalid ELF symbol version index")
            .map(Some)
    }

    /// Return true if the given symbol index satisfies the requirements of `need`.
    ///
    /// Returns false for any error.
    ///
    /// Note: this function hasn't been fully tested and is likely to be incomplete.
    pub fn matches(&self, endian: Elf::Endian, index: usize, need: Option<&Version<'_>>) -> bool {
        let version_index = self.version_index(endian, index);
        let def = match self.version(version_index) {
            Ok(def) => def,
            Err(_) => return false,
        };
        match (def, need) {
            (Some(def), Some(need)) => need.hash == def.hash && need.name == def.name,
            (None, Some(_need)) => {
                // Version must be present if needed.
                false
            }
            (Some(_def), None) => {
                // For a dlsym call, use the newest version.
                // TODO: if not a dlsym call, then use the oldest version.
                !version_index.is_hidden()
            }
            (None, None) => true,
        }
    }
}

/// An iterator over the entries in an ELF `SHT_GNU_verdef` section.
#[derive(Debug, Clone)]
pub struct VerdefIterator<'data, Elf: FileHeader> {
    endian: Elf::Endian,
    data: Bytes<'data>,
}

impl<'data, Elf: FileHeader> VerdefIterator<'data, Elf> {
    pub(super) fn new(endian: Elf::Endian, data: &'data [u8]) -> Self {
        VerdefIterator {
            endian,
            data: Bytes(data),
        }
    }

    /// Return the next `Verdef` entry.
    pub fn next(
        &mut self,
    ) -> Result<Option<(&'data elf::Verdef<Elf::Endian>, VerdauxIterator<'data, Elf>)>> {
        if self.data.is_empty() {
            return Ok(None);
        }

        let verdef = self
            .data
            .read_at::<elf::Verdef<_>>(0)
            .read_error("ELF verdef is too short")?;

        let mut verdaux_data = self.data;
        verdaux_data
            .skip(verdef.vd_aux.get(self.endian) as usize)
            .read_error("Invalid ELF vd_aux")?;
        let verdaux =
            VerdauxIterator::new(self.endian, verdaux_data.0, verdef.vd_cnt.get(self.endian));

        let next = verdef.vd_next.get(self.endian);
        if next != 0 {
            self.data
                .skip(next as usize)
                .read_error("Invalid ELF vd_next")?;
        } else {
            self.data = Bytes(&[]);
        }
        Ok(Some((verdef, verdaux)))
    }
}

/// An iterator over the auxiliary records for an entry in an ELF `SHT_GNU_verdef` section.
#[derive(Debug, Clone)]
pub struct VerdauxIterator<'data, Elf: FileHeader> {
    endian: Elf::Endian,
    data: Bytes<'data>,
    count: u16,
}

impl<'data, Elf: FileHeader> VerdauxIterator<'data, Elf> {
    pub(super) fn new(endian: Elf::Endian, data: &'data [u8], count: u16) -> Self {
        VerdauxIterator {
            endian,
            data: Bytes(data),
            count,
        }
    }

    /// Return the next `Verdaux` entry.
    pub fn next(&mut self) -> Result<Option<&'data elf::Verdaux<Elf::Endian>>> {
        if self.count == 0 {
            return Ok(None);
        }

        let verdaux = self
            .data
            .read_at::<elf::Verdaux<_>>(0)
            .read_error("ELF verdaux is too short")?;

        self.data
            .skip(verdaux.vda_next.get(self.endian) as usize)
            .read_error("Invalid ELF vda_next")?;
        self.count -= 1;
        Ok(Some(verdaux))
    }
}

/// An iterator over the entries in an ELF `SHT_GNU_verneed` section.
#[derive(Debug, Clone)]
pub struct VerneedIterator<'data, Elf: FileHeader> {
    endian: Elf::Endian,
    data: Bytes<'data>,
}

impl<'data, Elf: FileHeader> VerneedIterator<'data, Elf> {
    pub(super) fn new(endian: Elf::Endian, data: &'data [u8]) -> Self {
        VerneedIterator {
            endian,
            data: Bytes(data),
        }
    }

    /// Return the next `Verneed` entry.
    pub fn next(
        &mut self,
    ) -> Result<
        Option<(
            &'data elf::Verneed<Elf::Endian>,
            VernauxIterator<'data, Elf>,
        )>,
    > {
        if self.data.is_empty() {
            return Ok(None);
        }

        let verneed = self
            .data
            .read_at::<elf::Verneed<_>>(0)
            .read_error("ELF verneed is too short")?;

        let mut vernaux_data = self.data;
        vernaux_data
            .skip(verneed.vn_aux.get(self.endian) as usize)
            .read_error("Invalid ELF vn_aux")?;
        let vernaux =
            VernauxIterator::new(self.endian, vernaux_data.0, verneed.vn_cnt.get(self.endian));

        let next = verneed.vn_next.get(self.endian);
        if next != 0 {
            self.data
                .skip(next as usize)
                .read_error("Invalid ELF vn_next")?;
        } else {
            self.data = Bytes(&[]);
        }
        Ok(Some((verneed, vernaux)))
    }
}

/// An iterator over the auxiliary records for an entry in an ELF `SHT_GNU_verneed` section.
#[derive(Debug, Clone)]
pub struct VernauxIterator<'data, Elf: FileHeader> {
    endian: Elf::Endian,
    data: Bytes<'data>,
    count: u16,
}

impl<'data, Elf: FileHeader> VernauxIterator<'data, Elf> {
    pub(super) fn new(endian: Elf::Endian, data: &'data [u8], count: u16) -> Self {
        VernauxIterator {
            endian,
            data: Bytes(data),
            count,
        }
    }

    /// Return the next `Vernaux` entry.
    pub fn next(&mut self) -> Result<Option<&'data elf::Vernaux<Elf::Endian>>> {
        if self.count == 0 {
            return Ok(None);
        }

        let vernaux = self
            .data
            .read_at::<elf::Vernaux<_>>(0)
            .read_error("ELF vernaux is too short")?;

        self.data
            .skip(vernaux.vna_next.get(self.endian) as usize)
            .read_error("Invalid ELF vna_next")?;
        self.count -= 1;
        Ok(Some(vernaux))
    }
}

impl<Endian: endian::Endian> elf::Verdaux<Endian> {
    /// Parse the version name from the string table.
    pub fn name<'data, R: ReadRef<'data>>(
        &self,
        endian: Endian,
        strings: StringTable<'data, R>,
    ) -> Result<&'data [u8]> {
        strings
            .get(self.vda_name.get(endian))
            .read_error("Invalid ELF vda_name")
    }
}

impl<Endian: endian::Endian> elf::Verneed<Endian> {
    /// Parse the file from the string table.
    pub fn file<'data, R: ReadRef<'data>>(
        &self,
        endian: Endian,
        strings: StringTable<'data, R>,
    ) -> Result<&'data [u8]> {
        strings
            .get(self.vn_file.get(endian))
            .read_error("Invalid ELF vn_file")
    }
}

impl<Endian: endian::Endian> elf::Vernaux<Endian> {
    /// Parse the version name from the string table.
    pub fn name<'data, R: ReadRef<'data>>(
        &self,
        endian: Endian,
        strings: StringTable<'data, R>,
    ) -> Result<&'data [u8]> {
        strings
            .get(self.vna_name.get(endian))
            .read_error("Invalid ELF vna_name")
    }
}
