use crate::{
    budget::AsBudget,
    err,
    host::{
        metered_clone::{MeteredAlloc, MeteredClone},
        metered_write_xdr, ContractReentryMode, CreateContractArgs,
    },
    xdr::{
        Asset, ContractCodeEntry, ContractDataDurability, ContractExecutable, ContractIdPreimage,
        ContractIdPreimageFromAddress, ExtensionPoint, Hash, LedgerKey, LedgerKeyContractCode,
        ScAddress, ScErrorCode, ScErrorType,
    },
    AddressObject, BytesObject, Host, HostError, Symbol, TryFromVal, Vm,
};
use std::rc::Rc;
impl Host {
    fn create_contract_with_id(
        &self,
        contract_id: Hash,
        contract_executable: ContractExecutable,
    ) -> Result<(), HostError> {
        let storage_key = self.contract_instance_ledger_key(&contract_id)?;
        if self
            .try_borrow_storage_mut()?
            .has(&storage_key, self.as_budget())
            .map_err(|e| self.decorate_contract_instance_storage_error(e, &contract_id))?
        {
            return Err(self.err(
                ScErrorType::Storage,
                ScErrorCode::ExistingValue,
                "contract already exists",
                &[self
                    .add_host_object(self.scbytes_from_hash(&contract_id)?)?
                    .into()],
            ));
        }
        if let ContractExecutable::Wasm(wasm_hash) = &contract_executable {
            if !self.wasm_exists(wasm_hash)? {
                return Err(err!(
                    self,
                    (ScErrorType::Storage, ScErrorCode::MissingValue),
                    "Wasm does not exist",
                    *wasm_hash
                ));
            }
        }
        self.store_contract_instance(Some(contract_executable), None, contract_id, &storage_key)?;
        Ok(())
    }
    fn maybe_initialize_stellar_asset_contract(
        &self,
        contract_id: &Hash,
        id_preimage: &ContractIdPreimage,
    ) -> Result<(), HostError> {
        if let ContractIdPreimage::Asset(asset) = id_preimage {
            let mut asset_bytes: Vec<u8> = Default::default();
            metered_write_xdr(self.budget_ref(), asset, &mut asset_bytes)?;
            self.call_n_internal(
                contract_id,
                Symbol::try_from_val(self, &"init_asset")?,
                &[self
                    .add_host_object(self.scbytes_from_vec(asset_bytes)?)?
                    .into()],
                ContractReentryMode::Prohibited,
                false,
            )?;
            Ok(())
        } else {
            Ok(())
        }
    }
    pub(crate) fn create_contract_internal(
        &self,
        deployer: Option<AddressObject>,
        args: CreateContractArgs,
    ) -> Result<AddressObject, HostError> {
        let has_deployer = deployer.is_some();
        if has_deployer {
            self.try_borrow_authorization_manager()?
                .push_create_contract_host_fn_frame(self, args.metered_clone(self)?)?;
        }
        let res = self.create_contract_with_optional_auth(deployer, args);
        if has_deployer {
            self.try_borrow_authorization_manager()?
                .pop_frame(self, None)?;
        }
        res
    }
    fn create_contract_with_optional_auth(
        &self,
        deployer: Option<AddressObject>,
        args: CreateContractArgs,
    ) -> Result<AddressObject, HostError> {
        if let Some(deployer_address) = deployer {
            self.try_borrow_authorization_manager()?.require_auth(
                self,
                deployer_address,
                Default::default(),
            )?;
        }
        let id_preimage =
            self.get_full_contract_id_preimage(args.contract_id_preimage.metered_clone(self)?)?;
        let hash_id = Hash(self.metered_hash_xdr(&id_preimage)?);
        self.create_contract_with_id(hash_id.metered_clone(self)?, args.executable)?;
        self.maybe_initialize_stellar_asset_contract(&hash_id, &args.contract_id_preimage)?;
        self.add_host_object(ScAddress::Contract(hash_id))
    }
    pub(crate) fn get_contract_id_hash(
        &self,
        deployer: AddressObject,
        salt: BytesObject,
    ) -> Result<Hash, HostError> {
        let contract_id_preimage = ContractIdPreimage::Address(ContractIdPreimageFromAddress {
            address: self.visit_obj(deployer, |addr: &ScAddress| addr.metered_clone(self))?,
            salt: self.u256_from_bytesobj_input("contract_id_salt", salt)?,
        });
        let id_preimage =
            self.get_full_contract_id_preimage(contract_id_preimage.metered_clone(self)?)?;
        Ok(Hash(self.metered_hash_xdr(&id_preimage)?))
    }
    pub(crate) fn get_asset_contract_id_hash(&self, asset: Asset) -> Result<Hash, HostError> {
        let id_preimage = self.get_full_contract_id_preimage(ContractIdPreimage::Asset(asset))?;
        let id_arr: [u8; 32] = self.metered_hash_xdr(&id_preimage)?;
        Ok(Hash(id_arr))
    }
    pub(crate) fn upload_contract_wasm(&self, wasm: Vec<u8>) -> Result<BytesObject, HostError> {
        let hash_bytes: [u8; 32] = crypto::sha256_hash_from_bytes(wasm.as_slice(), self)?
            .try_into()
            .map_err(|_| {
                self.err(
                    ScErrorType::Value,
                    ScErrorCode::InternalError,
                    "unexpected hash length",
                    &[],
                )
            })?;
        let wasm_bytes_m: crate::xdr::BytesM = wasm.try_into().map_err(|_| {
            self.err(
                ScErrorType::Value,
                ScErrorCode::ExceededLimit,
                "Wasm code is too large",
                &[],
            )
        })?;
        if cfg!(any(test, feature = "testutils")) && wasm_bytes_m.as_slice().is_empty() {
            } else {
            let _check_vm = Vm::new(
                self,
                Hash(hash_bytes.metered_clone(self)?),
                wasm_bytes_m.as_slice(),
            )?;
        }
        let hash_obj = self.add_host_object(self.scbytes_from_slice(hash_bytes.as_slice())?)?;
        let code_key = Rc::metered_new(
            LedgerKey::ContractCode(LedgerKeyContractCode {
                hash: Hash(hash_bytes.metered_clone(self)?),
            }),
            self,
        )?;
        if !self
            .try_borrow_storage_mut()?
            .has(&code_key, self.as_budget())
            .map_err(|e| self.decorate_contract_code_storage_error(e, &Hash(hash_bytes)))?
        {
            self.with_mut_storage(|storage| {
                let data = ContractCodeEntry {
                    hash: Hash(hash_bytes),
                    ext: ExtensionPoint::V0,
                    code: wasm_bytes_m,
                };
                storage.put(
                    &code_key,
                    &Host::new_contract_code(self, data)?,
                    Some(self.get_min_live_until_ledger(ContractDataDurability::Persistent)?),
                    self.as_budget(),
                )
            })?;
        }
        Ok(hash_obj)
    }
}
use super::crypto;
#[cfg(any(test, feature = "testutils"))]
use super::ContractFunctionSet;
#[cfg(any(test, feature = "testutils"))]
impl Host {
    pub fn register_test_contract(
        &self,
        contract_address: AddressObject,
        contract_fns: Rc<dyn ContractFunctionSet>,
    ) -> Result<(), HostError> {
        let contract_id = self.contract_id_from_address(contract_address)?;
        let instance_key = self.contract_instance_ledger_key(&contract_id)?;
        let wasm_hash_obj = self.upload_contract_wasm(vec![])?;
        let wasm_hash = self.hash_from_bytesobj_input("wasm_hash", wasm_hash_obj)?;
        self.store_contract_instance(
            Some(ContractExecutable::Wasm(wasm_hash)),
            None,
            contract_id.clone(),
            &instance_key,
        )?;
        let mut contracts = self.try_borrow_contracts_mut()?;
        contracts.insert(contract_id, contract_fns);
        Ok(())
    }
}