// Clippy linter warning. TODO: remove after the Constaninople release
#![allow(clippy::type_complexity)]
// disable it because of possible frontend API break

// Clippy linter warning. TODO: refactor "this function has too many argument"
#![allow(clippy::too_many_arguments)] // disable it because of possible API break

// Ensure we're `no_std` when compiling for Wasm.
#![cfg_attr(not(feature = "std"), no_std)]

#[cfg(test)]
mod tests;

#[cfg(test)]
mod mock;

pub mod genesis;

#[cfg(feature = "std")]
use serde::{Deserialize, Serialize};

use codec::{Decode, Encode}; // Codec
                             //use rstd::collections::btree_map::BTreeMap;
use membership::{members, role_types};
use rstd::borrow::ToOwned;
use rstd::collections::btree_map::BTreeMap;
use rstd::collections::btree_set::BTreeSet;
use rstd::convert::From;
use rstd::prelude::*;
use sr_primitives::traits::{One, Zero}; // Member, SimpleArithmetic, MaybeSerialize
use srml_support::traits::{Currency, ExistenceRequirement, WithdrawReasons};
use srml_support::{
    decl_event,
    decl_module,
    decl_storage,
    dispatch, // , StorageMap, , Parameter
    ensure,
};
use system::{self, ensure_root, ensure_signed};

/// DIRTY IMPORT BECAUSE
/// InputValidationLengthConstraint has not been factored out yet!!!
use forum::InputValidationLengthConstraint;

/// Module configuration trait for this Substrate module.
pub trait Trait:
    system::Trait
    + minting::Trait
    + recurringrewards::Trait
    + stake::Trait
    + hiring::Trait
    + versioned_store_permissions::Trait
    + members::Trait
{
    // + Sized

    /// The event type.
    type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
}

/// Type constraint for identifer used for actors in members module in this runtime.
pub type ActorIdInMembersModule<T> = <T as members::Trait>::ActorId;

/// Type for identifier for channels.
/// The ChannelId must be capable of behaving like an actor id for membership module,
/// since publishers are identified by their channel id.
pub type ChannelId<T> = ActorIdInMembersModule<T>;

/// Type identifier for lead role, which must be same as membership actor identifeir
pub type LeadId<T> = ActorIdInMembersModule<T>;

/// Type identifier for curator role, which must be same as membership actor identifeir
pub type CuratorId<T> = ActorIdInMembersModule<T>;

/// Type for the identifer for an opening for a curator.
pub type CuratorOpeningId<T> = <T as hiring::Trait>::OpeningId;

/// Tyoe for the indentifier for an application as a curator.
pub type CuratorApplicationId<T> = <T as hiring::Trait>::ApplicationId;

/// Balance type of runtime
pub type BalanceOf<T> =
    <<T as stake::Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance;

/// Balance type of runtime
pub type CurrencyOf<T> = <T as stake::Trait>::Currency;

/// Negative imbalance of runtime.
pub type NegativeImbalance<T> =
    <<T as stake::Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::NegativeImbalance;

/// Type of mintin reward relationship identifiers
pub type RewardRelationshipId<T> = <T as recurringrewards::Trait>::RewardRelationshipId;

/// Stake identifier in staking module
pub type StakeId<T> = <T as stake::Trait>::StakeId;

/// Type of permissions module prinicipal identifiers
pub type PrincipalId<T> = <T as versioned_store_permissions::Trait>::Credential;

pub type CuratorApplicationIdToCuratorIdMap<T> = BTreeMap<CuratorApplicationId<T>, CuratorId<T>>;

// Workaround for BTreeSet type
pub type CuratorApplicationIdSet<T> = BTreeSet<CuratorApplicationId<T>>;

/*
 * MOVE ALL OF THESE OUT TO COMMON LATER
 */

pub static MSG_CHANNEL_HANDLE_TOO_SHORT: &str = "Channel handle too short.";
pub static MSG_CHANNEL_HANDLE_TOO_LONG: &str = "Channel handle too long.";
pub static MSG_CHANNEL_DESCRIPTION_TOO_SHORT: &str = "Channel description too short";
pub static MSG_CHANNEL_DESCRIPTION_TOO_LONG: &str = "Channel description too long";
pub static MSG_CHANNEL_ID_INVALID: &str = "Channel id invalid";
pub static MSG_CHANNEL_CREATION_DISABLED: &str = "Channel creation currently disabled";
static MSG_CHANNEL_HANDLE_ALREADY_TAKEN: &str = "Channel handle is already taken";
static MSG_CHANNEL_TITLE_TOO_SHORT: &str = "Channel title too short";
static MSG_CHANNEL_TITLE_TOO_LONG: &str = "Channel title too long";
static MSG_CHANNEL_AVATAR_TOO_SHORT: &str = "Channel avatar URL too short";
static MSG_CHANNEL_AVATAR_TOO_LONG: &str = "Channel avatar URL too long";
static MSG_CHANNEL_BANNER_TOO_SHORT: &str = "Channel banner URL too short";
static MSG_CHANNEL_BANNER_TOO_LONG: &str = "Channel banner URL too long";

//static MSG_MEMBER_CANNOT_BECOME_PUBLISHER: &str =
//    "Member cannot become a publisher";
static MSG_ORIGIN_DOES_NOT_MATCH_CHANNEL_ROLE_ACCOUNT: &str =
    "Origin does not match channel role account";
pub static MSG_CURRENT_LEAD_ALREADY_SET: &str = "Current lead is already set";
pub static MSG_CURRENT_LEAD_NOT_SET: &str = "Current lead is not set";
pub static MSG_ORIGIN_IS_NOT_LEAD: &str = "Origin is not lead";
pub static MSG_ORIGIN_IS_NOT_APPLICANT: &str = "Origin is not applicant";
pub static MSG_CURATOR_OPENING_DOES_NOT_EXIST: &str = "Curator opening does not exist";
pub static MSG_CURATOR_APPLICATION_DOES_NOT_EXIST: &str = "Curator application does not exist";
pub static MSG_INSUFFICIENT_BALANCE_TO_APPLY: &str = "Insufficient balance to apply";
pub static MSG_SUCCESSFUL_CURATOR_APPLICATION_DOES_NOT_EXIST: &str =
    "Successful curatora pplication does not exist";
pub static MSG_MEMBER_NO_LONGER_REGISTRABLE_AS_CURATOR: &str =
    "Member no longer registrable as curator";
pub static MSG_CURATOR_DOES_NOT_EXIST: &str = "Curator does not exist";
pub static MSG_CURATOR_IS_NOT_ACTIVE: &str = "Curator is not active";
pub static MSG_CURATOR_EXIT_RATIONALE_TEXT_TOO_LONG: &str =
    "Curator exit rationale text is too long";
pub static MSG_CURATOR_EXIT_RATIONALE_TEXT_TOO_SHORT: &str =
    "Curator exit rationale text is too short";
pub static MSG_CURATOR_APPLICATION_TEXT_TOO_LONG: &str = "Curator application text too long";
pub static MSG_CURATOR_APPLICATION_TEXT_TOO_SHORT: &str = "Curator application text too short";
pub static MSG_SIGNER_IS_NOT_CURATOR_ROLE_ACCOUNT: &str = "Signer is not curator role account";
pub static MSG_UNSTAKER_DOES_NOT_EXIST: &str = "Unstaker does not exist";
pub static MSG_CURATOR_HAS_NO_REWARD: &str = "Curator has no recurring reward";
pub static MSG_CURATOR_NOT_CONTROLLED_BY_MEMBER: &str = "Curator not controlled by member";
pub static MSG_INSUFFICIENT_BALANCE_TO_COVER_STAKE: &str = "Insuffieicnt balance to cover stake";

/*
 * The errors below, while in many cases encoding similar outcomes,
 * are scoped to the specific extrinsic for which they are used.
 * The reason for this is that it will later to easier to convert this
 * representation into into the type safe error encoding coming in
 * later versions of Substrate.
 */

// Errors for `accept_curator_applications`
pub static MSG_ACCEPT_CURATOR_APPLICATIONS_OPENING_DOES_NOT_EXIST: &str = "Opening does not exist";
pub static MSG_ACCEPT_CURATOR_APPLICATIONS_OPENING_IS_NOT_WAITING_TO_BEGIN: &str =
    "Opening Is Not in Waiting to begin";

// Errors for `begin_curator_applicant_review`
pub static MSG_BEGIN_CURATOR_APPLICANT_REVIEW_OPENING_DOES_NOT_EXIST: &str =
    "Opening does not exist";
pub static MSG_BEGIN_CURATOR_APPLICANT_REVIEW_OPENING_OPENING_IS_NOT_WAITING_TO_BEGIN: &str =
    "Opening Is Not in Waiting";

// Errors for `fill_curator_opening`
pub static MSG_FULL_CURATOR_OPENING_OPENING_DOES_NOT_EXIST: &str = "OpeningDoesNotExist";
pub static MSG_FULL_CURATOR_OPENING_OPENING_NOT_IN_REVIEW_PERIOD_STAGE: &str =
    "OpeningNotInReviewPeriodStage";
pub static MSG_FULL_CURATOR_OPENING_UNSUCCESSFUL_APPLICATION_STAKE_UNSTAKING_PERIOD_TOO_SHORT:
    &str = "Application stake unstaking period for successful applicants too short";
pub static MSG_FULL_CURATOR_OPENING_SUCCESSFUL_APPLICATION_STAKE_UNSTAKING_PERIOD_TOO_SHORT: &str =
    "Application stake unstaking period for failed applicants too short";
pub static MSG_FULL_CURATOR_OPENING_SUCCESSFUL_ROLE_STAKE_UNSTAKING_PERIOD_TOO_SHORT: &str =
    "Role stake unstaking period for successful applicants too short";
pub static MSG_FULL_CURATOR_OPENING_UNSUCCESSFUL_ROLE_STAKE_UNSTAKING_PERIOD_TOO_SHORT: &str =
    "Role stake unstaking period for failed applicants too short";
pub static MSG_FULL_CURATOR_OPENING_SUCCESSFUL_APPLICATION_STAKE_UNSTAKING_PERIOD_REDUNDANT: &str =
    "Application stake unstaking period for successful applicants redundant";
pub static MSG_FULL_CURATOR_OPENING_UNSUCCESSFUL_APPLICATION_STAKE_UNSTAKING_PERIOD_REDUNDANT:
    &str = "Application stake unstaking period for failed applicants redundant";
pub static MSG_FULL_CURATOR_OPENING_SUCCESSFUL_ROLE_STAKE_UNSTAKING_PERIOD_REDUNDANT: &str =
    "Role stake unstaking period for successful applicants redundant";
pub static MSG_FULL_CURATOR_OPENING_UNSUCCESSFUL_ROLE_STAKE_UNSTAKING_PERIOD_REDUNDANT: &str =
    "Role stake unstaking period for failed applicants redundant";
pub static MSG_FULL_CURATOR_OPENING_APPLICATION_DOES_NOT_EXIST: &str = "ApplicationDoesNotExist";
pub static MSG_FULL_CURATOR_OPENING_APPLICATION_NOT_ACTIVE: &str = "ApplicationNotInActiveStage";
pub static MSG_FILL_CURATOR_OPENING_INVALID_NEXT_PAYMENT_BLOCK: &str =
    "Reward policy has invalid next payment block number";
pub static MSG_FILL_CURATOR_OPENING_MINT_DOES_NOT_EXIST: &str = "Working group mint does not exist";
pub static MSG_FILL_CURATOR_OPENING_APPLICATION_FOR_WRONG_OPENING: &str =
    "Applications not for opening";
// Errors for `withdraw_curator_application`
pub static MSG_WITHDRAW_CURATOR_APPLICATION_APPLICATION_DOES_NOT_EXIST: &str =
    "ApplicationDoesNotExist";
pub static MSG_WITHDRAW_CURATOR_APPLICATION_APPLICATION_NOT_ACTIVE: &str = "ApplicationNotActive";
pub static MSG_WITHDRAW_CURATOR_APPLICATION_OPENING_NOT_ACCEPTING_APPLICATIONS: &str =
    "OpeningNotAcceptingApplications";
pub static MSG_WITHDRAW_CURATOR_APPLICATION_UNSTAKING_PERIOD_TOO_SHORT: &str =
    "UnstakingPeriodTooShort ..."; // <== SHOULD REALLY BE TWO SEPARATE, ONE FOR EACH STAKING PURPOSE
pub static MSG_WITHDRAW_CURATOR_APPLICATION_REDUNDANT_UNSTAKING_PERIOD: &str =
    "RedundantUnstakingPeriodProvided ...";

// Errors for `create_channel`
pub static MSG_CREATE_CHANNEL_IS_NOT_MEMBER: &str = "Is not a member";
pub static MSG_CREATE_CHANNEL_NOT_CONTROLLER_ACCOUNT: &str =
    "Account is not controller account of member";

// Errors for `add_curator_opening`
pub static MSG_ADD_CURATOR_OPENING_ACTIVATES_IN_THE_PAST: &str =
    "Opening does not activate in the future";
pub static MSG_ADD_CURATOR_OPENING_ROLE_STAKE_LESS_THAN_MINIMUM: &str =
    "Role stake amount less than minimum currency balance";
pub static MSG_ADD_CURATOR_OPENING_APPLIICATION_STAKE_LESS_THAN_MINIMUM: &str =
    "Application stake amount less than minimum currency balance";
pub static MSG_ADD_CURATOR_OPENING_OPENING_DOES_NOT_EXIST: &str = "OpeningDoesNotExist";
pub static MSG_ADD_CURATOR_OPENING_STAKE_PROVIDED_WHEN_REDUNDANT: &str =
    "StakeProvidedWhenRedundant ..."; // <== SHOULD REALLY BE TWO SEPARATE, ONE FOR EACH STAKING PURPOSE
pub static MSG_ADD_CURATOR_OPENING_STAKE_MISSING_WHEN_REQUIRED: &str =
    "StakeMissingWhenRequired ..."; // <== SHOULD REALLY BE TWO SEPARATE, ONE FOR EACH STAKING PURPOSE
pub static MSG_ADD_CURATOR_OPENING_STAKE_AMOUNT_TOO_LOW: &str = "StakeAmountTooLow ..."; // <== SHOULD REALLY BE TWO SEPARATE, ONE FOR EACH STAKING PURPOSE
pub static MSG_ADD_CURATOR_OPENING_OPENING_NOT_IN_ACCEPTING_APPLICATION_STAGE: &str =
    "OpeningNotInAcceptingApplicationsStage";
pub static MSG_ADD_CURATOR_OPENING_NEW_APPLICATION_WAS_CROWDED_OUT: &str =
    "NewApplicationWasCrowdedOut";
pub static MSG_ADD_CURATOR_OPENING_ZERO_MAX_APPLICANT_COUNT: &str =
    "Application rationing has zero max active applicants";

// Errors for `apply_on_curator_opening`
pub static MSG_APPLY_ON_CURATOR_OPENING_UNSIGNED_ORIGIN: &str = "Unsigned origin";
pub static MSG_APPLY_ON_CURATOR_OPENING_MEMBER_ID_INVALID: &str = "Member id is invalid";
pub static MSG_APPLY_ON_CURATOR_OPENING_SIGNER_NOT_CONTROLLER_ACCOUNT: &str =
    "Signer does not match controller account";
pub static MSG_ORIGIN_IS_NIETHER_MEMBER_CONTROLLER_OR_ROOT: &str =
    "Origin must be controller or root account of member";
pub static MSG_MEMBER_HAS_ACTIVE_APPLICATION_ON_OPENING: &str =
    "Member already has an active application on the opening";

/// The exit stage of a lead involvement in the working group.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Encode, Decode, Debug, Clone, PartialEq)]
pub struct ExitedLeadRole<BlockNumber> {
    /// When exit was initiated.
    pub initiated_at_block_number: BlockNumber,
}

/// The stage of the involvement of a lead in the working group.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Encode, Decode, Debug, Clone, PartialEq)]
pub enum LeadRoleState<BlockNumber> {
    /// Currently active.
    Active,

    /// No longer active, for some reason
    Exited(ExitedLeadRole<BlockNumber>),
}

/// Must be default constructible because it indirectly is a value in a storage map.
/// ***SHOULD NEVER ACTUALLY GET CALLED, IS REQUIRED TO DUE BAD STORAGE MODEL IN SUBSTRATE***
impl<BlockNumber> Default for LeadRoleState<BlockNumber> {
    fn default() -> Self {
        LeadRoleState::Active
    }
}

/// Working group lead: curator lead
/// For now this role is not staked or inducted through an structured process, like the hiring module,
/// hence information about this is missing. Recurring rewards is included, somewhat arbitrarily!
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
pub struct Lead<AccountId, RewardRelationshipId, BlockNumber> {
    /// Account used to authenticate in this role,
    pub role_account: AccountId,

    /// Whether the role has recurring reward, and if so an identifier for this.
    pub reward_relationship: Option<RewardRelationshipId>,

    /// When was inducted
    /// TODO: Add richer information about circumstances of induction, like referencing a council proposal?
    pub inducted: BlockNumber,

    /// The stage of the involvement of this lead in the working group.
    pub stage: LeadRoleState<BlockNumber>,
}

/// Origin of exit initiation on behalf of a curator.'
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Encode, Decode, Debug, Clone, PartialEq)]
pub enum CuratorExitInitiationOrigin {
    /// Lead is origin.
    Lead,

    /// The curator exiting is the origin.
    Curator,
}

/// The exit stage of a curators involvement in the working group.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Encode, Decode, Debug, Clone, PartialEq)]
pub struct CuratorExitSummary<BlockNumber> {
    /// Origin for exit.
    pub origin: CuratorExitInitiationOrigin,

    /// When exit was initiated.
    pub initiated_at_block_number: BlockNumber,

    /// Explainer for why exit was initited.
    pub rationale_text: Vec<u8>,
}

impl<BlockNumber: Clone> CuratorExitSummary<BlockNumber> {
    pub fn new(
        origin: &CuratorExitInitiationOrigin,
        initiated_at_block_number: &BlockNumber,
        rationale_text: &[u8],
    ) -> Self {
        CuratorExitSummary {
            origin: (*origin).clone(),
            initiated_at_block_number: (*initiated_at_block_number).clone(),
            rationale_text: rationale_text.to_owned(),
        }
    }
}

/// The stage of the involvement of a curator in the working group.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Encode, Decode, Debug, Clone, PartialEq)]
pub enum CuratorRoleStage<BlockNumber> {
    /// Currently active.
    Active,

    /// Currently unstaking
    Unstaking(CuratorExitSummary<BlockNumber>),

    /// No longer active and unstaked
    Exited(CuratorExitSummary<BlockNumber>),
}

/// Must be default constructible because it indirectly is a value in a storage map.
/// ***SHOULD NEVER ACTUALLY GET CALLED, IS REQUIRED TO DUE BAD STORAGE MODEL IN SUBSTRATE***
impl<BlockNumber> Default for CuratorRoleStage<BlockNumber> {
    fn default() -> Self {
        CuratorRoleStage::Active
    }
}

/// The induction of a curator in the working group.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
pub struct CuratorInduction<LeadId, CuratorApplicationId, BlockNumber> {
    /// Lead responsible for inducting curator
    pub lead: LeadId,

    /// Application through which curator was inducted
    pub curator_application_id: CuratorApplicationId,

    /// When induction occurred
    pub at_block: BlockNumber,
}

impl<LeadId: Clone, CuratorApplicationId: Clone, BlockNumber: Clone>
    CuratorInduction<LeadId, CuratorApplicationId, BlockNumber>
{
    pub fn new(
        lead: &LeadId,
        curator_application_id: &CuratorApplicationId,
        at_block: &BlockNumber,
    ) -> Self {
        CuratorInduction {
            lead: (*lead).clone(),
            curator_application_id: (*curator_application_id).clone(),
            at_block: (*at_block).clone(),
        }
    }
}

/// Role stake information for a curator.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
pub struct CuratorRoleStakeProfile<StakeId, BlockNumber> {
    /// Whether participant is staked, and if so, the identifier for this staking in the staking module.
    pub stake_id: StakeId,

    /// Unstaking period when terminated.
    pub termination_unstaking_period: Option<BlockNumber>,

    /// Unstaking period when exiting.
    pub exit_unstaking_period: Option<BlockNumber>,
}

impl<StakeId: Clone, BlockNumber: Clone> CuratorRoleStakeProfile<StakeId, BlockNumber> {
    pub fn new(
        stake_id: &StakeId,
        termination_unstaking_period: &Option<BlockNumber>,
        exit_unstaking_period: &Option<BlockNumber>,
    ) -> Self {
        Self {
            stake_id: (*stake_id).clone(),
            termination_unstaking_period: (*termination_unstaking_period).clone(),
            exit_unstaking_period: (*exit_unstaking_period).clone(),
        }
    }
}

/// Working group participant: curator
/// This role can be staked, have reward and be inducted through the hiring module.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
pub struct Curator<
    AccountId,
    RewardRelationshipId,
    StakeId,
    BlockNumber,
    LeadId,
    CuratorApplicationId,
    PrincipalId,
> {
    /// Account used to authenticate in this role,
    pub role_account: AccountId,

    /// Whether the role has recurring reward, and if so an identifier for this.
    pub reward_relationship: Option<RewardRelationshipId>,

    /// When set, describes role stake of curator.
    pub role_stake_profile: Option<CuratorRoleStakeProfile<StakeId, BlockNumber>>,

    /// The stage of this curator in the working group.
    pub stage: CuratorRoleStage<BlockNumber>,

    /// How the curator was inducted into the working group.
    pub induction: CuratorInduction<LeadId, CuratorApplicationId, BlockNumber>,

    /// Whether this curator can unilaterally alter the curation status of a channel.
    //pub can_update_channel_curation_status: bool,

    /// Permissions module principal id
    pub principal_id: PrincipalId,
}

impl<
        AccountId: Clone,
        RewardRelationshipId: Clone,
        StakeId: Clone,
        BlockNumber: Clone,
        LeadId: Clone,
        ApplicationId: Clone,
        PrincipalId: Clone,
    >
    Curator<
        AccountId,
        RewardRelationshipId,
        StakeId,
        BlockNumber,
        LeadId,
        ApplicationId,
        PrincipalId,
    >
{
    pub fn new(
        role_account: &AccountId,
        reward_relationship: &Option<RewardRelationshipId>,
        role_stake_profile: &Option<CuratorRoleStakeProfile<StakeId, BlockNumber>>,
        stage: &CuratorRoleStage<BlockNumber>,
        induction: &CuratorInduction<LeadId, ApplicationId, BlockNumber>,
        //can_update_channel_curation_status: bool,
        principal_id: &PrincipalId,
    ) -> Self {
        Curator {
            role_account: (*role_account).clone(),
            reward_relationship: (*reward_relationship).clone(),
            role_stake_profile: (*role_stake_profile).clone(),
            stage: (*stage).clone(),
            induction: (*induction).clone(),
            //can_update_channel_curation_status: can_update_channel_curation_status,
            principal_id: (*principal_id).clone(),
        }
    }
}

/// An opening for a curator role.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
pub struct CuratorOpening<OpeningId, BlockNumber, Balance, CuratorApplicationId: core::cmp::Ord> {
    /// Identifer for underlying opening in the hiring module.
    pub opening_id: OpeningId,

    /// Set of identifiers for all curator applications ever added
    pub curator_applications: BTreeSet<CuratorApplicationId>,

    /// Commitment to policies in opening.
    pub policy_commitment: OpeningPolicyCommitment<BlockNumber, Balance>, /*
                                                                           * Add other stuff here in the future?
                                                                           * Like default payment terms, privilidges etc.?
                                                                           * Not obvious that it serves much of a purpose, they are mutable
                                                                           * after all, they need to be.
                                                                           * Revisit.
                                                                           */
}

/// An application for the curator role.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
pub struct CuratorApplication<AccountId, CuratorOpeningId, MemberId, ApplicationId> {
    /// Account used to authenticate in this role,
    pub role_account: AccountId,

    /// Opening on which this application applies
    pub curator_opening_id: CuratorOpeningId,

    /// Member applying
    pub member_id: MemberId,

    /// Underlying application in hiring module
    pub application_id: ApplicationId,
}

impl<AccountId: Clone, CuratorOpeningId: Clone, MemberId: Clone, ApplicationId: Clone>
    CuratorApplication<AccountId, CuratorOpeningId, MemberId, ApplicationId>
{
    pub fn new(
        role_account: &AccountId,
        curator_opening_id: &CuratorOpeningId,
        member_id: &MemberId,
        application_id: &ApplicationId,
    ) -> Self {
        CuratorApplication {
            role_account: (*role_account).clone(),
            curator_opening_id: (*curator_opening_id).clone(),
            member_id: (*member_id).clone(),
            application_id: (*application_id).clone(),
        }
    }
}

/// Type of .... .
#[derive(Encode, Decode, Debug, Clone, PartialEq, Eq)]
pub enum CurationActor<CuratorId> {
    Lead,
    Curator(CuratorId),
}

/*
 * BEGIN: =========================================================
 * Channel stuff
 */

/// Type of channel content.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Encode, Decode, Debug, Clone, PartialEq)]
pub enum ChannelContentType {
    Video,
    Music,
    Ebook,
}

/// Must be default constructible because it indirectly is a value in a storage map.
/// ***SHOULD NEVER ACTUALLY GET CALLED, IS REQUIRED TO DUE BAD STORAGE MODEL IN SUBSTRATE***
impl Default for ChannelContentType {
    fn default() -> Self {
        ChannelContentType::Video
    }
}

/// Status of channel, as set by the owner.
/// Is only meant to affect visibility, mutation of channel and child content
/// is unaffected on runtime.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Encode, Decode, Debug, Clone, PartialEq)]
pub enum ChannelPublicationStatus {
    /// Compliant UIs should render.
    Public,

    /// Compliant UIs should not render it or any child content.
    Unlisted,
}

/// Must be default constructible because it indirectly is a value in a storage map.
/// ***SHOULD NEVER ACTUALLY GET CALLED, IS REQUIRED TO DUE BAD STORAGE MODEL IN SUBSTRATE***
impl Default for ChannelPublicationStatus {
    fn default() -> Self {
        ChannelPublicationStatus::Public
    }
}

/// Status of channel, as set by curators.
/// Is only meant to affect visibility currently, but in the future
/// it will also gate publication of new child content,
/// editing properties, revenue flows, etc.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Encode, Decode, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChannelCurationStatus {
    Normal,
    Censored,
}

/// Must be default constructible because it indirectly is a value in a storage map.
/// ***SHOULD NEVER ACTUALLY GET CALLED, IS REQUIRED TO DUE BAD STORAGE MODEL IN SUBSTRATE***
impl Default for ChannelCurationStatus {
    fn default() -> Self {
        ChannelCurationStatus::Normal
    }
}

pub type OptionalText = Option<Vec<u8>>;

/// A channel for publishing content.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
pub struct Channel<MemberId, AccountId, BlockNumber, PrincipalId> {
    /// Whether channel has been verified, in the normal Web2.0 platform sense of being authenticated.
    pub verified: bool,

    /// Unique channel handle that could be used in channel URL.
    pub handle: Vec<u8>,

    /// Human readable title of channel. Not required to be unique.
    pub title: OptionalText,

    /// Human readable description of channel purpose and scope.
    pub description: OptionalText,

    /// URL of a small avatar (logo) image of this channel.
    pub avatar: OptionalText,

    /// URL of a big background image of this channel.
    pub banner: OptionalText,

    /// The type of channel.
    pub content: ChannelContentType,

    /// Member who owns channel.
    pub owner: MemberId,

    /// Account used to authenticate as owner.
    /// Can be updated through membership role key.
    pub role_account: AccountId,

    /// Publication status of channel.
    pub publication_status: ChannelPublicationStatus,

    /// Curation status of channel.
    pub curation_status: ChannelCurationStatus,

    /// When channel was established.
    pub created: BlockNumber,

    /// Permissions module principal id
    pub principal_id: PrincipalId,
}

/*
 * END: =========================================================
 * Channel stuff
 */

/// Permissions module principal
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Encode, Decode, Debug, Clone, PartialEq)]
pub enum Principal<CuratorId, ChannelId> {
    /// Its sloppy to have this here, less safe,
    /// but its not worth the ffort to solve.
    Lead,

    Curator(CuratorId),

    ChannelOwner(ChannelId),
}

/// Must be default constructible because it indirectly is a value in a storage map.
/// ***SHOULD NEVER ACTUALLY GET CALLED, IS REQUIRED TO DUE BAD STORAGE MODEL IN SUBSTRATE***
impl<CuratorId, ChannelId> Default for Principal<CuratorId, ChannelId> {
    fn default() -> Self {
        Principal::Lead
    }
}

/// Terms for slashings applied to a given role
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Encode, Decode, Debug, Clone, PartialEq, Eq)]
pub struct SlashableTerms {
    /// Maximum number of slashes.
    pub max_count: u16,

    /// Maximum percentage points of remaining stake which may be slashed in a single slash.
    pub max_percent_pts_per_time: u16,
}

/// Terms for what slashing can be applied in some context
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Encode, Decode, Debug, Clone, PartialEq, Eq)]
pub enum SlashingTerms {
    Unslashable,
    Slashable(SlashableTerms),
}

/// Must be default constructible because it indirectly is a value in a storage map.
/// ***SHOULD NEVER ACTUALLY GET CALLED, IS REQUIRED TO DUE BAD STORAGE MODEL IN SUBSTRATE***
impl Default for SlashingTerms {
    fn default() -> Self {
        Self::Unslashable
    }
}

/// A commitment to the set of policy variables relevant to an opening.
/// An applicant can observe this commitment and be secure that the terms
/// of the application process cannot be changed ex-post.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Encode, Decode, Debug, Clone, Default, PartialEq, Eq)]
pub struct OpeningPolicyCommitment<BlockNumber, Balance> {
    /// Rationing to be used
    pub application_rationing_policy: Option<hiring::ApplicationRationingPolicy>,

    /// Maximum length of review period of applications
    pub max_review_period_length: BlockNumber,

    /// Staking policy for application
    pub application_staking_policy: Option<hiring::StakingPolicy<Balance, BlockNumber>>,

    /// Staking policy for role itself
    pub role_staking_policy: Option<hiring::StakingPolicy<Balance, BlockNumber>>,

    // Slashing terms during application
    // pub application_slashing_terms: SlashingTerms,

    // Slashing terms during role, NOT application itself!
    pub role_slashing_terms: SlashingTerms,

    /// When filling an opening: Unstaking period for application stake of successful applicants
    pub fill_opening_successful_applicant_application_stake_unstaking_period: Option<BlockNumber>,

    /// When filling an opening:
    pub fill_opening_failed_applicant_application_stake_unstaking_period: Option<BlockNumber>,

    /// When filling an opening:
    pub fill_opening_failed_applicant_role_stake_unstaking_period: Option<BlockNumber>,

    /// When terminating a curator:
    pub terminate_curator_application_stake_unstaking_period: Option<BlockNumber>,

    /// When terminating a curator:
    pub terminate_curator_role_stake_unstaking_period: Option<BlockNumber>,

    /// When a curator exists: ..
    pub exit_curator_role_application_stake_unstaking_period: Option<BlockNumber>,

    /// When a curator exists: ..
    pub exit_curator_role_stake_unstaking_period: Option<BlockNumber>,
}

/// Represents a possible unstaker in working group.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Encode, Decode, Debug, Eq, PartialEq, Clone, PartialOrd)]
pub enum WorkingGroupUnstaker<LeadId, CuratorId> {
    ///
    Lead(LeadId),

    ///
    Curator(CuratorId),
}

/// Must be default constructible because it indirectly is a value in a storage map.
/// ***SHOULD NEVER ACTUALLY GET CALLED, IS REQUIRED TO DUE BAD STORAGE MODEL IN SUBSTRATE***
impl<LeadId: Default, CuratorId> Default for WorkingGroupUnstaker<LeadId, CuratorId> {
    fn default() -> Self {
        Self::Lead(LeadId::default())
    }
}

/*
/// ...
#[derive(Encode, Decode, Debug, Eq, PartialEq, Clone, PartialOrd, Ord)]
pub struct OpeningHire<CuratorApplicationId, CuratorId, PrincipalId> {
    curator_application_id: CuratorApplicationId,
    curator_id: CuratorId,
    principal_id: PrincipalId
}
*/

/*
pub enum ChannelActor<T: Trait> {

    ///
    WorkingGroupActor(WorkingGroupActor<T>),

    ///
    Owner
}
*/

// ======================================================================== //
// Move section below, this out in its own file later                       //
// ======================================================================== //

/*
struct WrappedBeginAcceptingApplicationsError { // can this be made generic, or does that undermine the whole orhpan rule spirit?
    pub error: hiring::BeginAcceptingApplicationsError
}
*/

pub struct WrappedError<E> {
    // can this be made generic, or does that undermine the whole orhpan rule spirit?
    pub error: E,
}

/// ....
macro_rules! ensure_on_wrapped_error {
    ($call:expr) => {{
        { $call }.map_err(|err| WrappedError { error: err })
    }};
}

// Add macro here to make this
//derive_from_impl(hiring::BeginAcceptingApplicationsError)
//derive_from_impl(hiring::BeginAcceptingApplicationsError)

impl rstd::convert::From<WrappedError<hiring::BeginAcceptingApplicationsError>> for &str {
    fn from(wrapper: WrappedError<hiring::BeginAcceptingApplicationsError>) -> Self {
        match wrapper.error {
            hiring::BeginAcceptingApplicationsError::OpeningDoesNotExist => {
                MSG_ACCEPT_CURATOR_APPLICATIONS_OPENING_DOES_NOT_EXIST
            }
            hiring::BeginAcceptingApplicationsError::OpeningIsNotInWaitingToBeginStage => {
                MSG_ACCEPT_CURATOR_APPLICATIONS_OPENING_IS_NOT_WAITING_TO_BEGIN
            }
        }
    }
}

impl rstd::convert::From<WrappedError<hiring::AddOpeningError>> for &str {
    fn from(wrapper: WrappedError<hiring::AddOpeningError>) -> Self {
        match wrapper.error {
            hiring::AddOpeningError::OpeningMustActivateInTheFuture => {
                MSG_ADD_CURATOR_OPENING_ACTIVATES_IN_THE_PAST
            }
            hiring::AddOpeningError::StakeAmountLessThanMinimumCurrencyBalance(purpose) => {
                match purpose {
                    hiring::StakePurpose::Role => {
                        MSG_ADD_CURATOR_OPENING_ROLE_STAKE_LESS_THAN_MINIMUM
                    }
                    hiring::StakePurpose::Application => {
                        MSG_ADD_CURATOR_OPENING_APPLIICATION_STAKE_LESS_THAN_MINIMUM
                    }
                }
            }
            hiring::AddOpeningError::ApplicationRationingZeroMaxApplicants => {
                MSG_ADD_CURATOR_OPENING_ZERO_MAX_APPLICANT_COUNT
            }
        }
    }
}

impl rstd::convert::From<WrappedError<hiring::BeginReviewError>> for &str {
    fn from(wrapper: WrappedError<hiring::BeginReviewError>) -> Self {
        match wrapper.error {
            hiring::BeginReviewError::OpeningDoesNotExist => {
                MSG_BEGIN_CURATOR_APPLICANT_REVIEW_OPENING_DOES_NOT_EXIST
            }
            hiring::BeginReviewError::OpeningNotInAcceptingApplicationsStage => {
                MSG_BEGIN_CURATOR_APPLICANT_REVIEW_OPENING_OPENING_IS_NOT_WAITING_TO_BEGIN
            }
        }
    }
}

impl<T: hiring::Trait> rstd::convert::From<WrappedError<hiring::FillOpeningError<T>>> for &str {
    fn from(wrapper: WrappedError<hiring::FillOpeningError<T>>) -> Self {
        match wrapper.error {
            hiring::FillOpeningError::<T>::OpeningDoesNotExist => MSG_FULL_CURATOR_OPENING_OPENING_DOES_NOT_EXIST,
            hiring::FillOpeningError::<T>::OpeningNotInReviewPeriodStage => MSG_FULL_CURATOR_OPENING_OPENING_NOT_IN_REVIEW_PERIOD_STAGE,
            hiring::FillOpeningError::<T>::UnstakingPeriodTooShort(
                stake_purpose,
                outcome_in_filled_opening,
            ) => match stake_purpose {
                hiring::StakePurpose::Application => match outcome_in_filled_opening {
                    hiring::ApplicationOutcomeInFilledOpening::Success => MSG_FULL_CURATOR_OPENING_UNSUCCESSFUL_APPLICATION_STAKE_UNSTAKING_PERIOD_TOO_SHORT,
                    hiring::ApplicationOutcomeInFilledOpening::Failure => MSG_FULL_CURATOR_OPENING_SUCCESSFUL_APPLICATION_STAKE_UNSTAKING_PERIOD_TOO_SHORT
                },
                hiring::StakePurpose::Role => match outcome_in_filled_opening {
                    hiring::ApplicationOutcomeInFilledOpening::Success => MSG_FULL_CURATOR_OPENING_SUCCESSFUL_ROLE_STAKE_UNSTAKING_PERIOD_TOO_SHORT,
                    hiring::ApplicationOutcomeInFilledOpening::Failure => MSG_FULL_CURATOR_OPENING_UNSUCCESSFUL_ROLE_STAKE_UNSTAKING_PERIOD_TOO_SHORT
                },
            },
            hiring::FillOpeningError::<T>::RedundantUnstakingPeriodProvided(
                stake_purpose,
                outcome_in_filled_opening,
            ) => match stake_purpose {
                hiring::StakePurpose::Application => match outcome_in_filled_opening {
                    hiring::ApplicationOutcomeInFilledOpening::Success => MSG_FULL_CURATOR_OPENING_SUCCESSFUL_APPLICATION_STAKE_UNSTAKING_PERIOD_REDUNDANT,
                    hiring::ApplicationOutcomeInFilledOpening::Failure => MSG_FULL_CURATOR_OPENING_UNSUCCESSFUL_APPLICATION_STAKE_UNSTAKING_PERIOD_REDUNDANT
                },
                hiring::StakePurpose::Role => match outcome_in_filled_opening {
                    hiring::ApplicationOutcomeInFilledOpening::Success => MSG_FULL_CURATOR_OPENING_SUCCESSFUL_ROLE_STAKE_UNSTAKING_PERIOD_REDUNDANT,
                    hiring::ApplicationOutcomeInFilledOpening::Failure => MSG_FULL_CURATOR_OPENING_UNSUCCESSFUL_ROLE_STAKE_UNSTAKING_PERIOD_REDUNDANT
                },
            },
            hiring::FillOpeningError::<T>::ApplicationDoesNotExist(_application_id) => MSG_FULL_CURATOR_OPENING_APPLICATION_DOES_NOT_EXIST,
            hiring::FillOpeningError::<T>::ApplicationNotInActiveStage(_application_id) => MSG_FULL_CURATOR_OPENING_APPLICATION_NOT_ACTIVE,
            hiring::FillOpeningError::<T>::ApplicationForWrongOpening(_application_id) => MSG_FILL_CURATOR_OPENING_APPLICATION_FOR_WRONG_OPENING,
        }
    }
}

impl rstd::convert::From<WrappedError<hiring::DeactivateApplicationError>> for &str {
    fn from(wrapper: WrappedError<hiring::DeactivateApplicationError>) -> Self {
        match wrapper.error {
            hiring::DeactivateApplicationError::ApplicationDoesNotExist => {
                MSG_WITHDRAW_CURATOR_APPLICATION_APPLICATION_DOES_NOT_EXIST
            }
            hiring::DeactivateApplicationError::ApplicationNotActive => {
                MSG_WITHDRAW_CURATOR_APPLICATION_APPLICATION_NOT_ACTIVE
            }
            hiring::DeactivateApplicationError::OpeningNotAcceptingApplications => {
                MSG_WITHDRAW_CURATOR_APPLICATION_OPENING_NOT_ACCEPTING_APPLICATIONS
            }
            hiring::DeactivateApplicationError::UnstakingPeriodTooShort(_stake_purpose) => {
                MSG_WITHDRAW_CURATOR_APPLICATION_UNSTAKING_PERIOD_TOO_SHORT
            }
            hiring::DeactivateApplicationError::RedundantUnstakingPeriodProvided(
                _stake_purpose,
            ) => MSG_WITHDRAW_CURATOR_APPLICATION_REDUNDANT_UNSTAKING_PERIOD,
        }
    }
}

impl rstd::convert::From<WrappedError<members::ControllerAccountForMemberCheckFailed>> for &str {
    fn from(wrapper: WrappedError<members::ControllerAccountForMemberCheckFailed>) -> Self {
        match wrapper.error {
            members::ControllerAccountForMemberCheckFailed::NotMember => {
                MSG_CREATE_CHANNEL_IS_NOT_MEMBER
            }
            members::ControllerAccountForMemberCheckFailed::NotControllerAccount => {
                MSG_CREATE_CHANNEL_NOT_CONTROLLER_ACCOUNT
            }
        }
    }
}

impl rstd::convert::From<WrappedError<hiring::AddApplicationError>> for &str {
    fn from(wrapper: WrappedError<hiring::AddApplicationError>) -> Self {
        match wrapper.error {
            hiring::AddApplicationError::OpeningDoesNotExist => {
                MSG_ADD_CURATOR_OPENING_OPENING_DOES_NOT_EXIST
            }
            hiring::AddApplicationError::StakeProvidedWhenRedundant(_stake_purpose) => {
                MSG_ADD_CURATOR_OPENING_STAKE_PROVIDED_WHEN_REDUNDANT
            }
            hiring::AddApplicationError::StakeMissingWhenRequired(_stake_purpose) => {
                MSG_ADD_CURATOR_OPENING_STAKE_MISSING_WHEN_REQUIRED
            }
            hiring::AddApplicationError::StakeAmountTooLow(_stake_purpose) => {
                MSG_ADD_CURATOR_OPENING_STAKE_AMOUNT_TOO_LOW
            }
            hiring::AddApplicationError::OpeningNotInAcceptingApplicationsStage => {
                MSG_ADD_CURATOR_OPENING_OPENING_NOT_IN_ACCEPTING_APPLICATION_STAGE
            }
            hiring::AddApplicationError::NewApplicationWasCrowdedOut => {
                MSG_ADD_CURATOR_OPENING_NEW_APPLICATION_WAS_CROWDED_OUT
            }
        }
    }
}

impl rstd::convert::From<WrappedError<members::MemberControllerAccountDidNotSign>> for &str {
    fn from(wrapper: WrappedError<members::MemberControllerAccountDidNotSign>) -> Self {
        match wrapper.error {
            members::MemberControllerAccountDidNotSign::UnsignedOrigin => {
                MSG_APPLY_ON_CURATOR_OPENING_UNSIGNED_ORIGIN
            }
            members::MemberControllerAccountDidNotSign::MemberIdInvalid => {
                MSG_APPLY_ON_CURATOR_OPENING_MEMBER_ID_INVALID
            }
            members::MemberControllerAccountDidNotSign::SignerControllerAccountMismatch => {
                MSG_APPLY_ON_CURATOR_OPENING_SIGNER_NOT_CONTROLLER_ACCOUNT
            }
        }
    }
}

/// The recurring reward if any to be assigned to an actor when filling in the position.
#[derive(Encode, Decode, Clone, Eq, PartialEq, Debug)]
pub struct RewardPolicy<Balance, BlockNumber> {
    amount_per_payout: Balance,
    next_payment_at_block: BlockNumber,
    payout_interval: Option<BlockNumber>,
}

// ======================================================================== //
// Move section above, this out in its own file later                       //
// ======================================================================== //

decl_storage! {
    trait Store for Module<T: Trait> as ContentWorkingGroup {

        /// The mint currently funding the rewards for this module.
        pub Mint get(mint) : <T as minting::Trait>::MintId;

        /// The current lead.
        pub CurrentLeadId get(current_lead_id) : Option<LeadId<T>>;

        /// Maps identifier to corresponding lead.
        pub LeadById get(lead_by_id): linked_map LeadId<T> => Lead<T::AccountId, T::RewardRelationshipId, T::BlockNumber>;

        /// Next identifier for new current lead.
        pub NextLeadId get(next_lead_id): LeadId<T>;

        /// Maps identifeir to curator opening.
        pub CuratorOpeningById get(curator_opening_by_id) config(): linked_map CuratorOpeningId<T> => CuratorOpening<T::OpeningId, T::BlockNumber, BalanceOf<T>, CuratorApplicationId<T>>;

        /// Next identifier valuefor new curator opening.
        pub NextCuratorOpeningId get(next_curator_opening_id) config(): CuratorOpeningId<T>;

        /// Maps identifier to curator application on opening.
        pub CuratorApplicationById get(curator_application_by_id) config(): linked_map CuratorApplicationId<T> => CuratorApplication<T::AccountId, CuratorOpeningId<T>, T::MemberId, T::ApplicationId>;

        /// Next identifier value for new curator application.
        pub NextCuratorApplicationId get(next_curator_application_id) config(): CuratorApplicationId<T>;

        /// Maps identifier to corresponding channel.
        pub ChannelById get(channel_by_id) config(): linked_map ChannelId<T> => Channel<T::MemberId, T::AccountId, T::BlockNumber, PrincipalId<T>>;

        /// Identifier to be used by the next channel introduced.
        pub NextChannelId get(next_channel_id) config(): ChannelId<T>;

        /// Maps (unique) channel handle to the corresponding identifier for the channel.
        /// Mapping is required to allow efficient (O(log N)) on-chain verification that a proposed handle is indeed unique
        /// at the time it is being proposed.
        pub ChannelIdByHandle get(channel_id_by_handle) config(): linked_map Vec<u8> => ChannelId<T>;

        /// Maps identifier to corresponding curator.
        pub CuratorById get(curator_by_id) config(): linked_map CuratorId<T> => Curator<T::AccountId, T::RewardRelationshipId, T::StakeId, T::BlockNumber, LeadId<T>, CuratorApplicationId<T>, PrincipalId<T>>;

        /// Next identifier for new curator.
        pub NextCuratorId get(next_curator_id) config(): CuratorId<T>;

        /// Maps identifier to principal.
        pub PrincipalById get(principal_by_id) config(): linked_map PrincipalId<T> => Principal<CuratorId<T>, ChannelId<T>>;

        /// Next identifier for
        pub NextPrincipalId get(next_principal_id) config(): PrincipalId<T>;

        /// Whether it is currently possible to create a channel via `create_channel` extrinsic.
        pub ChannelCreationEnabled get(channel_creation_enabled) config(): bool;

        /// Recover curator by the role stake which is currently unstaking.
        pub UnstakerByStakeId get(unstaker_by_stake_id) config(): linked_map StakeId<T> => WorkingGroupUnstaker<LeadId<T>, CuratorId<T>>;

        // Limits

        /// Limits the total number of curators which can be active.
        //pub MaxSimultanouslyActiveCurators get(max_simultanously_active_curators) config(): Option<u16>;

        // Limits the total number of openings which are not yet deactivated.
        // pub MaxSimultaneouslyActiveOpenings get(max_simultaneously_active_openings) config(): Option<u16>,

        // Vector length input guards

        pub ChannelHandleConstraint get(channel_handle_constraint) config(): InputValidationLengthConstraint;
        pub ChannelTitleConstraint get(channel_title_constraint) config(): InputValidationLengthConstraint;
        pub ChannelDescriptionConstraint get(channel_description_constraint) config(): InputValidationLengthConstraint;
        pub ChannelAvatarConstraint get(channel_avatar_constraint) config(): InputValidationLengthConstraint;
        pub ChannelBannerConstraint get(channel_banner_constraint) config(): InputValidationLengthConstraint;
        pub OpeningHumanReadableText get(opening_human_readable_text) config(): InputValidationLengthConstraint;
        pub CuratorApplicationHumanReadableText get(curator_application_human_readable_text) config(): InputValidationLengthConstraint;
        pub CuratorExitRationaleText get(curator_exit_rationale_text) config(): InputValidationLengthConstraint;
    }
    add_extra_genesis {
        config(mint_capacity): minting::BalanceOf<T>;
        // config(mint_adjustment): minting::Adjustment<BalanceOf<T>, T::BlockNumber> (add serialize/deserialize derivation for type)
        build(|config: &GenesisConfig<T>| {
            // create mint
            let mint_id = <minting::Module<T>>::add_mint(config.mint_capacity, None).expect("Failed to create a mint for the content working group");
            Mint::<T>::put(mint_id);
        });
    }
}

decl_event! {
    pub enum Event<T> where
        ChannelId = ChannelId<T>,
        LeadId = LeadId<T>,
        CuratorOpeningId = CuratorOpeningId<T>,
        CuratorApplicationId = CuratorApplicationId<T>,
        CuratorId = CuratorId<T>,
        CuratorApplicationIdToCuratorIdMap = CuratorApplicationIdToCuratorIdMap<T>,
        MintBalanceOf = minting::BalanceOf<T>,
        <T as system::Trait>::AccountId,
        <T as minting::Trait>::MintId,
    {
        ChannelCreated(ChannelId),
        ChannelOwnershipTransferred(ChannelId),
        LeadSet(LeadId),
        LeadUnset(LeadId),
        CuratorOpeningAdded(CuratorOpeningId),
        AcceptedCuratorApplications(CuratorOpeningId),
        BeganCuratorApplicationReview(CuratorOpeningId),
        CuratorOpeningFilled(CuratorOpeningId, CuratorApplicationIdToCuratorIdMap), //BTreeSet<CuratorApplicationId>),
        TerminatedCurator(CuratorId),
        AppliedOnCuratorOpening(CuratorOpeningId, CuratorApplicationId),
        CuratorExited(CuratorId),
        CuratorUnstaking(CuratorId),
        CuratorApplicationTerminated(CuratorApplicationId),
        CuratorApplicationWithdrawn(CuratorApplicationId),
        CuratorRoleAccountUpdated(CuratorId, AccountId),
        CuratorRewardAccountUpdated(CuratorId, AccountId),
        ChannelUpdatedByCurationActor(ChannelId),
        ChannelCreationEnabledUpdated(bool),
        MintCapacityIncreased(MintId, MintBalanceOf, MintBalanceOf),
        MintCapacityDecreased(MintId, MintBalanceOf, MintBalanceOf),
    }
}

decl_module! {
    pub struct Module<T: Trait> for enum Call where origin: T::Origin {

        fn deposit_event() = default;

        /*
         * Channel management
         */

        /// Create a new channel.
        pub fn create_channel(
            origin,
            owner: T::MemberId,
            role_account: T::AccountId,
            content: ChannelContentType,
            handle: Vec<u8>,
            title: OptionalText,
            description: OptionalText,
            avatar: OptionalText,
            banner: OptionalText,
            publication_status: ChannelPublicationStatus
        ) {

            // Ensure that it is signed
            let signer_account = ensure_signed(origin)?;

            // Ensure that owner member can authenticate with signer account
            ensure_on_wrapped_error!(
                members::Module::<T>::ensure_is_controller_account_for_member(&owner, &signer_account)
            )?;

            // Ensure it is currently possible to create channels (ChannelCreationEnabled).
            ensure!(
                ChannelCreationEnabled::get(),
                MSG_CHANNEL_CREATION_DISABLED
            );

            // Ensure prospective owner member is currently allowed to become channel owner
            let (member_in_role, next_channel_id) = Self::ensure_can_register_channel_owner_role_on_member(&owner, None)?;

            // Ensure channel handle is acceptable length
            Self::ensure_channel_handle_is_valid(&handle)?;

            // Ensure title is acceptable length
            Self::ensure_channel_title_is_valid(&title)?;

            // Ensure description is acceptable length
            Self::ensure_channel_description_is_valid(&description)?;

            // Ensure avatar URL is acceptable length
            Self::ensure_channel_avatar_is_valid(&avatar)?;

            // Ensure banner URL is acceptable length
            Self::ensure_channel_banner_is_valid(&banner)?;

            //
            // == MUTATION SAFE ==
            //

            // Make and add new principal
            let principal_id = Self::add_new_principal(&Principal::ChannelOwner(next_channel_id));

            // Construct channel
            let new_channel = Channel {
                verified: false,
                handle: handle.clone(),
                title,
                description,
                avatar,
                banner,
                content,
                owner,
                role_account,
                publication_status,
                curation_status: ChannelCurationStatus::Normal,
                created: <system::Module<T>>::block_number(),
                principal_id,
            };

            // Add channel to ChannelById under id
            ChannelById::<T>::insert(next_channel_id, new_channel);

            // Add id to ChannelIdByHandle under handle
            ChannelIdByHandle::<T>::insert(handle, next_channel_id);

            // Increment NextChannelId
            NextChannelId::<T>::mutate(|id| *id += <ChannelId<T> as One>::one());

            // CREDENTIAL STUFF //

            // Dial out to membership module and inform about new role as channe owner.
            let registered_role = <members::Module<T>>::register_role_on_member(owner, &member_in_role).is_ok();

            assert!(registered_role);

            // Trigger event
            Self::deposit_event(RawEvent::ChannelCreated(next_channel_id));

        }

        /// An owner transfers channel ownership to a new owner.
        ///
        /// Notice that working group participants cannot do this.
        /// Notice that censored or unlisted channel may still be transferred.
        /// Notice that transfers are unilateral, so new owner cannot block. This may be problematic: https://github.com/Joystream/substrate-runtime-joystream/issues/95
        pub fn transfer_channel_ownership(origin, channel_id: ChannelId<T>, new_owner: T::MemberId, new_role_account: T::AccountId) {

            // Ensure channel owner has signed
            let channel = Self::ensure_channel_owner_signed(origin, &channel_id)?;

            // Ensure prospective new owner can actually become a channel owner (with a new channel id)
            // We do not pass the existing channel id because it is already owned and the call would
            // return with Err, since the membership system doesn't allow the same ActorInRole to be assigned
            // to more than one member, and we don't use the returned actor_in_role because its not
            // for the channel being transferred.
            Self::ensure_can_register_channel_owner_role_on_member(&new_owner, None)?;

            //
            // == MUTATION SAFE ==
            //

            // Construct new channel with altered properties
            let new_channel = Channel {
                owner: new_owner,
                role_account: new_role_account,
                ..channel
            };

            // Overwrite entry in ChannelById
            ChannelById::<T>::insert(channel_id, new_channel);

            let role = role_types::ActorInRole::new(
                role_types::Role::ChannelOwner,
                channel_id
            );

            // Remove
            let unregistered_role = <members::Module<T>>::unregister_role(
                role
            ).is_ok();

            assert!(unregistered_role);

            // Dial out to membership module and inform about new role as channel owner.
            let registered_role = <members::Module<T>>::register_role_on_member(
                new_owner,
                &role
            ).is_ok();

            assert!(registered_role);

            // Trigger event
            Self::deposit_event(RawEvent::ChannelOwnershipTransferred(channel_id));
        }

        /// Channel owner updates some channel properties
        pub fn update_channel_as_owner(
            origin,
            channel_id: ChannelId<T>,
            new_handle: Option<Vec<u8>>,
            new_title: Option<OptionalText>,
            new_description: Option<OptionalText>,
            new_avatar: Option<OptionalText>,
            new_banner: Option<OptionalText>,
            new_publication_status: Option<ChannelPublicationStatus>
        ) {

            // Ensure channel owner has signed
            Self::ensure_channel_owner_signed(origin, &channel_id)?;

            // If set, ensure handle is acceptable length
            if let Some(ref handle) = new_handle {
                Self::ensure_channel_handle_is_valid(handle)?;
            }

            // If set, ensure title is acceptable length
            if let Some(ref title) = new_title {
                Self::ensure_channel_title_is_valid(title)?;
            }

            // If set, ensure description is acceptable length
            if let Some(ref description) = new_description {
                Self::ensure_channel_description_is_valid(description)?;
            }

            // If set, ensure avatar image URL is acceptable length
            if let Some(ref avatar) = new_avatar {
                Self::ensure_channel_avatar_is_valid(avatar)?;
            }

            // If set, ensure banner image URL is acceptable length
            if let Some(ref banner) = new_banner {
                Self::ensure_channel_banner_is_valid(banner)?;
            }

            //
            // == MUTATION SAFE ==
            //

            Self::update_channel(
                &channel_id,
                None, // verified
                &new_handle,
                &new_title,
                &new_description,
                &new_avatar,
                &new_banner,
                new_publication_status,
                None // curation_status
            );
        }

        /// Update channel as a curation actor
        pub fn update_channel_as_curation_actor(
            origin,
            curation_actor: CurationActor<CuratorId<T>>,
            channel_id: ChannelId<T>,
            new_verified: Option<bool>,
            new_curation_status: Option<ChannelCurationStatus>
        ) {

            // Ensure curation actor signed
            Self::ensure_curation_actor_signed(origin, &curation_actor)?;

            //
            // == MUTATION SAFE ==
            //

            Self::update_channel(
                &channel_id,
                new_verified,
                &None, // handle
                &None, // title
                &None, // description,
                &None, // avatar
                &None, // banner
                None, // publication_status
                new_curation_status
            );
        }

        /// Add an opening for a curator role.
        pub fn add_curator_opening(origin, activate_at: hiring::ActivateOpeningAt<T::BlockNumber>, commitment: OpeningPolicyCommitment<T::BlockNumber, BalanceOf<T>>, human_readable_text: Vec<u8>)  {

            // Ensure lead is set and is origin signer
            Self::ensure_origin_is_set_lead(origin)?;

            // Ensure human radable text is valid
            Self::ensure_opening_human_readable_text_is_valid(&human_readable_text)?;

            // Add opening
            // NB: This call can in principle fail, because the staking policies
            // may not respect the minimum currency requirement.

            let policy_commitment = commitment.clone();

            let opening_id = ensure_on_wrapped_error!(
                hiring::Module::<T>::add_opening(
                    activate_at,
                    commitment.max_review_period_length,
                    commitment.application_rationing_policy,
                    commitment.application_staking_policy,
                    commitment.role_staking_policy,
                    human_readable_text,
                ))?;

            //
            // == MUTATION SAFE ==
            //

            let new_curator_opening_id = NextCuratorOpeningId::<T>::get();

            // Create and add curator opening.
            let new_opening_by_id = CuratorOpening {
                opening_id,
                curator_applications: BTreeSet::new(),
                policy_commitment,
            };

            CuratorOpeningById::<T>::insert(new_curator_opening_id, new_opening_by_id);

            // Update NextCuratorOpeningId
            NextCuratorOpeningId::<T>::mutate(|id| *id += <CuratorOpeningId<T> as One>::one());

            // Trigger event
            Self::deposit_event(RawEvent::CuratorOpeningAdded(new_curator_opening_id));
        }

        /// Begin accepting curator applications to an opening that is active.
        pub fn accept_curator_applications(origin, curator_opening_id: CuratorOpeningId<T>)  {

            // Ensure lead is set and is origin signer
            Self::ensure_origin_is_set_lead(origin)?;

            // Ensure opening exists in this working group
            // NB: Even though call to hiring modul will have implicit check for
            // existence of opening as well, this check is to make sure that the opening is for
            // this working group, not something else.
            let (curator_opening, _opening) = Self::ensure_curator_opening_exists(&curator_opening_id)?;

            // Attempt to begin accepting applicationsa
            // NB: Combined ensure check and mutation in hiring module
            ensure_on_wrapped_error!(
                hiring::Module::<T>::begin_accepting_applications(curator_opening.opening_id)
                )?;

            //
            // == MUTATION SAFE ==
            //

            // Trigger event
            Self::deposit_event(RawEvent::AcceptedCuratorApplications(curator_opening_id));
        }

        /// Begin reviewing, and therefore not accepting new applications.
        pub fn begin_curator_applicant_review(origin, curator_opening_id: CuratorOpeningId<T>) {

            // Ensure lead is set and is origin signer
            let (_lead_id, _lead) = Self::ensure_origin_is_set_lead(origin)?;

            // Ensure opening exists
            // NB: Even though call to hiring modul will have implicit check for
            // existence of opening as well, this check is to make sure that the opening is for
            // this working group, not something else.
            let (curator_opening, _opening) = Self::ensure_curator_opening_exists(&curator_opening_id)?;

            // Attempt to begin review of applications
            // NB: Combined ensure check and mutation in hiring module
            ensure_on_wrapped_error!(
                hiring::Module::<T>::begin_review(curator_opening.opening_id)
                )?;

            //
            // == MUTATION SAFE ==
            //

            // Trigger event
            Self::deposit_event(RawEvent::BeganCuratorApplicationReview(curator_opening_id));
        }

        /// Fill opening for curator
        pub fn fill_curator_opening(
            origin,
            curator_opening_id: CuratorOpeningId<T>,
            successful_curator_application_ids: CuratorApplicationIdSet<T>,
            reward_policy: Option<RewardPolicy<minting::BalanceOf<T>, T::BlockNumber>>
        ) {
            // Ensure lead is set and is origin signer
            let (lead_id, _lead) = Self::ensure_origin_is_set_lead(origin)?;

            // Ensure curator opening exists
            let (curator_opening, _) = Self::ensure_curator_opening_exists(&curator_opening_id)?;

            // Make iterator over successful curator application
            let successful_iter = successful_curator_application_ids
                                    .iter()
                                    // recover curator application from id
                                    .map(|curator_application_id| { Self::ensure_curator_application_exists(curator_application_id)})
                                    // remove Err cases, i.e. non-existing applications
                                    .filter_map(|result| result.ok());

            // Count number of successful curators provided
            let num_provided_successful_curator_application_ids = successful_curator_application_ids.len();

            // Ensure all curator applications exist
            let number_of_successful_applications = successful_iter
                                                    .clone()
                                                    .count();

            ensure!(
                number_of_successful_applications == num_provided_successful_curator_application_ids,
                MSG_SUCCESSFUL_CURATOR_APPLICATION_DOES_NOT_EXIST
            );

            // Attempt to fill opening
            let successful_application_ids = successful_iter
                                            .clone()
                                            .map(|(successful_curator_application, _, _)| successful_curator_application.application_id)
                                            .collect::<BTreeSet<_>>();

            // Ensure all applications are from members that _still_ can step into the given role
            let num_successful_applications_that_can_register_as_curator = successful_iter
                                                                        .clone()
                                                                        .map(|(successful_curator_application, _, _)| successful_curator_application.member_id)
                                                                        .filter_map(|successful_member_id| Self::ensure_can_register_curator_role_on_member(&successful_member_id).ok() )
                                                                        .count();

            ensure!(
                num_successful_applications_that_can_register_as_curator == num_provided_successful_curator_application_ids,
                MSG_MEMBER_NO_LONGER_REGISTRABLE_AS_CURATOR
            );

            // NB: Combined ensure check and mutation in hiring module
            ensure_on_wrapped_error!(
                hiring::Module::<T>::fill_opening(
                    curator_opening.opening_id,
                    successful_application_ids,
                    curator_opening.policy_commitment.fill_opening_successful_applicant_application_stake_unstaking_period,
                    curator_opening.policy_commitment.fill_opening_failed_applicant_application_stake_unstaking_period,
                    curator_opening.policy_commitment.fill_opening_failed_applicant_role_stake_unstaking_period
                )
            )?;

            let create_reward_settings = if let Some(policy) = reward_policy {
                // A reward will need to be created so ensure our configured mint exists
                let mint_id = Self::mint();

                ensure!(<minting::Mints<T>>::exists(mint_id), MSG_FILL_CURATOR_OPENING_MINT_DOES_NOT_EXIST);

                // Make sure valid parameters are selected for next payment at block number
                ensure!(policy.next_payment_at_block > <system::Module<T>>::block_number(), MSG_FILL_CURATOR_OPENING_INVALID_NEXT_PAYMENT_BLOCK);

                // The verified reward settings to use
                Some((mint_id, policy))
            } else {
                None
            };

            //
            // == MUTATION SAFE ==
            //

            let current_block = <system::Module<T>>::block_number();

            // For each successful application
            // - create and hold on to curator
            // - register role with membership module

            let mut curator_application_id_to_curator_id = BTreeMap::new();

            successful_iter
            .clone()
            .for_each(|(successful_curator_application, id, _)| {

                // Create a reward relationship
                let reward_relationship = if let Some((mint_id, checked_policy)) = create_reward_settings.clone() {

                    // Create a new recipient for the new relationship
                    let recipient = <recurringrewards::Module<T>>::add_recipient();

                    // member must exist, since it was checked that it can enter the role
                    let member_profile = <members::Module<T>>::member_profile(successful_curator_application.member_id).unwrap();

                    // rewards are deposited in the member's root account
                    let reward_destination_account = member_profile.root_account;

                    // values have been checked so this should not fail!
                    let relationship_id = <recurringrewards::Module<T>>::add_reward_relationship(
                        mint_id,
                        recipient,
                        reward_destination_account,
                        checked_policy.amount_per_payout,
                        checked_policy.next_payment_at_block,
                        checked_policy.payout_interval,
                    ).expect("Failed to create reward relationship!");

                    Some(relationship_id)
                } else {
                    None
                };

                // Get possible stake for role
                let application = hiring::ApplicationById::<T>::get(successful_curator_application.application_id);

                // Staking profile for curator
                let stake_profile =
                    if let Some(ref stake_id) = application.active_role_staking_id {

                        Some(
                            CuratorRoleStakeProfile::new(
                                stake_id,
                                &curator_opening.policy_commitment.terminate_curator_role_stake_unstaking_period,
                                &curator_opening.policy_commitment.exit_curator_role_stake_unstaking_period
                            )
                        )
                    } else {
                        None
                    };

                // Get curator id
                let new_curator_id = NextCuratorId::<T>::get();

                // Make and add new principal
                let principal_id = Self::add_new_principal(&Principal::Curator(new_curator_id));

                // Construct curator
                let curator = Curator::new(
                    &(successful_curator_application.role_account),
                    &reward_relationship,
                    &stake_profile,
                    &CuratorRoleStage::Active,
                    &CuratorInduction::new(&lead_id, &id, &current_block),
                    //false,
                    &principal_id
                );

                // Store curator
                CuratorById::<T>::insert(new_curator_id, curator);

                // Register role on member
                let registered_role = members::Module::<T>::register_role_on_member(
                    successful_curator_application.member_id,
                    &role_types::ActorInRole::new(role_types::Role::Curator, new_curator_id)
                ).is_ok();

                assert!(registered_role);

                // Update next curator id
                NextCuratorId::<T>::mutate(|id| *id += <CuratorId<T> as One>::one());

                curator_application_id_to_curator_id.insert(id, new_curator_id);
            });

            // Trigger event
            Self::deposit_event(RawEvent::CuratorOpeningFilled(curator_opening_id, curator_application_id_to_curator_id));

        }

        /*
        /// ...
        pub fn update_curator_reward(_origin) {

        }
        */

        /*
        /// ...
        pub fn slash_curator(_origin) {

        }
        */

        pub fn withdraw_curator_application(
            origin,
            curator_application_id: CuratorApplicationId<T>
        ) {
            // Ensuring curator application actually exists
            let (curator_application, _, curator_opening) = Self::ensure_curator_application_exists(&curator_application_id)?;

            // Ensure that it is signed
            let signer_account = ensure_signed(origin)?;

            // Ensure that signer is applicant role account
            ensure!(
                signer_account == curator_application.role_account,
                MSG_ORIGIN_IS_NOT_APPLICANT
            );

            // Attempt to deactivate application
            // NB: Combined ensure check and mutation in hiring module
            ensure_on_wrapped_error!(
                hiring::Module::<T>::deactive_application(
                    curator_application.application_id,
                    curator_opening.policy_commitment.exit_curator_role_application_stake_unstaking_period,
                    curator_opening.policy_commitment.exit_curator_role_stake_unstaking_period
                )
            )?;

            //
            // == MUTATION SAFE ==
            //

            // Trigger event
            Self::deposit_event(RawEvent::CuratorApplicationWithdrawn(curator_application_id));

        }

        /// Lead terminate curator application
        pub fn terminate_curator_application(
            origin,
            curator_application_id: CuratorApplicationId<T>
            ) {

            // Ensure lead is set and is origin signer
            Self::ensure_origin_is_set_lead(origin)?;

            // Ensuring curator application actually exists
            let (curator_application, _, curator_opening) = Self::ensure_curator_application_exists(&curator_application_id)?;

            // Attempt to deactivate application
            // NB: Combined ensure check and mutation in hiring module
            ensure_on_wrapped_error!(
                hiring::Module::<T>::deactive_application(
                    curator_application.application_id,
                    curator_opening.policy_commitment.terminate_curator_application_stake_unstaking_period,
                    curator_opening.policy_commitment.terminate_curator_role_stake_unstaking_period
                )
            )?;

            //
            // == MUTATION SAFE ==
            //

            // Trigger event
            Self::deposit_event(RawEvent::CuratorApplicationTerminated(curator_application_id));
        }

        /// Apply on a curator opening.
        pub fn apply_on_curator_opening(
            origin,
            member_id: T::MemberId,
            curator_opening_id: CuratorOpeningId<T>,
            role_account: T::AccountId,
            opt_role_stake_balance: Option<BalanceOf<T>>,
            opt_application_stake_balance: Option<BalanceOf<T>>,
            human_readable_text: Vec<u8>
        ) {
            // Ensure origin which will server as the source account for staked funds is signed
            let source_account = ensure_signed(origin)?;

            // In absense of a more general key delegation system which allows an account with some funds to
            // grant another account permission to stake from its funds, the origin of this call must have the funds
            // and cannot specify another arbitrary account as the source account.
            // Ensure the source_account is either the controller or root account of member with given id
            ensure!(
                members::Module::<T>::ensure_member_controller_account(&source_account, &member_id).is_ok() ||
                members::Module::<T>::ensure_member_root_account(&source_account, &member_id).is_ok(),
                MSG_ORIGIN_IS_NIETHER_MEMBER_CONTROLLER_OR_ROOT
            );

            // Ensure curator opening exists
            let (curator_opening, _opening) = Self::ensure_curator_opening_exists(&curator_opening_id)?;

            // Ensure new owner can actually become a curator
            let (_member_as_curator, _new_curator_id) = Self::ensure_can_register_curator_role_on_member(&member_id)?;

            // Ensure that there is sufficient balance to cover stake proposed
            Self::ensure_can_make_stake_imbalance(
                vec![&opt_role_stake_balance, &opt_application_stake_balance],
                &source_account)
                .map_err(|_err| MSG_INSUFFICIENT_BALANCE_TO_APPLY)?;

            // Ensure application text is valid
            Self::ensure_curator_application_text_is_valid(&human_readable_text)?;

            // Ensure application can actually be added
            ensure_on_wrapped_error!(
                hiring::Module::<T>::ensure_can_add_application(curator_opening.opening_id, opt_role_stake_balance, opt_application_stake_balance)
            )?;

            // Ensure member does not have an active application to this opening
            Self::ensure_member_has_no_active_application_on_opening(
                curator_opening.curator_applications,
                member_id
            )?;

            //
            // == MUTATION SAFE ==
            //

            // Make imbalances for staking
            let opt_role_stake_imbalance = Self::make_stake_opt_imbalance(&opt_role_stake_balance, &source_account);
            let opt_application_stake_imbalance = Self::make_stake_opt_imbalance(&opt_application_stake_balance, &source_account);

            // Call hiring module to add application
            let add_application_result = hiring::Module::<T>::add_application(
                curator_opening.opening_id,
                opt_role_stake_imbalance,
                opt_application_stake_imbalance,
                human_readable_text
            );

            // Has to hold
            assert!(add_application_result.is_ok());

            let application_id = add_application_result.unwrap().application_id_added;

            // Get id of new curator application
            let new_curator_application_id = NextCuratorApplicationId::<T>::get();

            // Make curator application
            let curator_application = CuratorApplication::new(&role_account, &curator_opening_id, &member_id, &application_id);

            // Store application
            CuratorApplicationById::<T>::insert(new_curator_application_id, curator_application);

            // Update next curator application identifier value
            NextCuratorApplicationId::<T>::mutate(|id| *id += <CuratorApplicationId<T> as One>::one());

            // Add application to set of application in curator opening
            CuratorOpeningById::<T>::mutate(curator_opening_id, |curator_opening| {
                curator_opening.curator_applications.insert(new_curator_application_id);
            });

            // Trigger event
            Self::deposit_event(RawEvent::AppliedOnCuratorOpening(curator_opening_id, new_curator_application_id));
        }

        /// An active curator can update the associated role account.
        pub fn update_curator_role_account(
            origin,
            member_id: T::MemberId,
            curator_id: CuratorId<T>,
            new_role_account: T::AccountId
        ) {
            // Ensure that origin is signed by member with given id.
            ensure_on_wrapped_error!(
                members::Module::<T>::ensure_member_controller_account_signed(origin, &member_id)
            )?;

            // Ensure that member is this curator
            let actor_in_role = role_types::ActorInRole::new(role_types::Role::Curator, curator_id);

            ensure!(
                members::MembershipIdByActorInRole::<T>::get(actor_in_role) == member_id,
                MSG_CURATOR_NOT_CONTROLLED_BY_MEMBER
            );

            //
            // == MUTATION SAFE ==
            //

            // Update role account
            CuratorById::<T>::mutate(curator_id, |curator| {
                curator.role_account = new_role_account.clone()
            });

            // Trigger event
            Self::deposit_event(RawEvent::CuratorRoleAccountUpdated(curator_id, new_role_account));
        }

        /// An active curator can update the reward account associated
        /// with a set reward relationship.
        pub fn update_curator_reward_account(
            origin,
            curator_id: CuratorId<T>,
            new_reward_account: T::AccountId
        ) {

            // Ensure there is a signer which matches role account of curator corresponding to provided id.
            let curator = Self::ensure_active_curator_signed(origin, &curator_id)?;

            // Ensure the curator actually has a recurring reward
            let relationship_id = Self::ensure_curator_has_recurring_reward(&curator)?;

            //
            // == MUTATION SAFE ==
            //

            // Update, only, reward account.
            recurringrewards::Module::<T>::set_reward_relationship(
                relationship_id,
                Some(new_reward_account.clone()), // new_account
                None, // new_payout
                None, //new_next_payment_at
                None //new_payout_interval
            )
            .expect("Must be set, since curator has recurring reward");

            // Trigger event
            Self::deposit_event(RawEvent::CuratorRewardAccountUpdated(curator_id, new_reward_account));
        }

        /// An active curator leaves role
        pub fn leave_curator_role(
            origin,
            curator_id: CuratorId<T>,
            rationale_text: Vec<u8>
        ) {
            // Ensure there is a signer which matches role account of curator corresponding to provided id.
            let active_curator = Self::ensure_active_curator_signed(origin, &curator_id)?;

            //
            // == MUTATION SAFE ==
            //

            Self::deactivate_curator(
                &curator_id,
                &active_curator,
                &CuratorExitInitiationOrigin::Curator,
                &rationale_text
            );
        }

        /// Lead can terminate and active curator
        pub fn terminate_curator_role(
            origin,
            curator_id: CuratorId<T>,
            rationale_text: Vec<u8>
        ) {

            // Ensure lead is set and is origin signer
            Self::ensure_origin_is_set_lead(origin)?;

            // Ensuring curator actually exists and is active
            let curator = Self::ensure_active_curator_exists(&curator_id)?;

            // Ensure rationale text is valid
            Self::ensure_curator_exit_rationale_text_is_valid(&rationale_text)?;

            //
            // == MUTATION SAFE ==
            //

            Self::deactivate_curator(
                &curator_id,
                &curator,
                &CuratorExitInitiationOrigin::Lead,
                &rationale_text
            );
        }

        /// Replace the current lead. First unsets the active lead if there is one.
        /// If a value is provided for new_lead it will then set that new lead.
        /// It is responsibility of the caller to ensure the new lead can be set
        /// to avoid the lead role being vacant at the end of the call.
        pub fn replace_lead(origin, new_lead: Option<(T::MemberId, T::AccountId)>) {
            // Ensure root is origin
            ensure_root(origin)?;

            // Unset current lead first
            if Self::ensure_lead_is_set().is_ok() {
                Self::unset_lead()?;
            }

            // Try to set new lead
            if let Some((member_id, role_account)) = new_lead {
                Self::set_lead(member_id, role_account)?;
            }
        }

        /// Add an opening for a curator role.
        pub fn set_channel_creation_enabled(origin, enabled: bool)  {

            // Ensure lead is set and is origin signer
            Self::ensure_origin_is_set_lead(origin)?;

            //
            // == MUTATION SAFE ==
            //

            // Update storage value
            ChannelCreationEnabled::put(enabled);

            // Trigger event
            Self::deposit_event(RawEvent::ChannelCreationEnabledUpdated(enabled));
        }

        /// Add to capacity of current acive mint.
        /// This may be deprecated in the future, since set_mint_capacity is sufficient to
        /// both increase and decrease capacity. Although when considering that it may be executed
        /// by a proposal, given the temporal delay in approving a proposal, it might be more suitable
        /// than set_mint_capacity?
        pub fn increase_mint_capacity(
            origin,
            additional_capacity: minting::BalanceOf<T>
        ) {
            ensure_root(origin)?;

            let mint_id = Self::mint();
            let mint = <minting::Module<T>>::mints(mint_id); // must exist
            let new_capacity = mint.capacity() + additional_capacity;
            <minting::Module<T>>::set_mint_capacity(mint_id, new_capacity)?;

            Self::deposit_event(RawEvent::MintCapacityIncreased(
                mint_id, additional_capacity, new_capacity
            ));
        }

        /// Sets the capacity of the current active mint
        pub fn set_mint_capacity(
            origin,
            new_capacity: minting::BalanceOf<T>
        ) {
            ensure_root(origin)?;

            let mint_id = Self::mint();

            // Mint must exist - it is set at genesis
            let mint = <minting::Module<T>>::mints(mint_id);

            let current_capacity = mint.capacity();

            if new_capacity != current_capacity {
                // Cannot fail if mint exists
                <minting::Module<T>>::set_mint_capacity(mint_id, new_capacity)?;

                if new_capacity > current_capacity {
                    Self::deposit_event(RawEvent::MintCapacityIncreased(
                        mint_id, new_capacity - current_capacity, new_capacity
                    ));
                } else {
                    Self::deposit_event(RawEvent::MintCapacityDecreased(
                        mint_id, current_capacity - new_capacity, new_capacity
                    ));
                }
            }

        }
    }
}

impl<T: Trait> versioned_store_permissions::CredentialChecker<T> for Module<T> {
    fn account_has_credential(account: &T::AccountId, id: PrincipalId<T>) -> bool {
        // Check that principal exists
        if !PrincipalById::<T>::exists(&id) {
            return false;
        }

        // Get principal
        let principal = PrincipalById::<T>::get(&id);

        // Get possible
        let opt_prinicipal_account = match principal {
            Principal::Lead => {
                // Try to get lead
                match Self::ensure_lead_is_set() {
                    Ok((_, lead)) => Some(lead.role_account),
                    Err(_) => None,
                }
            }

            Principal::Curator(curator_id) => Some(
                Self::ensure_curator_exists(&curator_id)
                    .expect("Curator must exist")
                    .role_account,
            ),

            Principal::ChannelOwner(channel_id) => Some(
                Self::ensure_channel_id_is_valid(&channel_id)
                    .expect("Channel must exist")
                    .role_account,
            ),
        };

        // Compare, possibly set, principal account with the given account
        match opt_prinicipal_account {
            Some(principal_account) => *account == principal_account,
            None => false,
        }
    }
}

impl<T: Trait> Module<T> {
    /// Introduce a lead when one is not currently set.
    fn set_lead(member: T::MemberId, role_account: T::AccountId) -> dispatch::Result {
        // Ensure there is no current lead
        ensure!(
            <CurrentLeadId<T>>::get().is_none(),
            MSG_CURRENT_LEAD_ALREADY_SET
        );

        let new_lead_id = <NextLeadId<T>>::get();

        let new_lead_role =
            role_types::ActorInRole::new(role_types::Role::CuratorLead, new_lead_id);

        //
        // == MUTATION SAFE ==
        //

        // Register in role - will fail if member cannot become lead
        members::Module::<T>::register_role_on_member(member, &new_lead_role)?;

        // Construct lead
        let new_lead = Lead {
            role_account,
            reward_relationship: None,
            inducted: <system::Module<T>>::block_number(),
            stage: LeadRoleState::Active,
        };

        // Store lead
        <LeadById<T>>::insert(new_lead_id, new_lead);

        // Update current lead
        <CurrentLeadId<T>>::put(new_lead_id); // Some(new_lead_id)

        // Update next lead counter
        <NextLeadId<T>>::mutate(|id| *id += <LeadId<T> as One>::one());

        // Trigger event
        Self::deposit_event(RawEvent::LeadSet(new_lead_id));

        Ok(())
    }

    /// Evict the currently set lead
    fn unset_lead() -> dispatch::Result {
        // Ensure there is a lead set
        let (lead_id, lead) = Self::ensure_lead_is_set()?;

        //
        // == MUTATION SAFE ==
        //

        // Unregister from role in membership model
        let current_lead_role = role_types::ActorInRole {
            role: role_types::Role::CuratorLead,
            actor_id: lead_id,
        };

        <members::Module<T>>::unregister_role(current_lead_role)?;

        // Update lead stage as exited
        let current_block = <system::Module<T>>::block_number();

        let new_lead = Lead {
            stage: LeadRoleState::Exited(ExitedLeadRole {
                initiated_at_block_number: current_block,
            }),
            ..lead
        };

        <LeadById<T>>::insert(lead_id, new_lead);

        // Update current lead
        <CurrentLeadId<T>>::take(); // None

        // Trigger event
        Self::deposit_event(RawEvent::LeadUnset(lead_id));

        Ok(())
    }

    fn ensure_member_has_no_active_application_on_opening(
        curator_applications: CuratorApplicationIdSet<T>,
        member_id: T::MemberId,
    ) -> Result<(), &'static str> {
        for curator_application_id in curator_applications {
            let curator_application = CuratorApplicationById::<T>::get(curator_application_id);
            // Look for application by the member for the opening
            if curator_application.member_id != member_id {
                continue;
            }
            // Get application details
            let application = <hiring::ApplicationById<T>>::get(curator_application.application_id);
            // Return error if application is in active stage
            if application.stage == hiring::ApplicationStage::Active {
                return Err(MSG_MEMBER_HAS_ACTIVE_APPLICATION_ON_OPENING);
            }
        }
        // Member does not have any active applications to the opening
        Ok(())
    }

    fn ensure_can_register_role_on_member(
        member_id: &T::MemberId,
        role: role_types::Role,
        actor_id: &ActorIdInMembersModule<T>,
    ) -> Result<members::ActorInRole<ActorIdInMembersModule<T>>, &'static str> {
        let new_actor_in_role = role_types::ActorInRole::new(role, *actor_id);

        <members::Module<T>>::can_register_role_on_member(member_id, &new_actor_in_role)
            .map(|_| new_actor_in_role)
    }

    fn ensure_can_register_curator_role_on_member(
        member_id: &T::MemberId,
    ) -> Result<
        (
            members::ActorInRole<ActorIdInMembersModule<T>>,
            CuratorId<T>,
        ),
        &'static str,
    > {
        let next_id = <NextCuratorId<T>>::get();

        Self::ensure_can_register_role_on_member(member_id, role_types::Role::Curator, &next_id)
            .map(|curator_in_role| (curator_in_role, next_id))
    }

    fn ensure_can_register_channel_owner_role_on_member(
        member_id: &T::MemberId,
        opt_channel_id: Option<ChannelId<T>>,
    ) -> Result<
        (
            members::ActorInRole<ActorIdInMembersModule<T>>,
            ChannelId<T>,
        ),
        &'static str,
    > {
        let next_channel_id = opt_channel_id.unwrap_or_else(NextChannelId::<T>::get);

        Self::ensure_can_register_role_on_member(
            member_id,
            role_types::Role::ChannelOwner,
            &next_channel_id,
        )
        .map(|member_in_role| (member_in_role, next_channel_id))
    }

    // TODO: convert InputConstraint ensurer routines into macroes

    fn ensure_channel_handle_is_valid(handle: &[u8]) -> dispatch::Result {
        ChannelHandleConstraint::get().ensure_valid(
            handle.len(),
            MSG_CHANNEL_HANDLE_TOO_SHORT,
            MSG_CHANNEL_HANDLE_TOO_LONG,
        )?;

        // Has to not already be occupied
        ensure!(
            !ChannelIdByHandle::<T>::exists(handle),
            MSG_CHANNEL_HANDLE_ALREADY_TAKEN
        );

        Ok(())
    }

    fn ensure_channel_title_is_valid(text_opt: &OptionalText) -> dispatch::Result {
        if let Some(text) = text_opt {
            ChannelTitleConstraint::get().ensure_valid(
                text.len(),
                MSG_CHANNEL_TITLE_TOO_SHORT,
                MSG_CHANNEL_TITLE_TOO_LONG,
            )
        } else {
            Ok(())
        }
    }

    fn ensure_channel_description_is_valid(text_opt: &OptionalText) -> dispatch::Result {
        if let Some(text) = text_opt {
            ChannelDescriptionConstraint::get().ensure_valid(
                text.len(),
                MSG_CHANNEL_DESCRIPTION_TOO_SHORT,
                MSG_CHANNEL_DESCRIPTION_TOO_LONG,
            )
        } else {
            Ok(())
        }
    }

    fn ensure_channel_avatar_is_valid(text_opt: &OptionalText) -> dispatch::Result {
        if let Some(text) = text_opt {
            ChannelAvatarConstraint::get().ensure_valid(
                text.len(),
                MSG_CHANNEL_AVATAR_TOO_SHORT,
                MSG_CHANNEL_AVATAR_TOO_LONG,
            )
        } else {
            Ok(())
        }
    }

    fn ensure_channel_banner_is_valid(text_opt: &OptionalText) -> dispatch::Result {
        if let Some(text) = text_opt {
            ChannelBannerConstraint::get().ensure_valid(
                text.len(),
                MSG_CHANNEL_BANNER_TOO_SHORT,
                MSG_CHANNEL_BANNER_TOO_LONG,
            )
        } else {
            Ok(())
        }
    }

    fn ensure_curator_application_text_is_valid(text: &[u8]) -> dispatch::Result {
        CuratorApplicationHumanReadableText::get().ensure_valid(
            text.len(),
            MSG_CURATOR_APPLICATION_TEXT_TOO_SHORT,
            MSG_CURATOR_APPLICATION_TEXT_TOO_LONG,
        )
    }

    fn ensure_curator_exit_rationale_text_is_valid(text: &[u8]) -> dispatch::Result {
        CuratorExitRationaleText::get().ensure_valid(
            text.len(),
            MSG_CURATOR_EXIT_RATIONALE_TEXT_TOO_SHORT,
            MSG_CURATOR_EXIT_RATIONALE_TEXT_TOO_LONG,
        )
    }

    fn ensure_opening_human_readable_text_is_valid(text: &[u8]) -> dispatch::Result {
        OpeningHumanReadableText::get().ensure_valid(
            text.len(),
            MSG_CHANNEL_DESCRIPTION_TOO_SHORT,
            MSG_CHANNEL_DESCRIPTION_TOO_LONG,
        )
    }

    fn ensure_channel_id_is_valid(
        channel_id: &ChannelId<T>,
    ) -> Result<Channel<T::MemberId, T::AccountId, T::BlockNumber, PrincipalId<T>>, &'static str>
    {
        if ChannelById::<T>::exists(channel_id) {
            let channel = ChannelById::<T>::get(channel_id);

            Ok(channel)
        } else {
            Err(MSG_CHANNEL_ID_INVALID)
        }
    }

    pub fn ensure_lead_is_set() -> Result<
        (
            LeadId<T>,
            Lead<T::AccountId, T::RewardRelationshipId, T::BlockNumber>,
        ),
        &'static str,
    > {
        // Ensure lead id is set
        let lead_id = Self::ensure_lead_id_set()?;

        // If so, grab actual lead
        let lead = <LeadById<T>>::get(lead_id);

        // and return both
        Ok((lead_id, lead))
    }

    fn ensure_lead_id_set() -> Result<LeadId<T>, &'static str> {
        let opt_current_lead_id = <CurrentLeadId<T>>::get();

        if let Some(lead_id) = opt_current_lead_id {
            Ok(lead_id)
        } else {
            Err(MSG_CURRENT_LEAD_NOT_SET)
        }
    }

    fn ensure_origin_is_set_lead(
        origin: T::Origin,
    ) -> Result<
        (
            LeadId<T>,
            Lead<T::AccountId, T::RewardRelationshipId, T::BlockNumber>,
        ),
        &'static str,
    > {
        // Ensure lead is actually set
        let (lead_id, lead) = Self::ensure_lead_is_set()?;

        // Ensure is signed
        let signer = ensure_signed(origin)?;

        // Ensure signer is lead
        ensure!(signer == lead.role_account, MSG_ORIGIN_IS_NOT_LEAD);

        Ok((lead_id, lead))
    }
    /*
        fn ensure_activate_opening_at_valid(activate_at: &hiring::ActivateOpeningAt<T::BlockNumber>) -> Result<T::BlockNumber, &'static str>{

            let current_block = <system::Module<T>>::block_number();

            let starting_block =
                match activate_at {
                    hiring::ActivateOpeningAt::CurrentBlock => current_block,
                    hiring::ActivateOpeningAt::ExactBlock(block_number) => block_number.clone()
            };

            ensure!(
                starting_block >= current_block,
                MSG_OPENING_CANNOT_ACTIVATE_IN_THE_PAST
            );

            Ok(starting_block)
        }
    */
    fn ensure_curator_opening_exists(
        curator_opening_id: &CuratorOpeningId<T>,
    ) -> Result<
        (
            CuratorOpening<T::OpeningId, T::BlockNumber, BalanceOf<T>, CuratorApplicationId<T>>,
            hiring::Opening<BalanceOf<T>, T::BlockNumber, <T as hiring::Trait>::ApplicationId>,
        ),
        &'static str,
    > {
        ensure!(
            CuratorOpeningById::<T>::exists(curator_opening_id),
            MSG_CURATOR_OPENING_DOES_NOT_EXIST
        );

        let curator_opening = CuratorOpeningById::<T>::get(curator_opening_id);

        let opening = hiring::OpeningById::<T>::get(curator_opening.opening_id);

        Ok((curator_opening, opening))
    }

    fn ensure_curator_exists(
        curator_id: &CuratorId<T>,
    ) -> Result<
        Curator<
            T::AccountId,
            T::RewardRelationshipId,
            T::StakeId,
            T::BlockNumber,
            LeadId<T>,
            T::ApplicationId,
            PrincipalId<T>,
        >,
        &'static str,
    > {
        ensure!(
            CuratorById::<T>::exists(curator_id),
            MSG_CURATOR_DOES_NOT_EXIST
        );

        let curator = CuratorById::<T>::get(curator_id);

        Ok(curator)
    }

    fn ensure_unstaker_exists(
        stake_id: &StakeId<T>,
    ) -> Result<WorkingGroupUnstaker<LeadId<T>, CuratorId<T>>, &'static str> {
        ensure!(
            UnstakerByStakeId::<T>::exists(stake_id),
            MSG_UNSTAKER_DOES_NOT_EXIST
        );

        let unstaker = UnstakerByStakeId::<T>::get(stake_id);

        Ok(unstaker)
    }

    fn ensure_active_curator_exists(
        curator_id: &CuratorId<T>,
    ) -> Result<
        Curator<
            T::AccountId,
            T::RewardRelationshipId,
            T::StakeId,
            T::BlockNumber,
            LeadId<T>,
            T::ApplicationId,
            PrincipalId<T>,
        >,
        &'static str,
    > {
        // Ensuring curator actually exists
        let curator = Self::ensure_curator_exists(curator_id)?;

        // Ensure curator is still active
        ensure!(
            match curator.stage {
                CuratorRoleStage::Active => true,
                _ => false,
            },
            MSG_CURATOR_IS_NOT_ACTIVE
        );

        Ok(curator)
    }

    fn ensure_active_curator_signed(
        origin: T::Origin,
        curator_id: &CuratorId<T>,
    ) -> Result<
        Curator<
            T::AccountId,
            T::RewardRelationshipId,
            T::StakeId,
            T::BlockNumber,
            LeadId<T>,
            T::ApplicationId,
            PrincipalId<T>,
        >,
        &'static str,
    > {
        // Ensure that it is signed
        let signer_account = ensure_signed(origin)?;

        // Ensure that id corresponds to active curator
        let curator = Self::ensure_active_curator_exists(&curator_id)?;

        // Ensure that signer is actually role account of curator
        ensure!(
            signer_account == curator.role_account,
            MSG_SIGNER_IS_NOT_CURATOR_ROLE_ACCOUNT
        );

        Ok(curator)
    }

    fn ensure_curation_actor_signed(
        origin: T::Origin,
        curation_actor: &CurationActor<CuratorId<T>>,
    ) -> Result<(), &'static str> {
        match curation_actor {
            CurationActor::Lead => {
                // Ensure lead is set and is origin signer
                Self::ensure_origin_is_set_lead(origin).map(|_| ())
            }
            CurationActor::Curator(curator_id) => {
                // Ensure there is a signer which matches role account of curator corresponding to provided id.
                Self::ensure_active_curator_signed(origin, &curator_id).map(|_| ())
            }
        }
    }

    /// Ensure origin is signed by account matching role account corresponding to the channel
    fn ensure_channel_owner_signed(
        origin: T::Origin,
        channel_id: &ChannelId<T>,
    ) -> Result<Channel<T::MemberId, T::AccountId, T::BlockNumber, PrincipalId<T>>, &'static str>
    {
        // Ensure that it is signed
        let signer_account = ensure_signed(origin)?;

        // Ensure channel id is valid
        let channel = Self::ensure_channel_id_is_valid(&channel_id)?;

        // Ensure origin matches channel role account
        ensure!(
            signer_account == channel.role_account,
            MSG_ORIGIN_DOES_NOT_MATCH_CHANNEL_ROLE_ACCOUNT
        );

        Ok(channel)
    }

    fn ensure_curator_application_exists(
        curator_application_id: &CuratorApplicationId<T>,
    ) -> Result<
        (
            CuratorApplication<T::AccountId, CuratorOpeningId<T>, T::MemberId, T::ApplicationId>,
            CuratorApplicationId<T>,
            CuratorOpening<T::OpeningId, T::BlockNumber, BalanceOf<T>, CuratorApplicationId<T>>,
        ),
        &'static str,
    > {
        //Result<(hiring::Application<<T as hiring::Trait>::OpeningId, T::BlockNumber, <T as stake::Trait>::StakeId>, CuratorOpening<T::OpeningId, T::BlockNumber, BalanceOf<T>, CuratorApplicationId<T>> ,hiring::Opening<BalanceOf<T>, T::BlockNumber, <T as hiring::Trait>::ApplicationId>), &'static str> {

        ensure!(
            CuratorApplicationById::<T>::exists(curator_application_id),
            MSG_CURATOR_APPLICATION_DOES_NOT_EXIST
        );

        let curator_application = CuratorApplicationById::<T>::get(curator_application_id);

        //let application = hiring::ApplicationById::<T>::get(curator_application.application_id);

        let curator_opening = CuratorOpeningById::<T>::get(curator_application.curator_opening_id);

        //let opening = hiring::OpeningById::<T>::get(curator_opening.opening_id);

        Ok((
            curator_application,
            *curator_application_id,
            curator_opening,
        ))
    }

    fn ensure_curator_has_recurring_reward(
        curator: &Curator<
            T::AccountId,
            T::RewardRelationshipId,
            T::StakeId,
            T::BlockNumber,
            LeadId<T>,
            T::ApplicationId,
            PrincipalId<T>,
        >,
    ) -> Result<T::RewardRelationshipId, &'static str> {
        ensure!(
            curator.reward_relationship.is_some(),
            MSG_CURATOR_HAS_NO_REWARD
        );

        let relationship_id = curator.reward_relationship.unwrap();

        Ok(relationship_id)
    }

    /// CRITICAL:
    /// https://github.com/Joystream/substrate-runtime-joystream/issues/92
    /// This assumes that ensure_can_withdraw can be don
    /// for a sum of balance that later will be actually withdrawn
    /// using individual terms in that sum.
    /// This needs to be fully checked across all possibly scenarios
    /// of actual balance, minimum balance limit, reservation, vesting and locking.
    fn ensure_can_make_stake_imbalance(
        opt_balances: Vec<&Option<BalanceOf<T>>>,
        source_account: &T::AccountId,
    ) -> Result<(), &'static str> {
        let zero_balance = <BalanceOf<T> as Zero>::zero();

        // Total amount to be staked
        let total_amount = opt_balances.iter().fold(zero_balance, |sum, opt_balance| {
            sum + if let Some(balance) = opt_balance {
                *balance
            } else {
                zero_balance
            }
        });

        if total_amount > zero_balance {
            // Ensure that
            if CurrencyOf::<T>::free_balance(source_account) < total_amount {
                Err(MSG_INSUFFICIENT_BALANCE_TO_COVER_STAKE)
            } else {
                let new_balance = CurrencyOf::<T>::free_balance(source_account) - total_amount;

                CurrencyOf::<T>::ensure_can_withdraw(
                    source_account,
                    total_amount,
                    WithdrawReasons::all(),
                    new_balance,
                )
            }
        } else {
            Ok(())
        }
    }

    fn make_stake_opt_imbalance(
        opt_balance: &Option<BalanceOf<T>>,
        source_account: &T::AccountId,
    ) -> Option<NegativeImbalance<T>> {
        if let Some(balance) = opt_balance {
            let withdraw_result = CurrencyOf::<T>::withdraw(
                source_account,
                *balance,
                WithdrawReasons::all(),
                ExistenceRequirement::AllowDeath,
            );

            assert!(withdraw_result.is_ok());

            withdraw_result.ok()
        } else {
            None
        }
    }

    fn deactivate_curator(
        curator_id: &CuratorId<T>,
        curator: &Curator<
            T::AccountId,
            T::RewardRelationshipId,
            T::StakeId,
            T::BlockNumber,
            LeadId<T>,
            CuratorApplicationId<T>,
            PrincipalId<T>,
        >,
        exit_initiation_origin: &CuratorExitInitiationOrigin,
        rationale_text: &[u8],
    ) {
        // Stop any possible recurring rewards
        let _did_deactivate_recurring_reward = if let Some(ref reward_relationship_id) =
            curator.reward_relationship
        {
            // Attempt to deactivate
            recurringrewards::Module::<T>::try_to_deactivate_relationship(*reward_relationship_id)
                .expect("Relationship must exist")
        } else {
            // Did not deactivate, there was no reward relationship!
            false
        };

        // When the curator is staked, unstaking must first be initiated,
        // otherwise they can be terminated right away.

        // Create exit summary for this termination
        let current_block = <system::Module<T>>::block_number();

        let curator_exit_summary =
            CuratorExitSummary::new(exit_initiation_origin, &current_block, rationale_text);

        // Determine new curator stage and event to emit
        let (new_curator_stage, unstake_directions, event) = if let Some(ref stake_profile) =
            curator.role_stake_profile
        {
            // Determine unstaknig period based on who initiated deactivation
            let unstaking_period = match curator_exit_summary.origin {
                CuratorExitInitiationOrigin::Lead => stake_profile.termination_unstaking_period,
                CuratorExitInitiationOrigin::Curator => stake_profile.exit_unstaking_period,
            };

            (
                CuratorRoleStage::Unstaking(curator_exit_summary),
                Some((stake_profile.stake_id, unstaking_period)),
                RawEvent::CuratorUnstaking(*curator_id),
            )
        } else {
            (
                CuratorRoleStage::Exited(curator_exit_summary.clone()),
                None,
                match curator_exit_summary.origin {
                    CuratorExitInitiationOrigin::Lead => RawEvent::TerminatedCurator(*curator_id),
                    CuratorExitInitiationOrigin::Curator => RawEvent::CuratorExited(*curator_id),
                },
            )
        };

        // Update curator
        let new_curator = Curator {
            stage: new_curator_stage,
            ..(curator.clone())
        };

        CuratorById::<T>::insert(curator_id, new_curator);

        // Unstake if directions provided
        if let Some(directions) = unstake_directions {
            // Keep track of curator unstaking
            let unstaker = WorkingGroupUnstaker::Curator(*curator_id);
            UnstakerByStakeId::<T>::insert(directions.0, unstaker);

            // Unstake
            stake::Module::<T>::initiate_unstaking(&directions.0, directions.1)
                .expect("Unstaking must be possible at this time");
        }

        // Trigger event
        Self::deposit_event(event);
    }

    /// Adds the given principal to storage under the returned identifier.
    fn add_new_principal(principal: &Principal<CuratorId<T>, ChannelId<T>>) -> PrincipalId<T> {
        // Get principal id for curator
        let principal_id = NextPrincipalId::<T>::get();

        // Update next principal id value
        NextPrincipalId::<T>::mutate(|id| *id += PrincipalId::<T>::one());

        // Store principal
        PrincipalById::<T>::insert(principal_id, principal);

        // Return id
        principal_id
    }

    fn update_channel(
        channel_id: &ChannelId<T>,
        new_verified: Option<bool>,
        new_handle: &Option<Vec<u8>>,
        new_title: &Option<OptionalText>,
        new_description: &Option<OptionalText>,
        new_avatar: &Option<OptionalText>,
        new_banner: &Option<OptionalText>,
        new_publication_status: Option<ChannelPublicationStatus>,
        new_curation_status: Option<ChannelCurationStatus>,
    ) {
        // Update channel id to handle mapping, if there is a new handle.
        if let Some(ref handle) = new_handle {
            // Remove mapping under old handle
            let current_handle = ChannelById::<T>::get(channel_id).handle;
            ChannelIdByHandle::<T>::remove(current_handle);

            // Establish mapping under new handle
            ChannelIdByHandle::<T>::insert(handle.clone(), channel_id);
        }

        // Update channel
        ChannelById::<T>::mutate(channel_id, |channel| {
            if let Some(ref verified) = new_verified {
                channel.verified = *verified;
            }

            if let Some(ref handle) = new_handle {
                channel.handle = handle.clone();
            }

            if let Some(ref title) = new_title {
                channel.title = title.clone();
            }

            if let Some(ref description) = new_description {
                channel.description = description.clone();
            }

            if let Some(ref avatar) = new_avatar {
                channel.avatar = avatar.clone();
            }

            if let Some(ref banner) = new_banner {
                channel.banner = banner.clone();
            }

            if let Some(ref publication_status) = new_publication_status {
                channel.publication_status = publication_status.clone();
            }

            if let Some(ref curation_status) = new_curation_status {
                channel.curation_status = *curation_status;
            }
        });

        // Trigger event
        Self::deposit_event(RawEvent::ChannelUpdatedByCurationActor(*channel_id));
    }

    /// The stake, with the given id, was unstaked. Infalliable. Has no side effects if stake_id is not relevant
    /// to this module.
    pub fn unstaked(stake_id: StakeId<T>) {
        // Ignore if unstaked doesn't exist
        if !<UnstakerByStakeId<T>>::exists(stake_id) {
            return;
        }

        // Unstaker must be in this group
        let unstaker = Self::ensure_unstaker_exists(&stake_id).unwrap();

        // Get curator doing the unstaking,
        // urrently the only possible unstaker in this module.
        let curator_id = if let WorkingGroupUnstaker::Curator(curator_id) = unstaker {
            curator_id
        } else {
            panic!("Should not be possible, only curators unstake in this module currently.");
        };

        // Grab curator from id, unwrap, because this curator _must_ exist.
        let unstaking_curator = Self::ensure_curator_exists(&curator_id).unwrap();

        //
        // == MUTATION SAFE ==
        //

        // Update stage of curator
        let curator_exit_summary =
            if let CuratorRoleStage::Unstaking(summary) = unstaking_curator.stage {
                summary
            } else {
                panic!("Curator must be in unstaking stage.");
            };

        let new_curator = Curator {
            stage: CuratorRoleStage::Exited(curator_exit_summary.clone()),
            ..unstaking_curator
        };

        CuratorById::<T>::insert(curator_id, new_curator);

        // Remove from unstaker
        UnstakerByStakeId::<T>::remove(stake_id);

        // Trigger event
        let event = match curator_exit_summary.origin {
            CuratorExitInitiationOrigin::Lead => RawEvent::TerminatedCurator(curator_id),
            CuratorExitInitiationOrigin::Curator => RawEvent::CuratorExited(curator_id),
        };

        Self::deposit_event(event);
    }
}
