//! Types for the Parity Transaction-Trace Filtering API
use crate::types::{Address, BlockNumber, Bytes, H160, H256, U256};
use bytes::{Buf, BufMut};
use open_fastrlp::DecodeError;
use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream};
use serde::{Deserialize, Serialize};
use std::fmt;

/// Trace filter
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct TraceFilter {
    /// From block
    #[serde(rename = "fromBlock", skip_serializing_if = "Option::is_none")]
    from_block: Option<BlockNumber>,
    /// To block
    #[serde(rename = "toBlock", skip_serializing_if = "Option::is_none")]
    to_block: Option<BlockNumber>,
    /// From address
    #[serde(rename = "fromAddress", skip_serializing_if = "Option::is_none")]
    from_address: Option<Vec<Address>>,
    /// To address
    #[serde(rename = "toAddress", skip_serializing_if = "Option::is_none")]
    to_address: Option<Vec<Address>>,
    /// Output offset
    #[serde(skip_serializing_if = "Option::is_none")]
    after: Option<usize>,
    /// Output amount
    #[serde(skip_serializing_if = "Option::is_none")]
    count: Option<usize>,
}

impl TraceFilter {
    /// Sets From block
    #[allow(clippy::wrong_self_convention)]
    #[must_use]
    pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
        self.from_block = Some(block.into());
        self
    }

    /// Sets to block
    #[allow(clippy::wrong_self_convention)]
    #[must_use]
    pub fn to_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
        self.to_block = Some(block.into());
        self
    }

    /// Sets to address
    #[allow(clippy::wrong_self_convention)]
    #[must_use]
    pub fn to_address(mut self, address: Vec<H160>) -> Self {
        self.to_address = Some(address);
        self
    }

    /// Sets from address
    #[allow(clippy::wrong_self_convention)]
    #[must_use]
    pub fn from_address(mut self, address: Vec<H160>) -> Self {
        self.from_address = Some(address);
        self
    }

    /// Sets after offset
    #[must_use]
    pub fn after(mut self, after: usize) -> Self {
        self.after = Some(after);
        self
    }

    /// Sets amount of traces to display
    #[must_use]
    pub fn count(mut self, count: usize) -> Self {
        self.count = Some(count);
        self
    }
}

// `LocalizedTrace` in Parity
/// Trace-Filtering API trace type
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
pub struct Trace {
    /// Action
    pub action: Action,
    /// Result
    pub result: Option<Res>,
    /// Trace address
    #[serde(rename = "traceAddress")]
    pub trace_address: Vec<usize>,
    /// Subtraces
    pub subtraces: usize,
    /// Transaction position
    #[serde(rename = "transactionPosition")]
    pub transaction_position: Option<usize>,
    /// Transaction hash
    #[serde(rename = "transactionHash")]
    pub transaction_hash: Option<H256>,
    /// Block Number
    #[serde(rename = "blockNumber")]
    pub block_number: u64,
    /// Block Hash
    #[serde(rename = "blockHash")]
    pub block_hash: H256,
    /// Action Type
    #[serde(rename = "type")]
    pub action_type: ActionType,
    /// Error, See also [`TraceError`]
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub error: Option<String>,
}

/// Response
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum Res {
    /// Call
    Call(CallResult),
    /// Create
    Create(CreateResult),
    /// None
    None,
}

impl Default for Res {
    fn default() -> Res {
        Res::None
    }
}

/// Action
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(untagged, rename_all = "lowercase")]
pub enum Action {
    /// Call
    Call(Call),
    /// Create
    Create(Create),
    /// Suicide
    Suicide(Suicide),
    /// Reward
    Reward(Reward),
}

/// An external action type.
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ActionType {
    /// Contract call.
    Call,
    /// Contract creation.
    Create,
    /// Contract suicide.
    Suicide,
    /// A block reward.
    Reward,
}

/// Call Result
#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize, Serialize)]
pub struct CallResult {
    /// Gas used
    #[serde(rename = "gasUsed")]
    pub gas_used: U256,
    /// Output bytes
    pub output: Bytes,
}

/// Create Result
#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize, Serialize)]
pub struct CreateResult {
    /// Gas used
    #[serde(rename = "gasUsed")]
    pub gas_used: U256,
    /// Code
    pub code: Bytes,
    /// Assigned address
    pub address: Address,
}

/// Call response
#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize, Serialize)]
pub struct Call {
    /// Sender
    pub from: Address,
    /// Recipient
    pub to: Address,
    /// Transferred Value
    pub value: U256,
    /// Gas
    pub gas: U256,
    /// Input data
    pub input: Bytes,
    /// The type of the call.
    #[serde(rename = "callType")]
    pub call_type: CallType,
}

/// Call type.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum CallType {
    /// None
    #[serde(rename = "none")]
    None,
    /// Call
    #[serde(rename = "call")]
    Call,
    /// Call code
    #[serde(rename = "callcode")]
    CallCode,
    /// Delegate call
    #[serde(rename = "delegatecall")]
    DelegateCall,
    /// Static call
    #[serde(rename = "staticcall")]
    StaticCall,
}

impl Default for CallType {
    fn default() -> CallType {
        CallType::None
    }
}

/// Create response
#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize, Serialize)]
pub struct Create {
    /// Sender
    pub from: Address,
    /// Value
    pub value: U256,
    /// Gas
    pub gas: U256,
    /// Initialization code
    pub init: Bytes,
}

/// Suicide
#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize, Serialize)]
pub struct Suicide {
    /// Address.
    pub address: Address,
    /// Refund address.
    #[serde(rename = "refundAddress")]
    pub refund_address: Address,
    /// Balance.
    pub balance: U256,
}

/// Reward action
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct Reward {
    /// Author's address.
    pub author: Address,
    /// Reward amount.
    pub value: U256,
    /// Reward type.
    #[serde(rename = "rewardType")]
    pub reward_type: RewardType,
}

/// Reward type.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum RewardType {
    /// Block
    #[serde(rename = "block")]
    Block,
    /// Uncle
    #[serde(rename = "uncle")]
    Uncle,
    /// EmptyStep (AuthorityRound)
    #[serde(rename = "emptyStep")]
    EmptyStep,
    /// External (attributed as part of an external protocol)
    #[serde(rename = "external")]
    External,
}

/// Trace evm errors.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum TraceError {
    /// `OutOfGas` is returned when transaction execution runs out of gas.
    OutOfGas = 0,
    /// `BadJumpDestination` is returned when execution tried to move
    /// to position that wasn't marked with JUMPDEST instruction
    BadJumpDestination = 1,
    /// `BadInstructions` is returned when given instruction is not supported
    BadInstruction = 2,
    /// `StackUnderflow` when there is not enough stack elements to execute instruction
    StackUnderflow = 3,
    /// When execution would exceed defined Stack Limit
    OutOfStack = 4,
    /// Returned on evm internal error. Should never be ignored during development.
    /// Likely to cause consensus issues.
    Internal = 5,
    /// When builtin contract failed on input data
    BuiltIn = 6,
    /// When execution tries to modify the state in static context
    MutableCallInStaticContext = 7,
    /// Wasm error
    Wasm = 8,
    /// Contract tried to access past the return data buffer.
    OutOfBounds = 9,
    /// Execution has been reverted with REVERT instruction.
    Reverted = 10,
    /// When there is not enough subroutine stack elements to return from
    SubStackUnderflow = 11,
    /// When execution would exceed defined subroutine Stack Limit
    OutOfSubStack = 12,
    /// When the code walks into a subroutine, that is not allowed
    InvalidSubEntry = 13,
    /// When invalid code was attempted to deploy
    InvalidCode = 14,
}

impl TraceError {
    /// Converts the given byte into the corresponding error variant.
    pub fn from_u8(val: u8) -> Result<Self, u8> {
        let val = match val {
            0 => TraceError::OutOfGas,
            1 => TraceError::BadJumpDestination,
            2 => TraceError::BadInstruction,
            3 => TraceError::StackUnderflow,
            4 => TraceError::OutOfStack,
            5 => TraceError::Internal,
            6 => TraceError::BuiltIn,
            7 => TraceError::MutableCallInStaticContext,
            8 => TraceError::Wasm,
            9 => TraceError::OutOfBounds,
            10 => TraceError::Reverted,
            11 => TraceError::SubStackUnderflow,
            12 => TraceError::OutOfSubStack,
            13 => TraceError::InvalidSubEntry,
            14 => TraceError::InvalidCode,
            _ => return Err(val),
        };
        Ok(val)
    }
}

impl fmt::Display for TraceError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let message = match *self {
            TraceError::OutOfGas => "Out of gas",
            TraceError::BadJumpDestination => "Bad jump destination",
            TraceError::BadInstruction => "Bad instruction",
            TraceError::StackUnderflow => "Stack underflow",
            TraceError::OutOfStack => "Out of stack",
            TraceError::SubStackUnderflow => "Subroutine stack underflow",
            TraceError::OutOfSubStack => "Subroutine stack overflow",
            TraceError::BuiltIn => "Built-in failed",
            TraceError::InvalidSubEntry => "Invalid subroutine entry",
            TraceError::InvalidCode => "Invalid code",
            TraceError::Wasm => "Wasm runtime error",
            TraceError::Internal => "Internal error",
            TraceError::MutableCallInStaticContext => "Mutable Call In Static Context",
            TraceError::OutOfBounds => "Out of bounds",
            TraceError::Reverted => "Reverted",
        };
        message.fmt(f)
    }
}

impl open_fastrlp::Encodable for TraceError {
    fn encode(&self, out: &mut dyn BufMut) {
        out.put_u8((*self) as u8)
    }
}

impl Encodable for TraceError {
    fn rlp_append(&self, s: &mut RlpStream) {
        let val = (*self) as u8;
        s.append_internal(&val);
    }
}

impl Decodable for TraceError {
    fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
        let value: u8 = rlp.as_val()?;
        Self::from_u8(value).map_err(|_| DecoderError::Custom("Invalid error code"))
    }
}

impl open_fastrlp::Decodable for TraceError {
    fn decode(buf: &mut &[u8]) -> Result<Self, DecodeError> {
        if buf.is_empty() {
            return Err(DecodeError::InputTooShort)
        }
        let val = buf[0];
        let val = Self::from_u8(val).map_err(|_| DecodeError::Custom("Invalid error code"))?;
        buf.advance(1);
        Ok(val)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    const EXAMPLE_TRACE_CALL: &str = r#"{
        "action": {
            "callType": "call",
            "from": "0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb",
            "gas": "0x63ab9",
            "input": "0xb9f256cd000000000000000000000000fb6916095ca1df60bb79ce92ce3ea74c37c5d3590000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000e85468697320697320746865206f6666696369616c20457468657265756d20466f756e646174696f6e20546970204a61722e20466f722065766572792061626f76652061206365727461696e2076616c756520646f6e6174696f6e207765276c6c2063726561746520616e642073656e6420746f20796f752061206272616e64206e657720556e69636f726e20546f6b656e2028f09fa684292e20436865636b2074686520756e69636f726e2070726963652062656c6f77202831206574686572203d20313030302066696e6e6579292e205468616e6b7320666f722074686520737570706f72742100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
            "to": "0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359",
            "value": "0x0"
        },
        "blockHash": "0x6474a53a9ebf72d306a1406ec12ded12e210b6c3141b4373bfb3a3cea987dfb8",
        "blockNumber": 988775,
        "result": {
            "gasUsed": "0x4b419",
            "output": "0x0000000000000000000000000000000000000000000000000000000000000000"
        },
        "subtraces": 1,
        "traceAddress": [],
        "transactionHash": "0x342c284238149db221f9d87db87f90ffad7ac0aac57c0c480142f4c21b63f652",
        "transactionPosition": 1,
        "type": "call"
    }"#;

    const EXAMPLE_TRACE_CREATE: &str = r#"{
        "action": {
            "from": "0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb",
            "gas": "0x63ab9",
            "init": "0xb9f256cd000000000000000000000000fb6916095ca1df60bb79ce92ce3ea74c37c5d3590000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000e85468697320697320746865206f6666696369616c20457468657265756d20466f756e646174696f6e20546970204a61722e20466f722065766572792061626f76652061206365727461696e2076616c756520646f6e6174696f6e207765276c6c2063726561746520616e642073656e6420746f20796f752061206272616e64206e657720556e69636f726e20546f6b656e2028f09fa684292e20436865636b2074686520756e69636f726e2070726963652062656c6f77202831206574686572203d20313030302066696e6e6579292e205468616e6b7320666f722074686520737570706f72742100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
            "value": "0x0"
        },
        "blockHash": "0x6474a53a9ebf72d306a1406ec12ded12e210b6c3141b4373bfb3a3cea987dfb8",
        "blockNumber": 988775,
        "result": {
            "gasUsed": "0x4b419",
            "output": "0x0000000000000000000000000000000000000000000000000000000000000000"
        },
        "subtraces": 1,
        "traceAddress": [],
        "transactionHash": "0x342c284238149db221f9d87db87f90ffad7ac0aac57c0c480142f4c21b63f652",
        "transactionPosition": 1,
        "type": "create"
    }"#;

    const EXAMPLE_TRACE_SUICIDE: &str = r#"{
        "action": {
            "address": "0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb",
            "refundAddress": "0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359",
            "balance": "0x0"
        },
        "blockHash": "0x6474a53a9ebf72d306a1406ec12ded12e210b6c3141b4373bfb3a3cea987dfb8",
        "blockNumber": 988775,
        "result": {
            "gasUsed": "0x4b419",
            "output": "0x0000000000000000000000000000000000000000000000000000000000000000"
        },
        "subtraces": 1,
        "traceAddress": [],
        "transactionHash": "0x342c284238149db221f9d87db87f90ffad7ac0aac57c0c480142f4c21b63f652",
        "transactionPosition": 1,
        "type": "suicide"
    }"#;

    const EXAMPLE_TRACE_REWARD: &str = r#"{
        "action": {
            "author": "0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb",
            "value": "0x0",
            "rewardType": "block"
        },
        "blockHash": "0x6474a53a9ebf72d306a1406ec12ded12e210b6c3141b4373bfb3a3cea987dfb8",
        "blockNumber": 988775,
        "result": {
            "gasUsed": "0x4b419",
            "output": "0x0000000000000000000000000000000000000000000000000000000000000000"
        },
        "subtraces": 1,
        "traceAddress": [],
        "transactionHash": "0x342c284238149db221f9d87db87f90ffad7ac0aac57c0c480142f4c21b63f652",
        "transactionPosition": 1,
        "type": "reward"
    }"#;

    #[test]
    fn test_deserialize_trace() {
        let _trace: Trace = serde_json::from_str(EXAMPLE_TRACE_CALL).unwrap();
        let _trace: Trace = serde_json::from_str(EXAMPLE_TRACE_CREATE).unwrap();
        let _trace: Trace = serde_json::from_str(EXAMPLE_TRACE_SUICIDE).unwrap();
        let _trace: Trace = serde_json::from_str(EXAMPLE_TRACE_REWARD).unwrap();
    }
}
