
use std::marker::PhantomData;

use feanor_math::algorithms::discrete_log::Subgroup;
use feanor_math::integer::BigIntRing;
use feanor_math::ring::*;
use feanor_math::serialization::*;
use feanor_serde::impl_deserialize_seed_for_dependent_struct;
use feanor_serde::seq::DeserializeSeedSeq;
use serde::{Deserializer, Serialize};
use serde::de::DeserializeSeed;
use crate::cache::DeserializeSeedDeserializableWithData;
use crate::cache::{SerializeDeserializeWith, SerializeSerializableWithData};
use crate::circuit::PlaintextCircuit;
use crate::number_ring::galois::CyclotomicGaloisGroup;
use crate::poly_eval::digit_extract::DigitExtract;
use crate::poly_eval::digit_extract::DigitExtractionCircuit;

#[derive(Serialize)]
#[serde(rename = "DigitExtractCircuitData", bound = "")]
pub(super) struct SerializableDigitExtractCircuit<'a, D, R>
    where R: ?Sized + SerializableElementRing,
        PlaintextCircuit<R>: SerializeDeserializeWith<D>
{
    pub(super) circuit: SerializeSerializableWithData<'a, D, PlaintextCircuit<R>>,
    pub(super) global_mod_exp: usize,
    pub(super) extracted_digit_mod_exp: &'a [usize],
    pub(super) ignore: PhantomData<()>
}

#[derive(Serialize)]
#[serde(rename = "DigitExtractData", bound = "")]
pub(super) struct SerializableDigitExtract<'a, D, R>
    where R: ?Sized + SerializableElementRing,
        PlaintextCircuit<R>: SerializeDeserializeWith<D>
{
    pub(super) extraction_circuits: Vec<SerializableDigitExtractCircuit<'a, D, R>>,
    pub(super) v: usize,
    pub(super) e: usize,
    pub(super) p: SerializeWithRing<'a, BigIntRing>,
    pub(super) ignore: PhantomData<()>
}

#[derive(Clone, Copy)]
pub(super) struct DeserializeSeedDigitExtract<'a, R>
    where R: RingStore,
        R::Type: SerializableElementRing
{
    pub(super) rings: &'a [R],
    pub(super) galois_group: Option<&'a Subgroup<CyclotomicGaloisGroup>>
}

impl<'a, 'de, R> DeserializeSeed<'de> for DeserializeSeedDigitExtract<'a, R>
    where R: RingStore,
        R::Type: SerializableElementRing
{
    type Value = DigitExtract<R::Type>;

    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
        where D: Deserializer<'de>
    {
        struct DeserializeSeedDigitExtractCircuitData<D, R: ?Sized> {
            data: D,
            ring: PhantomData<R>
        }

        impl_deserialize_seed_for_dependent_struct!{
            <{'de, Data, R}> pub struct DigitExtractCircuitData<{'de, R, Data}> using DeserializeSeedDigitExtractCircuitData<Data, R> {
                circuit: PlaintextCircuit<R>: |deserialize_seed: &DeserializeSeedDigitExtractCircuitData<Data, R>| DeserializeSeedDeserializableWithData::new(deserialize_seed.data.clone()),
                global_mod_exp: usize: |_| PhantomData::<usize>,
                extracted_digit_mod_exp: Vec<usize>: |_| PhantomData::<Vec<usize>>,
                ignore: PhantomData<Data>: |_| PhantomData::<PhantomData<Data>>
            } where R: ?Sized + SerializableElementRing,
                PlaintextCircuit<R>: SerializeDeserializeWith<Data>,
                Data: Clone
        }

        struct DeserializeSeedDigitExtractData<D, R: ?Sized>
        {
            circuit_deserialize_data: Vec<D>,
            ring: PhantomData<R>
        }

        fn derive_circuit_deserializer<'de, 'a, D, R>(deserialize_seed: &'a DeserializeSeedDigitExtractData<D, R>) -> impl use <'a, 'de, D, R> + DeserializeSeed<'de, Value = Vec<DigitExtractionCircuit<R>>>
            where R: ?Sized + SerializableElementRing,
                PlaintextCircuit<R>: SerializeDeserializeWith<D>,
                D: Clone
        {
            DeserializeSeedSeq::new(
                deserialize_seed.circuit_deserialize_data.iter().chain([deserialize_seed.circuit_deserialize_data.last().unwrap()]).map(|data| DeserializeSeedDigitExtractCircuitData { data: data.clone(), ring: PhantomData }),
                Vec::new(),
                |mut current, next| {
                    current.push(DigitExtractionCircuit {
                        circuit: next.circuit,
                        global_mod_exp: next.global_mod_exp,
                        extracted_digit_mod_exp: next.extracted_digit_mod_exp
                    }); 
                    current
                }
            )
        }
        
        impl_deserialize_seed_for_dependent_struct!{
            <{'de, R, Data}> pub struct DigitExtractData<{'de, R, Data}> using DeserializeSeedDigitExtractData<Data, R> {
                extraction_circuits: Vec<DigitExtractionCircuit<R>>: derive_circuit_deserializer,
                v: usize: |_| PhantomData::<usize>,
                e: usize: |_| PhantomData::<usize>,
                p: El<BigIntRing>: |_| DeserializeWithRing::new(BigIntRing::RING),
                ignore: PhantomData<Data>: |_| PhantomData::<PhantomData<Data>>
            } where R: ?Sized + SerializableElementRing,
                PlaintextCircuit<R>: SerializeDeserializeWith<Data>,
                Data: Clone
        }

        if let Some(galois_group) = self.galois_group {
            let result = DeserializeSeedDigitExtractData {
                ring: PhantomData,
                circuit_deserialize_data: self.rings.iter().skip(1).map(|ring| (ring, galois_group)).collect()
            }.deserialize(deserializer)?;
            return Ok(DigitExtract::new_with_circuits(self.rings, result.extraction_circuits));
        } else {
            let result = DeserializeSeedDigitExtractData {
                ring: PhantomData,
                circuit_deserialize_data: self.rings.iter().skip(1).map(|ring| (ring, )).collect()
            }.deserialize(deserializer)?;
            return Ok(DigitExtract::new_with_circuits(self.rings, result.extraction_circuits));
        }
    }
}