//! Hidden api items.

use crate::{Error, ReadSeek, Result, Serializable, Uleb128};
use std::io::{Cursor, Seek, SeekFrom, Write};

/// <https://source.android.com/docs/core/runtime/dex-format#hiddenapi-class-data-item>
/// Hard to serialize/deserialize without additional data like the number of classes defs
/// or the method/field of the classes.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct HiddenapiClassDataItem {
    //pub size: u32,
    pub data: Vec<u8>,
}

impl HiddenapiClassDataItem {
    pub fn size_field(&self) -> u32 {
        self.data.len() as u32
    }

    /// Return `hiddenapi_class_data_item.offsets[class_def_item_idx]`.
    ///
    /// If `0`: Either no data for this class or all API flags are zero.
    /// Else: offset from the begining of the [`HiddenapiClassDataItem`]
    /// (including the size /!\).
    ///
    /// # Warning
    ///
    /// `class_def_item_idx` is **NOT** `class_idx` (Contrary to what
    /// <https://source.android.com/docs/core/runtime/dex-format#hiddenapi-class-data-item> says).
    /// `class_def_item_idx` is the index of the `class_def_item`.
    ///
    /// They are no check weither the `class_def_item_idx` is valid one or not.
    /// Giving an invalid idx (like an idx >= nb class def) is UB.
    pub fn get_offset(&self, class_def_item_idx: usize) -> Result<u32> {
        let index = class_def_item_idx * 4;
        if self.data.len() <= index {
            Err(Error::InconsistantStruct(format!(
                "class index 0x{class_def_item_idx:x} out of bound of HiddenapiClassDataItem data"
            )))
        } else {
            u32::deserialize_from_slice(&self.data[index..])
        }
    }

    /// Return `hiddenapi_class_data_item.offsets`.
    ///
    /// # Warning
    ///
    /// They are no check weither the `nb_class` is valid one or not.
    /// Giving an invalid `nb_class` is UB.
    pub fn get_offsets(&self, nb_class: u32) -> Result<Vec<u32>> {
        let mut offsets = vec![];
        let mut buffer = Cursor::new(self.data.as_slice());
        for _ in 0..nb_class {
            offsets.push(u32::deserialize(&mut buffer)?);
        }
        Ok(offsets)
    }

    /// Get a list of `nb_flags` flags from the `offset`.
    /// `nb_flags` should be the number of field + the number of method
    /// of the class associated to `offset`.
    ///
    /// # Warning
    ///
    /// `class_def_item_idx` is **NOT** `class_idx` (Contrary to what
    /// <https://source.android.com/docs/core/runtime/dex-format#hiddenapi-class-data-item> says).
    /// `class_def_item_idx` is the index of the `class_def_item`.
    ///
    /// They are no check weither the `nb_flags` or `class_def_item_idx`
    /// are valid. Providing invalid values is UB.
    pub fn get_flags(
        &self,
        nb_flags: usize,
        class_def_item_idx: usize,
    ) -> Result<Vec<HiddenApiFlags>> {
        let offset = self.get_offset(class_def_item_idx)?;
        if offset == 0 {
            Ok(vec![HiddenApiFlags::DEFAULT; nb_flags])
        } else if offset < 4 {
            // < 8 is almost certainly false
            panic!()
        } else {
            let mut buffer = Cursor::new(self.data.as_slice());
            buffer
                .seek(SeekFrom::Start(offset as u64 - 4))
                .map_err(|_| {
                    Error::InconsistantStruct(format!(
                        "{offset} if out of data bound for HiddenApiClassData"
                    ))
                })?;
            let mut flags = vec![];
            for _ in 0..nb_flags {
                flags.push(HiddenApiFlags::from_uleb128(Uleb128::deserialize(
                    &mut buffer,
                )?));
            }
            Ok(flags)
        }
    }
}

impl Serializable for HiddenapiClassDataItem {
    fn serialize(&self, output: &mut dyn Write) -> Result<()> {
        self.size_field().serialize(output)?;
        for byte in &self.data {
            byte.serialize(output)?;
        }
        Ok(())
    }

    fn deserialize(input: &mut dyn ReadSeek) -> Result<Self> {
        let size = u32::deserialize(input)?;
        let mut data = vec![];
        for _ in 0..size {
            data.push(u8::deserialize(input)?);
        }
        Ok(Self { data })
    }

    fn size(&self) -> usize {
        self.size_field().size() + self.data.iter().map(|val| val.size()).sum::<usize>()
    }
}

#[derive(Clone, PartialEq, Eq)]
pub struct HiddenApiFlags {
    pub value: HiddenApiValue,
    pub domain_api: HiddenApiDomain,
}

impl HiddenApiFlags {
    pub const DEFAULT: Self = Self {
        value: HiddenApiValue::Whitelist,
        domain_api: HiddenApiDomain {
            is_test_api: false,
            is_core_platform_api: false,
            unknown_flags: 0,
        },
    };

    pub fn from_uleb128(Uleb128(val): Uleb128) -> Self {
        Self {
            value: HiddenApiValue::from_u32(val),
            domain_api: HiddenApiDomain::from_u32(val),
        }
    }

    pub fn to_uleb128(&self) -> Uleb128 {
        Uleb128(self.value.to_u32() | self.domain_api.to_u32())
    }
}

/// Flags for hidden api flag value
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum HiddenApiValue {
    /// Interfaces that can be freely used and are supported as
    /// part of the officially documented Android framework Package Index
    Whitelist,
    /// Non-SDK interfaces that can be used regardless of the
    /// application's target API level
    Greylist,
    /// Non-SDK interfaces that cannot be used regardless of the
    /// application's target API level. Accessing one of these
    /// interfaces causes a runtime error.
    Blacklist,
    /// Non-SDK interfaces that can be used for Android 8.x and
    /// below unless they are restricted (targetSdkVersion <= 27 (O_MR1)).
    GreylistMaxO,
    /// Non-SDK interfaces that can be used for Android 9.x unless
    /// they are restricted.
    GreylistMaxP,
    /// Non-SDK interfaces that can be used for Android 10.x unless
    /// they are restricted.
    GreylistMaxQ,
    /// Non-SDK interfaces that can be used for Android 11.x unless
    /// they are restricted.
    GreylistMaxR,
    GreylistMaxS,
    /// Unknown flag, either an error or this crate is out of date.
    Unknwon(u8),
}

impl HiddenApiValue {
    pub const MASK: u32 = 0b111;
    pub fn from_u32(flags: u32) -> Self {
        match flags & Self::MASK {
            0x00 => Self::Whitelist,
            0x01 => Self::Greylist,
            0x02 => Self::Blacklist,
            0x03 => Self::GreylistMaxO,
            0x04 => Self::GreylistMaxP,
            0x05 => Self::GreylistMaxQ,
            0x06 => Self::GreylistMaxR,
            0x07 => Self::GreylistMaxS,
            other => Self::Unknwon(other as u8),
        }
    }
    pub fn to_u32(&self) -> u32 {
        match self {
            Self::Whitelist => 0x00,
            Self::Greylist => 0x01,
            Self::Blacklist => 0x02,
            Self::GreylistMaxO => 0x03,
            Self::GreylistMaxP => 0x04,
            Self::GreylistMaxQ => 0x05,
            Self::GreylistMaxR => 0x06,
            Self::GreylistMaxS => 0x07,
            Self::Unknwon(other) => {
                if (0b1111_1000 & *other) != 0 {
                    panic!("HiddenApiValue is encoded on 3 bits but found value {other}");
                }
                *other as u32
            }
        }
    }
}

/// Flags for hidden api flag domain
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct HiddenApiDomain {
    pub is_core_platform_api: bool,
    pub is_test_api: bool,
    pub unknown_flags: u32,
}

impl HiddenApiDomain {
    pub const CORE_PLATFORM_API_FLAG: u32 = 0x08;
    pub const TEST_API_FLAG: u32 = 0x10;
    pub fn from_u32(flags: u32) -> Self {
        let flags = flags & !HiddenApiValue::MASK;
        Self {
            is_core_platform_api: (flags & Self::CORE_PLATFORM_API_FLAG) != 0,
            is_test_api: (flags & Self::TEST_API_FLAG) != 0,
            unknown_flags: flags & (!Self::CORE_PLATFORM_API_FLAG) & (!Self::TEST_API_FLAG),
        }
    }
    pub fn to_u32(&self) -> u32 {
        if ((0b111 | Self::CORE_PLATFORM_API_FLAG | Self::CORE_PLATFORM_API_FLAG)
            & self.unknown_flags)
            != 0
        {
            panic!(
                "The first 5 bits of HiddenApiDomain are reserved and \
                        shoud be set to 0, but value 0x{:x} was found",
                self.unknown_flags
            );
        }
        self.unknown_flags
            | if self.is_core_platform_api {
                Self::CORE_PLATFORM_API_FLAG
            } else {
                0
            }
            | if self.is_test_api {
                Self::TEST_API_FLAG
            } else {
                0
            }
    }
}
