// Copyright 2015-2020 Parity Technologies (UK) Ltd.
// This file is part of OpenEthereum.

// OpenEthereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// OpenEthereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with OpenEthereum.  If not, see <http://www.gnu.org/licenses/>.

//! Utilities and helpers for transaction dispatch.

mod full;
mod prospective_signer;

#[cfg(any(test, feature = "accounts"))]
mod signing;
#[cfg(not(any(test, feature = "accounts")))]
mod signing {
    use super::*;
    use v1::helpers::errors;

    /// Dummy signer implementation
    #[derive(Debug, Clone)]
    pub struct Signer;

    impl Signer {
        /// Create new instance of dummy signer (accept any AccountProvider)
        pub fn new<T>(_ap: T) -> Self {
            Signer
        }
    }

    impl super::Accounts for Signer {
        fn sign_transaction(
            &self,
            _filled: FilledTransactionRequest,
            _chain_id: Option<u64>,
            _nonce: U256,
            _password: SignWith,
        ) -> Result<WithToken<SignedTransaction>> {
            Err(errors::account("Signing unsupported", "See #9997"))
        }

        fn sign_message(
            &self,
            _address: Address,
            _password: SignWith,
            _hash: SignMessage,
        ) -> Result<WithToken<Signature>> {
            Err(errors::account("Signing unsupported", "See #9997"))
        }

        fn decrypt(
            &self,
            _address: Address,
            _password: SignWith,
            _data: Bytes,
        ) -> Result<WithToken<Bytes>> {
            Err(errors::account("Signing unsupported", "See #9997"))
        }

        fn supports_prospective_signing(&self, _address: &Address, _password: &SignWith) -> bool {
            false
        }

        fn default_account(&self) -> Address {
            Default::default()
        }

        fn is_unlocked(&self, _address: &Address) -> bool {
            false
        }
    }
}

pub use self::{full::FullDispatcher, signing::Signer};
pub use v1::helpers::nonce::Reservations;

use std::{fmt::Debug, ops::Deref, sync::Arc};

use bytes::Bytes;
use crypto::publickey::Signature;
use ethcore::{client::BlockChainClient, miner::MinerService};
use ethereum_types::{Address, H256, H520, U256};
use ethkey::Password;
use hash::keccak;
use types::{
    transaction::{PendingTransaction, SignedTransaction},
    BlockNumber,
};

use jsonrpc_core::{
    futures::{future, Future, IntoFuture},
    BoxFuture, Error, Result,
};
use v1::{
    helpers::{ConfirmationPayload, FilledTransactionRequest, TransactionRequest},
    types::{
        Bytes as RpcBytes, ConfirmationPayload as RpcConfirmationPayload, ConfirmationResponse,
        DecryptRequest as RpcDecryptRequest, EIP191SignRequest as RpcSignRequest,
        EthSignRequest as RpcEthSignRequest, RichRawTransaction as RpcRichRawTransaction,
    },
};

/// Has the capability to dispatch, sign, and decrypt.
///
/// Requires a clone implementation, with the implication that it be cheap;
/// usually just bumping a reference count or two.
pub trait Dispatcher: Send + Sync + Clone {
    // TODO: when ATC exist, use zero-cost
    // type Out<T>: IntoFuture<T, Error>

    /// Fill optional fields of a transaction request, fetching gas price but not nonce.
    fn fill_optional_fields(
        &self,
        request: TransactionRequest,
        default_sender: Address,
        force_nonce: bool,
    ) -> BoxFuture<FilledTransactionRequest>;

    /// Sign the given transaction request without dispatching, fetching appropriate nonce.
    fn sign<P>(
        &self,
        filled: FilledTransactionRequest,
        signer: &Arc<dyn Accounts>,
        password: SignWith,
        post_sign: P,
    ) -> BoxFuture<P::Item>
    where
        P: PostSign + 'static,
        <P::Out as futures::future::IntoFuture>::Future: Send;

    /// Converts a `SignedTransaction` into `RichRawTransaction`
    fn enrich(&self, _: SignedTransaction) -> RpcRichRawTransaction;

    /// "Dispatch" a local transaction.
    fn dispatch_transaction(&self, signed_transaction: PendingTransaction) -> Result<H256>;
}

/// Payload to sign
pub enum SignMessage {
    /// Eth-sign kind data (requires prefixing)
    Data(Bytes),
    /// Prefixed data hash
    Hash(H256),
}

/// Abstract transaction signer.
///
/// NOTE This signer is semi-correct, it's a temporary measure to avoid moving too much code.
/// If accounts are ultimately removed all password-dealing endpoints will be wiped out.
pub trait Accounts: Send + Sync {
    /// Sign given filled transaction request for the specified chain_id.
    fn sign_transaction(
        &self,
        filled: FilledTransactionRequest,
        chain_id: Option<u64>,
        nonce: U256,
        password: SignWith,
    ) -> Result<WithToken<SignedTransaction>>;

    /// Sign given message.
    fn sign_message(
        &self,
        address: Address,
        password: SignWith,
        hash: SignMessage,
    ) -> Result<WithToken<Signature>>;

    /// Decrypt given message.
    fn decrypt(
        &self,
        address: Address,
        password: SignWith,
        data: Bytes,
    ) -> Result<WithToken<Bytes>>;

    /// Returns `true` if the accounts can sign multiple times.
    fn supports_prospective_signing(&self, address: &Address, password: &SignWith) -> bool;

    /// Returns default account.
    fn default_account(&self) -> Address;

    /// Returns true if account is unlocked (i.e. can sign without a password)
    fn is_unlocked(&self, address: &Address) -> bool;
}

/// action to execute after signing
/// e.g importing a transaction into the chain
pub trait PostSign: Send {
    /// item that this PostSign returns
    type Item: Send;
    /// incase you need to perform async PostSign actions
    type Out: IntoFuture<Item = Self::Item, Error = Error> + Send;
    /// perform an action with the signed transaction
    fn execute(self, signer: WithToken<SignedTransaction>) -> Self::Out;
}

impl PostSign for () {
    type Item = WithToken<SignedTransaction>;
    type Out = Result<Self::Item>;
    fn execute(self, signed: WithToken<SignedTransaction>) -> Self::Out {
        Ok(signed)
    }
}

impl<F: Send, T: Send> PostSign for F
where
    F: FnOnce(WithToken<SignedTransaction>) -> Result<T>,
{
    type Item = T;
    type Out = Result<Self::Item>;
    fn execute(self, signed: WithToken<SignedTransaction>) -> Self::Out {
        (self)(signed)
    }
}

/// Single-use account token.
pub type AccountToken = Password;

/// Values used to unlock accounts for signing.
#[derive(Clone, PartialEq)]
pub enum SignWith {
    /// Nothing -- implies the account is already unlocked.
    Nothing,
    /// Unlock with password.
    Password(Password),
    /// Unlock with single-use token.
    Token(AccountToken),
}

impl SignWith {
    #[cfg(any(test, feature = "accounts"))]
    fn is_password(&self) -> bool {
        if let SignWith::Password(_) = *self {
            true
        } else {
            false
        }
    }
}

/// A value, potentially accompanied by a signing token.
pub enum WithToken<T> {
    /// No token.
    No(T),
    /// With token.
    Yes(T, AccountToken),
}

impl<T: Debug> Deref for WithToken<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        match *self {
            WithToken::No(ref v) => v,
            WithToken::Yes(ref v, _) => v,
        }
    }
}

impl<T: Debug> WithToken<T> {
    /// Map the value with the given closure, preserving the token.
    pub fn map<S, F>(self, f: F) -> WithToken<S>
    where
        S: Debug,
        F: FnOnce(T) -> S,
    {
        match self {
            WithToken::No(v) => WithToken::No(f(v)),
            WithToken::Yes(v, token) => WithToken::Yes(f(v), token),
        }
    }

    /// Convert into inner value, ignoring possible token.
    pub fn into_value(self) -> T {
        match self {
            WithToken::No(v) => v,
            WithToken::Yes(v, _) => v,
        }
    }

    /// Convert the `WithToken` into a tuple.
    pub fn into_tuple(self) -> (T, Option<AccountToken>) {
        match self {
            WithToken::No(v) => (v, None),
            WithToken::Yes(v, token) => (v, Some(token)),
        }
    }
}

impl<T: Debug> From<(T, AccountToken)> for WithToken<T> {
    fn from(tuple: (T, AccountToken)) -> Self {
        WithToken::Yes(tuple.0, tuple.1)
    }
}

impl<T: Debug> From<(T, Option<AccountToken>)> for WithToken<T> {
    fn from(tuple: (T, Option<AccountToken>)) -> Self {
        match tuple.1 {
            Some(token) => WithToken::Yes(tuple.0, token),
            None => WithToken::No(tuple.0),
        }
    }
}

/// Execute a confirmation payload.
pub fn execute<D: Dispatcher + 'static>(
    dispatcher: D,
    signer: &Arc<dyn Accounts>,
    payload: ConfirmationPayload,
    pass: SignWith,
) -> BoxFuture<WithToken<ConfirmationResponse>> {
    match payload {
        ConfirmationPayload::SendTransaction(request) => {
            let condition = request.condition.clone().map(Into::into);
            let cloned_dispatcher = dispatcher.clone();
            let post_sign = move |with_token_signed: WithToken<SignedTransaction>| {
                let (signed, token) = with_token_signed.into_tuple();
                let signed_transaction = PendingTransaction::new(signed, condition);
                cloned_dispatcher
                    .dispatch_transaction(signed_transaction)
                    .map(|hash| (hash, token))
            };

            Box::new(
                dispatcher
                    .sign(request, &signer, pass, post_sign)
                    .map(|(hash, token)| {
                        WithToken::from((ConfirmationResponse::SendTransaction(hash), token))
                    }),
            )
        }
        ConfirmationPayload::SignTransaction(request) => Box::new(
            dispatcher
                .sign(request, &signer, pass, ())
                .map(move |result| {
                    result
                        .map(move |tx| dispatcher.enrich(tx))
                        .map(ConfirmationResponse::SignTransaction)
                }),
        ),
        ConfirmationPayload::EthSignMessage(address, data) => {
            let res = signer
                .sign_message(address, pass, SignMessage::Data(data))
                .map(|result| {
                    result
                        .map(|s| H520(s.into_electrum()))
                        .map(ConfirmationResponse::Signature)
                });

            Box::new(future::done(res))
        }
        ConfirmationPayload::SignMessage(address, data) => {
            let res = signer
                .sign_message(address, pass, SignMessage::Hash(data))
                .map(|result| {
                    result
                        .map(|rsv| H520(rsv.into_electrum()))
                        .map(ConfirmationResponse::Signature)
                });

            Box::new(future::done(res))
        }
        ConfirmationPayload::Decrypt(address, data) => {
            let res = signer
                .decrypt(address, pass, data)
                .map(|result| result.map(RpcBytes).map(ConfirmationResponse::Decrypt));
            Box::new(future::done(res))
        }
    }
}

/// Returns a eth_sign-compatible hash of data to sign.
/// The data is prepended with special message to prevent
/// malicious DApps from using the function to sign forged transactions.
pub fn eth_data_hash(mut data: Bytes) -> H256 {
    let mut message_data = format!("\x19Ethereum Signed Message:\n{}", data.len()).into_bytes();
    message_data.append(&mut data);
    keccak(message_data)
}

/// Extract the default gas price from a client and miner.
pub fn default_gas_price<C, M>(client: &C, miner: &M, percentile: usize) -> U256
where
    C: BlockChainClient,
    M: MinerService,
{
    client
        .gas_price_corpus(100)
        .percentile(percentile)
        .cloned()
        .unwrap_or_else(|| miner.sensible_gas_price())
}

/// Extract the default priority gas price from a client and miner.
pub fn default_max_priority_fee_per_gas<C, M>(
    client: &C,
    miner: &M,
    percentile: usize,
    eip1559_transition: BlockNumber,
) -> U256
where
    C: BlockChainClient,
    M: MinerService,
{
    client
        .priority_gas_price_corpus(100, eip1559_transition)
        .percentile(percentile)
        .cloned()
        .unwrap_or_else(|| miner.sensible_max_priority_fee())
}

/// Convert RPC confirmation payload to signer confirmation payload.
/// May need to resolve in the future to fetch things like gas price.
pub fn from_rpc<D>(
    payload: RpcConfirmationPayload,
    default_account: Address,
    dispatcher: &D,
) -> BoxFuture<ConfirmationPayload>
where
    D: Dispatcher,
{
    match payload {
        RpcConfirmationPayload::SendTransaction(request) => Box::new(
            dispatcher
                .fill_optional_fields(request.into(), default_account, false)
                .map(ConfirmationPayload::SendTransaction),
        ),
        RpcConfirmationPayload::SignTransaction(request) => Box::new(
            dispatcher
                .fill_optional_fields(request.into(), default_account, false)
                .map(ConfirmationPayload::SignTransaction),
        ),
        RpcConfirmationPayload::Decrypt(RpcDecryptRequest { address, msg }) => Box::new(
            future::ok(ConfirmationPayload::Decrypt(address, msg.into())),
        ),
        RpcConfirmationPayload::EthSignMessage(RpcEthSignRequest { address, data }) => Box::new(
            future::ok(ConfirmationPayload::EthSignMessage(address, data.into())),
        ),
        RpcConfirmationPayload::EIP191SignMessage(RpcSignRequest { address, data }) => {
            Box::new(future::ok(ConfirmationPayload::SignMessage(address, data)))
        }
    }
}
