use crate::{error::PropParseError, proto};
use ini::Properties;
use std::{
    collections::HashMap,
    ops::{Deref, DerefMut},
};

macro_rules! str {
    ($a:expr, $b:literal) => {
        $a.get($b).map(|a| a.to_string())
    };
}

macro_rules! num {
    ($a:expr, $b:literal) => {
        $a.get($b).and_then(|a| a.parse().ok())
    };
}

macro_rules! list {
    ($a:expr, $b:literal) => {
        $a.get($b)
            .map(|a| a.split(",").map(|a| a.to_string()).collect::<Vec<_>>())
            .unwrap_or_default()
    };
}

macro_rules! prop_boo {
    ($a:expr, $b:literal) => {
        $a.get($b).map(|a| a.to_lowercase() == "true")
    };
}

/// the properties for a singular device
#[derive(Debug)]
pub struct DeviceProperties {
    pub device_config: proto::DeviceConfigurationProto,
    pub android_checkin: proto::AndroidCheckinProto,
    pub extra_info: HashMap<String, String>,
}

/// a map of parsed aurora store style device properties, each key being the device name
#[derive(Debug)]
pub struct PropsMap(HashMap<String, DeviceProperties>);

impl Deref for PropsMap {
    type Target = HashMap<String, DeviceProperties>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for PropsMap {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl PropsMap {
    /// parse aurora store style ini device props into a map
    ///
    /// # Errors
    /// - `InvalidIni` - for invalid ini
    pub fn parse_aurora_config<A: AsRef<str>>(aurora_config: A) -> Result<Self, PropParseError> {
        let string_config = aurora_config.as_ref();
        let props = ini::Ini::load_from_str(string_config)?;

        let map = props
            .into_iter()
            .filter_map(|(name, props)| name.map(|name| (name, parse_device(&props))))
            .collect();

        Ok(Self(map))
    }
}

fn parse_device(pro: &Properties) -> DeviceProperties {
    let extra_info: HashMap<_, _> = [
        "Build.ID",
        "Vending.versionString",
        "Vending.version",
        "Build.VERSION.RELEASE",
        "SimOperator",
    ]
    .iter()
    .map(|a| (a.to_string(), pro.get(a).unwrap_or_default().to_string()))
    .collect();

    let android_build = proto::AndroidBuildProto {
        sdk_version: num!(pro, "Build.VERSION.SDK_INT"),
        google_services: num!(pro, "GSF.version"),

        manufacturer: str!(pro, "Build.MANUFACTURER"),
        build_product: str!(pro, "Build.PRODUCT"),
        bootloader: str!(pro, "Build.BOOTLOADER"),
        product: str!(pro, "Build.HARDWARE"),
        device: str!(pro, "Build.DEVICE"),
        id: str!(pro, "Build.FINGERPRINT"),
        carrier: str!(pro, "Build.BRAND"),
        model: str!(pro, "Build.MODEL"),
        radio: str!(pro, "Build.RADIO"),
        client: str!(pro, "Client"),

        ota_installed: Some(false),
        ..Default::default()
    };

    let android_checkin = proto::AndroidCheckinProto {
        cell_operator: str!(pro, "CellOperator"),
        sim_operator: str!(pro, "SimOperator"),
        roaming: str!(pro, "Roaming"),

        build: Some(android_build),
        last_checkin_msec: Some(0),
        user_number: Some(0),

        ..Default::default()
    };

    let device_config = proto::DeviceConfigurationProto {
        has_five_way_navigation: prop_boo!(pro, "HasFiveWayNavigation"),
        has_hard_keyboard: prop_boo!(pro, "HasHardKeyboard"),

        system_shared_library: list!(pro, "SharedLibraries"),
        system_available_feature: list!(pro, "Features"),
        system_supported_locale: list!(pro, "Locales"),
        native_platform: list!(pro, "Platforms"),
        gl_extension: list!(pro, "GL.Extensions"),

        screen_density: num!(pro, "Screen.Density"),
        screen_height: num!(pro, "Screen.Height"),
        screen_layout: num!(pro, "ScreenLayout"),
        screen_width: num!(pro, "Screen.Width"),
        touch_screen: num!(pro, "TouchScreen"),
        gl_es_version: num!(pro, "GL.Version"),
        navigation: num!(pro, "Navigation"),
        keyboard: num!(pro, "Keyboard"),

        device_feature: pro
            .get("Features")
            .map(|a| {
                a.split(',')
                    .map(|a| proto::DeviceFeature {
                        name: Some(a.to_string()),
                        value: Some(0),
                    })
                    .collect::<Vec<_>>()
            })
            .unwrap_or_default(),

        ..Default::default()
    };

    DeviceProperties {
        device_config,
        android_checkin,
        extra_info,
    }
}
