use crate::{
    Result,
    types::{ChannelId, Identifier, PayloadSize, ShortString},
};
use std::collections::VecDeque;

#[derive(Debug, Default)]
pub(crate) struct ChannelReceiverStates(VecDeque<ChannelReceiverState>);

impl ChannelReceiverStates {
    #[cfg(test)]
    pub(crate) fn receiver_state(&self) -> ChannelReceiverState {
        self.0.front().unwrap().clone()
    }

    pub(crate) fn reset(&mut self) {
        self.0.clear()
    }

    pub(crate) fn set_will_receive(&mut self, class_id: Identifier, delivery_cause: DeliveryCause) {
        self.0.push_back(ChannelReceiverState::WillReceiveContent(
            class_id,
            delivery_cause,
        ));
    }

    pub(crate) fn set_content_length<
        Handler: FnOnce(&DeliveryCause, bool),
        OnInvalidClass: FnOnce(String) -> Result<()>,
        OnError: FnOnce(String) -> Result<()>,
    >(
        &mut self,
        channel_id: ChannelId,
        class_id: Identifier,
        length: PayloadSize,
        handler: Handler,
        invalid_class_hanlder: OnInvalidClass,
        error_handler: OnError,
        confirm_mode: bool,
    ) -> Result<()> {
        if let Some(ChannelReceiverState::WillReceiveContent(expected_class_id, delivery_cause)) =
            self.0.pop_front()
        {
            if expected_class_id == class_id {
                handler(&delivery_cause, confirm_mode);
                if length > 0 {
                    self.0.push_front(ChannelReceiverState::ReceivingContent(
                        delivery_cause,
                        length,
                    ));
                }
                Ok(())
            } else {
                invalid_class_hanlder(format!(
                    "content header frame with class id {class_id} instead of {expected_class_id} received on channel {channel_id}"
                ))
            }
        } else {
            error_handler(format!(
                "unexpected content header frame received on channel {channel_id}"
            ))
        }
    }

    pub(crate) fn receive<
        Handler: FnOnce(&DeliveryCause, PayloadSize, bool),
        OnError: FnOnce(String) -> Result<()>,
    >(
        &mut self,
        channel_id: ChannelId,
        length: PayloadSize,
        handler: Handler,
        error_handler: OnError,
        confirm_mode: bool,
    ) -> Result<()> {
        if let Some(ChannelReceiverState::ReceivingContent(delivery_cause, len)) =
            self.0.pop_front()
        {
            if let Some(remaining) = len.checked_sub(length) {
                handler(&delivery_cause, remaining, confirm_mode);
                if remaining > 0 {
                    self.0.push_front(ChannelReceiverState::ReceivingContent(
                        delivery_cause,
                        remaining,
                    ));
                }
                Ok(())
            } else {
                error_handler(format!(
                    "unexpectedly large content body frame received on channel {channel_id} ({length} bytes, expected {len} bytes)"
                ))
            }
        } else {
            error_handler(format!(
                "unexpected content body frame received on channel {channel_id}"
            ))
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum ChannelReceiverState {
    WillReceiveContent(Identifier, DeliveryCause),
    ReceivingContent(DeliveryCause, PayloadSize),
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum DeliveryCause {
    Consume(ShortString),
    Get,
    Return,
}
