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

use frame_support::{
    pallet_prelude::*,
    storage::PrefixIterator,
    traits::{tokens::fungible::Inspect, EnsureOrigin},
};
use frame_system::pallet_prelude::*;
use scale_info::TypeInfo;

use sp_runtime::{
    curve::PiecewiseLinear,
    traits::{
        AtLeast32BitUnsigned, CheckedAdd, CheckedSub, Member,
        Saturating, UniqueSaturatedInto,
    },
    DispatchResult, Perbill,
};
use sp_std::{convert::TryInto, prelude::*};

use ghost_traits::networks::{
    NetworkDataBasicHandler, NetworkDataInspectHandler, NetworkDataMutateHandler,
};

use ghost_helpers::networks::{
    MAX_ENDPOINT_LEN, MAX_GATEKEEPER_LEN, MAX_SELECTOR_LEN,
    NetworkData, NetworkType, NetworkCurve, NetworkInitiation,
};

mod weights;

pub use crate::weights::WeightInfo;
pub use module::*;

#[cfg(any(feature = "runtime-benchmarks", test))]
mod benchmarking;
#[cfg(all(feature = "std", test))]
mod mock;
#[cfg(all(feature = "std", test))]
mod tests;

pub type BalanceOf<T> =
    <<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;

#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct NetworkImbalanceState<Balance> {
    pub outgoing: Balance,
    pub incoming: Balance,
    pub curve_share: Balance,
}

pub struct BridgedInflationCurve<RewardCurve, T>(core::marker::PhantomData<(RewardCurve, T)>);
impl<Balance, RewardCurve, T> pallet_staking::EraPayout<Balance>
    for BridgedInflationCurve<RewardCurve, T>
where
    Balance: Default
        + Copy
        + From<BalanceOf<T>>
        + AtLeast32BitUnsigned
        + num_traits::ops::wrapping::WrappingAdd
        + num_traits::ops::overflowing::OverflowingAdd
        + sp_std::ops::AddAssign
        + sp_std::ops::Not<Output = Balance>
        + sp_std::ops::Shl<Output = Balance>
        + sp_std::ops::Shr<Output = Balance>
        + sp_std::ops::BitAnd<Balance, Output = Balance>,
    RewardCurve: Get<&'static PiecewiseLinear<'static>>,
    T: Config,
{
    fn era_payout(
        total_staked: Balance,
        total_issuance: Balance,
        _era_duration_in_millis: u64,
    ) -> (Balance, Balance) {
        let reward_curve = RewardCurve::get();
        let state = NetworkImbalance::<T>::take();

        let accumulated_commission: Balance = state.curve_share.into();
        let adjusted_issuance: Balance = total_issuance
            .saturating_add(state.outgoing.into())
            .saturating_sub(state.incoming.into());

        let estimated_reward =
            reward_curve.calculate_for_fraction_times_denominator(
                total_staked,
                adjusted_issuance,
            );

        let payout: Balance = sp_runtime::helpers_128bit::multiply_by_rational_with_rounding(
            estimated_reward.unique_saturated_into(),
            accumulated_commission.unique_saturated_into(),
            adjusted_issuance.unique_saturated_into(),
            sp_runtime::Rounding::NearestPrefUp,
        )
        .map(|result| result.unique_saturated_into())
        .unwrap_or_default();

        let rest_payout = accumulated_commission.saturating_sub(payout);

        (payout, rest_payout)
    }
}

#[frame_support::pallet]
pub mod module {
    use super::*;

    const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);

    #[pallet::config]
    pub trait Config: frame_system::Config {
        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;

        /// The type used for the internal balance storage.
        type Currency: Inspect<Self::AccountId>;

        /// The type used as a unique network id.
        type NetworkId: Parameter
            + Member
            + Parameter
            + AtLeast32BitUnsigned
            + Default
            + Copy
            + Ord
            + TypeInfo
            + MaybeSerializeDeserialize
            + MaxEncodedLen;

        /// The origin required to register new network.
        type RegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;
        /// The origin required to update network information.
        type UpdateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
        /// The origin required to remove network.
        type RemoveOrigin: EnsureOrigin<Self::RuntimeOrigin>;

        #[pallet::constant]
        type MaxNetworks: Get<u32>;

        /// Weight information for extrinsics in this module.
        type WeightInfo: WeightInfo;
    }

    #[pallet::error]
    pub enum Error<T> {
        /// Network already registered.
        NetworkAlreadyRegistered,
        /// Network does not exist.
        NetworkDoesNotExist,
        /// Gatekeeper address length not 42 or prefix `0x` missed.
        WrongGatekeeperAddress,
        /// Topic name length not 66 or prefix `0x` missed.
        WrongTopicName,
        /// Could not store networks into bounded vector.
        TooManyNetworks,
        /// Selector length should always be 4.
        InvalidSelectorLen,
        /// Index is out of possible range.
        IndexOutOfBounds,
        /// No more default endpoints available to store.
        TooManyEndpoints,
        /// Network type does not exist under specified curve.
        NetworkTypeDoesNotExist,
    }

    #[pallet::event]
    #[pallet::generate_deposit(pub(crate) fn deposit_event)]
    pub enum Event<T: Config> {
        NetworkRegistered { chain_id: T::NetworkId },
        NetworkSelectorUpdated { chain_id: T::NetworkId },
        NetworkEndpointUpdated { index: u32,  chain_id: T::NetworkId },
        NetworkEndpointRemoved { index: u32, chain_id: T::NetworkId },
        NetworkEndpointAdded { chain_id: T::NetworkId },
        NetworkFinalityDelayUpdated { chain_id: T::NetworkId, finality_delay: u64 },
        NetworkRateLimitDelayUpdated { chain_id: T::NetworkId, rate_limit_delay: u64 },
        NetworkBlockDeviationUpdated { chain_id: T::NetworkId, block_deviation: u64 },
        NetworkTypeUpdated { chain_id: T::NetworkId, network_type: NetworkType },
        NetworkCurveUpdated { chain_id: T::NetworkId, network_curve: NetworkCurve },
        NetworkGatekeeperUpdated { chain_id: T::NetworkId },
        NetworkIncomingShareUpdated { chain_id: T::NetworkId, incoming_share: u32 },
        NetworkOutgoingShareUpdated { chain_id: T::NetworkId, outgoing_share: u32 },
        NetworkAvgBlockSpeedUpdated { chain_id: T::NetworkId, avg_block_speed: u64 },
        NetworkRemoved { chain_id: T::NetworkId },
    }

    #[pallet::storage]
    #[pallet::getter(fn network_imbalance)]
    pub type NetworkImbalance<T: Config> = StorageValue<
        _,
        NetworkImbalanceState<BalanceOf<T>>,
        ValueQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn network_indexes)]
    pub type NetworkIndexes<T: Config> =
        StorageValue<_, BoundedVec<T::NetworkId, T::MaxNetworks>, ValueQuery>;

    #[pallet::storage]
    #[pallet::getter(fn network_curves)]
    pub type NetworkCurves<T: Config> = StorageMap<
        _,
        Twox64Concat, NetworkCurve,
        BoundedBTreeMap<NetworkType, u32, T::MaxNetworks>,
        ValueQuery,
    >;

     #[pallet::storage]
    #[pallet::getter(fn network_curves_vec)]
    pub type NetworkCurvesVec<T: Config> =
		StorageValue<_, BoundedVec<NetworkCurve, T::MaxNetworks>, ValueQuery>;

    #[pallet::storage]
    #[pallet::getter(fn networks)]
    pub type Networks<T: Config> = StorageMap<
        _,
        Twox64Concat, T::NetworkId,
        NetworkData,
        OptionQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn gatekeeper_amounts)]
    pub type GatekeeperAmounts<T: Config> =
        StorageMap<_, Twox64Concat, T::NetworkId, BalanceOf<T>, ValueQuery>;

    #[pallet::genesis_config]
    pub struct GenesisConfig<T: Config> {
        pub networks: Vec<NetworkInitiation<T::NetworkId, BalanceOf<T>>>,
    }

    impl<T: Config> Default for GenesisConfig<T> {
        fn default() -> Self {
            Self { networks: sp_std::vec![] }
        }
    }

    #[pallet::genesis_build]
    impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
        fn build(&self) {
            self.networks
                .iter()
                .for_each(|network| {
                    Pallet::<T>::do_register_network(network.id, network.data.clone())
                        .expect("Error registering network");
                    GatekeeperAmounts::<T>::insert(network.id, network.amount);
                });

        }
    }

    #[pallet::pallet]
    #[pallet::storage_version(STORAGE_VERSION)]
    #[pallet::without_storage_info]
    pub struct Pallet<T>(PhantomData<T>);

    #[pallet::call]
    impl<T: Config> Pallet<T> {
        #[pallet::call_index(0)]
        #[pallet::weight(T::WeightInfo::register_network(
            network.gatekeeper.len() as u32,
            network.selector.len() as u32,
            network.default_endpoints
                .iter()
                .map(|endpoint| endpoint.len())
                .max()
                .unwrap_or_default() as u32,
        ))]
        pub fn register_network(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            network: NetworkData,
        ) -> DispatchResult {
            T::RegisterOrigin::ensure_origin_or_root(origin)?;
            Self::do_register_network(chain_id, network)
        }

        #[pallet::call_index(1)]
        #[pallet::weight(T::WeightInfo::update_network_selector())]
        pub fn update_network_selector(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            selector: BoundedVec<u8, ConstU32<MAX_SELECTOR_LEN>>,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_nework_selector(chain_id, selector)
        }

        #[pallet::call_index(2)]
        #[pallet::weight(T::WeightInfo::update_network_endpoint(
            maybe_endpoint
                .as_ref()
                .map(|endpoint| endpoint.len())
                .unwrap_or_default() as u32
        ))]
        pub fn update_network_endpoint(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            maybe_index: Option<u32>,
            maybe_endpoint: Option<BoundedVec<u8, ConstU32<MAX_ENDPOINT_LEN>>>,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_network_endpoint(chain_id, maybe_index, maybe_endpoint)
        }

        #[pallet::call_index(3)]
        #[pallet::weight(T::WeightInfo::update_network_finality_delay())]
        pub fn update_network_finality_delay(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            finality_delay: u64,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_network_finality_delay(chain_id, finality_delay)
        }

        #[pallet::call_index(4)]
        #[pallet::weight(T::WeightInfo::update_network_rate_limit_delay())]
        pub fn update_network_rate_limit_delay(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            rate_limit_delay: u64,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_network_rate_limit_delay(chain_id, rate_limit_delay)
        }

        #[pallet::call_index(5)]
        #[pallet::weight(T::WeightInfo::update_network_block_deviation())]
        pub fn update_network_block_deviation(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            block_deviation: u64,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_network_block_deviation(chain_id, block_deviation)
        }

        #[pallet::call_index(6)]
        #[pallet::weight(T::WeightInfo::update_network_type())]
        pub fn update_network_type(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            network_type: NetworkType,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_network_type(chain_id, network_type)
        }

        #[pallet::call_index(7)]
        #[pallet::weight(T::WeightInfo::update_network_curve())]
        pub fn update_network_curve(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            network_curve: NetworkCurve,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_network_curve(chain_id, network_curve)
        }

        #[pallet::call_index(8)]
        #[pallet::weight(T::WeightInfo::update_network_gatekeeper())]
        pub fn update_network_gatekeeper(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            gatekeeper: BoundedVec<u8, ConstU32<MAX_GATEKEEPER_LEN>>,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_network_gatekeeper(chain_id, gatekeeper)
        }

        #[pallet::call_index(9)]
        #[pallet::weight(T::WeightInfo::update_incoming_network_share())]
        pub fn update_incoming_network_share(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            incoming_share: u32,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_incoming_network_share(chain_id, incoming_share)
        }

        #[pallet::call_index(10)]
        #[pallet::weight(T::WeightInfo::update_outgoing_network_share())]
        pub fn update_outgoing_network_share(
            origin: OriginFor<T>,
            chain_id: T::NetworkId,
            outgoing_share: u32,
        ) -> DispatchResult {
            T::UpdateOrigin::ensure_origin_or_root(origin)?;
            Self::do_update_outgoing_network_share(chain_id, outgoing_share)
        }

        #[pallet::call_index(11)]
        #[pallet::weight(T::WeightInfo::remove_network())]
        pub fn remove_network(origin: OriginFor<T>, chain_id: T::NetworkId) -> DispatchResult {
            T::RemoveOrigin::ensure_origin_or_root(origin)?;
            Self::do_remove_network(chain_id)
        }
    }
}

impl<T: Config> Pallet<T> {
    /// Register a new network.
    pub fn do_register_network(
        chain_id: T::NetworkId,
        network: NetworkData,
    ) -> DispatchResult {
        let network_curve = network.curve;
        let network_type = network.r#type;

        ensure!(
            !Networks::<T>::contains_key(&chain_id),
            Error::<T>::NetworkAlreadyRegistered,
        );

        let mut modified_indexes = NetworkIndexes::<T>::get();
        modified_indexes.try_push(chain_id.clone())
            .map_err(|_| Error::<T>::TooManyNetworks)?;

        let mut modified_curves_map = NetworkCurves::<T>::get(network_curve);
        if let Some(counter) = modified_curves_map.get_mut(&network_type) {
            *counter = counter.saturating_add(1);
        } else {
            modified_curves_map.try_insert(network_type, 1u32)
                .map_err(|_| Error::<T>::TooManyNetworks)?;
        }

        let mut modified_curves_vec = NetworkCurvesVec::<T>::get();
        if !modified_curves_vec.contains(&network_curve) {
            modified_curves_vec.try_push(network_curve)
                .map_err(|_| Error::<T>::TooManyNetworks)?;
        }

        Networks::<T>::insert(&chain_id, network);
        NetworkIndexes::<T>::put(modified_indexes);
        NetworkCurves::<T>::insert(network_curve, modified_curves_map);
        NetworkCurvesVec::<T>::put(modified_curves_vec);

        Self::deposit_event(Event::<T>::NetworkRegistered { chain_id });
        Ok(())
    }

    /// Remove existent network.
    pub fn do_remove_network(chain_id: T::NetworkId) -> DispatchResult {
        let network = Networks::<T>::get(&chain_id)
            .ok_or(Error::<T>::NetworkDoesNotExist)?;

        let network_curve = network.curve;
        let network_type = network.r#type;

        let mut modified_curves_map = NetworkCurves::<T>::get(network_curve);

        let counter = modified_curves_map
            .get_mut(&network_type)
            .ok_or(Error::<T>::NetworkTypeDoesNotExist)?;

        *counter = counter.saturating_sub(1);

        let mut should_remove_curve_from_vec = false;
        if *counter == 0 {
            modified_curves_map.remove(&network_type);
            if modified_curves_map.is_empty() {
                should_remove_curve_from_vec = true;
            }
        }

        let mut modified_indexes = NetworkIndexes::<T>::get();
        modified_indexes.retain(|id| id != &chain_id);

        let mut modified_curves_vec = NetworkCurvesVec::<T>::get();
        if should_remove_curve_from_vec {
            modified_curves_vec.retain(|curve| curve != &network_curve);
        }

        Networks::<T>::remove(&chain_id);
        NetworkIndexes::<T>::put(modified_indexes);
        NetworkCurvesVec::<T>::put(modified_curves_vec);

        if modified_curves_map.is_empty() {
            NetworkCurves::<T>::remove(network_curve);
        } else {
            NetworkCurves::<T>::insert(network_curve, modified_curves_map);
        }

        Self::deposit_event(Event::<T>::NetworkRemoved { chain_id });
        Ok(())
    }

    /// Update existent network name.
    pub fn do_update_nework_selector(
        chain_id: T::NetworkId,
        selector: BoundedVec<u8, ConstU32<MAX_SELECTOR_LEN>>,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(selector.len() == 4, Error::<T>::InvalidSelectorLen);
            let network = maybe_network.as_mut().ok_or(Error::<T>::NetworkDoesNotExist)?;
            network.selector = selector;
            Ok(())
        })?;

        Self::deposit_event(Event::<T>::NetworkSelectorUpdated { chain_id });

        Ok(())
    }

    /// Update existent network default endpoint.
    pub fn do_update_network_endpoint(
        chain_id: T::NetworkId,
        maybe_index: Option<u32>,
        maybe_endpoint: Option<BoundedVec<u8, ConstU32<MAX_ENDPOINT_LEN>>>,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            let network = maybe_network.as_mut().ok_or(Error::<T>::NetworkDoesNotExist)?;
            match (maybe_index, maybe_endpoint) {
                (Some(index), Some(endpoint)) => {
                    let idx = index as usize;
                    ensure!(idx < network.default_endpoints.len(), Error::<T>::IndexOutOfBounds);
                    let _ = core::mem::replace(&mut network.default_endpoints[idx], endpoint);
                    Self::deposit_event(Event::<T>::NetworkEndpointUpdated { chain_id, index });
                }
                (None, Some(endpoint)) => {
                    network.default_endpoints.try_push(endpoint).map_err(|_| Error::<T>::TooManyEndpoints)?;
                    Self::deposit_event(Event::<T>::NetworkEndpointAdded { chain_id });
                }
                (Some(index), None) => {
                    let idx = index as usize;
                    ensure!(idx < network.default_endpoints.len(), Error::<T>::IndexOutOfBounds);
                    network.default_endpoints.remove(idx);
                    Self::deposit_event(Event::<T>::NetworkEndpointRemoved { chain_id, index });
                }
                (None, None) => {}
            }
            Ok(())
        })
    }

    /// Update existent network default finality delay.
    pub fn do_update_network_finality_delay(
        chain_id: T::NetworkId,
        finality_delay: u64,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let net = maybe_network.as_mut().unwrap();
            net.finality_delay = finality_delay;
            *maybe_network = Some(net.clone());
            Ok(())
        })?;
        Self::deposit_event(Event::<T>::NetworkFinalityDelayUpdated {
            chain_id,
            finality_delay,
        });
        Ok(())
    }

    /// Update existent network default rate limit delay.
    pub fn do_update_network_rate_limit_delay(
        chain_id: T::NetworkId,
        rate_limit_delay: u64,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let net = maybe_network.as_mut().unwrap();
            net.rate_limit_delay = rate_limit_delay;
            *maybe_network = Some(net.clone());
            Ok(())
        })?;
        Self::deposit_event(Event::<T>::NetworkRateLimitDelayUpdated {
            chain_id,
            rate_limit_delay,
        });
        Ok(())
    }

    /// Update existent network default block deviation between blocks.
    pub fn do_update_network_block_deviation(
        chain_id: T::NetworkId,
        block_deviation: u64,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            let network = maybe_network.as_mut().ok_or(Error::<T>::NetworkDoesNotExist)?;
            network.block_deviation = block_deviation;
            Ok(())
        })?;

        Self::deposit_event(Event::<T>::NetworkBlockDeviationUpdated {
            chain_id,
            block_deviation,
        });

        Ok(())
    }

    /// Update existent network type.
    pub fn do_update_network_type(
        chain_id: T::NetworkId,
        network_type: NetworkType,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            let network = maybe_network.as_mut().ok_or(Error::<T>::NetworkDoesNotExist)?;
            network.r#type = network_type;
            Ok(())
        })?;
        Self::deposit_event(Event::<T>::NetworkTypeUpdated {
            chain_id,
            network_type,
        });
        Ok(())
    }

    /// Update underlying network curve.
    pub fn do_update_network_curve(
        chain_id: T::NetworkId,
        network_curve: NetworkCurve,
    ) -> DispatchResult {
        let mut network = Networks::<T>::get(&chain_id)
            .ok_or(Error::<T>::NetworkDoesNotExist)?;

        let prev_curve = network.curve;
        let network_type = network.r#type;

        if prev_curve == network_curve {
            return Ok(());
        }

        let mut modified_prev_map = NetworkCurves::<T>::get(prev_curve);
        let counter = modified_prev_map
            .get_mut(&network_type)
            .ok_or(Error::<T>::NetworkTypeDoesNotExist)?;

        *counter = counter.saturating_sub(1);

        let mut should_remove_prev_from_vec = false;
        if *counter == 0 {
            modified_prev_map.remove(&network_type);
            if modified_prev_map.is_empty() {
                should_remove_prev_from_vec = true;
            }
        }

        let mut modified_curr_map = NetworkCurves::<T>::get(network_curve);
        if let Some(counter) = modified_curr_map.get_mut(&network_type) {
            *counter = counter.saturating_add(1);
        } else {
            modified_curr_map.try_insert(network_type, 1u32)
                .map_err(|_| Error::<T>::TooManyNetworks)?;
        }

        let mut modified_curves_vec = NetworkCurvesVec::<T>::get();

        if should_remove_prev_from_vec {
            modified_curves_vec.retain(|curve| curve != &prev_curve);
        }

        if !modified_curves_vec.contains(&network_curve) {
            modified_curves_vec.try_push(network_curve)
                .map_err(|_| Error::<T>::TooManyNetworks)?;
        }

        network.curve = network_curve;

        Networks::<T>::insert(&chain_id, network);
        NetworkCurves::<T>::insert(network_curve, modified_curr_map);
        NetworkCurvesVec::<T>::put(modified_curves_vec);

        if modified_prev_map.is_empty() {
            NetworkCurves::<T>::remove(prev_curve);
        } else {
            NetworkCurves::<T>::insert(prev_curve, modified_prev_map);
        }

        Self::deposit_event(Event::<T>::NetworkCurveUpdated {
            chain_id,
            network_curve,
        });

        Ok(())
    }

    /// Update existent network gatekeeper.
    pub fn do_update_network_gatekeeper(
        chain_id: T::NetworkId,
        gatekeeper: BoundedVec<u8, ConstU32<MAX_GATEKEEPER_LEN>>,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            let network = maybe_network.as_mut().ok_or(Error::<T>::NetworkDoesNotExist)?;
            network.gatekeeper = gatekeeper;
            Ok(())
        })?;
        Self::deposit_event(Event::<T>::NetworkGatekeeperUpdated { chain_id });
        Ok(())
    }

    pub fn do_update_incoming_network_share(
        chain_id: T::NetworkId,
        incoming_share: u32,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let net = maybe_network.as_mut().unwrap();
            net.incoming_share = incoming_share;
            *maybe_network = Some(net.clone());
            Ok(())
        })?;
        Self::deposit_event(Event::<T>::NetworkIncomingShareUpdated {
            chain_id,
            incoming_share,
        });
        Ok(())
    }

    pub fn do_update_outgoing_network_share(
        chain_id: T::NetworkId,
        outgoing_share: u32,
    ) -> DispatchResult {
        Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
            ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
            let net = maybe_network.as_mut().unwrap();
            net.outgoing_share = outgoing_share;
            *maybe_network = Some(net.clone());
            Ok(())
        })?;
        Self::deposit_event(Event::<T>::NetworkOutgoingShareUpdated {
            chain_id,
            outgoing_share,
        });
        Ok(())
    }
}

impl<T: Config> NetworkDataBasicHandler for Pallet<T> {
    type NetworkId = T::NetworkId;
    type NetworkType = NetworkType;
    type NetworkCurve = NetworkCurve;
}

impl<T: Config> NetworkDataInspectHandler<NetworkData> for Pallet<T> {
    fn count() -> u32 {
        NetworkIndexes::<T>::decode_len()
            .map(|len| len as u32)
            .unwrap_or_default()
    }

    fn contains_key(n: &Self::NetworkId) -> bool {
        Networks::<T>::contains_key(n)
    }

    fn curve_exists(curve: &Self::NetworkCurve) -> bool {
        NetworkCurves::<T>::contains_key(curve)
    }

    fn get(n: &Self::NetworkId) -> Option<NetworkData> {
        Networks::<T>::get(n)
    }

    fn network_for_block(block_number: impl Into<usize>) -> Option<(Self::NetworkId, NetworkData)> {
        let network_indexes = NetworkIndexes::<T>::get();
        block_number
            .into()
            .checked_rem(network_indexes.len())
            .map(|id| {
                network_indexes.get(id).copied().and_then(|network_id| {
                    Self::get(&network_id).map(|network_data| (network_id, network_data))
                })
            })
            .flatten()
    }

    fn iter() -> PrefixIterator<(Self::NetworkId, NetworkData)> {
        Networks::<T>::iter()
    }

    fn iter_indexes() -> impl Iterator<Item = Self::NetworkId> {
        NetworkIndexes::<T>::get().into_iter()
    }

    fn iter_curves() -> impl Iterator<Item = Self::NetworkCurve> {
        NetworkCurvesVec::<T>::get().into_iter()
    }

    fn iter_types_by_curve(
        curve: &Self::NetworkCurve
    ) -> impl Iterator<Item = Self::NetworkType> {
        NetworkCurves::<T>::get(curve)
            .into_iter()
            .map(|(network_type, _)| network_type)
    }
}

impl<T: Config> NetworkDataMutateHandler<NetworkData, BalanceOf<T>> for Pallet<T> {
    fn register(chain_id: Self::NetworkId, network: NetworkData) -> DispatchResult {
        Self::do_register_network(chain_id, network)
    }

    fn remove(chain_id: Self::NetworkId) -> DispatchResult {
        Self::do_remove_network(chain_id)
    }

    fn register_incoming(
        network_id: &T::NetworkId,
        amount: BalanceOf<T>,
    ) -> Result<BalanceOf<T>, ()> {
        let incoming_share = Networks::<T>::get(&network_id)
            .map(|network| network.incoming_share)
            .ok_or(())?;

        GatekeeperAmounts::<T>::try_mutate(network_id, |gatekeeper_amount| -> Result<(), ()> {
            *gatekeeper_amount = gatekeeper_amount.checked_add(&amount).ok_or(())?;
            Ok(())
        })?;

        let curve_share = Perbill::from_parts(incoming_share).mul_ceil(amount);
        let final_amount = amount.saturating_sub(curve_share);

        NetworkImbalance::<T>::try_mutate(|imbalance_state| -> Result<(), ()> {
            imbalance_state.incoming = imbalance_state.incoming
                .checked_add(&final_amount)
                .ok_or(())?;
            imbalance_state.curve_share = imbalance_state.curve_share
                .checked_add(&curve_share)
                .ok_or(())?;
            Ok(())
        })?;

        Ok(final_amount)
    }

    fn register_outgoing(
        network_id: &T::NetworkId,
        amount: BalanceOf<T>
    ) -> Result<BalanceOf<T>, ()> {
        let outgoing_share = Networks::<T>::get(&network_id)
            .map(|network| network.outgoing_share)
            .ok_or(())?;

        GatekeeperAmounts::<T>::try_mutate(network_id, |gatekeeper_amount| -> Result<(), ()> {
            *gatekeeper_amount = gatekeeper_amount.checked_sub(&amount).ok_or(())?;
            Ok(())
        })?;

        let curve_share = Perbill::from_parts(outgoing_share).mul_ceil(amount);
        let final_amount = amount.saturating_sub(curve_share);

        NetworkImbalance::<T>::try_mutate(|imbalance_state| -> Result<(), ()> {
            imbalance_state.outgoing = imbalance_state.outgoing
                .checked_add(&final_amount)
                .ok_or(())?;
            imbalance_state.curve_share = imbalance_state.curve_share
                .checked_add(&curve_share)
                .ok_or(())?;
            Ok(())
        })?;

        Ok(final_amount)
    }
}
