use crate::*;

use proc_macro2::TokenStream;
use quote::quote;

pub fn extract_from(payloads: &[&Payload]) -> Result<TokenStream, E> {
    let mut variants = Vec::new();
    for payload in payloads.iter() {
        let fullname = payload.fullname()?;
        let fullpath = payload.fullpath()?;
        variants.push(quote! {
            match <#fullpath as brec::ReadPayloadFrom<#fullpath>>::read(buf, header, _ctx) {
                Ok(pl) => return Ok(Payload::#fullname(pl)),
                Err(err) => {
                    if !matches!(err, brec::Error::SignatureDismatch(_)) {
                        return Err(err);
                    }
                }
            }
        });
    }
    Ok(quote! {
        impl brec::ExtractPayloadFrom<Payload> for Payload {
            fn read<B: std::io::Read>(
                buf: &mut B,
                header: &brec::PayloadHeader,
                _ctx: &mut <Payload as brec::PayloadSchema>::Context<'_>,
            ) -> Result<Payload, brec::Error>
            where
                Self: Sized,
            {
                #(#variants)*
                match <Vec<u8> as brec::ReadPayloadFrom<Vec<u8>>>::read(buf, header, &mut brec::default_payload_context()) {
                    Ok(pl) => return Ok(Payload::Bytes(pl)),
                    Err(err) => {
                        if !matches!(err, brec::Error::SignatureDismatch(_)) {
                            return Err(err);
                        }
                    }
                }
                match <String as brec::ReadPayloadFrom<String>>::read(buf, header, &mut brec::default_payload_context()) {
                    Ok(pl) => return Ok(Payload::String(pl)),
                    Err(err) => {
                        if !matches!(err, brec::Error::SignatureDismatch(_)) {
                            return Err(err);
                        }
                    }
                }
                let mut unrecognized = brec::Unrecognized::payload(header.sig.as_slice().to_vec());
                unrecognized.len = Some(header.len as u64);
                Err(brec::Error::SignatureDismatch(unrecognized))
            }
        }
    })
}

pub fn try_extract_from(payloads: &[&Payload]) -> Result<TokenStream, E> {
    let mut variants = Vec::new();
    for payload in payloads.iter() {
        let fullname = payload.fullname()?;
        let fullpath = payload.fullpath()?;
        variants.push(quote! {
            match <#fullpath as brec::TryReadPayloadFrom<#fullpath>>::try_read(buf, header, _ctx) {
                Ok(brec::ReadStatus::Success(pl)) => {
                    return Ok(brec::ReadStatus::Success(Payload::#fullname(pl)))
                }
                Ok(brec::ReadStatus::NotEnoughData(needed)) => {
                    return Ok(brec::ReadStatus::NotEnoughData(needed))
                }
                Err(err) => {
                    if !matches!(err, brec::Error::SignatureDismatch(_)) {
                        return Err(err);
                    }
                }
            }
        });
    }
    Ok(quote! {
        impl brec::TryExtractPayloadFrom<Payload> for Payload {
            fn try_read<B: std::io::Read + std::io::Seek>(
                buf: &mut B,
                header: &brec::PayloadHeader,
                _ctx: &mut <Payload as brec::PayloadSchema>::Context<'_>,
            ) -> Result<brec::ReadStatus<Payload>, brec::Error> {
                #(#variants)*
                match <Vec<u8> as brec::TryReadPayloadFrom<Vec<u8>>>::try_read(buf, header, &mut brec::default_payload_context()) {
                    Ok(brec::ReadStatus::Success(pl)) => {
                        return Ok(brec::ReadStatus::Success(Payload::Bytes(pl)))
                    }
                    Ok(brec::ReadStatus::NotEnoughData(needed)) => {
                        return Ok(brec::ReadStatus::NotEnoughData(needed))
                    }
                    Err(err) => {
                        if !matches!(err, brec::Error::SignatureDismatch(_)) {
                            return Err(err);
                        }
                    }
                }
                match <String as brec::TryReadPayloadFrom<String>>::try_read(buf, header, &mut brec::default_payload_context()) {
                    Ok(brec::ReadStatus::Success(pl)) => {
                        return Ok(brec::ReadStatus::Success(Payload::String(pl)))
                    }
                    Ok(brec::ReadStatus::NotEnoughData(needed)) => {
                        return Ok(brec::ReadStatus::NotEnoughData(needed))
                    }
                    Err(err) => {
                        if !matches!(err, brec::Error::SignatureDismatch(_)) {
                            return Err(err);
                        }
                    }
                }
                let mut unrecognized = brec::Unrecognized::payload(header.sig.as_slice().to_vec());
                unrecognized.len = Some(header.len as u64);
                Err(brec::Error::SignatureDismatch(unrecognized))
            }
        }
    })
}

pub fn try_extract_from_buffered(payloads: &[&Payload]) -> Result<TokenStream, E> {
    let mut variants = Vec::new();
    for payload in payloads.iter() {
        let fullname = payload.fullname()?;
        let fullpath = payload.fullpath()?;
        variants.push(quote! {
            match <#fullpath as brec::TryReadPayloadFromBuffered<#fullpath>>::try_read(buf, header, _ctx) {
                Ok(brec::ReadStatus::Success(pl)) => {
                    return Ok(brec::ReadStatus::Success(Payload::#fullname(pl)))
                }
                Ok(brec::ReadStatus::NotEnoughData(needed)) => {
                    return Ok(brec::ReadStatus::NotEnoughData(needed))
                }
                Err(err) => {
                    if !matches!(err, brec::Error::SignatureDismatch(_)) {
                        return Err(err);
                    }
                }
            }
        });
    }
    Ok(quote! {
        impl brec::TryExtractPayloadFromBuffered<Payload> for Payload {
            fn try_read<B: std::io::BufRead>(
                buf: &mut B,
                header: &brec::PayloadHeader,
                _ctx: &mut <Payload as brec::PayloadSchema>::Context<'_>,
            ) -> Result<brec::ReadStatus<Payload>, brec::Error> {
                #(#variants)*
                match <Vec<u8> as brec::TryReadPayloadFromBuffered<Vec<u8>>>::try_read(buf, header, &mut brec::default_payload_context()) {
                    Ok(brec::ReadStatus::Success(pl)) => {
                        return Ok(brec::ReadStatus::Success(Payload::Bytes(pl)))
                    }
                    Ok(brec::ReadStatus::NotEnoughData(needed)) => {
                        return Ok(brec::ReadStatus::NotEnoughData(needed))
                    }
                    Err(err) => {
                        if !matches!(err, brec::Error::SignatureDismatch(_)) {
                            return Err(err);
                        }
                    }
                }
                match <String as brec::TryReadPayloadFromBuffered<String>>::try_read(buf, header, &mut brec::default_payload_context()) {
                    Ok(brec::ReadStatus::Success(pl)) => {
                        return Ok(brec::ReadStatus::Success(Payload::String(pl)))
                    }
                    Ok(brec::ReadStatus::NotEnoughData(needed)) => {
                        return Ok(brec::ReadStatus::NotEnoughData(needed))
                    }
                    Err(err) => {
                        if !matches!(err, brec::Error::SignatureDismatch(_)) {
                            return Err(err);
                        }
                    }
                }
                let mut unrecognized = brec::Unrecognized::payload(header.sig.as_slice().to_vec());
                unrecognized.len = Some(header.len as u64);
                Err(brec::Error::SignatureDismatch(unrecognized))
            }
        }
    })
}
