//! Implementations of option iteration.

use super::*;

use crate::option_iteration::{OptItem, OptPayloadReader};

/// An iterator producing [`MessageOption`]s by running along an encoded CoAP message options stream
/// by using a [`OptPayloadReader`] and discarding the payload.
///
/// As an implementation detail (which makes it non-[`Send`]), this keeps a reference to
/// memoization inside the message so that running the iterator up to the payload stores that
/// position for later easier access.
pub struct OptionsIter<'a>(
    pub(crate) OptPayloadReader<'a>,
    pub(crate) Option<&'a core::cell::Cell<Option<Processing>>>,
);

/// Option value used by this library to indicate a format error in the message that was not
/// detected by the time option / payload processing was started.
///
/// As this option is critical and proxy-unsafe, no application can expect to successfully process
/// any message that contains this option. (The number is from the experimental-use range;
/// implementations using this module will simply not support that option in experiments, as they
/// can not distinguish it from an option formatting error).
pub const OPTION_INVALID: u16 = u16::MAX;

impl<'a> Iterator for OptionsIter<'a> {
    type Item = MessageOption<'a>;

    fn next(&mut self) -> Option<MessageOption<'a>> {
        let (new_cache, result) = match self.0.next() {
            Some(OptItem::Option { number, data }) => (
                None,
                Some(MessageOption {
                    number,
                    value: data,
                }),
            ),
            Some(OptItem::Error { .. }) => (
                Some(Processing::Error),
                Some(MessageOption {
                    number: OPTION_INVALID,
                    value: &[],
                }),
            ),
            // No need to recurse or loop -- it's always the last one
            Some(OptItem::Payload(p)) => (Some(Processing::OkPayloadLength(p.len())), None),
            // This is not a fused iterator, so users might keep iterating -- and then writing
            // OkNoPayload would be erroneous. But we only write when first finding the end, and
            // that needs to have happened before we reach this in an unfused iteration.
            None => (Some(Processing::OkNoPayload), None),
        };

        if let (Some(new_value), Some(cache)) = (new_cache, self.1) {
            // See None case above for why we have to check
            if cache.get().is_none() {
                cache.set(Some(new_value));
            }
        }

        result
    }
}

/// A simple [`coap_message::MessageOption`] implementation for memory-mapped CoAP messages
pub struct MessageOption<'a> {
    number: u16,
    value: &'a [u8],
}

impl coap_message::MessageOption for MessageOption<'_> {
    fn number(&self) -> u16 {
        self.number
    }
    fn value(&self) -> &[u8] {
        self.value
    }
}
