//! Representation of the fields of a class.

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

use pyo3::prelude::*;

use crate::{
    DexAnnotationItem, DexString, DexValue, IdField, IdMethod, IdMethodType, IdType, MethodHandle,
    Result,
};
use androscalpel_serializer::consts::*;

/// Represent a field.
#[pyclass]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Field {
    /// The structure used to reference this field.
    #[pyo3(get)]
    pub descriptor: IdField,
    /// The field visibility
    #[pyo3(get)]
    pub visibility: FieldVisibility,
    /// If the field is defined for the class globally
    #[pyo3(get)]
    pub is_static: bool,
    /// If the field is immutable after construction
    #[pyo3(get)]
    pub is_final: bool,
    /// For thread safety
    #[pyo3(get)]
    pub is_volatile: bool,
    /// If the field should **not** be saved by default serialization
    #[pyo3(get)]
    pub is_transient: bool,
    /// If the field is not defined in the source code
    #[pyo3(get)]
    pub is_synthetic: bool,
    /// If the field is an enumerated value
    #[pyo3(get)]
    pub is_enum: bool,
    /// The default value of this field
    pub value: Option<DexValue>,
    /// The annotations for this field
    #[pyo3(get)]
    pub annotations: Vec<DexAnnotationItem>,
}

/// Represent the visibility of a field
#[pyclass]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub enum FieldVisibility {
    Public,
    Private,
    Protected,
    None_, // Actually quite common
}

#[pymethods]
impl Field {
    pub fn to_json(&self) -> Result<String> {
        Ok(serde_json::to_string(self)?)
    }

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

    #[new]
    pub fn new(descriptor: IdField) -> Self {
        Self {
            descriptor,
            visibility: FieldVisibility::Public,
            is_static: false,
            is_final: false,
            is_volatile: false,
            is_transient: false,
            is_synthetic: false,
            is_enum: false,
            value: None,
            annotations: vec![],
        }
    }

    pub fn __str__(&self) -> String {
        /*
        let name: String = (&self.name).into();
        let file = if let Some(file) = &self.source_file {
            let file: String = file.into();
            format!("  defined in {file}\n")
        } else {
            "".into()
        };
        let superclass = if let Some(spcl) = &self.superclass {
            let spcl: String = spcl.into();
            format!("    extends: {spcl}\n")
        } else {
            "".into()
        };
        let interfaces = if self.interfaces.is_empty() {
            "".into()
        } else {
            let mut interfaces: String = "  implements:\n".into();
            for it in &self.interfaces {
                let it: String = it.into();
                interfaces += &format!("    {it}\n");
            }
            interfaces
        };

        format!("{name}\n{file}{superclass}{interfaces}")
        */
        self.__repr__()
    }

    pub fn __repr__(&self) -> String {
        let descr = self.descriptor.__str__();
        format!("Field({descr})")
    }

    /// Return the value as a python object
    #[getter]
    pub fn get_value(&self) -> Option<DexValue> {
        self.value.clone()
    }

    /// Set the value from a python object
    #[setter]
    pub fn set_value(&mut self, ob: &PyAny) -> PyResult<()> {
        self.value = Some(ob.extract()?);
        // TODO: check type match
        Ok(())
    }

    /// Return all strings referenced in the field.
    pub fn get_all_strings(&self) -> HashSet<DexString> {
        let mut strings = HashSet::new();

        strings.extend(self.descriptor.get_all_strings());
        if let Some(val) = &self.value {
            strings.extend(val.get_all_strings());
        }
        for annot in &self.annotations {
            strings.extend(annot.get_all_strings());
        }
        strings
    }

    /// Return all types referenced in the field.
    pub fn get_all_types(&self) -> HashSet<IdType> {
        let mut types = HashSet::new();
        types.extend(self.descriptor.get_all_types());
        if let Some(value) = &self.value {
            types.extend(value.get_all_types());
        }
        for annot in &self.annotations {
            types.extend(annot.get_all_types());
        }
        types
    }

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

    /// Return all field ids referenced in the field.
    pub fn get_all_field_ids(&self) -> HashSet<IdField> {
        let mut fields = HashSet::new();
        fields.insert(self.descriptor.clone());
        if let Some(value) = &self.value {
            fields.extend(value.get_all_field_ids());
        }
        for annot in &self.annotations {
            fields.extend(annot.get_all_field_ids());
        }
        fields
    }

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

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

    /// Return the binary representation of access flags.
    ///
    /// # Note
    ///
    /// The return value is a u32, not the Uleb128
    /// encoded value found in the dex file.
    pub fn get_raw_access_flags(&self) -> u32 {
        let mut flags = 0;
        match self.visibility {
            FieldVisibility::Public => flags |= ACC_PUBLIC,
            FieldVisibility::Private => flags |= ACC_PRIVATE,
            FieldVisibility::Protected => flags |= ACC_PROTECTED,
            FieldVisibility::None_ => (),
        }
        if self.is_static {
            flags |= ACC_STATIC
        }
        if self.is_final {
            flags |= ACC_FINAL
        }
        if self.is_volatile {
            flags |= ACC_VOLATILE
        }
        if self.is_transient {
            flags |= ACC_TRANSIENT
        }
        if self.is_synthetic {
            flags |= ACC_SYNTHETIC
        }
        if self.is_enum {
            flags |= ACC_ENUM
        }
        flags
    }

    /// If the fields has annotations
    pub fn has_annotations(&self) -> bool {
        !self.annotations.is_empty()
    }

    pub fn __eq__(&self, other: &Self) -> bool {
        self == other
    }
}
