// Copyright 2015-2018 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 primal::is_prime;

pub const DATASET_BYTES_INIT: u64 = 1 << 30;
pub const DATASET_BYTES_GROWTH: u64 = 1 << 23;
pub const CACHE_BYTES_INIT: u64 = 1 << 24;
pub const CACHE_BYTES_GROWTH: u64 = 1 << 17;

pub const ETHASH_EPOCH_LENGTH: u64 = 30000;
pub const ETHASH_CACHE_ROUNDS: usize = 3;
pub const ETHASH_MIX_BYTES: usize = 128;
pub const ETHASH_ACCESSES: usize = 64;
pub const ETHASH_DATASET_PARENTS: u32 = 256;
pub const NODE_DWORDS: usize = NODE_WORDS / 2;
pub const NODE_WORDS: usize = NODE_BYTES / 4;
pub const NODE_BYTES: usize = 64;

pub fn epoch(block_number: u64) -> u64 {
	block_number / ETHASH_EPOCH_LENGTH
}

static CHARS: &'static [u8] = b"0123456789abcdef";
pub fn to_hex(bytes: &[u8]) -> String {
	let mut v = Vec::with_capacity(bytes.len() * 2);
	for &byte in bytes.iter() {
		v.push(CHARS[(byte >> 4) as usize]);
		v.push(CHARS[(byte & 0xf) as usize]);
	}

	unsafe { String::from_utf8_unchecked(v) }
}

pub fn get_cache_size(block_number: u64) -> usize {
	// TODO: Memoise
	let mut sz: u64 = CACHE_BYTES_INIT + CACHE_BYTES_GROWTH * (block_number / ETHASH_EPOCH_LENGTH);
	sz = sz - NODE_BYTES as u64;
	while !is_prime(sz / NODE_BYTES as u64) {
		sz = sz - 2 * NODE_BYTES as u64;
	}
	sz as usize
}

pub fn get_data_size(block_number: u64) -> usize {
	// TODO: Memoise
	let mut sz: u64 = DATASET_BYTES_INIT + DATASET_BYTES_GROWTH * (block_number / ETHASH_EPOCH_LENGTH);
	sz = sz - ETHASH_MIX_BYTES as u64;
	while !is_prime(sz / ETHASH_MIX_BYTES as u64) {
		sz = sz - 2 * ETHASH_MIX_BYTES as u64;
	}
	sz as usize
}

pub type NodeBytes = [u8; NODE_BYTES];
pub type NodeWords = [u32; NODE_WORDS];
pub type NodeDwords = [u64; NODE_DWORDS];

macro_rules! static_assert_size_eq {
	(@inner $a:ty, $b:ty, $($rest:ty),*) => {
		fn first() {
			static_assert_size_eq!($a, $b);
		}

		fn second() {
			static_assert_size_eq!($b, $($rest),*);
		}
	};
	(@inner $a:ty, $b:ty) => {
		unsafe {
			let val: $b = ::std::mem::uninitialized();
			let _: $a = ::std::mem::transmute(val);
		}
	};
	($($rest:ty),*) => {
		static_assert_size_eq!(size_eq: $($rest),*);
	};
	($name:ident : $($rest:ty),*) => {
		#[allow(dead_code)]
		fn $name() {
			static_assert_size_eq!(@inner $($rest),*);
		}
	};
}

static_assert_size_eq!(Node, NodeBytes, NodeWords, NodeDwords);

#[repr(C)]
pub union Node {
	pub dwords: NodeDwords,
	pub words: NodeWords,
	pub bytes: NodeBytes,
}

impl Clone for Node {
	fn clone(&self) -> Self {
		unsafe { Node { bytes: *&self.bytes } }
	}
}

// We use `inline(always)` because I was experiencing an 100% slowdown and `perf` showed that these
// calls were taking up ~30% of the runtime. Adding these annotations fixes the issue. Remove at
// your peril, if and only if you have benchmarks to prove that this doesn't reintroduce the
// performance regression. It's not caused by the `debug_assert_eq!` either, your guess is as good
// as mine.
impl Node {
	#[inline(always)]
	pub fn as_bytes(&self) -> &NodeBytes {
		unsafe { &self.bytes }
	}

	#[inline(always)]
	pub fn as_bytes_mut(&mut self) -> &mut NodeBytes {
		unsafe { &mut self.bytes }
	}

	#[inline(always)]
	pub fn as_words(&self) -> &NodeWords {
		unsafe { &self.words }
	}

	#[inline(always)]
	pub fn as_words_mut(&mut self) -> &mut NodeWords {
		unsafe { &mut self.words }
	}

	#[inline(always)]
	pub fn as_dwords(&self) -> &NodeDwords {
		unsafe { &self.dwords }
	}

	#[inline(always)]
	pub fn as_dwords_mut(&mut self) -> &mut NodeDwords {
		unsafe { &mut self.dwords }
	}
}
