#![cfg_attr(not(feature = "std"), no_std)]

pub mod elections;
pub mod impls;

#[cfg(feature = "try-runtime")]
pub mod elections;

#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;

use frame_support::{
    parameter_types,
    traits::ConstU32,
    weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight},
};
use frame_system::limits;
use primitives::{Balance, BlockNumber};
use sp_runtime::{FixedPointNumber, Perbill, Perquintill};
use static_assertions::const_assert;

pub use pallet_balances::Call as BalancesCall;
#[cfg(feature = "std")]
pub use pallet_staking::StakerStatus;
pub use pallet_timestamp::Call as TimestampCall;
use pallet_transaction_payment::{Multiplier, TargetedFeeAdjustment};
pub use sp_runtime::traits::Bounded;
#[cfg(any(feature = "std", test))]
pub use sp_runtime::BuildStorage;

pub use impls::ToAuthor;

/// We assume that an on-initialize consumes 30% of the weight on average, hence
/// a single extrinsic will not be allowed to consume more than
/// `AvailableBlockRatio - 30%`.
pub const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(30);

/// We allow `Normal` extrinsics to fill up the block up to 60%, the rest can
/// be used by `Operational` extrinsics.
pub const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(60);

/// We allow for 4 seconds of compute with a 12 seconds average block time.
/// The storage proof size is not limited so far.
pub const MAXIMUM_BLOCK_WEIGHT: Weight =
    Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_mul(4), u64::MAX);

const_assert!(NORMAL_DISPATCH_RATIO.deconstruct() >= AVERAGE_ON_INITIALIZE_RATIO.deconstruct());

// Common constants used in all runtimes.
parameter_types! {
    /// Maximum amount of block to be stored in ledger.
    pub const BlockHashCount: BlockNumber = 4096;

    /// The portion of the `NORMAL_DISPATCH_RATIO` that we adjust the fees
    /// with. Blocks filled less than will decrease the weight and more will
    /// increase.
    pub const TargetBlockFullness: Perquintill = Perquintill::from_percent(25);

    /// The adjustment variable of the runtime. Higher values will cause
    /// `TargetBlockFullness` to change the fees more rapidly.
    pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(75, 1_000_000);

    /// Minimum amount of the multiplier. This value cannot be too low. A test
    /// case should ensure that combined with `AdjustmentVariable`, we can
    /// recover from the minimum.
    pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 10u128);

    /// The maximum amount of the multiplier.
    pub MaximumMultiplier: Multiplier = Bounded::max_value();

    /// Maximum length of block. Up to 5MB.
    pub BlockLength: limits::BlockLength =
        limits::BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO);
}

/// Parametrized slow adjusting fee.
pub type SlowAdjustingFeeUpdate<R> = TargetedFeeAdjustment<
    R,
    TargetBlockFullness,
    AdjustmentVariable,
    MinimumMultiplier,
    MaximumMultiplier,
>;

/// Implements the weight types for a runtime.
/// It expects the passed runtime constants to contain a `weights` module.
/// The generated weight types were formerly part of the common
/// runtime but are now runtime dependant.
#[macro_export]
macro_rules! impl_runtime_weights {
    ($runtime:ident) => {
        use frame_support::{dispatch::DispatchClass, weights::Weight};
        use frame_system::limits;
        use pallet_transaction_payment::{Multiplier, TargetedFeeAdjustment};
        pub use runtime_common::{
            impl_elections_weights, AVERAGE_ON_INITIALIZE_RATIO, MAXIMUM_BLOCK_WEIGHT,
            NORMAL_DISPATCH_RATIO,
        };
        use sp_runtime::{FixedPointNumber, Perquintill};

        impl_elections_weights!($runtime);

        // Expose the weight from the runtime constants module.
        pub use $runtime::weights::{
            BlockExecutionWeight, ExtrinsicBaseWeight, ParityDbWeight, RocksDbWeight,
        };

        parameter_types! {
            /// Block weights base values and limits.
            pub BlockWeights: limits::BlockWeights = limits::BlockWeights::builder()
                .base_block($runtime::weights::BlockExecutionWeight::get())
                .for_class(DispatchClass::all(), |weights| {
                    weights.base_extrinsic = $runtime::weights::ExtrinsicBaseWeight::get();
                })
            .for_class(DispatchClass::Normal, |weights| {
                weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT);
            })
            .for_class(DispatchClass::Operational, |weights| {
                weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT);
                // Operational transactions have an extra reserved space, so that they
                // are included even if block reached `MAXIMUM_BLOCK_WEIGHT`.
                weights.reserved = Some(
                    MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT,
                );
            })
            .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO)
            .build_or_panic();
        }
    };
}

/// The type used for currency conversion.
///
/// This must be only be used as long as the balance type is `u128`.
pub type CurrencyToVote = sp_staking::currency_to_vote::U128CurrencyToVote;
static_assertions::assert_eq_size!(primitives::Balance, u128);

/// A reasonable benchmarking config for staking pallet.
pub struct StakingBenchmarkingConfig;
impl pallet_staking::BenchmarkingConfig for StakingBenchmarkingConfig {
    type MaxValidators = ConstU32<500>;
    type MaxNominators = ConstU32<10_000>;
}

/// Convert a balance to an unsigned 256-bit number, use in nomination pools.
pub struct BalanceToU256;
impl sp_runtime::traits::Convert<Balance, sp_core::U256> for BalanceToU256 {
    fn convert(n: Balance) -> sp_core::U256 {
        n.into()
    }
}

/// Convert an unsigned 256-bit number to balance, use in nomination pools.
pub struct U256ToBalance;
impl sp_runtime::traits::Convert<sp_core::U256, Balance> for U256ToBalance {
    fn convert(n: sp_core::U256) -> Balance {
        use frame_support::traits::Defensive;
        n.try_into().defensive_unwrap_or(Balance::MAX)
    }
}

/// Macro to set a value (e.g. when using the `parameter_types` macro) to
/// either a production value or to an environment variable or testing value
/// ( in case the `fast-runtime` feature is selected).
/// Note that the environment varable is evaluated _at compile time_.
#[macro_export]
macro_rules! prod_or_fast {
    ($prod:expr, $test:expr) => {
        if cfg!(feature = "fast-runtime") {
            $test
        } else {
            $prod
        }
    };
    ($prod:expr, $test:expr, $env:expr) => {
        if cfg!(feature = "fast-runtime") {
            core::option_env!($env)
                .map(|s| s.parse().ok())
                .flatten()
                .unwrap_or($test)
        } else {
            $prod
        }
    };
}
