//! The class identifying dex structure.

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

use pyo3::exceptions::PyTypeError;
use pyo3::prelude::*;

use crate::{dex_id::*, scalar::*, DexAnnotation, DexString, MethodHandle};

#[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<'source> FromPyObject<'source> for DexValue {
    fn extract(ob: &'source PyAny) -> PyResult<Self> {
        if let Ok(val) = DexByte::extract(ob) {
            Ok(Self::Byte(val))
        } else if let Ok(val) = DexShort::extract(ob) {
            Ok(Self::Short(val))
        } else if let Ok(val) = DexChar::extract(ob) {
            Ok(Self::Char(val))
        } else if let Ok(val) = DexInt::extract(ob) {
            Ok(Self::Int(val))
        } else if let Ok(val) = DexLong::extract(ob) {
            Ok(Self::Long(val))
        } else if let Ok(val) = DexFloat::extract(ob) {
            Ok(Self::Float(val))
        } else if let Ok(val) = DexDouble::extract(ob) {
            Ok(Self::Double(val))
        } else if let Ok(val) = DexString::extract(ob) {
            Ok(Self::String(val))
        } else if let Ok(val) = IdType::extract(ob) {
            Ok(Self::Type(val))
        } else if let Ok(val) = IdField::extract(ob) {
            Ok(Self::Field(val))
        } else if let Ok(val) = IdMethod::extract(ob) {
            Ok(Self::Method(val))
        } else if let Ok(val) = IdEnum::extract(ob) {
            Ok(Self::Enum(val))
        } else if let Ok(val) = DexArray::extract(ob) {
            Ok(Self::Array(val))
        } else if let Ok(val) = DexAnnotation::extract(ob) {
            Ok(Self::Annotation(val))
        } else if let Ok(val) = DexNull::extract(ob) {
            Ok(Self::Null(val))
        } else if let Ok(val) = DexBoolean::extract(ob) {
            Ok(Self::Boolean(val))
        } else if let Ok(val) = IdMethodType::extract(ob) {
            Ok(Self::MethodType(val))
        } else if let Ok(val) = MethodHandle::extract(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(),
        }
    }
}

impl IntoPy<PyObject> for DexValue {
    fn into_py(self, py: Python<'_>) -> PyObject {
        match self {
            DexValue::Byte(val) => val.into_py(py),
            DexValue::Short(val) => val.into_py(py),
            DexValue::Char(val) => val.into_py(py),
            DexValue::Int(val) => val.into_py(py),
            DexValue::Long(val) => val.into_py(py),
            DexValue::Float(val) => val.into_py(py),
            DexValue::Double(val) => val.into_py(py),
            DexValue::MethodType(val) => val.into_py(py),
            DexValue::MethodHandle(val) => val.into_py(py),
            DexValue::String(val) => val.into_py(py),
            DexValue::Type(val) => val.into_py(py),
            DexValue::Field(val) => val.into_py(py),
            DexValue::Method(val) => val.into_py(py),
            DexValue::Enum(val) => val.into_py(py),
            DexValue::Array(val) => val.into_py(py),
            DexValue::Annotation(val) => val.into_py(py),
            DexValue::Null(val) => val.into_py(py),
            DexValue::Boolean(val) => val.into_py(py),
        }
    }
}
