//! Custom origins for general governance interventions.

use super::ranks;
pub use pallet_cult_origins::*;

#[frame_support::pallet]
pub mod pallet_cult_origins {
    use super::ranks;
    use crate::{Balance, CSPR};
    use frame_support::pallet_prelude::*;
    use pallet_ranked_collective::Rank;

    #[pallet::config]
    pub trait Config: frame_system::Config {}

    #[pallet::pallet]
    pub struct Pallet<T>(_);

    #[derive(PartialEq, Eq, Clone, MaxEncodedLen, Encode, Decode, TypeInfo, RuntimeDebug)]
    #[pallet::origin]
    pub enum Origin {
        Geniuses,
        Degens,
        Zombies,
        Skeletons,
        Ghosts,

        RetainAt1Level,
        RetainAt2Level,
        RetainAt3Level,
        RetainAt4Level,
        RetainAt5Level,

        PromoteTo1Level,
        PromoteTo2Level,
        PromoteTo3Level,
        PromoteTo4Level,
        PromoteTo5Level,
    }

    impl Origin {
        pub fn as_voice(&self) -> Option<pallet_ranked_collective::Rank> {
            Some(match &self {
                Origin::Geniuses => ranks::LEVEL_1,
                Origin::Degens => ranks::LEVEL_2,
                Origin::Zombies => ranks::LEVEL_3,
                Origin::Skeletons => ranks::LEVEL_4,
                Origin::Ghosts => ranks::LEVEL_5,
                _ => return None,
            })
        }
    }

    pub struct ToVoice;
    impl<'a, O: 'a + TryInto<&'a Origin>> sp_runtime::traits::TryMorph<O> for ToVoice {
        type Outcome = pallet_ranked_collective::Rank;
        fn try_morph(o: O) -> Result<pallet_ranked_collective::Rank, ()> {
            o.try_into().ok().and_then(Origin::as_voice).ok_or(())
        }
    }

    macro_rules! decl_unit_ensures {
        ( $name:ident: $success_type:ty = $success:expr ) => {
            pub struct $name;
            impl<O: Into<Result<Origin, O>> + From<Origin>> EnsureOrigin<O> for $name {
                type Success = $success_type;
                fn try_origin(o: O) -> Result<Self::Success, O> {
                    o.into().and_then(|o| match o {
                        Origin::$name => Ok($success),
                        r => Err(O::from(r)),
                    })
                }
                #[cfg(feature = "runtime-benchmarks")]
                fn try_successful_origin() -> Result<O, ()> {
                    Ok(O::from(Origin::$name))
                }
            }
        };
        ( $name:ident ) => { decl_unit_ensures! { $name : () = () } };
        ( $name:ident: $success_type:ty = $success:expr, $( $rest:tt )* ) => {
            decl_unit_ensures! { $name: $success_type = $success }
            decl_unit_ensures! { $( $rest )* }
        };
        ( $name:ident, $( $rest:tt )* ) => {
            decl_unit_ensures! { $name }
            decl_unit_ensures! { $( $rest )* }
        };
        () => {}
    }
    decl_unit_ensures!(
        Geniuses: Rank = ranks::LEVEL_1,
        Degens: Rank = ranks::LEVEL_2,
        Zombies: Rank = ranks::LEVEL_3,
        Skeletons: Rank = ranks::LEVEL_4,
        Ghosts: Rank = ranks::LEVEL_5,
    );

    macro_rules! decl_ensure {
        (
            $vis:vis type $name:ident: EnsureOrigin<Success = $success_type:ty> {
                $( $item:ident = $success:expr, )*
            }
        ) => {
            $vis struct $name;
            impl<O: Into<Result<Origin, O>> + From<Origin>> EnsureOrigin<O> for $name {
                type Success = $success_type;
                fn try_origin(o: O) -> Result<Self::Success, O> {
                    o.into().and_then(|o| match o {
                        $(
                            Origin::$item => Ok($success),
                        )*
                            r => Err(O::from(r)),
                    })
                }
                #[cfg(feature = "runtime-benchmarks")]
                fn try_successful_origin() -> Result<O, ()> {
                    // By convention the more privileged origins go later,
                    // so for greatest chance of success, we want the last one.
                    let _result: Result<O, ()> = Err(());
                    $(
                        let _result: Result<O, ()> = Ok(O::from(Origin::$item));
                    )*
                    _result
                }
            }
        }
    }

    decl_ensure! {
        pub type EnsureCult: EnsureOrigin<Success = Rank> {
            Geniuses = ranks::LEVEL_1,
            Degens = ranks::LEVEL_2,
            Zombies = ranks::LEVEL_3,
            Skeletons = ranks::LEVEL_4,
            Ghosts = ranks::LEVEL_5,
        }
    }

    decl_ensure! {
        pub type EnsureCanRetainAt: EnsureOrigin<Success = Rank> {
            RetainAt1Level = ranks::LEVEL_1,
            RetainAt2Level = ranks::LEVEL_2,
            RetainAt3Level = ranks::LEVEL_3,
            RetainAt4Level = ranks::LEVEL_4,
            RetainAt5Level = ranks::LEVEL_5,
        }
    }

    decl_ensure! {
        pub type EnsureCanPromoteTo: EnsureOrigin<Success = Rank> {
            PromoteTo1Level = ranks::LEVEL_1,
            PromoteTo2Level = ranks::LEVEL_2,
            PromoteTo3Level = ranks::LEVEL_3,
            PromoteTo4Level = ranks::LEVEL_4,
            PromoteTo5Level = ranks::LEVEL_5,
        }
    }

    decl_ensure! {
        pub type CultTreasurySpender: EnsureOrigin<Success = Balance> {
            Geniuses = 50 * CSPR,
            Degens = 100 * CSPR,
            Zombies = 500 * CSPR,
            Skeletons = 1_000 * CSPR,
            Ghosts = 10_000 * CSPR,
        }
    }
}
