#![forbid(unsafe_code)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::type_complexity)]
mod stack;
pub use stack::*;
mod trace;
pub use trace::*;
mod traits;
pub use traits::*;
mod authorize;
mod deploy;
mod evaluate;
mod execute;
mod finalize;
mod verify_deployment;
mod verify_execution;
mod verify_fee;
#[cfg(test)]
mod tests;
use console::{
    account::PrivateKey,
    network::prelude::*,
    program::{Identifier, Literal, Locator, Plaintext, ProgramID, Record, Response, Value},
    types::{Field, U16, U64},
};
use ledger_block::{Deployment, Execution, Fee, Input, Transition};
use ledger_store::{atomic_batch_scope, FinalizeStorage, FinalizeStore};
use synthesizer_program::{
    Branch,
    Closure,
    Command,
    Finalize,
    FinalizeGlobalState,
    FinalizeOperation,
    Instruction,
    Program,
    RegistersLoad,
    RegistersStore,
    StackProgram,
};
use synthesizer_snark::{ProvingKey, UniversalSRS, VerifyingKey};
use aleo_std::prelude::{finish, lap, timer};
use indexmap::IndexMap;
use parking_lot::RwLock;
use std::{collections::HashMap, sync::Arc};
#[cfg(feature = "aleo-cli")]
use colored::Colorize;
#[derive(Clone)]
pub struct Process<N: Network> {
    universal_srs: Arc<UniversalSRS<N>>,
    stacks: IndexMap<ProgramID<N>, Stack<N>>,
}
impl<N: Network> Process<N> {
    #[inline]
    pub fn setup<A: circuit::Aleo<Network = N>, R: Rng + CryptoRng>(rng: &mut R) -> Result<Self> {
        let timer = timer!("Process:setup");
        let mut process = Self { universal_srs: Arc::new(UniversalSRS::load()?), stacks: IndexMap::new() };
        lap!(timer, "Initialize process");
        let program = Program::credits()?;
        lap!(timer, "Load credits program");
        let stack = Stack::new(&process, &program)?;
        lap!(timer, "Initialize stack");
        for function_name in program.functions().keys() {
            stack.synthesize_key::<A, _>(function_name, rng)?;
            lap!(timer, "Synthesize circuit keys for {function_name}");
        }
        lap!(timer, "Synthesize credits program keys");
        process.add_stack(stack);
        finish!(timer);
        Ok(process)
    }
    #[inline]
    pub fn add_program(&mut self, program: &Program<N>) -> Result<()> {
        self.add_stack(Stack::new(self, program)?);
        Ok(())
    }
    #[inline]
    pub fn add_stack(&mut self, stack: Stack<N>) {
        self.stacks.insert(*stack.program_id(), stack);
    }
}
impl<N: Network> Process<N> {
    #[inline]
    pub fn load() -> Result<Self> {
        let timer = timer!("Process::load");
        let mut process = Self { universal_srs: Arc::new(UniversalSRS::load()?), stacks: IndexMap::new() };
        lap!(timer, "Initialize process");
        let program = Program::credits()?;
        lap!(timer, "Load credits program");
        let stack = Stack::new(&process, &program)?;
        lap!(timer, "Initialize stack");
        for function_name in program.functions().keys() {
            let verifying_key = N::get_credits_verifying_key(function_name.to_string())?;
            stack.insert_verifying_key(function_name, VerifyingKey::new(verifying_key.clone()))?;
            lap!(timer, "Load verifying key for {function_name}");
        }
        lap!(timer, "Load circuit keys");
        process.add_stack(stack);
        finish!(timer, "Process::load");
        Ok(process)
    }
    #[inline]
    #[cfg(feature = "wasm")]
    pub fn load_web() -> Result<Self> {
        let mut process = Self { universal_srs: Arc::new(UniversalSRS::load()?), stacks: IndexMap::new() };
        let program = Program::credits()?;
        let stack = Stack::new(&process, &program)?;
        process.add_stack(stack);
        Ok(process)
    }
    #[inline]
    pub const fn universal_srs(&self) -> &Arc<UniversalSRS<N>> {
        &self.universal_srs
    }
    #[inline]
    pub fn contains_program(&self, program_id: &ProgramID<N>) -> bool {
        self.stacks.contains_key(program_id)
    }
    #[inline]
    pub fn get_stack(&self, program_id: impl TryInto<ProgramID<N>>) -> Result<&Stack<N>> {
        let program_id = program_id.try_into().map_err(|_| anyhow!("Invalid program ID"))?;
        let stack = self.stacks.get(&program_id).ok_or_else(|| anyhow!("Program '{program_id}' does not exist"))?;
        ensure!(stack.program_id() == &program_id, "Expected program '{}', found '{program_id}'", stack.program_id());
        Ok(stack)
    }
    #[inline]
    pub fn get_program(&self, program_id: impl TryInto<ProgramID<N>>) -> Result<&Program<N>> {
        self.get_stack(program_id).map(Stack::program)
    }
    #[inline]
    pub fn get_proving_key(
        &self,
        program_id: impl TryInto<ProgramID<N>>,
        function_name: impl TryInto<Identifier<N>>,
    ) -> Result<ProvingKey<N>> {
        let function_name = function_name.try_into().map_err(|_| anyhow!("Invalid function name"))?;
        self.get_stack(program_id)?.get_proving_key(&function_name)
    }
    #[inline]
    pub fn get_verifying_key(
        &self,
        program_id: impl TryInto<ProgramID<N>>,
        function_name: impl TryInto<Identifier<N>>,
    ) -> Result<VerifyingKey<N>> {
        let function_name = function_name.try_into().map_err(|_| anyhow!("Invalid function name"))?;
        self.get_stack(program_id)?.get_verifying_key(&function_name)
    }
    #[inline]
    pub fn insert_proving_key(
        &self,
        program_id: &ProgramID<N>,
        function_name: &Identifier<N>,
        proving_key: ProvingKey<N>,
    ) -> Result<()> {
        self.get_stack(program_id)?.insert_proving_key(function_name, proving_key)
    }
    #[inline]
    pub fn insert_verifying_key(
        &self,
        program_id: &ProgramID<N>,
        function_name: &Identifier<N>,
        verifying_key: VerifyingKey<N>,
    ) -> Result<()> {
        self.get_stack(program_id)?.insert_verifying_key(function_name, verifying_key)
    }
    #[inline]
    pub fn synthesize_key<A: circuit::Aleo<Network = N>, R: Rng + CryptoRng>(
        &self,
        program_id: &ProgramID<N>,
        function_name: &Identifier<N>,
        rng: &mut R,
    ) -> Result<()> {
        self.get_stack(program_id)?.synthesize_key::<A, R>(function_name, rng)
    }
}
#[cfg(any(test, feature = "test"))]
pub mod test_helpers {
    use super::*;
    use console::{account::PrivateKey, network::Testnet3, program::Identifier};
    use ledger_block::Transition;
    use ledger_query::Query;
    use ledger_store::{helpers::memory::BlockMemory, BlockStore};
    use synthesizer_program::Program;
    use once_cell::sync::OnceCell;
    type CurrentNetwork = Testnet3;
    type CurrentAleo = circuit::network::AleoV0;
    pub fn sample_key() -> (Identifier<CurrentNetwork>, ProvingKey<CurrentNetwork>, VerifyingKey<CurrentNetwork>) {
        static INSTANCE: OnceCell<(
            Identifier<CurrentNetwork>,
            ProvingKey<CurrentNetwork>,
            VerifyingKey<CurrentNetwork>,
        )> = OnceCell::new();
        INSTANCE
            .get_or_init(|| {
                let (string, program) = Program::<CurrentNetwork>::parse(
                    r"
program testing.aleo;
function compute:
    input r0 as u32.private;
    input r1 as u32.public;
    add r0 r1 into r2;
    output r2 as u32.public;",
                )
                .unwrap();
                assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
                let function_name = Identifier::from_str("compute").unwrap();
                let rng = &mut TestRng::default();
                let process = sample_process(&program);
                process.synthesize_key::<CurrentAleo, _>(program.id(), &function_name, rng).unwrap();
                let proving_key = process.get_proving_key(program.id(), function_name).unwrap();
                let verifying_key = process.get_verifying_key(program.id(), function_name).unwrap();
                (function_name, proving_key, verifying_key)
            })
            .clone()
    }
    pub(crate) fn sample_execution() -> Execution<CurrentNetwork> {
        static INSTANCE: OnceCell<Execution<CurrentNetwork>> = OnceCell::new();
        INSTANCE
            .get_or_init(|| {
                let (string, program) = Program::<CurrentNetwork>::parse(
                    r"
program testing.aleo;
function compute:
    input r0 as u32.private;
    input r1 as u32.public;
    add r0 r1 into r2;
    output r2 as u32.public;",
                )
                .unwrap();
                assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
                let function_name = Identifier::from_str("compute").unwrap();
                let rng = &mut TestRng::default();
                let caller_private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
                let block_store = BlockStore::<CurrentNetwork, BlockMemory<_>>::open(None).unwrap();
                let process = sample_process(&program);
                let authorization = process
                    .authorize::<CurrentAleo, _>(
                        &caller_private_key,
                        program.id(),
                        function_name,
                        ["5u32", "10u32"].into_iter(),
                        rng,
                    )
                    .unwrap();
                assert_eq!(authorization.len(), 1);
                let (_response, mut trace) = process.execute::<CurrentAleo>(authorization).unwrap();
                assert_eq!(trace.transitions().len(), 1);
                trace.prepare(Query::from(block_store)).unwrap();
                trace.prove_execution::<CurrentAleo, _>("testing", rng).unwrap()
            })
            .clone()
    }
    pub fn sample_transition() -> Transition<CurrentNetwork> {
        let mut execution = sample_execution();
        assert!(!execution.is_empty());
        execution.pop().unwrap()
    }
    pub(crate) fn sample_process(program: &Program<CurrentNetwork>) -> Process<CurrentNetwork> {
        let mut process = Process::load().unwrap();
        process.add_program(program).unwrap();
        process
    }
}