// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

use crate::{
    account_address::AccountAddress,
    account_config::aptos_root_address,
    event::{EventHandle, EventKey},
};
use aptos_crypto::HashValue;
use move_deps::move_core_types::{
    ident_str,
    identifier::IdentStr,
    move_resource::{MoveResource, MoveStructType},
};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};

/// Struct that will be persisted on chain to store the information of the current block.
///
/// The flow will look like following:
/// 1. The executor will pass this struct to VM at the end of a block proposal.
/// 2. The VM will use this struct to create a special system transaction that will emit an event
///    represents the information of the current block. This transaction can't
///    be emitted by regular users and is generated by each of the validators on the fly. Such
///    transaction will be executed before all of the user-submitted transactions in the blocks.
/// 3. Once that special resource is modified, the other user transactions can read the consensus
///    info by calling into the read method of that resource, which would thus give users the
///    information such as the current leader.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct BlockMetadata {
    id: HashValue,
    epoch: u64,
    round: u64,
    previous_block_votes: Vec<bool>,
    proposer: AccountAddress,
    failed_proposer_indices: Vec<u32>,
    timestamp_usecs: u64,
}

impl BlockMetadata {
    pub fn new(
        id: HashValue,
        epoch: u64,
        round: u64,
        previous_block_votes: Vec<bool>,
        proposer: AccountAddress,
        failed_proposer_indices: Vec<u32>,
        timestamp_usecs: u64,
    ) -> Self {
        Self {
            id,
            epoch,
            round,
            previous_block_votes,
            proposer,
            failed_proposer_indices,
            timestamp_usecs,
        }
    }

    pub fn id(&self) -> HashValue {
        self.id
    }

    pub fn into_inner(self) -> (u64, u64, u64, Vec<bool>, AccountAddress, Vec<u32>) {
        (
            self.epoch,
            self.round,
            self.timestamp_usecs,
            self.previous_block_votes.clone(),
            self.proposer,
            self.failed_proposer_indices,
        )
    }

    pub fn timestamp_usecs(&self) -> u64 {
        self.timestamp_usecs
    }

    pub fn proposer(&self) -> AccountAddress {
        self.proposer
    }

    pub fn previous_block_votes(&self) -> &Vec<bool> {
        &self.previous_block_votes
    }

    pub fn epoch(&self) -> u64 {
        self.epoch
    }

    pub fn round(&self) -> u64 {
        self.round
    }
}

pub fn new_block_event_key() -> EventKey {
    EventKey::new_from_address(&aptos_root_address(), 6)
}

/// The path to the new block event handle under a Block::BlockMetadata resource.
pub static NEW_BLOCK_EVENT_PATH: Lazy<Vec<u8>> = Lazy::new(|| {
    let mut path = BlockResource::resource_path();
    // it can be anything as long as it's referenced in AccountState::get_event_handle_by_query_path
    path.extend_from_slice(b"/new_block_event/");
    path
});

#[derive(Deserialize, Serialize)]
pub struct BlockResource {
    height: u64,
    new_block_events: EventHandle,
}

impl BlockResource {
    pub fn new_block_events(&self) -> &EventHandle {
        &self.new_block_events
    }

    pub fn height(&self) -> u64 {
        self.height
    }
}

impl MoveStructType for BlockResource {
    const MODULE_NAME: &'static IdentStr = ident_str!("Block");
    const STRUCT_NAME: &'static IdentStr = ident_str!("BlockMetadata");
}

impl MoveResource for BlockResource {}

#[derive(Clone, Deserialize, Serialize)]
pub struct NewBlockEvent {
    epoch: u64,
    round: u64,
    previous_block_votes: Vec<bool>,
    proposer: AccountAddress,
    failed_proposer_indices: Vec<u64>,
    timestamp: u64,
}

impl NewBlockEvent {
    pub fn new(
        epoch: u64,
        round: u64,
        previous_block_votes: Vec<bool>,
        proposer: AccountAddress,
        failed_proposer_indices: Vec<u64>,
        timestamp: u64,
    ) -> Self {
        Self {
            epoch,
            round,
            previous_block_votes,
            proposer,
            failed_proposer_indices,
            timestamp,
        }
    }

    pub fn epoch(&self) -> u64 {
        self.epoch
    }

    pub fn round(&self) -> u64 {
        self.round
    }

    pub fn previous_block_votes(&self) -> &Vec<bool> {
        &self.previous_block_votes
    }

    pub fn proposer(&self) -> AccountAddress {
        self.proposer
    }

    /// The list of indices in the validators list,
    /// of consecutive proposers from the immediately preceeding
    /// rounds that didn't produce a successful block
    pub fn failed_proposer_indices(&self) -> &Vec<u64> {
        &self.failed_proposer_indices
    }
}
