//! The class identifying dex structure.

use crate::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;

use crate::{
    DexString, DexValue, IdField, IdMethod, IdMethodType, IdType, MethodHandle, Visitable,
    VisitableMut, Visitor, VisitorMut,
};
#[cfg(feature = "python")]
use pyo3::prelude::*;

#[cfg_attr(feature = "python", pyclass(eq))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub struct DexByte(pub i8);

#[cfg_attr(feature = "python", pymethods)]
impl DexByte {
    pub fn to_json(&self) -> Result<String> {
        Ok(serde_json::to_string(self)?)
    }

    #[cfg_attr(feature = "python", staticmethod)]
    pub fn from_json(json: &str) -> Result<Self> {
        Ok(serde_json::from_str(json)?)
    }

    #[cfg_attr(feature = "python", new)]
    pub fn new(val: i8) -> Self {
        Self(val)
    }

    pub fn get_value(&self) -> i8 {
        self.0
    }

    pub fn __str__(&self) -> String {
        self.__repr__()
    }

    pub fn __repr__(&self) -> String {
        format!("DexByte({})", self.0)
    }
}

#[cfg_attr(feature = "python", pyclass(eq))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub struct DexShort(pub i16);
#[cfg_attr(feature = "python", pymethods)]
impl DexShort {
    pub fn to_json(&self) -> Result<String> {
        Ok(serde_json::to_string(self)?)
    }

    #[cfg_attr(feature = "python", staticmethod)]
    pub fn from_json(json: &str) -> Result<Self> {
        Ok(serde_json::from_str(json)?)
    }

    #[cfg_attr(feature = "python", new)]
    pub fn new(val: i16) -> Self {
        Self(val)
    }

    pub fn get_value(&self) -> i16 {
        self.0
    }

    pub fn __str__(&self) -> String {
        self.__repr__()
    }

    pub fn __repr__(&self) -> String {
        format!("DexShort({})", self.0)
    }
}

#[cfg_attr(feature = "python", pyclass(eq))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub struct DexChar(pub u16);
#[cfg_attr(feature = "python", pymethods)]
impl DexChar {
    pub fn to_json(&self) -> Result<String> {
        Ok(serde_json::to_string(self)?)
    }

    #[cfg_attr(feature = "python", staticmethod)]
    pub fn from_json(json: &str) -> Result<Self> {
        Ok(serde_json::from_str(json)?)
    }

    #[cfg_attr(feature = "python", new)]
    pub fn new(val: u16) -> Self {
        Self(val)
    }

    pub fn get_value(&self) -> u16 {
        self.0
    }

    pub fn __str__(&self) -> String {
        self.__repr__()
    }

    pub fn __repr__(&self) -> String {
        format!("DexChar({})", self.0)
    }
}

#[cfg_attr(feature = "python", pyclass(eq))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub struct DexInt(pub i32);
#[cfg_attr(feature = "python", pymethods)]
impl DexInt {
    pub fn to_json(&self) -> Result<String> {
        Ok(serde_json::to_string(self)?)
    }

    #[cfg_attr(feature = "python", staticmethod)]
    pub fn from_json(json: &str) -> Result<Self> {
        Ok(serde_json::from_str(json)?)
    }

    #[cfg_attr(feature = "python", new)]
    pub fn new(val: i32) -> Self {
        Self(val)
    }

    pub fn get_value(&self) -> i32 {
        self.0
    }

    pub fn __str__(&self) -> String {
        self.__repr__()
    }

    pub fn __repr__(&self) -> String {
        format!("DexInt({})", self.0)
    }
}

#[cfg_attr(feature = "python", pyclass(eq))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub struct DexLong(pub i64);
#[cfg_attr(feature = "python", pymethods)]
impl DexLong {
    pub fn to_json(&self) -> Result<String> {
        Ok(serde_json::to_string(self)?)
    }

    #[cfg_attr(feature = "python", staticmethod)]
    pub fn from_json(json: &str) -> Result<Self> {
        Ok(serde_json::from_str(json)?)
    }

    #[cfg_attr(feature = "python", new)]
    pub fn new(val: i64) -> Self {
        Self(val)
    }

    pub fn get_value(&self) -> i64 {
        self.0
    }

    pub fn __str__(&self) -> String {
        self.__repr__()
    }

    pub fn __repr__(&self) -> String {
        format!("DexLong({})", self.0)
    }
}

#[cfg_attr(feature = "python", pyclass(eq))]
#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
pub struct DexFloat(pub f32);
#[cfg_attr(feature = "python", pymethods)]
impl DexFloat {
    pub fn to_json(&self) -> Result<String> {
        Ok(serde_json::to_string(self)?)
    }

    #[cfg_attr(feature = "python", staticmethod)]
    pub fn from_json(json: &str) -> Result<Self> {
        Ok(serde_json::from_str(json)?)
    }

    #[cfg_attr(feature = "python", new)]
    pub fn new(val: f32) -> Self {
        Self(val)
    }

    pub fn get_value(&self) -> f32 {
        self.0
    }

    pub fn __str__(&self) -> String {
        self.__repr__()
    }

    pub fn __repr__(&self) -> String {
        format!("DexFloat({})", self.0)
    }
}

#[cfg_attr(feature = "python", pyclass(eq))]
#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
pub struct DexDouble(pub f64);
#[cfg_attr(feature = "python", pymethods)]
impl DexDouble {
    pub fn to_json(&self) -> Result<String> {
        Ok(serde_json::to_string(self)?)
    }

    #[cfg_attr(feature = "python", staticmethod)]
    pub fn from_json(json: &str) -> Result<Self> {
        Ok(serde_json::from_str(json)?)
    }

    #[cfg_attr(feature = "python", new)]
    pub fn new(val: f64) -> Self {
        Self(val)
    }

    pub fn get_value(&self) -> f64 {
        self.0
    }

    pub fn __str__(&self) -> String {
        self.__repr__()
    }

    pub fn __repr__(&self) -> String {
        format!("DexDouble({})", self.0)
    }
}

/* DexString is already define in lib.rs, TODO: move the version in lib.rs here
#[cfg_attr(feature = "python", pyclass(eq))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DexString(pub u32);
#[cfg_attr(feature = "python", pymethods)]
impl DexString {
    pub fn to_json(&self) -> Result<String> {
        Ok(serde_json::to_string(self)?)
    }

   #[cfg_attr(feature = "python", staticmethod)]
    pub fn from_json(json: &str) -> Result<Self> {
        Ok(serde_json::from_str(json)?)
    }

    #[cfg_attr(feature = "python", new)]
    pub fn new(val: u32) -> Self {
        Self(val)
    }

    pub fn get_value(&self) -> u32 {
        self.0
    }

    pub fn __str__(&self) -> String {
        self.__repr__()
    }

    pub fn __repr__(&self) -> String {
        format!("DexString({})", self.0)
    }
}
*/

#[cfg_attr(feature = "python", pyclass(eq))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub struct DexNull;
#[cfg_attr(feature = "python", pymethods)]
impl DexNull {
    pub fn to_json(&self) -> Result<String> {
        Ok(serde_json::to_string(self)?)
    }

    #[cfg_attr(feature = "python", staticmethod)]
    pub fn from_json(json: &str) -> Result<Self> {
        Ok(serde_json::from_str(json)?)
    }

    #[cfg_attr(feature = "python", new)]
    pub fn _new() -> Self {
        Self
    }

    pub fn __str__(&self) -> String {
        self.__repr__()
    }

    pub fn __repr__(&self) -> String {
        "DexNull".into()
    }
}

#[cfg_attr(feature = "python", pyclass(eq))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub struct DexBoolean(pub bool);
#[cfg_attr(feature = "python", pymethods)]
impl DexBoolean {
    pub fn to_json(&self) -> Result<String> {
        Ok(serde_json::to_string(self)?)
    }

    #[cfg_attr(feature = "python", staticmethod)]
    pub fn from_json(json: &str) -> Result<Self> {
        Ok(serde_json::from_str(json)?)
    }

    #[cfg_attr(feature = "python", new)]
    pub fn new(val: bool) -> Self {
        Self(val)
    }

    pub fn get_value(&self) -> bool {
        self.0
    }

    pub fn __str__(&self) -> String {
        self.__repr__()
    }

    pub fn __repr__(&self) -> String {
        format!("DexBoolean({})", self.0)
    }
}

#[cfg_attr(feature = "python", pyclass(eq))]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct DexArray(pub Vec<DexValue>);
#[cfg_attr(feature = "python", pymethods)]
impl DexArray {
    pub fn to_json(&self) -> Result<String> {
        Ok(serde_json::to_string(self)?)
    }

    #[cfg_attr(feature = "python", staticmethod)]
    pub fn from_json(json: &str) -> Result<Self> {
        Ok(serde_json::from_str(json)?)
    }

    #[cfg_attr(feature = "python", new)]
    pub fn _new(arr: Vec<DexValue>) -> Self {
        Self(arr)
    }

    pub fn get_value(&self) -> Vec<DexValue> {
        self.0.clone() // TODO: this will becom a problem...
    }

    pub fn __str__(&self) -> String {
        format!(
            "[{}]",
            self.0
                .iter()
                .map(|val| val.__str__())
                .collect::<Vec<String>>()
                .join(", ")
        )
    }

    pub fn __repr__(&self) -> String {
        "DexArray(...)".into()
    }

    /// Return all strings referenced in the value.
    pub fn get_all_strings(&self) -> HashSet<DexString> {
        let mut strings = HashSet::new();
        for val in &self.0 {
            strings.extend(val.get_all_strings());
        }
        strings
    }

    /// Return all type referenced in the value.
    pub fn get_all_types(&self) -> HashSet<IdType> {
        let mut types = HashSet::new();
        for val in &self.0 {
            types.extend(val.get_all_types());
        }
        types
    }

    /// Return all prototypes referenced in the value.
    pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
        let mut protos = HashSet::new();
        for val in &self.0 {
            protos.extend(val.get_all_protos());
        }
        protos
    }

    /// Return all field ids referenced in the value.
    pub fn get_all_field_ids(&self) -> HashSet<IdField> {
        let mut fields = HashSet::new();
        for val in &self.0 {
            fields.extend(val.get_all_field_ids());
        }
        fields
    }

    /// Return all method ids referenced in the value.
    pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
        let mut methods = HashSet::new();
        for val in &self.0 {
            methods.extend(val.get_all_method_ids());
        }
        methods
    }

    /// Return all method handles referenced in the value.
    pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
        let mut methods = HashSet::new();
        for val in &self.0 {
            methods.extend(val.get_all_method_handles());
        }
        methods
    }
}

impl<V: Visitor> Visitable<V> for DexArray {
    fn default_visit(&self, v: &mut V) -> Result<()> {
        for val in &self.0 {
            v.visit_value(val)?;
        }
        Ok(())
    }
}

impl<V: VisitorMut> VisitableMut<V> for DexArray {
    fn default_visit_mut(self, v: &mut V) -> Result<Self> {
        Ok(Self(
            self.0
                .into_iter()
                .map(|val| v.visit_value(val))
                .collect::<Result<_>>()?,
        ))
    }
}
