#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use {
    crate::{
        check_program_account,
        instruction::{encode_instruction, TokenInstruction},
    },
    alloc::vec,
    bytemuck::{Pod, Zeroable},
    num_enum::{IntoPrimitive, TryFromPrimitive},
    solana_address::Address,
    solana_instruction::{AccountMeta, Instruction},
    solana_program_error::ProgramError,
};

/// Pausable extension instructions
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Debug, PartialEq, IntoPrimitive, TryFromPrimitive)]
#[repr(u8)]
pub enum PausableInstruction {
    /// Initialize the pausable extension for the given mint account
    ///
    /// Fails if the account has already been initialized, so must be called
    /// before `InitializeMint`.
    ///
    /// Accounts expected by this instruction:
    ///
    ///   0. `[writable]`  The mint account to initialize.
    ///
    /// Data expected by this instruction:
    ///   `crate::extension::pausable::instruction::InitializeInstructionData`
    Initialize,
    /// Pause minting, burning, and transferring for the mint.
    ///
    /// Accounts expected by this instruction:
    ///
    ///   0. `[writable]` The mint to update.
    ///   1. `[signer]` The mint's pause authority.
    ///
    ///   * Multisignature authority
    ///   0. `[writable]` The mint to update.
    ///   1. `[]` The mint's multisignature pause authority.
    ///   2. `..2+M` `[signer]` M signer accounts.
    Pause,
    /// Resume minting, burning, and transferring for the mint.
    ///
    /// Accounts expected by this instruction:
    ///
    ///   0. `[writable]` The mint to update.
    ///   1. `[signer]` The mint's pause authority.
    ///
    ///   * Multisignature authority
    ///   0. `[writable]` The mint to update.
    ///   1. `[]` The mint's multisignature pause authority.
    ///   2. `..2+M` `[signer]` M signer accounts.
    Resume,
}

/// Data expected by `PausableInstruction::Initialize`
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct InitializeInstructionData {
    /// The public key for the account that can pause the mint
    pub authority: Address,
}

/// Create an `Initialize` instruction
pub fn initialize(
    token_program_id: &Address,
    mint: &Address,
    authority: &Address,
) -> Result<Instruction, ProgramError> {
    check_program_account(token_program_id)?;
    let accounts = vec![AccountMeta::new(*mint, false)];
    Ok(encode_instruction(
        token_program_id,
        accounts,
        TokenInstruction::PausableExtension,
        PausableInstruction::Initialize,
        &InitializeInstructionData {
            authority: *authority,
        },
    ))
}

/// Create a `Pause` instruction
pub fn pause(
    token_program_id: &Address,
    mint: &Address,
    authority: &Address,
    signers: &[&Address],
) -> Result<Instruction, ProgramError> {
    check_program_account(token_program_id)?;
    let mut accounts = vec![
        AccountMeta::new(*mint, false),
        AccountMeta::new_readonly(*authority, signers.is_empty()),
    ];
    for signer_pubkey in signers.iter() {
        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
    }
    Ok(encode_instruction(
        token_program_id,
        accounts,
        TokenInstruction::PausableExtension,
        PausableInstruction::Pause,
        &(),
    ))
}

/// Create a `Resume` instruction
pub fn resume(
    token_program_id: &Address,
    mint: &Address,
    authority: &Address,
    signers: &[&Address],
) -> Result<Instruction, ProgramError> {
    check_program_account(token_program_id)?;
    let mut accounts = vec![
        AccountMeta::new(*mint, false),
        AccountMeta::new_readonly(*authority, signers.is_empty()),
    ];
    for signer_pubkey in signers.iter() {
        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
    }
    Ok(encode_instruction(
        token_program_id,
        accounts,
        TokenInstruction::PausableExtension,
        PausableInstruction::Resume,
        &(),
    ))
}
