//! Protocol name/value parsing used by object and avatar updates.

use crate::{Error, NameValueClassType, NameValueSendtoType, NameValueValueType};
use libremetaverse_types::compat::Object;
use std::hash::{Hash, Hasher};

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct NameValue {
    pub class_: NameValueClassType,
    pub name: String,
    pub sendto: NameValueSendtoType,
    pub type_: NameValueValueType,
    pub value: Option<Object>,
}

impl NameValue {
    pub fn new_with_string(data: String) -> Result<Self, Error> {
        let mut fields = data.splitn(5, char::is_whitespace);
        let Some(name) = fields.next().filter(|value| !value.is_empty()) else {
            return Ok(Self::empty());
        };
        let type_name = fields.next().unwrap_or_default();
        let class_name = fields.next().unwrap_or_default();
        let sendto_name = fields.next().unwrap_or_default();
        let value_text = fields.next().unwrap_or_default().trim_start();

        let type_ = value_type(type_name);
        let class_ = class_type(class_name);
        let sendto = sendto_type(sendto_name);
        let value = parse_value(type_, value_text);
        Ok(Self {
            class_,
            name: name.to_owned(),
            sendto,
            type_,
            value,
        })
    }

    pub fn new_with_string_value_type_class_type_sendto_type_object(
        name: String,
        value_type: NameValueValueType,
        class_type: NameValueClassType,
        sendto_type: NameValueSendtoType,
        value: Object,
    ) -> Result<Self, Error> {
        Ok(Self {
            class_: class_type,
            name,
            sendto: sendto_type,
            type_: value_type,
            value: Some(value),
        })
    }

    fn empty() -> Self {
        Self {
            class_: NameValueClassType::Unknown,
            name: String::new(),
            sendto: NameValueSendtoType::Unknown,
            type_: NameValueValueType::Unknown,
            value: None,
        }
    }

    #[must_use]
    pub fn equals_with_name_value(&self, other: Self) -> bool {
        self == &other
    }

    #[must_use]
    pub fn equals_with_object(&self, object: Option<Object>) -> bool {
        object
            .as_ref()
            .and_then(Object::downcast_ref::<Self>)
            .is_some_and(|other| self == other)
    }

    #[must_use]
    pub fn get_hash_code(&self) -> i32 {
        let mut hasher = std::collections::hash_map::DefaultHasher::new();
        self.hash(&mut hasher);
        hasher.finish() as i32
    }

    pub fn name_values_to_string(values: Vec<Self>) -> Result<String, Error> {
        let mut rendered = Vec::new();
        for value in values {
            let Some(payload) = value.value.as_ref() else {
                continue;
            };
            rendered.push(format!(
                "{} {} {} {} {}",
                value.name,
                type_string(value.type_).ok_or(Error::IndexOutOfRange)?,
                class_string(value.class_).ok_or(Error::IndexOutOfRange)?,
                sendto_string(value.sendto).ok_or(Error::IndexOutOfRange)?,
                object_string(payload)
            ));
        }
        Ok(rendered.join("\n"))
    }
}

fn value_type(value: &str) -> NameValueValueType {
    match value {
        "STRING" => NameValueValueType::String,
        "F32" => NameValueValueType::F32,
        "S32" => NameValueValueType::S32,
        "VEC3" => NameValueValueType::VEC3,
        "U32" => NameValueValueType::U32,
        "ASSET" => NameValueValueType::Asset,
        "U64" => NameValueValueType::U64,
        _ => NameValueValueType::String,
    }
}

fn class_type(value: &str) -> NameValueClassType {
    match value {
        "R" => NameValueClassType::ReadOnly,
        "RW" => NameValueClassType::ReadWrite,
        "CB" => NameValueClassType::Callback,
        _ => NameValueClassType::ReadOnly,
    }
}

fn sendto_type(value: &str) -> NameValueSendtoType {
    match value {
        "S" => NameValueSendtoType::Sim,
        "DS" => NameValueSendtoType::DataSim,
        "SV" => NameValueSendtoType::SimViewer,
        "DSV" => NameValueSendtoType::DataSimViewer,
        _ => NameValueSendtoType::Sim,
    }
}

fn parse_value(type_: NameValueValueType, value: &str) -> Option<Object> {
    Some(match type_ {
        NameValueValueType::String | NameValueValueType::Asset => Object::String(value.to_owned()),
        NameValueValueType::F32 => Object::Real(value.parse::<f32>().unwrap_or_default().into()),
        NameValueValueType::S32 => Object::Integer(value.parse().unwrap_or_default()),
        NameValueValueType::U32 => Object::UInteger(value.parse().unwrap_or_default()),
        NameValueValueType::U64 => Object::ULong(value.parse().unwrap_or_default()),
        NameValueValueType::VEC3 => Object::Vector3(
            libremetaverse_types::Vector3::parse(value.to_owned())
                .unwrap_or_else(|_| libremetaverse_types::Vector3::zero()),
        ),
        NameValueValueType::Unknown | NameValueValueType::CAMERA => return None,
    })
}

fn type_string(value: NameValueValueType) -> Option<&'static str> {
    match value {
        NameValueValueType::String => Some("STRING"),
        NameValueValueType::F32 => Some("F32"),
        NameValueValueType::S32 => Some("S32"),
        NameValueValueType::VEC3 => Some("VEC3"),
        NameValueValueType::U32 => Some("U32"),
        NameValueValueType::Asset => Some("ASSET"),
        NameValueValueType::U64 => Some("U64"),
        NameValueValueType::Unknown | NameValueValueType::CAMERA => None,
    }
}

fn class_string(value: NameValueClassType) -> Option<&'static str> {
    match value {
        NameValueClassType::ReadOnly => Some("R"),
        NameValueClassType::ReadWrite => Some("RW"),
        NameValueClassType::Callback => Some("CB"),
        NameValueClassType::Unknown => None,
    }
}

fn sendto_string(value: NameValueSendtoType) -> Option<&'static str> {
    match value {
        NameValueSendtoType::Sim => Some("S"),
        NameValueSendtoType::DataSim => Some("DS"),
        NameValueSendtoType::SimViewer => Some("SV"),
        NameValueSendtoType::DataSimViewer => Some("DSV"),
        NameValueSendtoType::Unknown => None,
    }
}

fn object_string(value: &Object) -> String {
    match value {
        Object::Undefined => String::new(),
        Object::Boolean(value) => value.to_string(),
        Object::Integer(value) => value.to_string(),
        Object::UInteger(value) => value.to_string(),
        Object::Long(value) => value.to_string(),
        Object::ULong(value) => value.to_string(),
        Object::Real(value) => value.to_string(),
        Object::String(value) => value.clone(),
        Object::UUID(value) => value.to_string(),
        Object::Vector3(value) => value.to_string(),
        other => format!("{other:?}"),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parses_and_round_trips_protocol_name_values() {
        let value = NameValue::new_with_string("FirstName STRING R S Ada".into()).unwrap();
        assert_eq!(value.name, "FirstName");
        assert_eq!(value.type_, NameValueValueType::String);
        assert_eq!(value.value, Some(Object::String("Ada".into())));
        assert_eq!(
            NameValue::name_values_to_string(vec![value]).unwrap(),
            "FirstName STRING R S Ada"
        );
    }
}
