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

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

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

//! Structures and functions to return blocks whose changes are to be included
//! in given block's changes trie.

use num_traits::Zero;
use crate::changes_trie::{ConfigurationRange, BlockNumber};

/// Returns iterator of OTHER blocks that are required for inclusion into
/// changes trie of given block. Blocks are guaranteed to be returned in
/// ascending order.
///
/// Skewed digest is built IF block >= config.end.
pub fn digest_build_iterator<'a, Number: BlockNumber>(
	config: ConfigurationRange<'a, Number>,
	block: Number,
) -> DigestBuildIterator<Number> {
	// prepare digest build parameters
	let (_, _, digest_step) = match config.config.digest_level_at_block(config.zero, block.clone()) {
		Some((current_level, digest_interval, digest_step)) =>
			(current_level, digest_interval, digest_step),
		None => return DigestBuildIterator::empty(),
	};

	DigestBuildIterator::new(block.clone(), config.end.unwrap_or(block), config.config.digest_interval, digest_step)
}

/// Changes trie build iterator that returns numbers of OTHER blocks that are
/// required for inclusion into changes trie of given block.
#[derive(Debug)]
pub struct DigestBuildIterator<Number: BlockNumber> {
	/// Block we're building changes trie for. It could (logically) be a post-end block if we are creating
	/// skewed digest.
	block: Number,
	/// Block that is a last block where current configuration is active. We have never yet created anything
	/// after this block => digest that we're creating can't reference any blocks that are >= end.
	end: Number,
	/// Interval of L1 digest blocks.
	digest_interval: u32,
	/// Max step that could be used when digest is created.
	max_step: u32,

	// Mutable data below:

	/// Step of current blocks range.
	current_step: u32,
	/// Reverse step of current blocks range.
	current_step_reverse: u32,
	/// Current blocks range.
	current_range: Option<BlocksRange<Number>>,
	/// Last block that we have returned.
	last_block: Option<Number>,
}

impl<Number: BlockNumber> DigestBuildIterator<Number> {
	/// Create new digest build iterator.
	pub fn new(block: Number, end: Number, digest_interval: u32, max_step: u32) -> Self {
		DigestBuildIterator {
			block,
			end,
			digest_interval,
			max_step,
			current_step: max_step,
			current_step_reverse: 0,
			current_range: None,
			last_block: None,
		}
	}

	/// Create empty digest build iterator.
	pub fn empty() -> Self {
		Self::new(Zero::zero(), Zero::zero(), 0, 0)
	}
}

impl<Number: BlockNumber> Iterator for DigestBuildIterator<Number> {
	type Item = Number;

	fn next(&mut self) -> Option<Self::Item> {
		// when we're building skewed digest, we might want to skip some blocks if
		// they're not covered by current configuration
		loop {
			if let Some(next) = self.current_range.as_mut().and_then(|iter| iter.next()) {
				if next < self.end {
					self.last_block = Some(next.clone());
					return Some(next);
				}
			}

			// we are safe to use non-checking mul/sub versions here because:
			// DigestBuildIterator is created only by internal function that is checking
			// that all multiplications/subtractions are safe within max_step limit

			let next_step_reverse = if self.current_step_reverse == 0 {
				1
			} else {
				self.current_step_reverse * self.digest_interval
			};
			if next_step_reverse > self.max_step {
				return None;
			}

			self.current_step_reverse = next_step_reverse;
			self.current_range = Some(BlocksRange::new(
				match self.last_block.clone() {
					Some(last_block) => last_block + self.current_step.into(),
					None => self.block.clone() - (self.current_step * self.digest_interval - self.current_step).into(),
				},
				self.block.clone(),
				self.current_step.into(),
			));

			self.current_step = self.current_step / self.digest_interval;
			if self.current_step == 0 {
				self.current_step = 1;
			}
		}
	}
}

/// Blocks range iterator with builtin step_by support.
#[derive(Debug)]
struct BlocksRange<Number: BlockNumber> {
	current: Number,
	end: Number,
	step: Number,
}

impl<Number: BlockNumber> BlocksRange<Number> {
	pub fn new(begin: Number, end: Number, step: Number) -> Self {
		BlocksRange {
			current: begin,
			end,
			step,
		}
	}
}

impl<Number: BlockNumber> Iterator for BlocksRange<Number> {
	type Item = Number;

	fn next(&mut self) -> Option<Self::Item> {
		if self.current >= self.end {
			return None;
		}

		let current = Some(self.current.clone());
		self.current += self.step.clone();
		current
	}
}

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

	fn digest_build_iterator(
		digest_interval: u32,
		digest_levels: u32,
		zero: u64,
		block: u64,
		end: Option<u64>,
	) -> DigestBuildIterator<u64> {
		super::digest_build_iterator(
			ConfigurationRange {
				config: &Configuration {
					digest_interval,
					digest_levels,
				},
				zero,
				end,
			},
			block,
		)
	}

	fn digest_build_iterator_basic(
		digest_interval: u32,
		digest_levels: u32,
		zero: u64,
		block: u64,
	) -> (u64, u32, u32) {
		let iter = digest_build_iterator(digest_interval, digest_levels, zero, block, None);
		(iter.block, iter.digest_interval, iter.max_step)
	}

	fn digest_build_iterator_blocks(
		digest_interval: u32,
		digest_levels: u32,
		zero: u64,
		block: u64,
		end: Option<u64>,
	) -> Vec<u64> {
		digest_build_iterator(digest_interval, digest_levels, zero, block, end).collect()
	}

	#[test]
	fn suggest_digest_inclusion_returns_empty_iterator() {
		fn test_with_zero(zero: u64) {
			let empty = (0, 0, 0);
			assert_eq!(digest_build_iterator_basic(4, 16, zero, zero + 0), empty, "block is 0");
			assert_eq!(digest_build_iterator_basic(0, 16, zero, zero + 64), empty, "digest_interval is 0");
			assert_eq!(digest_build_iterator_basic(1, 16, zero, zero + 64), empty, "digest_interval is 1");
			assert_eq!(digest_build_iterator_basic(4, 0, zero, zero + 64), empty, "digest_levels is 0");
			assert_eq!(
				digest_build_iterator_basic(4, 16, zero, zero + 1),
				empty,
				"digest is not required for this block",
			);
			assert_eq!(
				digest_build_iterator_basic(4, 16, zero, zero + 2),
				empty,
				"digest is not required for this block",
			);
			assert_eq!(
				digest_build_iterator_basic(4, 16, zero, zero + 15),
				empty,
				"digest is not required for this block",
			);
			assert_eq!(
				digest_build_iterator_basic(4, 16, zero, zero + 17),
				empty,
				"digest is not required for this block",
			);
			assert_eq!(digest_build_iterator_basic(
				::std::u32::MAX / 2 + 1,
				16,
				zero,
				::std::u64::MAX,
			), empty, "digest_interval * 2 is greater than u64::MAX");
		}

		test_with_zero(0);
		test_with_zero(1);
		test_with_zero(2);
		test_with_zero(4);
		test_with_zero(17);
	}

	#[test]
	fn suggest_digest_inclusion_returns_level1_iterator() {
		fn test_with_zero(zero: u64) {
			assert_eq!(
				digest_build_iterator_basic(16, 1, zero, zero + 16),
				(zero + 16, 16, 1),
				"!(block % interval) && first digest level == block",
			);
			assert_eq!(
				digest_build_iterator_basic(16, 1, zero, zero + 256),
				(zero + 256, 16, 1),
				"!(block % interval^2), but there's only 1 digest level",
			);
			assert_eq!(
				digest_build_iterator_basic(16, 2, zero, zero + 32),
				(zero + 32, 16, 1),
				"second level digest is not required for this block",
			);
			assert_eq!(
				digest_build_iterator_basic(16, 3, zero, zero + 4080),
				(zero + 4080, 16, 1),
				"second && third level digest are not required for this block",
			);
		}

		test_with_zero(0);
		test_with_zero(16);
		test_with_zero(17);
	}

	#[test]
	fn suggest_digest_inclusion_returns_level2_iterator() {
		fn test_with_zero(zero: u64) {
			assert_eq!(
				digest_build_iterator_basic(16, 2, zero, zero + 256),
				(zero + 256, 16, 16),
				"second level digest",
			);
			assert_eq!(
				digest_build_iterator_basic(16, 2, zero, zero + 4096),
				(zero + 4096, 16, 16),
				"!(block % interval^3), but there's only 2 digest levels",
			);
		}

		test_with_zero(0);
		test_with_zero(16);
		test_with_zero(17);
	}

	#[test]
	fn suggest_digest_inclusion_returns_level3_iterator() {
		fn test_with_zero(zero: u64) {
			assert_eq!(
				digest_build_iterator_basic(16, 3, zero, zero + 4096),
				(zero + 4096, 16, 256),
				"third level digest: beginning",
			);
			assert_eq!(
				digest_build_iterator_basic(16, 3, zero, zero + 8192),
				(zero + 8192, 16, 256),
				"third level digest: next",
			);
		}

		test_with_zero(0);
		test_with_zero(16);
		test_with_zero(17);
	}

	#[test]
	fn digest_iterator_returns_level1_blocks() {
		fn test_with_zero(zero: u64) {
			assert_eq!(digest_build_iterator_blocks(16, 1, zero, zero + 16, None),
				[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
					.iter().map(|item| zero + item).collect::<Vec<_>>());
			assert_eq!(digest_build_iterator_blocks(16, 1, zero, zero + 256, None),
				[241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255]
					.iter().map(|item| zero + item).collect::<Vec<_>>());
			assert_eq!(digest_build_iterator_blocks(16, 2, zero, zero + 32, None),
				[17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
					.iter().map(|item| zero + item).collect::<Vec<_>>());
			assert_eq!(digest_build_iterator_blocks(16, 3, zero, zero + 4080, None),
				[4065, 4066, 4067, 4068, 4069, 4070, 4071, 4072, 4073, 4074, 4075, 4076, 4077, 4078, 4079]
					.iter().map(|item| zero + item).collect::<Vec<_>>());
		}

		test_with_zero(0);
		test_with_zero(16);
		test_with_zero(17);
	}

	#[test]
	fn digest_iterator_returns_level1_and_level2_blocks() {
		fn test_with_zero(zero: u64) {
			assert_eq!(digest_build_iterator_blocks(16, 2, zero, zero + 256, None),
				[
					// level2 points to previous 16-1 level1 digests:
					16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240,
					// level2 is a level1 digest of 16-1 previous blocks:
					241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,
				].iter().map(|item| zero + item).collect::<Vec<_>>(),
			);
			assert_eq!(digest_build_iterator_blocks(16, 2, zero, zero + 4096, None),
				[
					// level2 points to previous 16-1 level1 digests:
					3856, 3872, 3888, 3904, 3920, 3936, 3952, 3968, 3984, 4000, 4016, 4032, 4048, 4064, 4080,
					// level2 is a level1 digest of 16-1 previous blocks:
					4081, 4082, 4083, 4084, 4085, 4086, 4087, 4088, 4089, 4090, 4091, 4092, 4093, 4094, 4095,
				].iter().map(|item| zero + item).collect::<Vec<_>>(),
			);
		}

		test_with_zero(0);
		test_with_zero(16);
		test_with_zero(17);
	}

	#[test]
	fn digest_iterator_returns_level1_and_level2_and_level3_blocks() {
		fn test_with_zero(zero: u64) {
			assert_eq!(digest_build_iterator_blocks(16, 3, zero, zero + 4096, None),
				[
					// level3 points to previous 16-1 level2 digests:
					256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, 3840,
					// level3 points to previous 16-1 level1 digests:
					3856, 3872, 3888, 3904, 3920, 3936, 3952, 3968, 3984, 4000, 4016, 4032, 4048, 4064, 4080,
					// level3 is a level1 digest of 16-1 previous blocks:
					4081, 4082, 4083, 4084, 4085, 4086, 4087, 4088, 4089, 4090, 4091, 4092, 4093, 4094, 4095,
				].iter().map(|item| zero + item).collect::<Vec<_>>(),
			);
		}

		test_with_zero(0);
		test_with_zero(16);
		test_with_zero(17);
	}

	#[test]
	fn digest_iterator_returns_skewed_digest_blocks() {
		fn test_with_zero(zero: u64) {
			assert_eq!(digest_build_iterator_blocks(16, 3, zero, zero + 4096, Some(zero + 1338)),
				[
					// level3 MUST point to previous 16-1 level2 digests, BUT there are only 5:
					256, 512, 768, 1024, 1280,
					// level3 MUST point to previous 16-1 level1 digests, BUT there are only 3:
					1296, 1312, 1328,
					// level3 MUST be a level1 digest of 16-1 previous blocks, BUT there are only 9:
					1329, 1330, 1331, 1332, 1333, 1334, 1335, 1336, 1337,
				].iter().map(|item| zero + item).collect::<Vec<_>>(),
			);
		}

		test_with_zero(0);
		test_with_zero(16);
		test_with_zero(17);
	}

	#[test]
	fn digest_iterator_returns_skewed_digest_blocks_skipping_level() {
		fn test_with_zero(zero: u64) {
			assert_eq!(digest_build_iterator_blocks(16, 3, zero, zero + 4096, Some(zero + 1284)),
				[
					// level3 MUST point to previous 16-1 level2 digests, BUT there are only 5:
					256, 512, 768, 1024, 1280,
					// level3 MUST point to previous 16-1 level1 digests, BUT there are NO ANY L1-digests:
					// level3 MUST be a level1 digest of 16-1 previous blocks, BUT there are only 3:
					1281, 1282, 1283,
				].iter().map(|item| zero + item).collect::<Vec<_>>(),
			);
		}

		test_with_zero(0);
		test_with_zero(16);
		test_with_zero(17);
	}
}
