use crate::CodecLayer;
use crate::protocol::client::ClientProtocol;
use dope_core::driver::{DriverOps, PoolDriver, ProvidedRingOps};
use dope_core::profile::Profile;
use dope_transport::Transport;

use super::super::Pool;
use super::super::core::RecvDispatch;
use super::super::slot::ConnFlags;
use super::Client;
use super::connect_source::ConnectSource;
use crate::SlotId;

impl<P, T, S, F, B> super::super::PoolRecv<B> for Pool<Client<P, T, S>, F, B>
where
    P: ClientProtocol,
    T: Transport,
    T::Addr: Clone,
    S: ConnectSource<T>,
    F: Profile,
    B: PoolDriver,
{
    fn on_recv_event(
        &mut self,
        driver: &mut B::Driver,
        token: B::Token,
        result: i32,
        more: bool,
        bid: Option<u16>,
    ) {
        match self.core.classify_recv(driver, token, result, more, bid) {
            RecvDispatch::Skipped => {}
            RecvDispatch::EofClose { slot_idx } => {
                self.disconnect_and_free(driver, slot_idx);
            }
            RecvDispatch::Errno {
                slot_idx,
                errno,
                more,
            } => {
                if errno == libc::ECANCELED {
                    if !more {
                        self.core.slots[slot_idx].io.recv.armed = false;
                    }
                    return;
                }
                self.core.slots[slot_idx].io.recv.armed = false;
                self.disconnect_and_free(driver, slot_idx);
            }
            RecvDispatch::Chunk {
                slot_idx,
                ptr,
                len,
                bid,
                more,
            } => {
                let src: &[u8] = unsafe { std::slice::from_raw_parts(ptr, len) };
                if <P::CodecLayer as CodecLayer>::IS_PASSTHROUGH {
                    self.ingest_response_bytes(slot_idx, src);
                } else {
                    self.ingest_codec_responses(driver, slot_idx, src);
                }
                driver.provided_ring().defer(bid);
                if !self.core.slots[slot_idx]
                    .conn
                    .flags
                    .contains(ConnFlags::LIVE)
                {
                    return;
                }
                self.try_submit_outbound(driver, slot_idx);
                if !more {
                    let _ = self.core.arm_recv(driver, slot_idx);
                }
            }
            RecvDispatch::DrainOnly { slot_idx, more } => {
                if !more {
                    let _ = self.core.arm_recv(driver, slot_idx);
                }
            }
        }
    }
}

impl<P, T, S, F, B> Pool<Client<P, T, S>, F, B>
where
    P: ClientProtocol,
    T: Transport,
    T::Addr: Clone,
    S: ConnectSource<T>,
    F: Profile,
    B: PoolDriver,
{
    #[cold]
    fn ingest_codec_responses(&mut self, driver: &mut B::Driver, slot_idx: usize, src: &[u8]) {
        let Some(plaintext) = self.core.process_codec_inbound(driver, slot_idx, src) else {
            return;
        };
        self.ingest_response_bytes(slot_idx, &plaintext);
        self.core.install_plaintext(slot_idx, plaintext);
    }

    fn ingest_response_bytes(&mut self, slot_idx: usize, src: &[u8]) {
        let conn_id =
            SlotId::from_parts(slot_idx as u32, self.core.slots[slot_idx].conn.generation);
        let entry = &mut self.core.slots[slot_idx];
        let user = entry.user.get_mut();
        let codec_state = entry.codec_state.get_mut();
        user.session
            .ingest(src, &mut self.direction.protocol, conn_id, codec_state);
    }
}
