use aldrin_core::tags::Tag;
use aldrin_core::{
    Deserialize, DeserializeError, DeserializePrimary, SerializedValue, SerializedValueSlice, Value,
};
use std::time::Instant;

/// Reply of a call.
#[derive(Debug, Clone)]
pub struct Reply {
    id: u32,
    timestamp: Instant,
    args: Result<SerializedValue, SerializedValue>,
}

impl Reply {
    pub(crate) fn new(
        id: u32,
        timestamp: Instant,
        args: Result<SerializedValue, SerializedValue>,
    ) -> Self {
        Self {
            id,
            timestamp,
            args,
        }
    }

    /// Returns the reply's function id.
    pub fn id(&self) -> u32 {
        self.id
    }

    /// Returns the timestamp when the reply was received.
    pub fn timestamp(&self) -> Instant {
        self.timestamp
    }

    /// Returns the reply's arguments as slices.
    pub fn args(&self) -> Result<&SerializedValueSlice, &SerializedValueSlice> {
        match self.args {
            Ok(ref ok) => Ok(ok),
            Err(ref err) => Err(err),
        }
    }

    /// Takes out the reply's arguments and leaves an
    /// [empty `SerializedValue`](SerializedValue::empty) in its place.
    pub fn take_args(&mut self) -> Result<SerializedValue, SerializedValue> {
        match self.args {
            Ok(ref mut args) => Ok(args.take()),
            Err(ref mut args) => Err(args.take()),
        }
    }

    /// Returns the arguments of the reply.
    pub fn into_args(self) -> Result<SerializedValue, SerializedValue> {
        self.args
    }

    /// Deserializes the arguments of the reply.
    pub fn deserialize_as<T, U, E, F>(&self) -> Result<Result<U, F>, DeserializeError>
    where
        T: Tag,
        U: Deserialize<T>,
        E: Tag,
        F: Deserialize<E>,
    {
        match self.args {
            Ok(ref ok) => ok.deserialize_as().map(Ok),
            Err(ref err) => err.deserialize_as().map(Err),
        }
    }

    /// Deserializes the arguments of the reply.
    pub fn deserialize<T: DeserializePrimary, E: DeserializePrimary>(
        &self,
    ) -> Result<Result<T, E>, DeserializeError> {
        self.deserialize_as()
    }

    /// Deserializes the arguments of the reply as generic [`Value`s](Value).
    pub fn deserialize_as_value(&self) -> Result<Result<Value, Value>, DeserializeError> {
        self.deserialize()
    }

    /// Deserializes the arguments and casts the reply to a high-level [`Reply`](crate::Reply).
    pub fn deserialize_and_cast_as<T, U, E, F>(
        &self,
    ) -> Result<crate::Reply<U, F>, DeserializeError>
    where
        T: Tag,
        U: Deserialize<T>,
        E: Tag,
        F: Deserialize<E>,
    {
        self.deserialize_as()
            .map(|args| crate::Reply::new(self.id, self.timestamp, args))
    }

    /// Deserializes the arguments and casts the reply to a high-level [`Reply`](crate::Reply).
    pub fn deserialize_and_cast<T: DeserializePrimary, E: DeserializePrimary>(
        &self,
    ) -> Result<crate::Reply<T, E>, DeserializeError> {
        self.deserialize_and_cast_as()
    }
}
