use crate::{bytes::Bytes, hash::KECCAK_EMPTY};
use cfx_types::{
    address_util::AddressUtil, Address, AddressSpaceUtil, AddressWithSpace,
    Space, H256, U256,
};
use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream};
use rlp_derive::{RlpDecodable, RlpEncodable};
use serde_derive::{Deserialize, Serialize};
use std::{
    fmt,
    ops::{Deref, DerefMut},
    sync::Arc,
};
#[derive(Debug, PartialEq, Clone)]
pub enum AddressSpace {
    Builtin,
    User,
    Contract,
}
#[derive(Debug, PartialEq, Clone)]
pub enum AccountError {
    ReservedAddressSpace(Address),
    AddressSpaceMismatch(Address, AddressSpace),
    InvalidRlp(DecoderError),
}
#[derive(
    Clone,
    Debug,
    RlpDecodable,
    RlpEncodable,
    Ord,
    PartialOrd,
    Eq,
    PartialEq,
    Serialize,
    Deserialize,
)]
#[serde(rename_all = "camelCase")]
pub struct DepositInfo {
    pub amount: U256,
    pub deposit_time: U256,
    pub accumulated_interest_rate: U256,
}
#[derive(
    Clone,
    Debug,
    RlpDecodable,
    RlpEncodable,
    Ord,
    PartialOrd,
    Eq,
    PartialEq,
    Serialize,
    Deserialize,
)]
#[serde(rename_all = "camelCase")]
pub struct VoteStakeInfo {
    pub amount: U256,
    pub unlock_block_number: U256,
}
#[derive(Clone, Debug, Default, Ord, PartialOrd, Eq, PartialEq)]
pub struct DepositList(pub Vec<DepositInfo>);
impl Encodable for DepositList {
    fn rlp_append(&self, s: &mut RlpStream) { s.append_list(&self.0); }
}
impl Decodable for DepositList {
    fn decode(d: &Rlp) -> Result<Self, DecoderError> {
        let deposit_vec = d.as_list()?;
        Ok(DepositList(deposit_vec))
    }
}
impl Deref for DepositList {
    type Target = Vec<DepositInfo>;
    fn deref(&self) -> &Self::Target { &self.0 }
}
impl DerefMut for DepositList {
    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
}
#[derive(Clone, Debug, Default, Ord, PartialOrd, Eq, PartialEq)]
pub struct VoteStakeList(pub Vec<VoteStakeInfo>);
impl Encodable for VoteStakeList {
    fn rlp_append(&self, s: &mut RlpStream) { s.append_list(&self.0); }
}
impl Decodable for VoteStakeList {
    fn decode(d: &Rlp) -> Result<Self, DecoderError> {
        let vote_vec = d.as_list()?;
        Ok(VoteStakeList(vote_vec))
    }
}
impl Deref for VoteStakeList {
    type Target = Vec<VoteStakeInfo>;
    fn deref(&self) -> &Self::Target { &self.0 }
}
impl DerefMut for VoteStakeList {
    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
}
impl VoteStakeList {
    pub fn withdrawable_staking_balance(
        &self, staking_balance: U256, block_number: u64,
    ) -> U256 {
        let block_number: U256 = block_number.into();
        if !self.is_empty() {
            let idx = self
                .binary_search_by(|vote_info| {
                    vote_info.unlock_block_number.cmp(&(block_number + 1))
                })
                .unwrap_or_else(|x| x);
            if idx == self.len() {
                staking_balance
            } else {
                staking_balance - self[idx].amount
            }
        } else {
            staking_balance
        }
    }
    pub fn remove_expired_vote_stake_info(&mut self, block_number: u64) {
        let block_number: U256 = block_number.into();
        if !self.is_empty() && self[0].unlock_block_number <= block_number {
            let idx = self
                .binary_search_by(|vote_info| {
                    vote_info.unlock_block_number.cmp(&(block_number + 1))
                })
                .unwrap_or_else(|x| x);
            self.0 = self.split_off(idx)
        }
    }
    pub fn vote_lock(&mut self, amount: U256, unlock_block_number: u64) {
        let unlock_block_number: U256 = unlock_block_number.into();
        let mut updated = false;
        let mut updated_index = 0;
        match self.binary_search_by(|vote_info| {
            vote_info.unlock_block_number.cmp(&unlock_block_number)
        }) {
            Ok(index) => {
                if amount > self[index].amount {
                    self[index].amount = amount;
                    updated = true;
                    updated_index = index;
                }
            }
            Err(index) => {
                if index >= self.len() || self[index].amount < amount {
                    self.insert(
                        index,
                        VoteStakeInfo {
                            amount,
                            unlock_block_number,
                        },
                    );
                    updated = true;
                    updated_index = index;
                }
            }
        }
        if updated {
            let rest = self.split_off(updated_index);
            while !self.is_empty()
                && self.last().unwrap().amount <= rest[0].amount
            {
                self.pop();
            }
            self.extend_from_slice(&rest);
        }
    }
}
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
pub struct CodeInfo {
    pub code: Arc<Bytes>,
    pub owner: Address,
}
impl CodeInfo {
    #[inline]
    pub fn code_size(&self) -> usize { self.code.len() }
}
impl Encodable for CodeInfo {
    fn rlp_append(&self, stream: &mut RlpStream) {
        stream.begin_list(2).append(&*self.code).append(&self.owner);
    }
}
impl Decodable for CodeInfo {
    fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
        Ok(Self {
            code: Arc::new(rlp.val_at(0)?),
            owner: rlp.val_at(1)?,
        })
    }
}
#[derive(
    Clone,
    Debug,
    Ord,
    PartialOrd,
    Eq,
    PartialEq,
    Default,
    RlpDecodable,
    RlpEncodable,
)]
pub struct StoragePoints {
    pub unused: U256,
    pub used: U256,
}
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Default)]
pub struct SponsorInfo {
    pub sponsor_for_gas: Address,
    pub sponsor_for_collateral: Address,
    pub sponsor_gas_bound: U256,
    pub sponsor_balance_for_gas: U256,
    pub sponsor_balance_for_collateral: U256,
    pub storage_points: Option<StoragePoints>,
}
impl SponsorInfo {
    pub fn unused_storage_points(&self) -> U256 {
        self.storage_points
            .as_ref()
            .map_or(U256::zero(), |x| x.unused)
    }
}
impl Encodable for SponsorInfo {
    fn rlp_append(&self, s: &mut RlpStream) {
        match &self.storage_points {
            None => {
                s.begin_list(5);
                s.append(&self.sponsor_for_gas);
                s.append(&self.sponsor_for_collateral);
                s.append(&self.sponsor_gas_bound);
                s.append(&self.sponsor_balance_for_gas);
                s.append(&self.sponsor_balance_for_collateral);
            }
            Some(points) => {
                s.begin_list(6);
                s.append(&self.sponsor_for_gas);
                s.append(&self.sponsor_for_collateral);
                s.append(&self.sponsor_gas_bound);
                s.append(&self.sponsor_balance_for_gas);
                s.append(&self.sponsor_balance_for_collateral);
                s.append(points);
            }
        }
    }
}
impl Decodable for SponsorInfo {
    fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
        match rlp.item_count()? {
            5 => Ok(SponsorInfo {
                sponsor_for_gas: rlp.val_at(0)?,
                sponsor_for_collateral: rlp.val_at(1)?,
                sponsor_gas_bound: rlp.val_at(2)?,
                sponsor_balance_for_gas: rlp.val_at(3)?,
                sponsor_balance_for_collateral: rlp.val_at(4)?,
                storage_points: None,
            }),
            6 => Ok(SponsorInfo {
                sponsor_for_gas: rlp.val_at(0)?,
                sponsor_for_collateral: rlp.val_at(1)?,
                sponsor_gas_bound: rlp.val_at(2)?,
                sponsor_balance_for_gas: rlp.val_at(3)?,
                sponsor_balance_for_collateral: rlp.val_at(4)?,
                storage_points: Some(rlp.val_at(5)?),
            }),
            _ => Err(DecoderError::RlpInvalidLength),
        }
    }
}
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
pub struct Account {
    address_local_info: AddressWithSpace,
    pub balance: U256,
    pub nonce: U256,
    pub code_hash: H256,
    pub staking_balance: U256,
    pub collateral_for_storage: U256,
    pub accumulated_interest_return: U256,
    pub admin: Address,
    pub sponsor_info: SponsorInfo,
}
#[derive(RlpEncodable, RlpDecodable)]
pub struct BasicAccount {
    pub balance: U256,
    pub nonce: U256,
    pub staking_balance: U256,
    pub collateral_for_storage: U256,
    pub accumulated_interest_return: U256,
}
#[derive(RlpEncodable, RlpDecodable)]
pub struct ContractAccount {
    pub balance: U256,
    pub nonce: U256,
    pub code_hash: H256,
    pub staking_balance: U256,
    pub collateral_for_storage: U256,
    pub accumulated_interest_return: U256,
    pub admin: Address,
    pub sponsor_info: SponsorInfo,
}
#[derive(RlpEncodable, RlpDecodable)]
pub struct EthereumAccount {
    pub balance: U256,
    pub nonce: U256,
    pub code_hash: H256,
}
impl Account {
    pub fn address(&self) -> &AddressWithSpace { &self.address_local_info }
    pub fn set_address(&mut self, address: AddressWithSpace) {
        self.address_local_info = address;
    }
    pub fn new_empty(address: &AddressWithSpace) -> Account {
        Self::new_empty_with_balance(address, &U256::from(0), &U256::from(0))
    }
    pub fn new_empty_with_balance(
        address: &AddressWithSpace, balance: &U256, nonce: &U256,
    ) -> Account {
        Self {
            address_local_info: *address,
            balance: *balance,
            nonce: *nonce,
            code_hash: KECCAK_EMPTY,
            staking_balance: 0.into(),
            collateral_for_storage: 0.into(),
            accumulated_interest_return: 0.into(),
            admin: Address::zero(),
            sponsor_info: Default::default(),
        }
    }
    fn from_basic_account(address: Address, a: BasicAccount) -> Self {
        Self {
            address_local_info: address.with_native_space(),
            balance: a.balance,
            nonce: a.nonce,
            code_hash: KECCAK_EMPTY,
            staking_balance: a.staking_balance,
            collateral_for_storage: a.collateral_for_storage,
            accumulated_interest_return: a.accumulated_interest_return,
            admin: Address::zero(),
            sponsor_info: Default::default(),
        }
    }
    pub fn from_contract_account(address: Address, a: ContractAccount) -> Self {
        Self {
            address_local_info: address.with_native_space(),
            balance: a.balance,
            nonce: a.nonce,
            code_hash: a.code_hash,
            staking_balance: a.staking_balance,
            collateral_for_storage: a.collateral_for_storage,
            accumulated_interest_return: a.accumulated_interest_return,
            admin: a.admin,
            sponsor_info: a.sponsor_info,
        }
    }
    fn from_ethereum_account(address: Address, a: EthereumAccount) -> Self {
        let address = address.with_evm_space();
        Self {
            address_local_info: address,
            balance: a.balance,
            nonce: a.nonce,
            code_hash: a.code_hash,
            ..Self::new_empty(&address)
        }
    }
    pub fn to_basic_account(&self) -> BasicAccount {
        assert_eq!(self.address_local_info.space, Space::Native);
        BasicAccount {
            balance: self.balance,
            nonce: self.nonce,
            staking_balance: self.staking_balance,
            collateral_for_storage: self.collateral_for_storage,
            accumulated_interest_return: self.accumulated_interest_return,
        }
    }
    pub fn to_contract_account(&self) -> ContractAccount {
        assert_eq!(self.address_local_info.space, Space::Native);
        ContractAccount {
            balance: self.balance,
            nonce: self.nonce,
            code_hash: self.code_hash,
            staking_balance: self.staking_balance,
            collateral_for_storage: self.collateral_for_storage,
            accumulated_interest_return: self.accumulated_interest_return,
            admin: self.admin,
            sponsor_info: self.sponsor_info.clone(),
        }
    }
    pub fn to_evm_account(&self) -> EthereumAccount {
        assert_eq!(self.address_local_info.space, Space::Ethereum);
        assert!(self.staking_balance.is_zero());
        assert!(self.collateral_for_storage.is_zero());
        assert!(self.accumulated_interest_return.is_zero());
        assert!(self.admin.is_zero());
        assert_eq!(self.sponsor_info, Default::default());
        EthereumAccount {
            balance: self.balance,
            nonce: self.nonce,
            code_hash: self.code_hash,
        }
    }
    pub fn new_from_rlp(
        address: Address, rlp: &Rlp,
    ) -> Result<Self, AccountError> {
        let account = match rlp.item_count()? {
            8 => Self::from_contract_account(
                address,
                ContractAccount::decode(rlp)?,
            ),
            5 => Self::from_basic_account(address, BasicAccount::decode(rlp)?),
            3 => Self::from_ethereum_account(
                address,
                EthereumAccount::decode(rlp)?,
            ),
            _ => {
                return Err(AccountError::InvalidRlp(
                    DecoderError::RlpIncorrectListLen,
                ));
            }
        };
        Ok(account)
    }
}
impl Encodable for Account {
    fn rlp_append(&self, stream: &mut RlpStream) {
        if self.address_local_info.space == Space::Ethereum {
            stream.append_internal(&self.to_evm_account());
            return;
        }
        if self.code_hash != KECCAK_EMPTY && !self.code_hash.is_zero()
            || self.address_local_info.address.is_contract_address()
        {
            stream.append_internal(&self.to_contract_account());
        } else {
            stream.append_internal(&self.to_basic_account());
        }
    }
}
impl From<DecoderError> for AccountError {
    fn from(err: DecoderError) -> Self { AccountError::InvalidRlp(err) }
}
impl fmt::Display for AccountError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let msg = match self {
            AccountError::ReservedAddressSpace(address) => {
                format!("Address space is reserved for {:?}", address)
            }
            AccountError::AddressSpaceMismatch(address, address_space) => {
                format!(
                    "Address {:?} not in address space {:?}",
                    address, address_space
                )
            }
            AccountError::InvalidRlp(err) => {
                format!("Transaction has invalid RLP structure: {}.", err)
            }
        };
        f.write_fmt(format_args!("Account error ({})", msg))
    }
}
impl std::error::Error for AccountError {
    fn description(&self) -> &str { "Account error" }
}
#[cfg(test)]
fn test_random_account(
    type_bit: Option<u8>, non_empty_hash: bool, contract_type: bool,
) {
    let mut address = Address::random();
    address.set_address_type_bits(type_bit.unwrap_or(0x40));
    let admin = Address::random();
    let sponsor_info = SponsorInfo {
        sponsor_for_gas: Address::random(),
        sponsor_for_collateral: Address::random(),
        sponsor_balance_for_gas: U256::from(123),
        sponsor_balance_for_collateral: U256::from(124),
        sponsor_gas_bound: U256::from(2),
        storage_points: None,
    };
    let code_hash = if non_empty_hash {
        H256::random()
    } else {
        KECCAK_EMPTY
    };
    let account = if contract_type {
        Account::from_contract_account(
            address,
            ContractAccount {
                balance: 1000.into(),
                nonce: 123.into(),
                code_hash,
                staking_balance: 10000000.into(),
                collateral_for_storage: 23.into(),
                accumulated_interest_return: 456.into(),
                admin,
                sponsor_info,
            },
        )
    } else {
        Account::from_basic_account(
            address,
            BasicAccount {
                balance: 1000.into(),
                nonce: 123.into(),
                staking_balance: 10000000.into(),
                collateral_for_storage: 23.into(),
                accumulated_interest_return: 456.into(),
            },
        )
    };
    assert_eq!(
        account,
        Account::new_from_rlp(
            account.address_local_info.address,
            &Rlp::new(&account.rlp_bytes()),
        )
        .unwrap()
    );
}
#[test]
fn test_account_serde() {
    test_random_account(Some(0x10), false, false);
    test_random_account(Some(0x80), true, true);
    test_random_account(Some(0x80), false, true);
    test_random_account(None, false, false);
    test_random_account(Some(0x80), false, false);
    test_random_account(None, true, true);
    test_random_account(Some(0x80), true, true);
}