//! Implementations of the [`Message`] and related types

use super::*;

use core::cell::Cell;

impl<'a> Message<SliceBuffer<'a>> {
    /// Creates a message that is parsed from a slice.
    ///
    /// This is a short-cut for creating a [`SliceBuffer`] and passing it to [`Self::new()`].
    #[must_use]
    pub fn new_from_slice(code: u8, options_and_payload: &'a [u8]) -> Self {
        Message::new(SliceBuffer::new(code, options_and_payload))
    }
}

/// A CoAP message that resides in contiguous readable memory.
///
/// This implementation does not attempt to do any early validation. On encoding errors discovered
/// at runtime, it simply emits the critical-and-not-safe-to-forward CoAP option 65535
/// ([`OPTION_INVALID`]), which to the indication indicates that something went wrong
///
/// Message is covariant over its lifetime because it only reads from there, effectively
/// fullfilling the expectations of [`LifetimesMatterLittle`]. That
/// property is currently not exposed.
///
/// # Implementation details
///
/// When used with [`coap_message::helpers::RefWithStaticType`], it always uses `Message<EMS>`
/// where `EMS` is the type inside the `LifetimesMatterLittle` of the `EM::static_variant()`
/// result. This is needed to run [`Self::downcast_from`], and restores the lifetime based on the
/// given impl Trait reference's lifetime using its covariance property.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Message<EM: MessageBuffer> {
    /// The buffer message buffer that is being decoded.
    inner: EM,
    /// Indicator of where the payload is, which is only populated when the message is first parsed
    /// to the payload.
    ///
    /// When this is set, it also indicates whether or not the full CoAP options-and-payload are
    /// well-formed at the CoAP level.
    payload_cache: Cell<Option<Processing>>,
    /// Length of used part of `.inner.tail()`.
    ///
    /// Storing this is wasteful at first glance, as it is redundant with inner's length, but
    ///
    /// * This is not the case when inner is backed by a `&[u8; N]` rather than a `&[u8]` (although
    ///   a more realistic version thereof would likely be a `*mut [u8; N]` with offsets for code
    ///   and start-of-options).
    ///
    /// * When the back-end is mutable, it allows upgrading to a mutable message.
    end: usize,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub(crate) enum Processing {
    /// The options were iterated over successfully, and payload was found and has a length of .0.
    ///
    /// (Implementation-wise, this is easier than storing the index, because we can't compute the
    /// index while iterating over the options).
    // It'd be great to have a PositiveIsize type so that the whole thing would be word-sized.
    OkPayloadLength(usize),
    /// The options were iterated over and ended at the data's end.
    OkNoPayload,
    /// The data did not contain a correctly formatted CoAP options-and-payload sequence
    Error,
}

/// Helper for extracting the type ID used with an MessageBuffer out of a value with unnamed
/// (RPIT) type.
#[cfg(feature = "downcast")]
fn type_id_of_emv_of_lml<T: 'static + MessageBuffer>(
    _val: &LifetimesMatterLittle<T>,
) -> core::any::TypeId {
    core::any::TypeId::of::<Message<T>>()
}

impl<EM: MessageBuffer> Message<EM> {
    /// Creates a new instance backed by an encoded message buffer.
    ///
    /// Decoding assumes that the full tail of the buffer contains options and payload.
    pub fn new(inner: EM) -> Self {
        Self {
            end: inner.tail().len(),
            payload_cache: Cell::default(),
            inner,
        }
    }

    /// Creates a new instance backed by an encoded message buffer.
    ///
    /// Decoding assumes that the first `length` bytes contain options and payload. (The buffer
    /// having the option to be longer is mostly useful when it is also an
    /// [`MessageBufferMut`][MessageBufferMut], and might later get
    /// turned [`.into_mutable()`][Self::into_mutable()]).
    pub fn new_until(inner: EM, length: usize) -> Self {
        Self {
            end: length,
            payload_cache: Cell::default(),
            inner,
        }
    }

    #[cfg(feature = "downcast")]
    pub fn downcast_from<M: ReadableMessage>(generic: &M) -> Option<&Self> {
        let (reference, type_id) = generic.with_static_type_annotation()?.into_inner();

        let our_static = EM::static_variant()?;
        let our_id = type_id_of_emv_of_lml(&our_static);

        if type_id != our_id {
            return None;
        }
        // One of us, one of us.
        let ptr = reference as *const M as *const Self;
        // SAFETY: The RefWithStaticType matching our type ID will only be constructed if M is
        // Message, and whatever Message<'b> it was before, it will live at least for 'a (because
        // we got a &'a Message<'b> and is covariant.
        Some(unsafe { &*ptr })
    }

    /// Makes sure that the own processing state is set.
    fn ensure_parsed(&self) -> Processing {
        if let Some(v) = self.payload_cache.get() {
            v
        } else {
            // At the point where this is done, ie. practically at the time of processing the
            // payload, users *should* iterate over options first anyway, but let's not crash
            // their program just becuase they do CoAP oddly.

            // Doing it for the side effect
            self.options().for_each(|_| ());

            self.payload_cache
                .get()
                .expect("Side effect of options iteration is that this should have been set")
        }
    }

    /// Converts the message into a mutable writable message.
    ///
    /// This can be useful to re-use a buffer that has been passed to the application, e.g. for
    /// in-place decryption or to shove around indefinite-length CBOR encoding inside payloads.
    ///
    /// # Errors
    ///
    /// … are only raised if the message contains parsing errors (for a mutable message is stricter
    /// in that regard).
    pub fn into_mutable(self) -> Result<MessageMut<EM>, ParsingError>
    where
        EM: MessageBufferMut,
    {
        let payload_len = match self.ensure_parsed() {
            Processing::OkPayloadLength(l) => l,
            Processing::OkNoPayload => 0,
            Processing::Error => return Err(ParsingError(())),
        };

        Ok(MessageMut {
            encoded: self.inner,
            end: self.end,
            // FIXME: Do we get away with that? Adding options won't work, but inserting should.
            latest: u16::MAX,
            payload_start: match payload_len {
                0 => None,
                l => Some(self.end - l),
            },
        })
    }
}

/// Error type indicating a malformed CoAP message.
///
/// While usually, [`Message`] does not err on parsing trouble as per its documentation (it
/// maps the exotic error case into the more regular error case that needs to be handled by the
/// user anyway), but in rare cases it does need to err (e.g. when converting to a
/// [`MessageMut`] that has an internal invariant on well-formedness).
#[derive(Debug, thiserror::Error)]
#[error("CoAP message was not well-formed")]
pub struct ParsingError(());

/// When the inner item stores more than just the code and encoded options, this can be used to
/// gain read-only access to any additional data.
///
/// Note that no matter whether access is shared or exclusive, the [`MessageBuffer`] must uphold
/// its promise to always return the same content.
impl<EM: MessageBuffer> AsRef<EM> for Message<EM> {
    fn as_ref(&self) -> &EM {
        &self.inner
    }
}

impl<EM: MessageBuffer> ReadableMessage for Message<EM> {
    type Code = u8;
    type MessageOption<'a>
        = MessageOption<'a>
    where
        Self: 'a;
    type OptionsIter<'a>
        = OptionsIter<'a>
    where
        Self: 'a;

    fn code(&self) -> u8 {
        self.inner.code()
    }

    fn payload(&self) -> &[u8] {
        let len = match self.ensure_parsed() {
            Processing::OkPayloadLength(l) => l,
            Processing::OkNoPayload => 0,
            // Silently produce empty payload -- the user will need to have checked the
            // options and have found the 65535 option.
            Processing::Error => 0,
        };

        let optpayload = &self.inner.tail()[..self.end];
        &optpayload[optpayload.len() - len..]
    }

    fn options(&self) -> OptionsIter<'_> {
        OptionsIter(
            crate::option_iteration::OptPayloadReader::new(&self.inner.tail()[..self.end]),
            Some(&self.payload_cache),
        )
    }

    #[cfg(feature = "downcast")]
    fn with_static_type_annotation(
        &self,
    ) -> Option<coap_message::helpers::RefWithStaticType<'_, Self>> {
        // SAFETY: It is this type's policy that its RefWithStaticType ID is the given
        // Message<'static>.
        let em_static = EM::static_variant()?;

        Some(unsafe {
            coap_message::helpers::RefWithStaticType::new(self, type_id_of_emv_of_lml(&em_static))
        })
    }
}

impl<EM: MessageBuffer> WithSortedOptions for Message<EM> {}
