use crate::sanitize::Sanitize;
use crate::{pubkey::Pubkey, short_vec};
use bincode::serialize;
use serde::Serialize;
use thiserror::Error;
#[derive(Serialize, Deserialize, Debug, Error, PartialEq, Eq, Clone, AbiExample, AbiEnumVisitor)]
pub enum InstructionError {
    
    
    #[error("generic instruction error")]
    GenericError,
    
    #[error("invalid program argument")]
    InvalidArgument,
    
    #[error("invalid instruction data")]
    InvalidInstructionData,
    
    #[error("invalid account data for instruction")]
    InvalidAccountData,
    
    #[error("account data too small for instruction")]
    AccountDataTooSmall,
    
    #[error("insufficient funds for instruction")]
    InsufficientFunds,
    
    #[error("incorrect program id for instruction")]
    IncorrectProgramId,
    
    #[error("missing required signature for instruction")]
    MissingRequiredSignature,
    
    #[error("instruction requires an uninitialized account")]
    AccountAlreadyInitialized,
    
    #[error("instruction requires an initialized account")]
    UninitializedAccount,
    
    #[error("sum of account balances before and after instruction do not match")]
    UnbalancedInstruction,
    
    #[error("instruction modified the program id of an account")]
    ModifiedProgramId,
    
    #[error("instruction spent from the balance of an account it does not own")]
    ExternalAccountLamportSpend,
    
    #[error("instruction modified data of an account it does not own")]
    ExternalAccountDataModified,
    
    #[error("instruction changed the balance of a read-only account")]
    ReadonlyLamportChange,
    
    #[error("instruction modified data of a read-only account")]
    ReadonlyDataModified,
    
    
    #[error("instruction contains duplicate accounts")]
    DuplicateAccountIndex,
    
    #[error("instruction changed executable bit of an account")]
    ExecutableModified,
    
    #[error("instruction modified rent epoch of an account")]
    RentEpochModified,
    
    #[error("insufficient account keys for instruction")]
    NotEnoughAccountKeys,
    
    #[error("non-system instruction changed account size")]
    AccountDataSizeChanged,
    
    #[error("instruction expected an executable account")]
    AccountNotExecutable,
    
    #[error("instruction tries to borrow reference for an account which is already borrowed")]
    AccountBorrowFailed,
    
    #[error("instruction left account with an outstanding reference borrowed")]
    AccountBorrowOutstanding,
    
    
    
    #[error("instruction modifications of multiply-passed account differ")]
    DuplicateAccountOutOfSync,
    
    
    
    #[error("custom program error: {0:#x}")]
    Custom(u32),
    
    
    #[error("program returned invalid error code")]
    InvalidError,
    
    #[error("instruction changed executable accounts data")]
    ExecutableDataModified,
    
    #[error("instruction changed the balance of a executable account")]
    ExecutableLamportChange,
    
    #[error("executable accounts must be rent exempt")]
    ExecutableAccountNotRentExempt,
    
    #[error("Unsupported program id")]
    UnsupportedProgramId,
    
    #[error("Cross-program invocation call depth too deep")]
    CallDepth,
    
    #[error("An account required by the instruction is missing")]
    MissingAccount,
    
    #[error("Cross-program invocation reentrancy not allowed for this instruction")]
    ReentrancyNotAllowed,
    
    #[error("Length of the seed is too long for address generation")]
    MaxSeedLengthExceeded,
    
    #[error("Provided seeds do not result in a valid address")]
    InvalidSeeds,
    
    #[error("Failed to reallocate account data")]
    InvalidRealloc,
    
    #[error("Computational budget exceeded")]
    ComputationalBudgetExceeded,
    
    #[error("Cross-program invocation with unauthorized signer or writable account")]
    PrivilegeEscalation,
    #[error("Failed to create program execution environment")]
    ProgramEnvironmentSetupFailure,
    #[error("Program failed to complete")]
    ProgramFailedToComplete,
    #[error("Program failed to compile")]
    ProgramFailedToCompile,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Instruction {
    
    pub program_id: Pubkey,
    
    pub accounts: Vec<AccountMeta>,
    
    pub data: Vec<u8>,
}
impl Instruction {
    pub fn new<T: Serialize>(program_id: Pubkey, data: &T, accounts: Vec<AccountMeta>) -> Self {
        let data = serialize(data).unwrap();
        Self {
            program_id,
            data,
            accounts,
        }
    }
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct AccountMeta {
    
    pub pubkey: Pubkey,
    
    pub is_signer: bool,
    
    pub is_writable: bool,
}
impl AccountMeta {
    pub fn new(pubkey: Pubkey, is_signer: bool) -> Self {
        Self {
            pubkey,
            is_signer,
            is_writable: true,
        }
    }
    pub fn new_readonly(pubkey: Pubkey, is_signer: bool) -> Self {
        Self {
            pubkey,
            is_signer,
            is_writable: false,
        }
    }
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, AbiExample)]
#[serde(rename_all = "camelCase")]
pub struct CompiledInstruction {
    
    pub program_id_index: u8,
    
    #[serde(with = "short_vec")]
    pub accounts: Vec<u8>,
    
    #[serde(with = "short_vec")]
    pub data: Vec<u8>,
}
impl Sanitize for CompiledInstruction {}
impl CompiledInstruction {
    pub fn new<T: Serialize>(program_ids_index: u8, data: &T, accounts: Vec<u8>) -> Self {
        let data = serialize(data).unwrap();
        Self {
            program_id_index: program_ids_index,
            data,
            accounts,
        }
    }
    pub fn program_id<'a>(&self, program_ids: &'a [Pubkey]) -> &'a Pubkey {
        &program_ids[self.program_id_index as usize]
    }
    
    pub fn visit_each_account(
        &self,
        work: &mut dyn FnMut(usize, usize) -> Result<(), InstructionError>,
    ) -> Result<(), InstructionError> {
        let mut unique_index = 0;
        'root: for (i, account_index) in self.accounts.iter().enumerate() {
            
            
            for account_index_before in self.accounts[..i].iter() {
                if account_index_before == account_index {
                    continue 'root; 
                }
            }
            work(unique_index, *account_index as usize)?;
            unique_index += 1;
        }
        Ok(())
    }
}
#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn test_visit_each_account() {
        let do_work = |accounts: &[u8]| -> (usize, usize) {
            let mut unique_total = 0;
            let mut account_total = 0;
            let mut work = |unique_index: usize, account_index: usize| {
                unique_total += unique_index;
                account_total += account_index;
                Ok(())
            };
            let instruction = CompiledInstruction::new(0, &[0], accounts.to_vec());
            instruction.visit_each_account(&mut work).unwrap();
            (unique_total, account_total)
        };
        assert_eq!((6, 6), do_work(&[0, 1, 2, 3]));
        assert_eq!((6, 6), do_work(&[0, 1, 1, 2, 3]));
        assert_eq!((6, 6), do_work(&[0, 1, 2, 3, 3]));
        assert_eq!((6, 6), do_work(&[0, 0, 1, 1, 2, 2, 3, 3]));
        assert_eq!((0, 2), do_work(&[2, 2]));
    }
}