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

// Parity 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.

// Parity 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 Parity.  If not, see <http://www.gnu.org/licenses/>.

use ethereum_types::U256;
use transaction::{self, PendingTransaction};
use txpool;

use pool::{verifier, TransactionQueue, PrioritizationStrategy};

pub mod tx;
pub mod client;

use self::tx::{Tx, TxExt, PairExt};
use self::client::TestClient;

fn new_queue() -> TransactionQueue {
	TransactionQueue::new(
		txpool::Options {
			max_count: 3,
			max_per_sender: 3,
			max_mem_usage: 50
		},
		verifier::Options {
			minimal_gas_price: 1.into(),
			block_gas_limit: 1_000_000.into(),
			tx_gas_limit: 1_000_000.into(),
		},
		PrioritizationStrategy::GasPriceOnly,
	)
}

#[test]
fn should_return_correct_nonces_when_dropped_because_of_limit() {
	// given
	let txq = TransactionQueue::new(
		txpool::Options {
			max_count: 3,
			max_per_sender: 1,
			max_mem_usage: 50
		},
		verifier::Options {
			minimal_gas_price: 1.into(),
			block_gas_limit: 1_000_000.into(),
			tx_gas_limit: 1_000_000.into(),
		},
		PrioritizationStrategy::GasPriceOnly,
	);
	let (tx1, tx2) = Tx::gas_price(2).signed_pair();
	let sender = tx1.sender();
	let nonce = tx1.nonce;

	// when
	let result = txq.import(TestClient::new(), vec![tx1, tx2].local());
	assert_eq!(result, vec![Ok(()), Err(transaction::Error::LimitReached)]);
	assert_eq!(txq.status().status.transaction_count, 1);

	// then
	assert_eq!(txq.next_nonce(TestClient::new(), &sender), Some(nonce + 1.into()));

	// when
	let tx1 = Tx::gas_price(2).signed();
	let tx2 = Tx::gas_price(2).signed();
	let tx3 = Tx::gas_price(1).signed();
	let tx4 = Tx::gas_price(3).signed();
	let res = txq.import(TestClient::new(), vec![tx1, tx2].local());
	let res2 = txq.import(TestClient::new(), vec![tx3, tx4].local());

	// then
	assert_eq!(res, vec![Ok(()), Ok(())]);
	assert_eq!(res2, vec![Err(transaction::Error::LimitReached), Ok(())]);
	assert_eq!(txq.status().status.transaction_count, 3);
	// First inserted transacton got dropped because of limit
	assert_eq!(txq.next_nonce(TestClient::new(), &sender), None);
}

#[test]
fn should_handle_same_transaction_imported_twice_with_different_state_nonces() {
	// given
	let txq = new_queue();
	let (tx, tx2) = Tx::default().signed_replacement();
	let hash = tx2.hash();
	let client = TestClient::new().with_nonce(122);

	// First insert one transaction to future
	let res = txq.import(client.clone(), vec![tx].local());
	assert_eq!(res, vec![Ok(())]);
	// next_nonce === None -> transaction is in future
	assert_eq!(txq.next_nonce(client.clone(), &tx2.sender()), None);

	// now import second transaction to current
	let res = txq.import(TestClient::new(), vec![tx2.local()]);

	// and then there should be only one transaction in current (the one with higher gas_price)
	assert_eq!(res, vec![Ok(())]);
	assert_eq!(txq.status().status.transaction_count, 1);
	let top = txq.pending(TestClient::new(), 0, 0, None);
	assert_eq!(top[0].hash, hash);
}

#[test]
fn should_move_all_transactions_from_future() {
	// given
	let txq = new_queue();
	let txs = Tx::default().signed_pair();
	let (hash, hash2) = txs.hash();
	let (tx, tx2) = txs;
	let client = TestClient::new().with_nonce(122);

	// First insert one transaction to future
	let res = txq.import(client.clone(), vec![tx.local()]);
	assert_eq!(res, vec![Ok(())]);
	// next_nonce === None -> transaction is in future
	assert_eq!(txq.next_nonce(client.clone(), &tx2.sender()), None);

	// now import second transaction to current
	let res = txq.import(client.clone(), vec![tx2.local()]);

	// then
	assert_eq!(res, vec![Ok(())]);
	assert_eq!(txq.status().status.transaction_count, 2);
	let top = txq.pending(TestClient::new(), 0, 0, None);
	assert_eq!(top[0].hash, hash);
	assert_eq!(top[1].hash, hash2);
}

#[test]
fn should_drop_transactions_from_senders_without_balance() {
	// given
	let txq = new_queue();
	let tx = Tx::default().signed();
	let client = TestClient::new().with_balance(1);

	// when
	let res = txq.import(client, vec![tx.local()]);

	// then
	assert_eq!(res, vec![Err(transaction::Error::InsufficientBalance {
		balance: U256::from(1),
		cost: U256::from(21_100),
	})]);
	assert_eq!(txq.status().status.transaction_count, 0);
}

#[test]
fn should_not_import_transaction_below_min_gas_price_threshold_if_external() {
	// given
	let txq = new_queue();
	let tx = Tx::default();
	txq.set_verifier_options(verifier::Options {
		minimal_gas_price: 3.into(),
		..Default::default()
	});

	// when
	let res = txq.import(TestClient::new(), vec![tx.signed().unverified()]);

	// then
	assert_eq!(res, vec![Err(transaction::Error::InsufficientGasPrice {
		minimal: U256::from(3),
		got: U256::from(1),
	})]);
	assert_eq!(txq.status().status.transaction_count, 0);
}

#[test]
fn should_import_transaction_below_min_gas_price_threshold_if_local() {
	// given
	let txq = new_queue();
	let tx = Tx::default();
	txq.set_verifier_options(verifier::Options {
		minimal_gas_price: 3.into(),
		..Default::default()
	});

	// when
	let res = txq.import(TestClient::new(), vec![tx.signed().local()]);

	// then
	assert_eq!(res, vec![Ok(())]);
	assert_eq!(txq.status().status.transaction_count, 1);
}

#[test]
fn should_import_txs_from_same_sender() {
	// given
	let txq = new_queue();

	let txs = Tx::default().signed_pair();
	let (hash, hash2) = txs.hash();

	// when
	txq.import(TestClient::new(), txs.local().into_vec());

	// then
	let top = txq.pending(TestClient::new(), 0 ,0, None);
	assert_eq!(top[0].hash, hash);
	assert_eq!(top[1].hash, hash2);
	assert_eq!(top.len(), 2);
}

#[test]
fn should_prioritize_local_transactions_within_same_nonce_height() {
	// given
	let txq = new_queue();
	let tx = Tx::default().signed();
	// the second one has same nonce but higher `gas_price`
	let tx2 = Tx::gas_price(2).signed();
	let (hash, hash2) = (tx.hash(), tx2.hash());
	let client = TestClient::new().with_local(&tx.sender());

	// when
	// first insert the one with higher gas price
	let res = txq.import(client.clone(), vec![tx.local(), tx2.unverified()]);
	assert_eq!(res, vec![Ok(()), Ok(())]);

	// then
	let top = txq.pending(client, 0, 0, None);
	assert_eq!(top[0].hash, hash); // local should be first
	assert_eq!(top[1].hash, hash2);
	assert_eq!(top.len(), 2);
}

#[test]
fn should_prioritize_reimported_transactions_within_same_nonce_height() {
	// given
	let txq = new_queue();
	let tx = Tx::default().signed();
	// the second one has same nonce but higher `gas_price`
	let tx2 = Tx::gas_price(2).signed();
	let (hash, hash2) = (tx.hash(), tx2.hash());

	// when
	// first insert local one with higher gas price
	// then the one with lower gas price, but from retracted block
	let res = txq.import(TestClient::new(), vec![tx2.unverified(), tx.retracted()]);
	assert_eq!(res, vec![Ok(()), Ok(())]);

	// then
	let top = txq.pending(TestClient::new(), 0, 0, None);
	assert_eq!(top[0].hash, hash); // retracted should be first
	assert_eq!(top[1].hash, hash2);
	assert_eq!(top.len(), 2);
}

#[test]
fn should_not_prioritize_local_transactions_with_different_nonce_height() {
	// given
	let txq = new_queue();
	let txs = Tx::default().signed_pair();
	let (hash, hash2) = txs.hash();
	let (tx, tx2) = txs;

	// when
	let res = txq.import(TestClient::new(), vec![tx.unverified(), tx2.local()]);
	assert_eq!(res, vec![Ok(()), Ok(())]);

	// then
	let top = txq.pending(TestClient::new(), 0, 0, None);
	assert_eq!(top[0].hash, hash);
	assert_eq!(top[1].hash, hash2);
	assert_eq!(top.len(), 2);
}

#[test]
fn should_put_transaction_to_futures_if_gap_detected() {
	// given
	let txq = new_queue();
	let (tx, _, tx2) = Tx::default().signed_triple();
	let hash = tx.hash();

	// when
	let res = txq.import(TestClient::new(), vec![tx, tx2].local());

	// then
	assert_eq!(res, vec![Ok(()), Ok(())]);
	let top = txq.pending(TestClient::new(), 0, 0, None);
	assert_eq!(top.len(), 1);
	assert_eq!(top[0].hash, hash);
}

#[test]
fn should_handle_min_block() {
	// given
	let txq = new_queue();

	let (tx, tx2) = Tx::default().signed_pair();

	// when
	let res = txq.import(TestClient::new(), vec![
		verifier::Transaction::Local(PendingTransaction::new(tx, transaction::Condition::Number(1).into())),
		tx2.local()
	]);
	assert_eq!(res, vec![Ok(()), Ok(())]);

	// then
	let top = txq.pending(TestClient::new(), 0, 0, None);
	assert_eq!(top.len(), 0);
	let top = txq.pending(TestClient::new(), 1, 0, None);
	assert_eq!(top.len(), 2);
}

#[test]
fn should_correctly_update_futures_when_removing() {
	// given
	let txq = new_queue();
	let txs= Tx::default().signed_pair();

	let res = txq.import(TestClient::new().with_nonce(121), txs.local().into_vec());
	assert_eq!(res, vec![Ok(()), Ok(())]);
	assert_eq!(txq.status().status.transaction_count, 2);

	// when
	txq.cull(TestClient::new().with_nonce(125));
	// should remove both transactions since they are stalled

	// then
	assert_eq!(txq.status().status.transaction_count, 0);
}

#[test]
fn should_move_transactions_if_gap_filled() {
	// given
	let txq = new_queue();
	let (tx, tx1, tx2) = Tx::default().signed_triple();

	let res = txq.import(TestClient::new(), vec![tx, tx2].local());
	assert_eq!(res, vec![Ok(()), Ok(())]);
	assert_eq!(txq.status().status.transaction_count, 2);
	assert_eq!(txq.pending(TestClient::new(), 0, 0, None).len(), 1);

	// when
	let res = txq.import(TestClient::new(), vec![tx1.local()]);
	assert_eq!(res, vec![Ok(())]);

	// then
	assert_eq!(txq.status().status.transaction_count, 3);
	assert_eq!(txq.pending(TestClient::new(), 0, 0, None).len(), 3);
}

#[test]
fn should_remove_transaction() {
	// given
	let txq = new_queue();
	let (tx, _, tx2) = Tx::default().signed_triple();

	let res = txq.import(TestClient::default(), vec![tx, tx2].local());
	assert_eq!(res, vec![Ok(()), Ok(())]);
	assert_eq!(txq.status().status.transaction_count, 2);
	assert_eq!(txq.pending(TestClient::new(), 0, 0, None).len(), 1);

	// when
	txq.cull(TestClient::new().with_nonce(124));
	assert_eq!(txq.status().status.transaction_count, 1);
	assert_eq!(txq.pending(TestClient::new().with_nonce(125), 0, 0, None).len(), 1);
	txq.cull(TestClient::new().with_nonce(126));

	// then
	assert_eq!(txq.status().status.transaction_count, 0);
}

#[test]
fn should_move_transactions_to_future_if_gap_introduced() {
	// given
	let txq = new_queue();
	let (tx, tx2) = Tx::default().signed_pair();
	let hash = tx.hash();
	let tx3 = Tx::default().signed();

	let res = txq.import(TestClient::new(), vec![tx3, tx2].local());
	assert_eq!(res, vec![Ok(()), Ok(())]);
	assert_eq!(txq.status().status.transaction_count, 2);
	assert_eq!(txq.pending(TestClient::new(), 0, 0, None).len(), 1);

	let res = txq.import(TestClient::new(), vec![tx].local());
	assert_eq!(res, vec![Ok(())]);
	assert_eq!(txq.status().status.transaction_count, 3);
	assert_eq!(txq.pending(TestClient::new(), 0, 0, None).len(), 3);

	// when
	txq.remove(vec![&hash], true);

	// then
	assert_eq!(txq.status().status.transaction_count, 2);
	assert_eq!(txq.pending(TestClient::new(), 0, 0, None).len(), 1);
}

#[test]
fn should_clear_queue() {
	// given
	let txq = new_queue();
	let txs = Tx::default().signed_pair();

	// add
	txq.import(TestClient::new(), txs.local().into_vec());
	assert_eq!(txq.status().status.transaction_count, 2);

	// when
	txq.clear();

	// then
	assert_eq!(txq.status().status.transaction_count, 0);
}

#[test]
fn should_prefer_current_transactions_when_hitting_the_limit() {
	// given
	let txq = TransactionQueue::new(
		txpool::Options {
			max_count: 1,
			max_per_sender: 2,
			max_mem_usage: 50
		},
		verifier::Options {
			minimal_gas_price: 1.into(),
			block_gas_limit: 1_000_000.into(),
			tx_gas_limit: 1_000_000.into(),
		},
		PrioritizationStrategy::GasPriceOnly,
	);
	let (tx, tx2) = Tx::default().signed_pair();
	let hash = tx.hash();
	let sender = tx.sender();

	let res = txq.import(TestClient::new(), vec![tx2.unverified()]);
	assert_eq!(res, vec![Ok(())]);
	assert_eq!(txq.status().status.transaction_count, 1);

	// when
	let res = txq.import(TestClient::new(), vec![tx.unverified()]);

	// then
	assert_eq!(res, vec![Ok(())]);
	assert_eq!(txq.status().status.transaction_count, 1);

	let top = txq.pending(TestClient::new(), 0, 0, None);
	assert_eq!(top.len(), 1);
	assert_eq!(top[0].hash, hash);
	assert_eq!(txq.next_nonce(TestClient::new(), &sender), Some(124.into()));
}

#[test]
fn should_drop_transactions_with_old_nonces() {
	let txq = new_queue();
	let tx = Tx::default().signed();

	// when
	let res = txq.import(TestClient::new().with_nonce(125), vec![tx.unverified()]);

	// then
	assert_eq!(res, vec![Err(transaction::Error::Old)]);
	assert_eq!(txq.status().status.transaction_count, 0);
}

#[test]
fn should_not_insert_same_transaction_twice() {
	// given
	let txq = new_queue();
	let (_tx1, tx2) = Tx::default().signed_pair();
	let res = txq.import(TestClient::new(), vec![tx2.clone().local()]);
	assert_eq!(res, vec![Ok(())]);
	assert_eq!(txq.status().status.transaction_count, 1);

	// when
	let res = txq.import(TestClient::new(), vec![tx2.local()]);

	// then
	assert_eq!(res, vec![Err(transaction::Error::AlreadyImported)]);
	assert_eq!(txq.status().status.transaction_count, 1);
}

#[test]
fn should_accept_same_transaction_twice_if_removed() {
	// given
	let txq = new_queue();
	let txs = Tx::default().signed_pair();
	let (tx1, _) = txs.clone();
	let (hash, _) = txs.hash();


	let res = txq.import(TestClient::new(), txs.local().into_vec());
	assert_eq!(res, vec![Ok(()), Ok(())]);
	assert_eq!(txq.status().status.transaction_count, 2);
	assert_eq!(txq.pending(TestClient::new(), 0, 0, None).len(), 2);

	// when
	txq.remove(vec![&hash], true);
	assert_eq!(txq.status().status.transaction_count, 1);
	assert_eq!(txq.pending(TestClient::new(), 0, 0, None).len(), 0);

	let res = txq.import(TestClient::new(), vec![tx1].local());
	assert_eq!(res, vec![Ok(())]);

	// then
	assert_eq!(txq.status().status.transaction_count, 2);
	assert_eq!(txq.pending(TestClient::new(), 0, 0, None).len(), 2);
}

#[test]
fn should_not_replace_same_transaction_if_the_fee_is_less_than_minimal_bump() {
	// given
	let txq = new_queue();
	let (tx, tx2) = Tx::gas_price(20).signed_replacement();
	let (tx3, tx4) = Tx::gas_price(1).signed_replacement();
	let client = TestClient::new().with_balance(1_000_000);

	// when
	let res = txq.import(client.clone(), vec![tx, tx3].local());
	assert_eq!(res, vec![Ok(()), Ok(())]);

	let res = txq.import(client.clone(), vec![tx2, tx4].local());

	// then
	assert_eq!(res, vec![Err(transaction::Error::TooCheapToReplace), Ok(())]);
	assert_eq!(txq.status().status.transaction_count, 2);
	assert_eq!(txq.pending(client.clone(), 0, 0, None)[0].signed().gas_price, U256::from(20));
	assert_eq!(txq.pending(client.clone(), 0, 0, None)[1].signed().gas_price, U256::from(2));
}

#[test]
fn should_return_none_when_transaction_from_given_address_does_not_exist() {
	// given
	let txq = new_queue();

	// then
	assert_eq!(txq.next_nonce(TestClient::new(), &Default::default()), None);
}

#[test]
fn should_return_correct_nonce_when_transactions_from_given_address_exist() {
	// given
	let txq = new_queue();
	let tx = Tx::default().signed();
	let from = tx.sender();
	let nonce = tx.nonce;

	// when
	txq.import(TestClient::new(), vec![tx.local()]);

	// then
	assert_eq!(txq.next_nonce(TestClient::new(), &from), Some(nonce + 1.into()));
}

#[test]
fn should_return_valid_last_nonce_after_cull() {
	// given
	let txq = new_queue();
	let (tx1, _, tx2) = Tx::default().signed_triple();
	let sender = tx1.sender();

	// when
	// Second should go to future
	let res = txq.import(TestClient::new(), vec![tx1, tx2].local());
	assert_eq!(res, vec![Ok(()), Ok(())]);
	// Now block is imported
	let client = TestClient::new().with_nonce(124);
	txq.cull(client.clone());
	// tx2 should be not be promoted to current
	assert_eq!(txq.pending(client.clone(), 0, 0, None).len(), 0);

	// then
	assert_eq!(txq.next_nonce(client.clone(), &sender), None);
	assert_eq!(txq.next_nonce(client.with_nonce(125), &sender), Some(126.into()));
}

#[test]
fn should_return_true_if_there_is_local_transaction_pending() {
	// given
	let txq = new_queue();
	let (tx1, tx2) = Tx::default().signed_pair();
	assert_eq!(txq.has_local_pending_transactions(), false);
	let client = TestClient::new().with_local(&tx1.sender());

	// when
	let res = txq.import(client.clone(), vec![tx1.unverified(), tx2.local()]);
	assert_eq!(res, vec![Ok(()), Ok(())]);

	// then
	assert_eq!(txq.has_local_pending_transactions(), true);
}

#[test]
fn should_reject_transactions_below_base_gas() {
	// given
	let txq = new_queue();
	let tx = Tx::default().signed();

	// when
	let res = txq.import(TestClient::new().with_gas_required(100_001), vec![tx].local());

	// then
	assert_eq!(res, vec![Err(transaction::Error::InsufficientGas {
		minimal: 100_001.into(),
		got: 21_000.into(),
	})]);
}

#[test]
fn should_remove_out_of_date_transactions_occupying_queue() {
	// given
	let txq = TransactionQueue::new(
		txpool::Options {
			max_count: 105,
			max_per_sender: 3,
			max_mem_usage: 5_000_000,
		},
		verifier::Options {
			minimal_gas_price: 10.into(),
			..Default::default()
		},
		PrioritizationStrategy::GasPriceOnly,
	);
	// that transaction will be occupying the queue
	let (_, tx) = Tx::default().signed_pair();
	let res = txq.import(TestClient::new(), vec![tx.local()]);
	assert_eq!(res, vec![Ok(())]);
	// This should not clear the transaction (yet)
	txq.cull(TestClient::new());
	assert_eq!(txq.status().status.transaction_count, 1);

	// Now insert at least 100 transactions to have the other one marked as future.
	for _ in 0..34 {
		let (tx1, tx2, tx3) = Tx::default().signed_triple();
		txq.import(TestClient::new(), vec![tx1, tx2, tx3].local());
	}
	assert_eq!(txq.status().status.transaction_count, 103);

	// when
	txq.cull(TestClient::new());

	// then
	assert_eq!(txq.status().status.transaction_count, 102);
}

#[test]
fn should_accept_local_transactions_below_min_gas_price() {
	// given
	let txq = TransactionQueue::new(
		txpool::Options {
			max_count: 3,
			max_per_sender: 3,
			max_mem_usage: 50
		},
		verifier::Options {
			minimal_gas_price: 10.into(),
			..Default::default()
		},
		PrioritizationStrategy::GasPriceOnly,
	);
	let tx = Tx::gas_price(1).signed();

	// when
	let res = txq.import(TestClient::new(), vec![tx.local()]);
	assert_eq!(res, vec![Ok(())]);

	// then
	assert_eq!(txq.pending(TestClient::new(), 0, 0, None).len(), 1);
}

#[test]
fn should_accept_local_service_transaction() {
	// given
	let txq = new_queue();
	let tx = Tx::gas_price(0).signed();

	// when
	let res = txq.import(
		TestClient::new()
			.with_local(&tx.sender()),
		vec![tx.local()]
	);
	assert_eq!(res, vec![Ok(())]);

	// then
	assert_eq!(txq.pending(TestClient::new(), 0, 0, None).len(), 1);
}

#[test]
fn should_not_accept_external_service_transaction_if_sender_not_certified() {
	// given
	let txq = new_queue();
	let tx1 = Tx::gas_price(0).signed().unverified();
	let tx2 = Tx::gas_price(0).signed().retracted();
	let tx3 = Tx::gas_price(0).signed().unverified();

	// when
	let res = txq.import(TestClient::new(), vec![tx1, tx2]);
	assert_eq!(res, vec![
		Err(transaction::Error::InsufficientGasPrice {
			minimal: 1.into(),
			got: 0.into(),
		}),
		Err(transaction::Error::InsufficientGasPrice {
			minimal: 1.into(),
			got: 0.into(),
		}),
	]);

	// then
	let res = txq.import(TestClient::new().with_service_transaction(), vec![tx3]);
	assert_eq!(res, vec![Ok(())]);
}

#[test]
fn should_not_return_transactions_over_nonce_cap() {
	// given
	let txq = new_queue();
	let (tx1, tx2, tx3) = Tx::default().signed_triple();
	let res = txq.import(
		TestClient::new(),
		vec![tx1, tx2, tx3].local()
	);
	assert_eq!(res, vec![Ok(()), Ok(()), Ok(())]);

	// when
	let all = txq.pending(TestClient::new(), 0, 0, None);
	// This should invalidate the cache!
	let limited = txq.pending(TestClient::new(), 0, 0, Some(123.into()));


	// then
	assert_eq!(all.len(), 3);
	assert_eq!(limited.len(), 1);
}

#[test]
fn should_clear_cache_after_timeout_for_local() {
	// given
	let txq = new_queue();
	let (tx, tx2) = Tx::default().signed_pair();
	let res = txq.import(TestClient::new(), vec![
		verifier::Transaction::Local(PendingTransaction::new(tx, transaction::Condition::Timestamp(1000).into())),
		tx2.local()
	]);
	assert_eq!(res, vec![Ok(()), Ok(())]);

	// This should populate cache and set timestamp to 1
	// when
	assert_eq!(txq.pending(TestClient::new(), 0, 1, None).len(), 0);
	assert_eq!(txq.pending(TestClient::new(), 0, 1000, None).len(), 0);

	// This should invalidate the cache and trigger transaction ready.
	// then
	assert_eq!(txq.pending(TestClient::new(), 0, 1002, None).len(), 2);
}
