use crate::Result;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::cmp::{Ord, PartialOrd};
use std::collections::hash_map::DefaultHasher;
use std::collections::HashSet;
use std::hash::{Hash, Hasher};

use pyo3::class::basic::CompareOp;
use pyo3::exceptions::PyTypeError;
use pyo3::prelude::*;

#[pyclass]
#[derive(Clone, PartialEq, Eq, Ord, PartialOrd)]
pub struct DexString(pub androscalpel_serializer::StringDataItem);

impl std::fmt::Debug for DexString {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
        if let Ok(string) = TryInto::<String>::try_into(self) {
            f.write_str(&format!(
                "DexString({}, {:#x})",
                string, self.0.utf16_size.0
            ))
            /*
            f.debug_tuple("DexString")
                .field(&string)
                .field(&self.0.utf16_size.0)
                .finish()
            */
        } else {
            f.write_str(&format!(
                "DexString({:?}, {:#x})",
                self.0.data, self.0.utf16_size.0
            ))
            /*f.debug_tuple("DexString")
                .field(&self.0.data)
                .field(&self.0.utf16_size.0)
                .finish()
            */
        }
    }
}

impl Serialize for DexString {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        Serialize::serialize(&SerdeDexString::from(self), serializer)
    }
}

impl<'de> Deserialize<'de> for DexString {
    fn deserialize<D>(deserializer: D) -> Result<DexString, D::Error>
    where
        D: Deserializer<'de>,
    {
        <SerdeDexString as Deserialize>::deserialize(deserializer).map(|string| (&string).into())
    }
}

#[derive(Serialize, Deserialize)]
#[serde(rename = "DexString")]
enum SerdeDexString {
    String(String),
    BinString(Vec<u8>, u32),
}

impl From<&DexString> for SerdeDexString {
    fn from(DexString(string): &DexString) -> Self {
        if let Ok(string) = string.try_into() {
            SerdeDexString::String(string) // TODO: utf16_size is remove, is this ok?
        } else {
            let androscalpel_serializer::StringDataItem {
                data,
                utf16_size: androscalpel_serializer::Uleb128(len),
            } = string;
            SerdeDexString::BinString(data.to_vec(), *len)
        }
    }
}

impl From<&SerdeDexString> for DexString {
    fn from(string: &SerdeDexString) -> Self {
        match string {
            SerdeDexString::String(string) => string.as_str().into(),
            SerdeDexString::BinString(data, len) => {
                DexString(androscalpel_serializer::StringDataItem {
                    data: data.clone(),
                    utf16_size: androscalpel_serializer::Uleb128(*len),
                })
            }
        }
    }
}

impl From<DexString> for androscalpel_serializer::StringDataItem {
    fn from(DexString(string): DexString) -> Self {
        string
    }
}

impl From<androscalpel_serializer::StringDataItem> for DexString {
    fn from(string: androscalpel_serializer::StringDataItem) -> Self {
        Self(string)
    }
}

impl From<&DexString> for String {
    fn from(DexString(string): &DexString) -> Self {
        string
            .try_into()
            .unwrap_or(format!("InvalidEncoding:{:x?}", string.data))
    }
}

impl From<DexString> for String {
    fn from(string: DexString) -> Self {
        (&string).into()
    }
}

impl From<&str> for DexString {
    fn from(string: &str) -> Self {
        Self(string.into())
    }
}

impl From<String> for DexString {
    fn from(string: String) -> Self {
        Self(string.as_str().into())
    }
}

impl Hash for DexString {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.get_utf16_size().hash(state);
        self.get_bytes().hash(state);
    }
}

pub fn as_dex_string(obj: &PyAny) -> PyResult<DexString> {
    if let Ok(string) = DexString::extract(obj) {
        Ok(string)
    } else if let Ok(string) = String::extract(obj) {
        Ok(string.into())
    } else {
        Err(PyErr::new::<PyTypeError, _>(format!(
            "{} cannot be converted to a DexString",
            obj.repr()?
        )))
    }
}

#[pymethods]
impl DexString {
    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(s: &str) -> Self {
        s.into()
    }

    /// Return the binary mutf-8 encoded string (minus the trailling 0)
    pub fn get_bytes(&self) -> &[u8] {
        &self.0.data
    }

    /// Return the 'utf-16' size of the string (number of unicode code point, ie its lenght in 'java-land')
    pub fn get_utf16_size(&self) -> u32 {
        self.0.utf16_size.0
    }

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

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

    fn __hash__(&self) -> u64 {
        let mut hasher = DefaultHasher::new();
        self.hash(&mut hasher);
        hasher.finish()
    }

    /// Return all strings references in the value.
    pub fn get_all_strings(&self) -> HashSet<DexString> {
        let mut strings = HashSet::new();
        strings.insert(self.clone());
        strings
    }

    fn __richcmp__(&self, other: &PyAny, op: CompareOp) -> PyResult<bool> {
        let other: Self = other
            .extract()
            .or(<String as FromPyObject>::extract(other).map(|string| string.into()))?;
        Ok(op.matches(self.0.cmp(&other.0)))
    }
}
