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

// Open Ethereum 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.

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

//! Types pertaining to sending messages and finding routes through the chain. Used mostly by the
//! ChainNotify trait.

use bytes::Bytes;
use ethereum_types::H256;
use crate::{
	import_route::ImportRoute,
};
use std::time::Duration;
use std::collections::HashMap;

/// Messages to broadcast via chain
pub enum ChainMessageType {
	/// Consensus message
	Consensus(Vec<u8>),
	/// Message with private transaction
	PrivateTransaction(H256, Vec<u8>),
	/// Message with signed private transaction
	SignedPrivateTransaction(H256, Vec<u8>),
	/// Private state request for the particular private contract
	PrivateStateRequest(H256),
}

/// Route type to indicate whether it is enacted or retracted.
#[derive(Clone)]
pub enum ChainRouteType {
	/// Enacted block
	Enacted,
	/// Retracted block
	Retracted
}

/// A complete chain enacted retracted route.
#[derive(Default, Clone)]
pub struct ChainRoute {
	route: Vec<(H256, ChainRouteType)>,
	enacted: Vec<H256>,
	retracted: Vec<H256>,
}

impl<'a> From<&'a [ImportRoute]> for ChainRoute {
	fn from(import_results: &'a [ImportRoute]) -> ChainRoute {
		ChainRoute::new(import_results.iter().flat_map(|route| {
			route.retracted.iter().map(|h| (*h, ChainRouteType::Retracted))
				.chain(route.enacted.iter().map(|h| (*h, ChainRouteType::Enacted)))
		}).collect())
	}
}

impl ChainRoute {
	/// Create a new ChainRoute based on block hash and route type pairs.
	pub fn new(route: Vec<(H256, ChainRouteType)>) -> Self {
		let (enacted, retracted) = Self::to_enacted_retracted(&route);

		Self { route, enacted, retracted }
	}

	/// Gather all non-duplicate enacted and retracted blocks.
	fn to_enacted_retracted(route: &[(H256, ChainRouteType)]) -> (Vec<H256>, Vec<H256>) {
		fn map_to_vec(map: Vec<(H256, bool)>) -> Vec<H256> {
			map.into_iter().map(|(k, _v)| k).collect()
		}

		// Because we are doing multiple inserts some of the blocks that were enacted in import `k`
		// could be retracted in import `k+1`. This is why to understand if after all inserts
		// the block is enacted or retracted we iterate over all routes and at the end final state
		// will be in the hashmap
		let map = route.iter().fold(HashMap::new(), |mut map, route| {
			match &route.1 {
				&ChainRouteType::Enacted => {
					map.insert(route.0, true);
				},
				&ChainRouteType::Retracted => {
					map.insert(route.0, false);
				},
			}
			map
		});

		// Split to enacted retracted (using hashmap value)
		let (enacted, retracted) = map.into_iter().partition(|&(_k, v)| v);
		// And convert tuples to keys
		(map_to_vec(enacted), map_to_vec(retracted))
	}

	/// Consume route and return the enacted retracted form.
	pub fn into_enacted_retracted(self) -> (Vec<H256>, Vec<H256>) {
		(self.enacted, self.retracted)
	}

	/// All non-duplicate enacted blocks.
	pub fn enacted(&self) -> &[H256] {
		&self.enacted
	}

	/// All non-duplicate retracted blocks.
	pub fn retracted(&self) -> &[H256] {
		&self.retracted
	}

	/// All blocks in the route.
	pub fn route(&self) -> &[(H256, ChainRouteType)] {
		&self.route
	}
}

/// Used by `ChainNotify` `new_blocks()` and contains information about new blocks imported to the
/// chain.
pub struct NewBlocks {
	/// Imported blocks
	pub imported: Vec<H256>,
	/// Invalid blocks
	pub invalid: Vec<H256>,
	/// Route
	pub route: ChainRoute,
	/// Sealed
	pub sealed: Vec<H256>,
	/// Block bytes. Only used in EthSync.
	pub proposed: Vec<Bytes>,
	/// Duration
	pub duration: Duration,
	/// Has more blocks to import
	pub has_more_blocks_to_import: bool,
}

impl NewBlocks {
	/// Constructor
	pub fn new(
		imported: Vec<H256>,
		invalid: Vec<H256>,
		route: ChainRoute,
		sealed: Vec<H256>,
		proposed: Vec<Bytes>,
		duration: Duration,
		has_more_blocks_to_import: bool,
	) -> NewBlocks {
		NewBlocks {
			imported,
			invalid,
			route,
			sealed,
			proposed,
			duration,
			has_more_blocks_to_import,
		}
	}
}
