//! The structure use to reference a method invocation.

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

#[cfg(feature = "python")]
use pyo3::prelude::*;

use crate::dex_id::*;
use crate::{
    DexString, FieldIdCollector, MethodHandleCollector, MethodIdCollector, MethodTypeCollector,
    Result, StringCollector, TypeCollector, Visitable, VisitableMut, Visitor, VisitorMut,
};

/// The structure use to reference a method invocation.
#[cfg_attr(feature = "python", pyclass(eq))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub enum MethodHandle {
    StaticPut { field: IdField },
    StaticGet { field: IdField },
    InstancePut { field: IdField },
    InstanceGet { field: IdField },
    InvokeStatic { method: IdMethod },
    InvokeInstance { method: IdMethod },
    InvokeConstructor { method: IdMethod },
    InvokeDirect { method: IdMethod },
    InvokeInterface { method: IdMethod },
}

impl<V: Visitor> Visitable<V> for MethodHandle {
    fn default_visit(&self, v: &mut V) -> Result<()> {
        match self {
            Self::StaticPut { field } => v.visit_field_id(field)?,
            Self::StaticGet { field } => v.visit_field_id(field)?,
            Self::InstancePut { field } => v.visit_field_id(field)?,
            Self::InstanceGet { field } => v.visit_field_id(field)?,
            Self::InvokeStatic { method } => v.visit_method_id(method)?,
            Self::InvokeInstance { method } => v.visit_method_id(method)?,
            Self::InvokeConstructor { method } => v.visit_method_id(method)?,
            Self::InvokeDirect { method } => v.visit_method_id(method)?,
            Self::InvokeInterface { method } => v.visit_method_id(method)?,
        }
        Ok(())
    }
}

impl<V: VisitorMut> VisitableMut<V> for MethodHandle {
    fn default_visit_mut(self, v: &mut V) -> Result<Self> {
        match self {
            Self::StaticPut { field } => Ok(Self::StaticPut {
                field: v.visit_field_id(field)?,
            }),
            Self::StaticGet { field } => Ok(Self::StaticGet {
                field: v.visit_field_id(field)?,
            }),
            Self::InstancePut { field } => Ok(Self::InstancePut {
                field: v.visit_field_id(field)?,
            }),
            Self::InstanceGet { field } => Ok(Self::InstanceGet {
                field: v.visit_field_id(field)?,
            }),
            Self::InvokeStatic { method } => Ok(Self::InvokeStatic {
                method: v.visit_method_id(method)?,
            }),
            Self::InvokeInstance { method } => Ok(Self::InvokeInstance {
                method: v.visit_method_id(method)?,
            }),
            Self::InvokeConstructor { method } => Ok(Self::InvokeConstructor {
                method: v.visit_method_id(method)?,
            }),
            Self::InvokeDirect { method } => Ok(Self::InvokeDirect {
                method: v.visit_method_id(method)?,
            }),
            Self::InvokeInterface { method } => Ok(Self::InvokeInterface {
                method: v.visit_method_id(method)?,
            }),
        }
    }
}

#[cfg_attr(feature = "python", pymethods)]
impl MethodHandle {
    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)?)
    }

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

    pub fn __repr__(&self) -> String {
        match self {
            Self::StaticPut { field } => format!("StaticPut({})", field.__str__()),
            Self::StaticGet { field } => format!("StaticGet({})", field.__str__()),
            Self::InstancePut { field } => format!("InstancePut({})", field.__str__()),
            Self::InstanceGet { field } => format!("InstanceGet({})", field.__str__()),
            Self::InvokeStatic { method } => format!("InvokeStatic({})", method.__str__()),
            Self::InvokeInstance { method } => format!("InvokeInstance({})", method.__str__()),
            Self::InvokeConstructor { method } => {
                format!("InvokeConstructor({})", method.__str__())
            }
            Self::InvokeDirect { method } => format!("InvokeDirect({})", method.__str__()),
            Self::InvokeInterface { method } => format!("InvokeInterface({})", method.__str__()),
        }
    }

    /// Return all strings referenced in the Handle.
    pub fn get_all_strings(&self) -> HashSet<DexString> {
        let mut visitor = StringCollector::default();
        visitor.visit_method_handle(self).unwrap();
        visitor.result()
    }

    /// Return all types referenced in the Handle.
    pub fn get_all_types(&self) -> HashSet<IdType> {
        let mut visitor = TypeCollector::default();
        visitor.visit_method_handle(self).unwrap();
        visitor.result()
    }

    /// Return all prototypes referenced in the handle.
    pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
        let mut visitor = MethodTypeCollector::default();
        visitor.visit_method_handle(self).unwrap();
        visitor.result()
    }

    /// Return all field ids referenced in the handle.
    pub fn get_all_field_ids(&self) -> HashSet<IdField> {
        let mut visitor = FieldIdCollector::default();
        visitor.visit_method_handle(self).unwrap();
        visitor.result()
    }

    /// Return all method ids referenced in the handle.
    pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
        let mut visitor = MethodIdCollector::default();
        visitor.visit_method_handle(self).unwrap();
        visitor.result()
    }

    /// Return all method handles referenced in the handle.
    pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
        let mut visitor = MethodHandleCollector::default();
        visitor.visit_method_handle(self).unwrap();
        visitor.result()
    }
}
