//! Instruction types

use {
    pinocchio::{
        program_error::ProgramError,
        pubkey::{Pubkey, PUBKEY_BYTES},
    },
    shank::ShankInstruction,
};

/// Instructions supported by the monetary policy program.
#[repr(C)]
#[derive(Clone, Debug, PartialEq, ShankInstruction)]
pub enum MonetaryPolicyInstruction {
    /// Initializes the monetary policy account, setting its initial state
    /// with the initial authority.
    ///
    /// Accounts expected by this instruction:
    ///
    ///   0. `[writable]` The monetary policy account to initialize
    ///   1. `[signer]` The authority that will control the policy
    ///   2. `[signer]` Account that will pay for the account creation
    ///   3. `[]` The system program
    #[account(
        0,
        writable,
        name = "monetary_policy_account",
        desc = "The monetary policy account to initialize"
    )]
    #[account(
        1,
        signer,
        name = "monetary_policy_authority",
        desc = "The authority that will control the policy"
    )]
    #[account(
        2,
        signer,
        name = "funder",
        desc = "Account that will pay for the account creation"
    )]
    #[account(3, name = "system_program", desc = "System program")]
    CreateMonetaryPolicy {},

    /// Proposes a new authority for the monetary policy. The current
    /// authority must sign this instruction. The proposed new_authority
    /// must then sign an `AcceptAuthorityTransfer` instruction to finalize
    /// the transfer.
    ///
    /// Accounts expected by this instruction:
    ///
    ///   0. `[writable]` The monetary policy account
    ///   1. `[signer]` The current monetary policy authority
    #[account(
        0,
        writable,
        name = "monetary_policy_account",
        desc = "The monetary policy account"
    )]
    #[account(
        1,
        signer,
        name = "monetary_policy_authority",
        desc = "The monetary policy authority"
    )]
    InitiateAuthorityTransfer { new_authority: Pubkey },

    /// Accepts the pending authority transfer for the monetary policy. The
    /// pending authority must sign this instruction to assume authority
    /// over the policy.
    ///
    /// Accounts expected by this instruction:
    ///
    ///   0. `[writable]` The monetary policy account
    ///   1. `[signer]` The pending monetary policy authority
    #[account(
        0,
        writable,
        name = "monetary_policy_account",
        desc = "The monetary policy account"
    )]
    #[account(
        1,
        signer,
        name = "pending_monetary_policy_authority",
        desc = "The pending monetary policy authority"
    )]
    AcceptAuthorityTransfer,

    /// Cancels a pending authority transfer for the monetary policy. The
    /// current authority must sign this instruction.
    ///
    /// Accounts expected by this instruction:
    ///
    ///   0. `[writable]` The monetary policy account
    ///   1. `[signer]` The current monetary policy authority
    #[account(
        0,
        writable,
        name = "monetary_policy_account",
        desc = "The monetary policy account"
    )]
    #[account(
        1,
        signer,
        name = "monetary_policy_authority",
        desc = "The monetary policy authority"
    )]
    CancelAuthorityTransfer {},

    /// Updates the inflation rate (in basis points) parameter of the monetary policy.
    /// The current authority must sign this instruction.
    ///
    /// Accounts expected by this instruction:
    ///
    ///   0. `[writable]` The monetary policy account
    ///   1. `[signer]` The current monetary policy authority
    #[account(
        0,
        writable,
        name = "monetary_policy_account",
        desc = "The monetary policy account"
    )]
    #[account(
        1,
        signer,
        name = "monetary_policy_authority",
        desc = "The monetary policy authority"
    )]
    UpdateInflationRateBips { new_rate_bips: u64 },

    /// Updates the lamports per signature (transaction fee) parameter of the monetary policy.
    /// The current authority must sign this instruction.
    ///
    /// Accounts expected by this instruction:
    ///
    ///   0. `[writable]` The monetary policy account
    ///   1. `[signer]` The current monetary policy authority
    #[account(
        0,
        writable,
        name = "monetary_policy_account",
        desc = "The monetary policy account"
    )]
    #[account(
        1,
        signer,
        name = "monetary_policy_authority",
        desc = "The monetary policy authority"
    )]
    UpdateLamportsPerSignature { new_lamports_per_signature: u64 },

    /// Updates the burn percentage parameter of the monetary policy.
    /// The current authority must sign this instruction.
    ///
    /// Accounts expected by this instruction:
    ///
    ///   0. `[writable]` The monetary policy account
    ///   1. `[signer]` The current monetary policy authority
    #[account(
        0,
        writable,
        name = "monetary_policy_account",
        desc = "The monetary policy account"
    )]
    #[account(
        1,
        signer,
        name = "monetary_policy_authority",
        desc = "The monetary policy authority"
    )]
    UpdateBurnPercent { new_percent: u8 },
}

impl MonetaryPolicyInstruction {
    /// Unpack a monetary policy instruction from a byte slice.
    pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
        match input.split_first() {
            Some((&0, [])) => Ok(Self::CreateMonetaryPolicy {}),
            Some((&1, remaining)) if remaining.len() == PUBKEY_BYTES => {
                let new_authority = Pubkey::try_from(remaining).unwrap();
                Ok(Self::InitiateAuthorityTransfer { new_authority })
            }
            Some((&2, [])) => Ok(Self::AcceptAuthorityTransfer),
            Some((&3, [])) => Ok(Self::CancelAuthorityTransfer {}),
            Some((&4, remaining)) if remaining.len() == 8 => {
                let new_rate_bips = u64::from_le_bytes(remaining.try_into().unwrap());
                Ok(Self::UpdateInflationRateBips { new_rate_bips })
            }
            Some((&5, remaining)) if remaining.len() == 8 => {
                let new_lamports_per_signature = u64::from_le_bytes(remaining.try_into().unwrap());
                Ok(Self::UpdateLamportsPerSignature {
                    new_lamports_per_signature,
                })
            }
            Some((&6, remaining)) if remaining.len() == 1 => {
                let new_percent = remaining[0];
                Ok(Self::UpdateBurnPercent { new_percent })
            }
            _ => Err(ProgramError::InvalidInstructionData),
        }
    }
}
