use crate::Address;
use crate::Digest;
use crate::EpochId;
use crate::GasCostSummary;
use crate::TypeTag;
use crate::execution_status::ExecutionStatus;
use crate::object::Owner;
use crate::object::Version;

/// Version 2 of TransactionEffects
///
/// # BCS
///
/// The BCS serialized form for this type is defined by the following ABNF:
///
/// ```text
/// effects-v2 = execution-status
///              u64                                ; epoch
///              gas-cost-summary
///              digest                             ; transaction digest
///              (option u32)                       ; gas object index
///              (option digest)                    ; events digest
///              (vector digest)                    ; list of transaction dependencies
///              u64                                ; lamport version
///              (vector changed-object)
///              (vector unchanged-consensus-object)
///              (option digest)                    ; auxiliary data digest
/// ```
#[derive(Eq, PartialEq, Clone, Debug)]
#[cfg_attr(
    feature = "serde",
    derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
pub struct TransactionEffectsV2 {
    /// The status of the execution
    pub status: ExecutionStatus,

    /// The epoch when this transaction was executed.
    pub epoch: EpochId,

    /// The gas used by this transaction
    pub gas_used: GasCostSummary,

    /// The transaction digest
    pub transaction_digest: Digest,

    /// The updated gas object reference, as an index into the `changed_objects` vector.
    /// Having a dedicated field for convenient access.
    /// System transaction that don't require gas will leave this as None.
    pub gas_object_index: Option<u32>,

    /// The digest of the events emitted during execution,
    /// can be None if the transaction does not emit any event.
    pub events_digest: Option<Digest>,

    /// The set of transaction digests this transaction depends on.
    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=5).lift()))]
    pub dependencies: Vec<Digest>,

    /// The version number of all the written Move objects by this transaction.
    pub lamport_version: Version,

    /// Objects whose state are changed in the object store.
    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
    pub changed_objects: Vec<ChangedObject>,

    /// Consensus objects that are not mutated in this transaction. Unlike owned objects,
    /// read-only consensus objects' version are not committed in the transaction,
    /// and in order for a node to catch up and execute it without consensus sequencing,
    /// the version needs to be committed in the effects.
    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
    pub unchanged_consensus_objects: Vec<UnchangedConsensusObject>,

    /// Auxiliary data that are not protocol-critical, generated as part of the effects but are stored separately.
    /// Storing it separately allows us to avoid bloating the effects with data that are not critical.
    /// It also provides more flexibility on the format and type of the data.
    pub auxiliary_data_digest: Option<Digest>,
}

/// Input/output state of an object that was changed during execution
///
/// # BCS
///
/// The BCS serialized form for this type is defined by the following ABNF:
///
/// ```text
/// changed-object = address object-in object-out id-operation
/// ```
#[derive(Eq, PartialEq, Clone, Debug)]
#[cfg_attr(
    feature = "serde",
    derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
pub struct ChangedObject {
    /// Id of the object
    pub object_id: Address,

    /// State of the object in the store prior to this transaction.
    pub input_state: ObjectIn,

    /// State of the object in the store after this transaction.
    pub output_state: ObjectOut,

    /// Whether this object ID is created or deleted in this transaction.
    /// This information isn't required by the protocol but is useful for providing more detailed
    /// semantics on object changes.
    pub id_operation: IdOperation,
}

/// A Consensus object that wasn't changed during execution
///
/// # BCS
///
/// The BCS serialized form for this type is defined by the following ABNF:
///
/// ```text
/// unchanged-consensus-object = address unchanged-consensus-object-kind
/// ```
#[derive(Eq, PartialEq, Clone, Debug)]
#[cfg_attr(
    feature = "serde",
    derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
pub struct UnchangedConsensusObject {
    pub object_id: Address,
    pub kind: UnchangedConsensusKind,
}

/// Type of unchanged consensus object
///
/// # BCS
///
/// The BCS serialized form for this type is defined by the following ABNF:
///
/// ```text
/// unchanged-consensus-object-kind =  read-only-root
///                                 =/ mutate-deleted
///                                 =/ read-deleted
///                                 =/ canceled
///                                 =/ per-epoch-config
///
/// read-only-root                           = %x00 u64 digest
/// mutate-deleted                           = %x01 u64
/// read-deleted                             = %x02 u64
/// canceled                                 = %x03 u64
/// per-epoch-config                         = %x04
/// per-epoch-config-with-sequence-number    = %x05 u64
/// ```
#[derive(Eq, PartialEq, Clone, Debug)]
#[cfg_attr(
    feature = "serde",
    derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
#[non_exhaustive]
pub enum UnchangedConsensusKind {
    /// Read-only consensus objects from the input. We don't really need ObjectDigest
    /// for protocol correctness, but it will make it easier to verify untrusted read.
    ReadOnlyRoot { version: Version, digest: Digest },

    /// Deleted consensus objects that appear mutably/owned in the input.
    MutateDeleted { version: Version },

    /// Deleted consensus objects that appear as read-only in the input.
    ReadDeleted { version: Version },

    /// Consensus objects in canceled transaction. The sequence number embed cancellation reason.
    Canceled { version: Version },

    /// Read of a per-epoch config object that should remain the same during an epoch.
    PerEpochConfig,
}

/// State of an object prior to execution
///
/// If an object exists (at root-level) in the store prior to this transaction,
/// it should be Exist, otherwise it's NonExist, e.g. wrapped objects should be
/// NonExist.
///
/// # BCS
///
/// The BCS serialized form for this type is defined by the following ABNF:
///
/// ```text
/// object-in = object-in-not-exist / object-in-exist
///
/// object-in-not-exist = %x00
/// object-in-exist     = %x01 u64 digest owner
/// ```
#[derive(Eq, PartialEq, Clone, Debug)]
#[cfg_attr(
    feature = "serde",
    derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
#[non_exhaustive]
pub enum ObjectIn {
    NotExist,

    /// The old version, digest and owner.
    Exist {
        version: Version,
        digest: Digest,
        owner: Owner,
    },
}

/// State of an object after execution
///
/// # BCS
///
/// The BCS serialized form for this type is defined by the following ABNF:
///
/// ```text
/// object-out  =  object-out-not-exist
///             =/ object-out-object-write
///             =/ object-out-package-write
///
///
/// object-out-not-exist        = %x00
/// object-out-object-write     = %x01 digest owner
/// object-out-package-write    = %x02 version digest
/// ```
#[derive(Eq, PartialEq, Clone, Debug)]
#[cfg_attr(
    feature = "serde",
    derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
#[non_exhaustive]
pub enum ObjectOut {
    /// Same definition as in ObjectIn.
    NotExist,

    /// Any written object, including all of mutated, created, unwrapped today.
    ObjectWrite { digest: Digest, owner: Owner },

    /// Packages writes need to be tracked separately with version because
    /// we don't use lamport version for package publish and upgrades.
    PackageWrite { version: Version, digest: Digest },

    /// This isn't an object write, but a special write to an accumulator.
    AccumulatorWrite(AccumulatorWrite),
}

#[derive(Eq, PartialEq, Clone, Debug)]
#[cfg_attr(
    feature = "serde",
    derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
pub struct AccumulatorWrite {
    /// Accumulator objects are named by an address (can be an account address or a UID)
    /// and a type tag.
    address: Address,
    type_: TypeTag,

    /// The operation to be applied to the accumulator.
    operation: AccumulatorOperation,
    /// The value to be merged into or split from the accumulator.
    value: AccumulatorValue,
}

impl AccumulatorWrite {
    pub fn new(
        address: Address,
        type_: TypeTag,
        operation: AccumulatorOperation,
        value: AccumulatorValue,
    ) -> Self {
        Self {
            address,
            type_,
            operation,
            value,
        }
    }

    pub fn address(&self) -> &Address {
        &self.address
    }

    pub fn accumulator_type(&self) -> &TypeTag {
        &self.type_
    }

    pub fn operation(&self) -> AccumulatorOperation {
        self.operation
    }

    pub fn value(&self) -> &AccumulatorValue {
        &self.value
    }
}

#[derive(Eq, PartialEq, Clone, Copy, Debug)]
#[cfg_attr(
    feature = "serde",
    derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
#[non_exhaustive]
pub enum AccumulatorOperation {
    /// Merge the value into the accumulator.
    Merge,
    /// Split the value from the accumulator.
    Split,
}

#[derive(Eq, PartialEq, Clone, Debug)]
#[cfg_attr(
    feature = "serde",
    derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
#[non_exhaustive]
pub enum AccumulatorValue {
    Integer(u64),

    IntegerTuple(u64, u64),

    /// A non-empty list of `(event_index_in_transaction, event_digest)` pairs.
    ///
    /// This variant is used for authenticated event streams, where an accumulator
    /// records which events (by index within their transaction) have occurred,
    /// allowing cryptographic verification of the event log.
    EventDigest(
        #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(1..=4).lift()))]
        Vec<(u64, Digest)>,
    ),
}

/// Defines what happened to an ObjectId during execution
///
/// # BCS
///
/// The BCS serialized form for this type is defined by the following ABNF:
///
/// ```text
/// id-operation =  id-operation-none
///              =/ id-operation-created
///              =/ id-operation-deleted
///
/// id-operation-none       = %x00
/// id-operation-created    = %x01
/// id-operation-deleted    = %x02
/// ```
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
#[cfg_attr(
    feature = "serde",
    derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
#[non_exhaustive]
pub enum IdOperation {
    None,
    Created,
    Deleted,
}

impl TransactionEffectsV2 {
    /// The status of the execution
    pub fn status(&self) -> &ExecutionStatus {
        &self.status
    }

    /// The epoch when this transaction was executed.
    pub fn epoch(&self) -> EpochId {
        self.epoch
    }

    /// The gas used in this transaction.
    pub fn gas_summary(&self) -> &GasCostSummary {
        &self.gas_used
    }
}
