use engine_wasm_prep::wasm_costs::{WasmCosts, WASM_COSTS_SERIALIZED_LENGTH};
use types::{
    bytesrepr::{self, FromBytes, ToBytes},
    AccessRights, URef, UREF_SERIALIZED_LENGTH,
};
const PROTOCOL_DATA_SERIALIZED_LENGTH: usize =
    WASM_COSTS_SERIALIZED_LENGTH + 3 * UREF_SERIALIZED_LENGTH;
const DEFAULT_UREF_ADDRESS: [u8; 32] = [0; 32];
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct ProtocolData {
    wasm_costs: WasmCosts,
    mint: URef,
    proof_of_stake: URef,
    standard_payment: URef,
}
impl Default for ProtocolData {
    fn default() -> ProtocolData {
        ProtocolData {
            wasm_costs: WasmCosts::default(),
            mint: URef::new(DEFAULT_UREF_ADDRESS, AccessRights::READ),
            proof_of_stake: URef::new(DEFAULT_UREF_ADDRESS, AccessRights::READ),
            standard_payment: URef::new(DEFAULT_UREF_ADDRESS, AccessRights::READ),
        }
    }
}
impl ProtocolData {
    
    pub fn new(
        wasm_costs: WasmCosts,
        mint: URef,
        proof_of_stake: URef,
        standard_payment: URef,
    ) -> Self {
        ProtocolData {
            wasm_costs,
            mint,
            proof_of_stake,
            standard_payment,
        }
    }
    
    
    
    pub fn partial_with_mint(mint: URef) -> Self {
        ProtocolData {
            mint,
            ..Default::default()
        }
    }
    
    
    
    
    pub fn partial_without_standard_payment(
        wasm_costs: WasmCosts,
        mint: URef,
        proof_of_stake: URef,
    ) -> Self {
        ProtocolData {
            wasm_costs,
            mint,
            proof_of_stake,
            ..Default::default()
        }
    }
    
    pub fn wasm_costs(&self) -> &WasmCosts {
        &self.wasm_costs
    }
    pub fn mint(&self) -> URef {
        self.mint
    }
    pub fn proof_of_stake(&self) -> URef {
        self.proof_of_stake
    }
    pub fn standard_payment(&self) -> URef {
        self.standard_payment
    }
    
    pub fn system_contracts(&self) -> Vec<URef> {
        let mut vec = Vec::with_capacity(3);
        if self.mint.addr() != DEFAULT_UREF_ADDRESS {
            vec.push(self.mint)
        }
        if self.proof_of_stake.addr() != DEFAULT_UREF_ADDRESS {
            vec.push(self.proof_of_stake)
        }
        if self.standard_payment.addr() != DEFAULT_UREF_ADDRESS {
            vec.push(self.standard_payment)
        }
        vec
    }
}
impl ToBytes for ProtocolData {
    fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
        let mut ret = bytesrepr::unchecked_allocate_buffer(self);
        ret.append(&mut self.wasm_costs.to_bytes()?);
        ret.append(&mut self.mint.to_bytes()?);
        ret.append(&mut self.proof_of_stake.to_bytes()?);
        ret.append(&mut self.standard_payment.to_bytes()?);
        Ok(ret)
    }
    fn serialized_length(&self) -> usize {
        PROTOCOL_DATA_SERIALIZED_LENGTH
    }
}
impl FromBytes for ProtocolData {
    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
        let (wasm_costs, rem) = WasmCosts::from_bytes(bytes)?;
        let (mint, rem) = URef::from_bytes(rem)?;
        let (proof_of_stake, rem) = URef::from_bytes(rem)?;
        let (standard_payment, rem) = URef::from_bytes(rem)?;
        Ok((
            ProtocolData {
                wasm_costs,
                mint,
                proof_of_stake,
                standard_payment,
            },
            rem,
        ))
    }
}
#[cfg(test)]
pub(crate) mod gens {
    use proptest::prop_compose;
    use engine_wasm_prep::wasm_costs::gens as wasm_costs_gens;
    use types::gens;
    use super::ProtocolData;
    prop_compose! {
        pub fn protocol_data_arb()(
            wasm_costs in wasm_costs_gens::wasm_costs_arb(),
            mint in gens::uref_arb(),
            proof_of_stake in gens::uref_arb(),
            standard_payment in gens::uref_arb(),
        ) -> ProtocolData {
            ProtocolData {
                wasm_costs,
                mint,
                proof_of_stake,
                standard_payment,
            }
        }
    }
}
#[cfg(test)]
mod tests {
    use proptest::proptest;
    use engine_wasm_prep::wasm_costs::WasmCosts;
    use types::{bytesrepr, AccessRights, URef};
    use super::{gens, ProtocolData};
    fn wasm_costs_mock() -> WasmCosts {
        WasmCosts {
            regular: 1,
            div: 16,
            mul: 4,
            mem: 2,
            initial_mem: 4096,
            grow_mem: 8192,
            memcpy: 1,
            max_stack_height: 64 * 1024,
            opcodes_mul: 3,
            opcodes_div: 8,
        }
    }
    fn wasm_costs_free() -> WasmCosts {
        WasmCosts {
            regular: 0,
            div: 0,
            mul: 0,
            mem: 0,
            initial_mem: 4096,
            grow_mem: 8192,
            memcpy: 0,
            max_stack_height: 64 * 1024,
            opcodes_mul: 1,
            opcodes_div: 1,
        }
    }
    #[test]
    fn should_serialize_and_deserialize() {
        let mock = {
            let costs = wasm_costs_mock();
            let mint_reference = URef::new([0u8; 32], AccessRights::READ_ADD_WRITE);
            let proof_of_stake_reference = URef::new([1u8; 32], AccessRights::READ_ADD_WRITE);
            let standard_payment_reference = URef::new([2u8; 32], AccessRights::READ_ADD_WRITE);
            ProtocolData::new(
                costs,
                mint_reference,
                proof_of_stake_reference,
                standard_payment_reference,
            )
        };
        let free = {
            let costs = wasm_costs_free();
            let mint_reference = URef::new([0u8; 32], AccessRights::READ_ADD_WRITE);
            let proof_of_stake_reference = URef::new([1u8; 32], AccessRights::READ_ADD_WRITE);
            let standard_payment_reference = URef::new([2u8; 32], AccessRights::READ_ADD_WRITE);
            ProtocolData::new(
                costs,
                mint_reference,
                proof_of_stake_reference,
                standard_payment_reference,
            )
        };
        bytesrepr::test_serialization_roundtrip(&mock);
        bytesrepr::test_serialization_roundtrip(&free);
    }
    #[test]
    fn should_return_all_system_contracts() {
        let mint_reference = URef::new([197u8; 32], AccessRights::READ_ADD_WRITE);
        let proof_of_stake_reference = URef::new([198u8; 32], AccessRights::READ_ADD_WRITE);
        let standard_payment_reference = URef::new([199u8; 32], AccessRights::READ_ADD_WRITE);
        let protocol_data = {
            let costs = wasm_costs_mock();
            ProtocolData::new(
                costs,
                mint_reference,
                proof_of_stake_reference,
                standard_payment_reference,
            )
        };
        let actual = {
            let mut items = protocol_data.system_contracts();
            items.sort();
            items
        };
        assert_eq!(actual.len(), 3);
        assert_eq!(actual[0], mint_reference);
        assert_eq!(actual[1], proof_of_stake_reference);
        assert_eq!(actual[2], standard_payment_reference);
    }
    #[test]
    fn should_return_only_valid_system_contracts() {
        assert_eq!(ProtocolData::default().system_contracts(), &[]);
        let mint_reference = URef::new([197u8; 32], AccessRights::READ_ADD_WRITE);
        let proof_of_stake_reference = URef::new([0u8; 32], AccessRights::READ);
        let standard_payment_reference = URef::new([199u8; 32], AccessRights::READ_ADD_WRITE);
        let protocol_data = {
            let costs = wasm_costs_mock();
            ProtocolData::new(
                costs,
                mint_reference,
                proof_of_stake_reference,
                standard_payment_reference,
            )
        };
        let actual = {
            let mut items = protocol_data.system_contracts();
            items.sort();
            items
        };
        assert_eq!(actual.len(), 2);
        assert_eq!(actual[0], mint_reference);
        assert_eq!(actual[1], standard_payment_reference);
    }
    proptest! {
        #[test]
        fn should_serialize_and_deserialize_with_arbitrary_values(
            protocol_data in gens::protocol_data_arb()
        ) {
            bytesrepr::test_serialization_roundtrip(&protocol_data);
        }
    }
}