//! The class identifying dex structure.

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

#[cfg(feature = "python")]
use pyo3::{exceptions::PyTypeError, prelude::*};

use crate::{
    dex_id::*, scalar::*, DexAnnotation, DexString, MethodHandle, Result, Visitable, VisitableMut,
    Visitor, VisitorMut,
};

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum DexValue {
    Byte(DexByte),
    Short(DexShort),
    Char(DexChar),
    Int(DexInt),
    Long(DexLong),
    Float(DexFloat),
    Double(DexDouble),
    MethodType(IdMethodType),
    MethodHandle(MethodHandle),
    String(DexString),
    Type(IdType),
    Field(IdField),
    Method(IdMethod),
    Enum(IdEnum),
    Array(DexArray),
    Annotation(DexAnnotation),
    Null(DexNull),
    Boolean(DexBoolean),
}

impl<V: Visitor> Visitable<V> for DexValue {
    fn default_visit(&self, v: &mut V) -> Result<()> {
        match self {
            Self::Byte(val) => v.visit_byte(val),
            Self::Short(val) => v.visit_short(val),
            Self::Char(val) => v.visit_char(val),
            Self::Int(val) => v.visit_int(val),
            Self::Long(val) => v.visit_long(val),
            Self::Float(val) => v.visit_float(val),
            Self::Double(val) => v.visit_double(val),
            Self::MethodType(val) => v.visit_method_type(val),
            Self::MethodHandle(val) => v.visit_method_handle(val),
            Self::String(val) => v.visit_string(val),
            Self::Type(val) => v.visit_type(val),
            Self::Field(val) => v.visit_field_id(val),
            Self::Method(val) => v.visit_method_id(val),
            Self::Enum(val) => v.visit_enum_id(val),
            Self::Array(val) => v.visit_array(val),
            Self::Annotation(val) => v.visit_annotation(val),
            Self::Null(val) => v.visit_null(val),
            Self::Boolean(val) => v.visit_bool(val),
        }
    }
}

impl<V: VisitorMut> VisitableMut<V> for DexValue {
    fn default_visit_mut(self, v: &mut V) -> Result<Self> {
        match self {
            Self::Byte(val) => Ok(Self::Byte(v.visit_byte(val)?)),
            Self::Short(val) => Ok(Self::Short(v.visit_short(val)?)),
            Self::Char(val) => Ok(Self::Char(v.visit_char(val)?)),
            Self::Int(val) => Ok(Self::Int(v.visit_int(val)?)),
            Self::Long(val) => Ok(Self::Long(v.visit_long(val)?)),
            Self::Float(val) => Ok(Self::Float(v.visit_float(val)?)),
            Self::Double(val) => Ok(Self::Double(v.visit_double(val)?)),
            Self::MethodType(val) => Ok(Self::MethodType(v.visit_method_type(val)?)),
            Self::MethodHandle(val) => Ok(Self::MethodHandle(v.visit_method_handle(val)?)),
            Self::String(val) => Ok(Self::String(v.visit_string(val)?)),
            Self::Type(val) => Ok(Self::Type(v.visit_type(val)?)),
            Self::Field(val) => Ok(Self::Field(v.visit_field_id(val)?)),
            Self::Method(val) => Ok(Self::Method(v.visit_method_id(val)?)),
            Self::Enum(val) => Ok(Self::Enum(v.visit_enum_id(val)?)),
            Self::Array(val) => Ok(Self::Array(v.visit_array(val)?)),
            Self::Annotation(val) => Ok(Self::Annotation(v.visit_annotation(val)?)),
            Self::Null(val) => Ok(Self::Null(v.visit_null(val)?)),
            Self::Boolean(val) => Ok(Self::Boolean(v.visit_bool(val)?)),
        }
    }
}

#[cfg(feature = "python")]
impl<'source> FromPyObject<'source> for DexValue {
    fn extract_bound(ob: &Bound<'source, PyAny>) -> PyResult<Self> {
        if let Ok(val) = DexByte::extract_bound(ob) {
            Ok(Self::Byte(val))
        } else if let Ok(val) = DexShort::extract_bound(ob) {
            Ok(Self::Short(val))
        } else if let Ok(val) = DexChar::extract_bound(ob) {
            Ok(Self::Char(val))
        } else if let Ok(val) = DexInt::extract_bound(ob) {
            Ok(Self::Int(val))
        } else if let Ok(val) = DexLong::extract_bound(ob) {
            Ok(Self::Long(val))
        } else if let Ok(val) = DexFloat::extract_bound(ob) {
            Ok(Self::Float(val))
        } else if let Ok(val) = DexDouble::extract_bound(ob) {
            Ok(Self::Double(val))
        } else if let Ok(val) = DexString::extract_bound(ob) {
            Ok(Self::String(val))
        } else if let Ok(val) = IdType::extract_bound(ob) {
            Ok(Self::Type(val))
        } else if let Ok(val) = IdField::extract_bound(ob) {
            Ok(Self::Field(val))
        } else if let Ok(val) = IdMethod::extract_bound(ob) {
            Ok(Self::Method(val))
        } else if let Ok(val) = IdEnum::extract_bound(ob) {
            Ok(Self::Enum(val))
        } else if let Ok(val) = DexArray::extract_bound(ob) {
            Ok(Self::Array(val))
        } else if let Ok(val) = DexAnnotation::extract_bound(ob) {
            Ok(Self::Annotation(val))
        } else if let Ok(val) = DexNull::extract_bound(ob) {
            Ok(Self::Null(val))
        } else if let Ok(val) = DexBoolean::extract_bound(ob) {
            Ok(Self::Boolean(val))
        } else if let Ok(val) = IdMethodType::extract_bound(ob) {
            Ok(Self::MethodType(val))
        } else if let Ok(val) = MethodHandle::extract_bound(ob) {
            Ok(Self::MethodHandle(val))
        } else {
            Err(PyErr::new::<PyTypeError, _>(format!(
                "{} is not a castable as a DexValue",
                ob.repr()?
            )))
        }
    }
}

impl DexValue {
    // Not exposed to python, but meh, let's keep it coherent
    pub fn __str__(&self) -> String {
        match self {
            DexValue::Byte(val) => val.__str__(),
            DexValue::Short(val) => val.__str__(),
            DexValue::Char(val) => val.__str__(),
            DexValue::Int(val) => val.__str__(),
            DexValue::Long(val) => val.__str__(),
            DexValue::Float(val) => val.__str__(),
            DexValue::Double(val) => val.__str__(),
            DexValue::MethodType(val) => val.__str__(),
            DexValue::MethodHandle(val) => val.__str__(),
            DexValue::String(val) => val.__str__(),
            DexValue::Type(val) => val.__str__(),
            DexValue::Field(val) => val.__str__(),
            DexValue::Method(val) => val.__str__(),
            DexValue::Enum(val) => val.__str__(),
            DexValue::Array(val) => val.__str__(),
            DexValue::Annotation(val) => val.__str__(),
            DexValue::Null(val) => val.__str__(),
            DexValue::Boolean(val) => val.__str__(),
        }
    }

    // TODO: implement for each variant struct for python binding
    /// Return all strings referenced in the value.
    pub fn get_all_strings(&self) -> HashSet<DexString> {
        match self {
            DexValue::Byte(_) => HashSet::new(),
            DexValue::Short(_) => HashSet::new(),
            DexValue::Char(_) => HashSet::new(),
            DexValue::Int(_) => HashSet::new(),
            DexValue::Long(_) => HashSet::new(),
            DexValue::Float(_) => HashSet::new(),
            DexValue::Double(_) => HashSet::new(),
            DexValue::MethodType(val) => val.get_all_strings(),
            DexValue::MethodHandle(val) => val.get_all_strings(),
            DexValue::String(val) => val.get_all_strings(),
            DexValue::Type(val) => val.get_all_strings(),
            DexValue::Field(val) => val.get_all_strings(),
            DexValue::Method(val) => val.get_all_strings(),
            DexValue::Enum(val) => val.get_all_strings(),
            DexValue::Array(val) => val.get_all_strings(),
            DexValue::Annotation(val) => val.get_all_strings(),
            DexValue::Null(_) => HashSet::new(),
            DexValue::Boolean(_) => HashSet::new(),
        }
    }

    // TODO: implement for each variant struct for python binding
    /// Return all types referenced in the value.
    pub fn get_all_types(&self) -> HashSet<IdType> {
        match self {
            DexValue::Byte(_) => HashSet::new(),
            DexValue::Short(_) => HashSet::new(),
            DexValue::Char(_) => HashSet::new(),
            DexValue::Int(_) => HashSet::new(),
            DexValue::Long(_) => HashSet::new(),
            DexValue::Float(_) => HashSet::new(),
            DexValue::Double(_) => HashSet::new(),
            DexValue::MethodType(val) => val.get_all_types(),
            DexValue::MethodHandle(val) => val.get_all_types(),
            DexValue::String(_) => HashSet::new(),
            DexValue::Type(val) => val.get_all_types(),
            DexValue::Field(val) => val.get_all_types(),
            DexValue::Method(val) => val.get_all_types(),
            DexValue::Enum(val) => val.get_all_types(),
            DexValue::Array(val) => val.get_all_types(),
            DexValue::Annotation(val) => val.get_all_types(),
            DexValue::Null(_) => HashSet::new(),
            DexValue::Boolean(_) => HashSet::new(),
        }
    }

    // TODO: implement for each variant struct for python binding
    /// Return all prototypes referenced in the value.
    pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
        match self {
            DexValue::Byte(_) => HashSet::new(),
            DexValue::Short(_) => HashSet::new(),
            DexValue::Char(_) => HashSet::new(),
            DexValue::Int(_) => HashSet::new(),
            DexValue::Long(_) => HashSet::new(),
            DexValue::Float(_) => HashSet::new(),
            DexValue::Double(_) => HashSet::new(),
            DexValue::MethodType(val) => val.get_all_protos(),
            DexValue::MethodHandle(val) => val.get_all_protos(),
            DexValue::String(_) => HashSet::new(),
            DexValue::Type(_) => HashSet::new(),
            DexValue::Field(_) => HashSet::new(),
            DexValue::Method(val) => val.get_all_protos(),
            DexValue::Enum(_) => HashSet::new(),
            DexValue::Array(val) => val.get_all_protos(),
            DexValue::Annotation(val) => val.get_all_protos(),
            DexValue::Null(_) => HashSet::new(),
            DexValue::Boolean(_) => HashSet::new(),
        }
    }

    // TODO: implement for each variant struct for python binding
    /// Return all field ids referenced in the value.
    pub fn get_all_field_ids(&self) -> HashSet<IdField> {
        match self {
            DexValue::Byte(_) => HashSet::new(),
            DexValue::Short(_) => HashSet::new(),
            DexValue::Char(_) => HashSet::new(),
            DexValue::Int(_) => HashSet::new(),
            DexValue::Long(_) => HashSet::new(),
            DexValue::Float(_) => HashSet::new(),
            DexValue::Double(_) => HashSet::new(),
            DexValue::MethodType(_) => HashSet::new(),
            DexValue::MethodHandle(val) => val.get_all_field_ids(),
            DexValue::String(_) => HashSet::new(),
            DexValue::Type(_) => HashSet::new(),
            DexValue::Field(val) => val.get_all_field_ids(),
            DexValue::Method(_) => HashSet::new(),
            DexValue::Enum(val) => val.get_all_field_ids(),
            DexValue::Array(val) => val.get_all_field_ids(),
            DexValue::Annotation(val) => val.get_all_field_ids(),
            DexValue::Null(_) => HashSet::new(),
            DexValue::Boolean(_) => HashSet::new(),
        }
    }

    // TODO: implement for each variant struct for python binding
    /// Return all method ids referenced in the value.
    pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
        match self {
            DexValue::Byte(_) => HashSet::new(),
            DexValue::Short(_) => HashSet::new(),
            DexValue::Char(_) => HashSet::new(),
            DexValue::Int(_) => HashSet::new(),
            DexValue::Long(_) => HashSet::new(),
            DexValue::Float(_) => HashSet::new(),
            DexValue::Double(_) => HashSet::new(),
            DexValue::MethodType(_) => HashSet::new(),
            DexValue::MethodHandle(val) => val.get_all_method_ids(),
            DexValue::String(_) => HashSet::new(),
            DexValue::Type(_) => HashSet::new(),
            DexValue::Field(_) => HashSet::new(),
            DexValue::Method(val) => val.get_all_method_ids(),
            DexValue::Enum(_) => HashSet::new(),
            DexValue::Array(val) => val.get_all_method_ids(),
            DexValue::Annotation(val) => val.get_all_method_ids(),
            DexValue::Null(_) => HashSet::new(),
            DexValue::Boolean(_) => HashSet::new(),
        }
    }

    // TODO: implement for each variant struct for python binding
    /// Return all method handles referenced in the value.
    pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
        match self {
            DexValue::Byte(_) => HashSet::new(),
            DexValue::Short(_) => HashSet::new(),
            DexValue::Char(_) => HashSet::new(),
            DexValue::Int(_) => HashSet::new(),
            DexValue::Long(_) => HashSet::new(),
            DexValue::Float(_) => HashSet::new(),
            DexValue::Double(_) => HashSet::new(),
            DexValue::MethodType(_) => HashSet::new(),
            DexValue::MethodHandle(val) => val.get_all_method_handles(),
            DexValue::String(_) => HashSet::new(),
            DexValue::Type(_) => HashSet::new(),
            DexValue::Field(_) => HashSet::new(),
            DexValue::Method(_) => HashSet::new(),
            DexValue::Enum(_) => HashSet::new(),
            DexValue::Array(val) => val.get_all_method_handles(),
            DexValue::Annotation(val) => val.get_all_method_handles(),
            DexValue::Null(_) => HashSet::new(),
            DexValue::Boolean(_) => HashSet::new(),
        }
    }
}
#[cfg(feature = "python")]
impl<'py> IntoPyObject<'py> for DexValue {
    type Target = PyAny;
    type Output = Bound<'py, Self::Target>;
    type Error = PyErr;

    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
        match self {
            DexValue::Byte(val) => Bound::new(py, val).map(Bound::<_>::into_any),
            DexValue::Short(val) => Bound::new(py, val).map(Bound::<_>::into_any),
            DexValue::Char(val) => Bound::new(py, val).map(Bound::<_>::into_any),
            DexValue::Int(val) => Bound::new(py, val).map(Bound::<_>::into_any),
            DexValue::Long(val) => Bound::new(py, val).map(Bound::<_>::into_any),
            DexValue::Float(val) => Bound::new(py, val).map(Bound::<_>::into_any),
            DexValue::Double(val) => Bound::new(py, val).map(Bound::<_>::into_any),
            DexValue::MethodType(val) => Bound::new(py, val).map(Bound::<_>::into_any),
            DexValue::MethodHandle(val) => Bound::new(py, val).map(Bound::<_>::into_any),
            DexValue::String(val) => Bound::new(py, val).map(Bound::<_>::into_any),
            DexValue::Type(val) => Bound::new(py, val).map(Bound::<_>::into_any),
            DexValue::Field(val) => Bound::new(py, val).map(Bound::<_>::into_any),
            DexValue::Method(val) => Bound::new(py, val).map(Bound::<_>::into_any),
            DexValue::Enum(val) => Bound::new(py, val).map(Bound::<_>::into_any),
            DexValue::Array(val) => Bound::new(py, val).map(Bound::<_>::into_any),
            DexValue::Annotation(val) => Bound::new(py, val).map(Bound::<_>::into_any),
            DexValue::Null(val) => Bound::new(py, val).map(Bound::<_>::into_any),
            DexValue::Boolean(val) => Bound::new(py, val).map(Bound::<_>::into_any),
        }
    }
}
