use std::cell::RefMut;
use std::convert::TryInto;
use std::mem::size_of;
use std::ops::DerefMut;
use bonfida_utils::BorshSize;
use borsh::{BorshDeserialize, BorshSerialize};
use bytemuck::{cast_slice, from_bytes, from_bytes_mut, Pod, try_cast_slice_mut, Zeroable};
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::FromPrimitive;
use solana_program::account_info::AccountInfo;
use solana_program::clock::Clock;
use solana_program::entrypoint::ProgramResult;
use solana_program::msg;
use solana_program::program_error::ProgramError;
use solana_program::pubkey::Pubkey;
use solana_program::sysvar::Sysvar;
use crate::error::AccessError;
use crate::instruction::ProgramInstruction;
use crate::instruction::ProgramInstruction::AdminProgramFreeze;
use crate::utils::is_admin_renouncable_instruction;
pub const ACCESS_MINT: Pubkey =
    solana_program::pubkey!("5MAYDfq5yxtudAhtfyuMBuHZjgAbaS9tbEyEQYAhDS5y");
pub const ACCESS_CNFT_PROGRAM_SIGNER: Pubkey =
    solana_program::pubkey!("AF7bqw2GjPPX25nWNRYtDpNALMp9B4D9FEaFekur2LEr");
pub const SECONDS_IN_DAY: u64 = if cfg!(feature = "days-to-sec-15m") {
    15 * 60
} else if cfg!(feature = "days-to-sec-10s") {
    10
} else {
    3600 * 24
};
pub const V1_INSTRUCTIONS_ALLOWED: bool = cfg!(feature = "v1-instructions-allowed");
pub const DEFAULT_STAKER_MULTIPLIER: u64 = 50;
pub const STAKE_BUFFER_LEN: u64 = 274;
pub const MAX_FEE_RECIPIENTS: usize = 10;
pub const MIN_DISTRIBUTE_AMOUNT: u64 = 100_000_000;
pub const MAX_FEE_SPLIT_SETUP_DELAY: u64 = 5 * 60; pub const DEFAULT_FEE_BASIS_POINTS: u16 = 200;
#[derive(
BorshSerialize, BorshDeserialize, BorshSize, PartialEq, FromPrimitive, ToPrimitive, Debug
)]
#[repr(u8)]
#[allow(missing_docs)]
pub enum Tag {
    Uninitialized,
    StakePool,
    InactiveStakePool,
    StakeAccount,
    InactiveBondAccount,
    BondAccount,
    CentralState,
    Deleted,
    FrozenStakePool,
    FrozenStakeAccount,
    FrozenBondAccount,
    BondV2Account,
    CentralStateV2,
    RoyaltyAccount,
}
impl Tag {
    pub fn opposite(&self) -> Result<Tag, ProgramError> {
        let tag = match self {
            Tag::StakePool => Tag::FrozenStakePool,
            Tag::StakeAccount => Tag::FrozenStakeAccount,
            Tag::BondAccount => Tag::FrozenBondAccount,
            Tag::FrozenStakePool => Tag::StakePool,
            Tag::FrozenStakeAccount => Tag::StakeAccount,
            Tag::FrozenBondAccount => Tag::BondAccount,
            _ => return Err(AccessError::InvalidTagChange.into()),
        };
        Ok(tag)
    }
}
#[derive(BorshSerialize, BorshDeserialize, BorshSize, Copy, Clone, Pod, Zeroable, Debug)]
#[repr(C)]
#[allow(missing_docs)]
pub struct StakePoolHeader {
    pub tag: u8,
    pub nonce: u8,
    pub current_day_idx: u16,
    pub _padding: [u8; 4],
    pub minimum_stake_amount: u64,
    pub total_staked: u64,
    pub last_claimed_offset: u64,
    pub stakers_part: u64,
    pub owner: [u8; 32],
    pub vault: [u8; 32],
}
#[allow(missing_docs)]
pub struct StakePool<H, B> {
    pub header: H,
    pub balances: B,
}
#[derive(Pod, Clone, Copy, Zeroable, Debug)]
#[repr(C)]
pub struct RewardsTuple {
    pub(crate) pool_reward: u128,
    pub(crate) stakers_reward: u128,
}
#[allow(missing_docs)]
pub type StakePoolRef<'a> = StakePool<RefMut<'a, StakePoolHeader>, RefMut<'a, [RewardsTuple]>>;
#[allow(missing_docs)]
pub type StakePoolHeaped = StakePool<Box<StakePoolHeader>, Box<[RewardsTuple]>>;
#[allow(missing_docs)]
impl<'a> StakePoolRef<'a> {
    pub fn get_checked<'b: 'a>(
        account_info: &'a AccountInfo<'b>,
        allowed_tags: Vec<Tag>,
    ) -> Result<Self, ProgramError> {
        let (header, balances) = RefMut::map_split(account_info.data.borrow_mut(), |s| {
            let (hd, rem) = s.split_at_mut(size_of::<StakePoolHeader>());
            (
                from_bytes_mut::<StakePoolHeader>(hd),
                try_cast_slice_mut(rem).unwrap(),
            )
        });
        let tag = FromPrimitive::from_u8(header.tag).ok_or(ProgramError::InvalidAccountData)?;
        if !allowed_tags.contains(&tag) {
            return Err(AccessError::DataTypeMismatch.into());
        }
        Ok(StakePool { header, balances })
    }
}
#[allow(missing_docs)]
impl StakePoolHeaped {
    pub fn from_buffer(buf: &[u8]) -> Self {
        println!("StakePoolHeaped::from_buffer: buf.len() = {}", buf.len());
        let (header, balances) = buf.split_at(size_of::<StakePoolHeader>());
        println!(
            "StakePoolHeaped::from_buffer: header.len() = {}",
            header.len()
        );
        let header = from_bytes::<StakePoolHeader>(header);
        println!("StakePoolHeaped::from_buffer: header = {:?}", header);
        let balances = cast_slice::<_, RewardsTuple>(balances);
        println!(
            "StakePoolHeaped::from_buffer: balances.len() = {}",
            balances.len()
        );
        Self {
            header: Box::new(*header),
            balances: Box::from(balances),
        }
    }
}
#[allow(missing_docs)]
impl<H: DerefMut<Target=StakePoolHeader>, B: DerefMut<Target=[RewardsTuple]>> StakePool<H, B> {
    pub fn push_balances_buff(
        &mut self,
        current_offset: u64,
        rewards: RewardsTuple,
    ) -> Result<(), ProgramError> {
        let nb_days_passed = current_offset
            .checked_sub(self.header.current_day_idx as u64)
            .ok_or(AccessError::Overflow)?;
        for i in 1..nb_days_passed {
            self.balances[(((self.header.current_day_idx as u64)
                .checked_add(i)
                .ok_or(AccessError::Overflow)?)
                % STAKE_BUFFER_LEN) as usize] = RewardsTuple {
                pool_reward: 0,
                stakers_reward: 0,
            };
        }
        self.header.current_day_idx = self
            .header
            .current_day_idx
            .checked_add(
                nb_days_passed
                    .try_into()
                    .map_err(|_| AccessError::Overflow)?,
            )
            .ok_or(AccessError::Overflow)?;
        self.balances[(((self.header.current_day_idx - 1) as u64) % STAKE_BUFFER_LEN) as usize] =
            rewards;
        Ok(())
    }
    pub fn create_key(
        nonce: &u8,
        owner: &Pubkey,
        program_id: &Pubkey,
    ) -> Result<Pubkey, ProgramError> {
        let seeds: &[&[u8]] = &[StakePoolHeader::SEED, &owner.to_bytes(), &[*nonce]];
        Pubkey::create_program_address(seeds, program_id).map_err(|_| ProgramError::InvalidSeeds)
    }
}
#[allow(missing_docs)]
impl StakePool<(), ()> {
    pub fn find_key(owner: &Pubkey, program_id: &Pubkey) -> (Pubkey, u8) {
        let seeds: &[&[u8]] = &[StakePoolHeader::SEED, &owner.to_bytes()];
        Pubkey::find_program_address(seeds, program_id)
    }
}
#[allow(missing_docs)]
impl StakePoolHeader {
    pub const SEED: &'static [u8; 10] = b"stake_pool";
    pub fn new(
        owner: Pubkey,
        nonce: u8,
        vault: Pubkey,
        minimum_stake_amount: u64,
    ) -> Result<Self, ProgramError> {
        Ok(Self {
            tag: Tag::InactiveStakePool as u8,
            total_staked: 0,
            current_day_idx: 0,
            _padding: [0; 4],
            last_claimed_offset: 0,
            owner: owner.to_bytes(),
            nonce,
            vault: vault.to_bytes(),
            minimum_stake_amount,
            stakers_part: DEFAULT_STAKER_MULTIPLIER,
        })
    }
    pub fn close(&mut self) {
        self.tag = Tag::Deleted as u8
    }
    pub fn deposit(&mut self, amount: u64) -> ProgramResult {
        self.total_staked = self
            .total_staked
            .checked_add(amount)
            .ok_or(AccessError::Overflow)?;
        Ok(())
    }
    pub fn withdraw(&mut self, amount: u64) -> ProgramResult {
        self.total_staked = self
            .total_staked
            .checked_sub(amount)
            .ok_or(AccessError::Overflow)?;
        Ok(())
    }
}
#[derive(BorshSerialize, BorshDeserialize, BorshSize, Debug)]
#[allow(missing_docs)]
pub struct StakeAccount {
    pub tag: Tag,
    pub owner: Pubkey,
    pub stake_amount: u64,
    pub stake_pool: Pubkey,
    pub last_claimed_offset: u64,
    pub pool_minimum_at_creation: u64,
}
#[allow(missing_docs)]
impl StakeAccount {
    pub const SEED: &'static [u8; 13] = b"stake_account";
    pub fn new(owner: Pubkey, stake_pool: Pubkey, pool_minimum_at_creation: u64) -> Self {
        Self {
            tag: Tag::StakeAccount,
            owner,
            stake_amount: 0,
            stake_pool,
            last_claimed_offset: 0,
            pool_minimum_at_creation,
        }
    }
    pub fn create_key(
        nonce: &u8,
        owner: &Pubkey,
        stake_pool: &Pubkey,
        program_id: &Pubkey,
    ) -> Result<Pubkey, ProgramError> {
        let seeds: &[&[u8]] = &[
            StakeAccount::SEED,
            &owner.to_bytes(),
            &stake_pool.to_bytes(),
            &[*nonce],
        ];
        Pubkey::create_program_address(seeds, program_id).map_err(|_| ProgramError::InvalidSeeds)
    }
    pub fn find_key(owner: &Pubkey, stake_pool: &Pubkey, program_id: &Pubkey) -> (Pubkey, u8) {
        let seeds: &[&[u8]] = &[
            StakeAccount::SEED,
            &owner.to_bytes(),
            &stake_pool.to_bytes(),
        ];
        Pubkey::find_program_address(seeds, program_id)
    }
    pub fn save(&self, mut dst: &mut [u8]) -> ProgramResult {
        self.serialize(&mut dst)
            .map_err(|_| ProgramError::InvalidAccountData)
    }
    pub fn from_account_info(a: &AccountInfo) -> Result<StakeAccount, ProgramError> {
        let mut data = &a.data.borrow() as &[u8];
        if data[0] != Tag::StakeAccount as u8 && data[0] != Tag::Uninitialized as u8 {
            return Err(AccessError::DataTypeMismatch.into());
        }
        let result = StakeAccount::deserialize(&mut data)?;
        Ok(result)
    }
    pub fn close(&mut self) {
        self.tag = Tag::Deleted
    }
    pub fn deposit(&mut self, amount: u64) -> ProgramResult {
        self.stake_amount = self
            .stake_amount
            .checked_add(amount)
            .ok_or(AccessError::Overflow)?;
        Ok(())
    }
    pub fn withdraw(&mut self, amount: u64) -> ProgramResult {
        self.stake_amount = self
            .stake_amount
            .checked_sub(amount)
            .ok_or(AccessError::Overflow)?;
        Ok(())
    }
}
#[derive(BorshSerialize, BorshDeserialize, BorshSize)]
#[allow(missing_docs)]
pub struct CentralState {
    pub tag: Tag,
    pub signer_nonce: u8,
    pub daily_inflation: u64,
    pub token_mint: Pubkey,
    pub authority: Pubkey,
    pub creation_time: i64,
    pub total_staked: u64,
    pub total_staked_snapshot: u64,
    pub last_snapshot_offset: u64,
}
impl CentralState {
    #[allow(missing_docs)]
    pub fn new(
        signer_nonce: u8,
        daily_inflation: u64,
        token_mint: Pubkey,
        authority: Pubkey,
        total_staked: u64,
    ) -> Result<Self, ProgramError> {
        Ok(Self {
            tag: Tag::CentralState,
            signer_nonce,
            daily_inflation,
            token_mint,
            authority,
            creation_time: Clock::get()?.unix_timestamp,
            total_staked,
            total_staked_snapshot: 0,
            last_snapshot_offset: 0,
        })
    }
    #[allow(missing_docs)]
    pub fn create_key(signer_nonce: &u8, program_id: &Pubkey) -> Result<Pubkey, ProgramError> {
        let signer_seeds: &[&[u8]] = &[&program_id.to_bytes(), &[*signer_nonce]];
        Pubkey::create_program_address(signer_seeds, program_id)
            .map_err(|_| ProgramError::InvalidSeeds)
    }
    #[allow(missing_docs)]
    pub fn find_key(program_id: &Pubkey) -> (Pubkey, u8) {
        Pubkey::find_program_address(&[&program_id.to_bytes()], program_id)
    }
    #[allow(missing_docs)]
    pub fn save(&self, mut dst: &mut [u8]) -> ProgramResult {
        self.serialize(&mut dst)
            .map_err(|_| ProgramError::InvalidAccountData)
    }
    #[allow(missing_docs)]
    pub fn from_account_info(a: &AccountInfo) -> Result<CentralState, ProgramError> {
        let mut data = &a.data.borrow() as &[u8];
        if data[0] != Tag::CentralState as u8 && data[0] != Tag::Uninitialized as u8 {
            return Err(AccessError::DataTypeMismatch.into());
        }
        let result = CentralState::deserialize(&mut data)?;
        Ok(result)
    }
    #[allow(missing_docs)]
    pub fn get_current_offset(&self) -> Result<u64, ProgramError> {
        let current_time = Clock::get()?.unix_timestamp as u64;
        Ok((current_time - self.creation_time as u64) / SECONDS_IN_DAY)
    }
}
#[derive(BorshSerialize, BorshDeserialize, BorshSize, Debug)]
#[allow(missing_docs)]
pub struct CentralStateV2 {
    pub tag: Tag,
    pub bump_seed: u8,
    pub daily_inflation: u64,
    pub token_mint: Pubkey,
    pub authority: Pubkey,
    pub creation_time: i64,
    pub total_staked: u64,
    pub total_staked_snapshot: u64,
    pub last_snapshot_offset: u64,
    pub ix_gate: u128,
    pub freeze_authority: Pubkey,
    pub admin_ix_gate: u128,
    pub fee_basis_points: u16,
    pub last_fee_distribution_time: i64,
    pub recipients: Vec<FeeRecipient>,
}
impl CentralStateV2 {
    #[allow(missing_docs)]
    pub fn from_central_state(
        central_state: CentralState,
    ) -> Result<Self, ProgramError> {
        Ok(Self {
            tag: Tag::CentralStateV2,
            bump_seed: central_state.signer_nonce,
            daily_inflation: central_state.daily_inflation,
            token_mint: central_state.token_mint,
            authority: central_state.authority,
            creation_time: central_state.creation_time,
            total_staked: central_state.total_staked,
            total_staked_snapshot: central_state.total_staked_snapshot,
            last_snapshot_offset: central_state.last_snapshot_offset,
            freeze_authority: central_state.authority,
            ix_gate: u128::MAX, admin_ix_gate: u128::MAX, fee_basis_points: DEFAULT_FEE_BASIS_POINTS,
            last_fee_distribution_time: Clock::get()?.unix_timestamp,
            recipients: vec![], })
    }
    #[allow(missing_docs)]
    pub fn create_key(signer_nonce: &u8, program_id: &Pubkey) -> Result<Pubkey, ProgramError> {
        let signer_seeds: &[&[u8]] = &[&program_id.to_bytes(), &[*signer_nonce]];
        Pubkey::create_program_address(signer_seeds, program_id)
            .map_err(|_| ProgramError::InvalidSeeds)
    }
    #[allow(missing_docs)]
    pub fn find_key(program_id: &Pubkey) -> (Pubkey, u8) {
        Pubkey::find_program_address(&[&program_id.to_bytes()], program_id)
    }
    #[allow(missing_docs)]
    pub fn save(&self, mut dst: &mut [u8]) -> ProgramResult {
        self.serialize(&mut dst)
            .map_err(|_| ProgramError::InvalidAccountData)
    }
    #[allow(missing_docs)]
    pub fn from_account_info(a: &AccountInfo) -> Result<CentralStateV2, ProgramError> {
        let mut data = &a.data.borrow() as &[u8];
        if data[0] != Tag::CentralStateV2 as u8 && data[0] != Tag::Uninitialized as u8 {
            return Err(AccessError::DataTypeMismatch.into());
        }
        let result = CentralStateV2::deserialize(&mut data)?;
        Ok(result)
    }
    #[allow(missing_docs)]
    pub fn get_current_offset(&self) -> Result<u64, ProgramError> {
        let current_time = Clock::get()?.unix_timestamp as u64;
        Ok((current_time - self.creation_time as u64) / SECONDS_IN_DAY)
    }
    pub fn assert_instruction_allowed(&self, ix: &ProgramInstruction) -> ProgramResult {
        let ix_num = *ix as u32;
        let ix_mask = 1_u128.checked_shl(ix_num).ok_or(AccessError::Overflow)?;
        if ix_mask & self.ix_gate == 0 && ix_num != AdminProgramFreeze as u32 {
            return Err(AccessError::FrozenInstruction.into());
        }
        if is_admin_renouncable_instruction(ix) && ix_mask & self.admin_ix_gate == 0 {
            return Err(AccessError::AlreadyRenounced.into());
        }
        Ok(())
    }
    pub fn calculate_fee(&self, amount: u64) -> Result<u64, ProgramError> {
        let fee = amount
            .checked_mul(self.fee_basis_points as u64)
            .ok_or(AccessError::Overflow)?
            .checked_add(9_999).ok_or(AccessError::Overflow)?
            .checked_div(10_000)
            .ok_or(AccessError::Overflow)?;
        Ok(fee)
    }
}
pub const BOND_SIGNER_THRESHOLD: u64 = 1;
pub const AUTHORIZED_BOND_SELLERS: [Pubkey; 1] = [solana_program::pubkey!(
    "3Nrq6mCNL5i8Qk4APhggbwXismcsF23gNVDEaKycZBL8"
)];
#[derive(BorshSerialize, BorshDeserialize, BorshSize)]
#[allow(missing_docs)]
pub struct BondAccount {
    pub tag: Tag,
    pub owner: Pubkey,
    pub total_amount_sold: u64,
    pub total_staked: u64,
    pub total_quote_amount: u64,
    pub quote_mint: Pubkey,
    pub seller_token_account: Pubkey,
    pub unlock_start_date: i64,
    pub unlock_period: i64,
    pub unlock_amount: u64,
    pub last_unlock_time: i64,
    pub total_unlocked_amount: u64,
    pub pool_minimum_at_creation: u64,
    pub stake_pool: Pubkey,
    pub last_claimed_offset: u64,
    pub sellers: Vec<Pubkey>,
}
#[allow(missing_docs)]
impl BondAccount {
    pub const SEED: &'static [u8; 12] = b"bond_account";
    pub fn create_key(owner: &Pubkey, total_amount_sold: u64, program_id: &Pubkey) -> (Pubkey, u8) {
        let seeds: &[&[u8]] = &[
            BondAccount::SEED,
            &owner.to_bytes(),
            &total_amount_sold.to_le_bytes(),
        ];
        Pubkey::find_program_address(seeds, program_id)
    }
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        owner: Pubkey,
        total_amount_sold: u64,
        total_quote_amount: u64,
        quote_mint: Pubkey,
        seller_token_account: Pubkey,
        unlock_start_date: i64,
        unlock_period: i64,
        unlock_amount: u64,
        last_unlock_time: i64,
        pool_minimum_at_creation: u64,
        stake_pool: Pubkey,
        seller: Pubkey,
    ) -> Self {
        let sellers = vec![seller];
        Self {
            tag: Tag::InactiveBondAccount,
            owner,
            total_amount_sold,
            total_staked: total_amount_sold,
            total_quote_amount,
            quote_mint,
            seller_token_account,
            unlock_start_date,
            unlock_period,
            unlock_amount,
            last_unlock_time,
            total_unlocked_amount: 0,
            stake_pool,
            last_claimed_offset: 0,
            sellers,
            pool_minimum_at_creation,
        }
    }
    pub fn save(&self, mut dst: &mut [u8]) -> ProgramResult {
        self.serialize(&mut dst)
            .map_err(|_| ProgramError::InvalidAccountData)
    }
    pub fn is_active(&self) -> bool {
        self.tag == Tag::BondAccount
    }
    pub fn activate(&mut self, current_offset: u64) -> ProgramResult {
        self.tag = Tag::BondAccount;
        self.last_claimed_offset = current_offset;
        let current_time = Clock::get()?.unix_timestamp;
        self.last_unlock_time = std::cmp::max(current_time, self.unlock_start_date);
        Ok(())
    }
    pub fn from_account_info(
        a: &AccountInfo,
        allow_inactive: bool,
    ) -> Result<BondAccount, ProgramError> {
        let mut data = &a.data.borrow() as &[u8];
        let tag = if allow_inactive {
            Tag::InactiveBondAccount
        } else {
            Tag::BondAccount
        };
        if data[0] != tag as u8 && data[0] != Tag::Uninitialized as u8 {
            return Err(AccessError::DataTypeMismatch.into());
        }
        let result = BondAccount::deserialize(&mut data)?;
        Ok(result)
    }
    pub fn calc_unlock_amount(&self, missed_periods: u64) -> Result<u64, ProgramError> {
        msg!("Missed periods {}", missed_periods);
        let cumulated_unlock_amnt = (missed_periods)
            .checked_mul(self.unlock_amount)
            .ok_or(AccessError::Overflow)?;
        let unlock_amnt = std::cmp::min(
            cumulated_unlock_amnt,
            self.total_amount_sold
                .checked_sub(self.total_unlocked_amount)
                .ok_or(AccessError::Overflow)?,
        );
        msg!(
            "Unlock amount {} Total amount {}",
            unlock_amnt,
            self.total_amount_sold
        );
        Ok(unlock_amnt)
    }
}
#[derive(BorshSerialize, BorshDeserialize, BorshSize)]
#[allow(missing_docs)]
pub struct BondV2Account {
    pub tag: Tag,
    pub owner: Pubkey,
    pub amount: u64,
    pub pool: Pubkey,
    pub last_claimed_offset: u64,
    pub pool_minimum_at_creation: u64,
    pub unlock_timestamp: Option<i64>,
}
#[allow(missing_docs)]
impl BondV2Account {
    pub const SEED: &'static [u8; 15] = b"bond_v2_account";
    pub fn create_key(
        owner: &Pubkey,
        pool: &Pubkey,
        unlock_timestamp: Option<i64>,
        program_id: &Pubkey,
    ) -> (Pubkey, u8) {
        let seeds: &[&[u8]] = &[
            BondV2Account::SEED,
            &owner.to_bytes(),
            &pool.to_bytes(),
            &unlock_timestamp.unwrap_or(0).to_le_bytes(),
        ];
        Pubkey::find_program_address(seeds, program_id)
    }
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        owner: Pubkey,
        pool: Pubkey,
        pool_minimum_at_creation: u64,
        unlock_timestamp: Option<i64>,
    ) -> Self {
        Self {
            tag: Tag::BondV2Account,
            owner,
            amount: 0,
            pool,
            last_claimed_offset: 0,
            pool_minimum_at_creation,
            unlock_timestamp,
        }
    }
    pub fn save(&self, mut dst: &mut [u8]) -> ProgramResult {
        self.serialize(&mut dst)
            .map_err(|_| ProgramError::InvalidAccountData)
    }
    pub fn from_account_info(a: &AccountInfo) -> Result<BondV2Account, ProgramError> {
        let mut data = &a.data.borrow() as &[u8];
        let tag = Tag::BondV2Account;
        if data[0] != tag as u8 && data[0] != Tag::Uninitialized as u8 {
            return Err(AccessError::DataTypeMismatch.into());
        }
        let result = BondV2Account::deserialize(&mut data)?;
        Ok(result)
    }
    pub fn withdraw(&mut self, amount: u64) -> ProgramResult {
        self.amount = self
            .amount
            .checked_sub(amount)
            .ok_or(AccessError::Overflow)?;
        Ok(())
    }
}
#[derive(BorshSerialize, BorshDeserialize, BorshSize)]
#[allow(missing_docs)]
pub struct RoyaltyAccount {
    pub tag: Tag,
    pub rent_payer: Pubkey,
    pub royalty_payer: Pubkey,
    pub recipient_ata: Pubkey,
    pub expiration_date: u64,
    pub royalty_basis_points: u16,
}
#[allow(missing_docs)]
impl RoyaltyAccount {
    pub const SEED: &'static [u8; 15] = b"royalty_account";
    pub fn create_key(
        payer: &Pubkey,
        program_id: &Pubkey,
    ) -> (Pubkey, u8) {
        let seeds: &[&[u8]] = &[
            RoyaltyAccount::SEED,
            &payer.to_bytes(),
        ];
        Pubkey::find_program_address(seeds, program_id)
    }
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        fee_payer: Pubkey,
        royalty_payer: Pubkey,
        recipient_ata: Pubkey,
        expiration_date: u64,
        royalty_basis_points: u16,
    ) -> Self {
        Self {
            tag: Tag::RoyaltyAccount,
            rent_payer: fee_payer,
            royalty_payer,
            recipient_ata,
            expiration_date,
            royalty_basis_points,
        }
    }
    pub fn save(&self, mut dst: &mut [u8]) -> ProgramResult {
        self.serialize(&mut dst)
            .map_err(|_| ProgramError::InvalidAccountData)
    }
    pub fn from_account_info(a: &AccountInfo) -> Result<RoyaltyAccount, ProgramError> {
        let mut data = &a.data.borrow() as &[u8];
        let tag = Tag::RoyaltyAccount;
        if data[0] != tag as u8 && data[0] != Tag::Uninitialized as u8 {
            return Err(AccessError::DataTypeMismatch.into());
        }
        let result = RoyaltyAccount::deserialize(&mut data)?;
        Ok(result)
    }
    pub fn close(&mut self) {
        self.tag = Tag::Deleted
    }
    pub fn calculate_royalty_amount(&self, amount: u64) -> Result<u64, ProgramError> {
        let royalty = amount
            .checked_mul(self.royalty_basis_points as u64)
            .ok_or(AccessError::Overflow)?
            .checked_add(9_999).ok_or(AccessError::Overflow)?
            .checked_div(10_000)
            .ok_or(AccessError::Overflow)?;
        if royalty > amount {
            return Err(AccessError::Overflow.into());
        }
        Ok(royalty)
    }
}
#[derive(BorshSerialize, BorshDeserialize, BorshSize, Clone, Debug)]
#[allow(missing_docs)]
pub struct FeeRecipient {
    pub owner: Pubkey,
    pub percentage: u64,
}