use crate::{
    instruction::{AccountMeta, Instruction, InstructionError},
    loader_upgradeable_instruction::UpgradeableLoaderInstruction,
    pubkey::Pubkey,
    system_instruction, sysvar,
};
use bincode::serialized_size;
crate::declare_id!("BPFLoaderUpgradeab1e11111111111111111111111");
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)]
pub enum UpgradeableLoaderState {
    
    Uninitialized,
    
    Buffer {
        
        authority_address: Option<Pubkey>,
        
        
    },
    
    Program {
        
        programdata_address: Pubkey,
    },
    
    ProgramData {
        
        slot: u64,
        
        upgrade_authority_address: Option<Pubkey>,
        
        
    },
}
impl UpgradeableLoaderState {
    
    pub fn buffer_len(program_len: usize) -> Result<usize, InstructionError> {
        Ok(serialized_size(&Self::Buffer {
            authority_address: Some(Pubkey::default()),
        })
        .map(|len| len as usize)
        .map_err(|_| InstructionError::InvalidInstructionData)?
        .saturating_add(program_len))
    }
    
    pub fn buffer_data_offset() -> Result<usize, InstructionError> {
        Self::buffer_len(0)
    }
    
    pub fn program_len() -> Result<usize, InstructionError> {
        serialized_size(&Self::Program {
            programdata_address: Pubkey::default(),
        })
        .map(|len| len as usize)
        .map_err(|_| InstructionError::InvalidInstructionData)
    }
    
    pub fn programdata_len(program_len: usize) -> Result<usize, InstructionError> {
        Ok(serialized_size(&Self::ProgramData {
            slot: 0,
            upgrade_authority_address: Some(Pubkey::default()),
        })
        .map(|len| len as usize)
        .map_err(|_| InstructionError::InvalidInstructionData)?
        .saturating_add(program_len))
    }
    
    pub fn programdata_data_offset() -> Result<usize, InstructionError> {
        Self::programdata_len(0)
    }
}
pub fn create_buffer(
    payer_address: &Pubkey,
    buffer_address: &Pubkey,
    authority_address: &Pubkey,
    lamports: u64,
    program_len: usize,
) -> Result<Vec<Instruction>, InstructionError> {
    Ok(vec![
        system_instruction::create_account(
            payer_address,
            buffer_address,
            lamports,
            UpgradeableLoaderState::buffer_len(program_len)? as u64,
            &id(),
        ),
        Instruction::new(
            id(),
            &UpgradeableLoaderInstruction::InitializeBuffer,
            vec![
                AccountMeta::new(*buffer_address, false),
                AccountMeta::new_readonly(*authority_address, false),
            ],
        ),
    ])
}
pub fn write(
    buffer_address: &Pubkey,
    authority_address: &Pubkey,
    offset: u32,
    bytes: Vec<u8>,
) -> Instruction {
    Instruction::new(
        id(),
        &UpgradeableLoaderInstruction::Write { offset, bytes },
        vec![
            AccountMeta::new(*buffer_address, false),
            AccountMeta::new_readonly(*authority_address, true),
        ],
    )
}
pub fn deploy_with_max_program_len(
    payer_address: &Pubkey,
    program_address: &Pubkey,
    buffer_address: &Pubkey,
    upgrade_authority_address: &Pubkey,
    program_lamports: u64,
    max_data_len: usize,
) -> Result<Vec<Instruction>, InstructionError> {
    let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id());
    Ok(vec![
        system_instruction::create_account(
            payer_address,
            program_address,
            program_lamports,
            UpgradeableLoaderState::program_len()? as u64,
            &id(),
        ),
        Instruction::new(
            id(),
            &UpgradeableLoaderInstruction::DeployWithMaxDataLen { max_data_len },
            vec![
                AccountMeta::new(*payer_address, true),
                AccountMeta::new(programdata_address, false),
                AccountMeta::new(*program_address, false),
                AccountMeta::new(*buffer_address, false),
                AccountMeta::new_readonly(sysvar::rent::id(), false),
                AccountMeta::new_readonly(sysvar::clock::id(), false),
                AccountMeta::new_readonly(crate::system_program::id(), false),
                AccountMeta::new_readonly(*upgrade_authority_address, true),
            ],
        ),
    ])
}
pub fn upgrade(
    program_address: &Pubkey,
    buffer_address: &Pubkey,
    authority_address: &Pubkey,
    spill_address: &Pubkey,
) -> Instruction {
    let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id());
    Instruction::new(
        id(),
        &UpgradeableLoaderInstruction::Upgrade,
        vec![
            AccountMeta::new(programdata_address, false),
            AccountMeta::new(*program_address, false),
            AccountMeta::new(*buffer_address, false),
            AccountMeta::new(*spill_address, false),
            AccountMeta::new_readonly(sysvar::rent::id(), false),
            AccountMeta::new_readonly(sysvar::clock::id(), false),
            AccountMeta::new_readonly(*authority_address, true),
        ],
    )
}
pub fn is_upgrade_instruction(instruction_data: &[u8]) -> bool {
    3 == instruction_data[0]
}
pub fn set_buffer_authority(
    buffer_address: &Pubkey,
    current_authority_address: &Pubkey,
    new_authority_address: &Pubkey,
) -> Instruction {
    Instruction::new(
        id(),
        &UpgradeableLoaderInstruction::SetAuthority,
        vec![
            AccountMeta::new(*buffer_address, false),
            AccountMeta::new_readonly(*current_authority_address, true),
            AccountMeta::new_readonly(*new_authority_address, false),
        ],
    )
}
pub fn set_upgrade_authority(
    program_address: &Pubkey,
    current_authority_address: &Pubkey,
    new_authority_address: Option<&Pubkey>,
) -> Instruction {
    let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id());
    let mut metas = vec![
        AccountMeta::new(programdata_address, false),
        AccountMeta::new_readonly(*current_authority_address, true),
    ];
    if let Some(address) = new_authority_address {
        metas.push(AccountMeta::new_readonly(*address, false));
    }
    Instruction::new(id(), &UpgradeableLoaderInstruction::SetAuthority, metas)
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_account_lengths() {
        assert_eq!(
            4,
            serialized_size(&UpgradeableLoaderState::Uninitialized).unwrap()
        );
        assert_eq!(36, UpgradeableLoaderState::program_len().unwrap());
        assert_eq!(
            45,
            UpgradeableLoaderState::programdata_data_offset().unwrap()
        );
        assert_eq!(
            45 + 42,
            UpgradeableLoaderState::programdata_len(42).unwrap()
        );
    }
    #[test]
    fn test_is_upgrade_instruction() {
        assert_eq!(
            false,
            is_upgrade_instruction(
                &bincode::serialize(&UpgradeableLoaderInstruction::InitializeBuffer).unwrap()
            )
        );
        assert_eq!(
            false,
            is_upgrade_instruction(
                &bincode::serialize(&UpgradeableLoaderInstruction::Write {
                    offset: 0,
                    bytes: vec![],
                })
                .unwrap()
            )
        );
        assert_eq!(
            false,
            is_upgrade_instruction(
                &bincode::serialize(&UpgradeableLoaderInstruction::DeployWithMaxDataLen {
                    max_data_len: 0,
                })
                .unwrap()
            )
        );
        assert_eq!(
            true,
            is_upgrade_instruction(
                &bincode::serialize(&UpgradeableLoaderInstruction::Upgrade).unwrap()
            )
        );
        assert_eq!(
            false,
            is_upgrade_instruction(
                &bincode::serialize(&UpgradeableLoaderInstruction::SetAuthority).unwrap()
            )
        );
    }
}