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

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 = ::mem::MaybeUninit::uninit().assume_init();
			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 }
    }
}
