//! Proof generated by the prover in Sigma protocol

use std::io;

use ergotree_interpreter::sigma_protocol::prover::ContextExtension;
use ergotree_interpreter::sigma_protocol::prover::ProofBytes;
use ergotree_ir::serialization::sigma_byte_reader::SigmaByteRead;
use ergotree_ir::serialization::sigma_byte_writer::SigmaByteWrite;
use ergotree_ir::serialization::SerializationError;
use ergotree_ir::serialization::SigmaSerializable;
#[cfg(feature = "json")]
use serde::{Deserialize, Serialize};

/// Wrapped IR [`ProverResult`] for Serde
#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
#[derive(PartialEq, Debug, Clone)]
pub struct ProverResult {
    /// proof that satisfies final sigma proposition
    #[cfg_attr(
        feature = "json",
        serde(rename = "proofBytes"),
        serde(with = "crate::chain::json::ProofBytesSerde")
    )]
    pub proof: ProofBytes,
    /// user-defined variables to be put into context
    #[cfg_attr(
        feature = "json",
        serde(rename = "extension"),
        serde(with = "crate::chain::json::ContextExtensionSerde")
    )]
    pub extension: ContextExtension,
}

impl From<ergotree_interpreter::sigma_protocol::prover::ProverResult> for ProverResult {
    fn from(ir: ergotree_interpreter::sigma_protocol::prover::ProverResult) -> Self {
        ProverResult {
            proof: ir.proof,
            extension: ir.extension,
        }
    }
}

impl SigmaSerializable for ProverResult {
    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), io::Error> {
        self.proof.sigma_serialize(w)?;
        self.extension.sigma_serialize(w)?;
        Ok(())
    }
    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
        let proof = ProofBytes::sigma_parse(r)?;
        let extension = ContextExtension::sigma_parse(r)?;
        Ok(ProverResult { proof, extension })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use ergotree_ir::serialization::sigma_serialize_roundtrip;
    use proptest::prelude::*;

    impl Arbitrary for ProverResult {
        type Parameters = ();
        type Strategy = BoxedStrategy<Self>;

        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
            (any::<ProofBytes>(), any::<ContextExtension>())
                .prop_map(|(proof, extension)| Self { proof, extension })
                .boxed()
        }
    }

    proptest! {

        #[test]
        fn ser_roundtrip(v in any::<ProverResult>()) {
            prop_assert_eq![sigma_serialize_roundtrip(&v), v];
        }
    }
}
