/*
 * \brief  Block session testing - random test
 * \author Josef Soentgen
 * \date   2016-07-04
 */

/*
 * Copyright (C) 2016-2018 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

#ifndef _TEST_RANDOM_H_
#define _TEST_RANDOM_H_

namespace Test { struct Random; }


namespace Util {

	using namespace Genode;

	/*
	 * Xoroshiro128+ written in 2014-2016 by Sebastiano Vigna (vigna@acm.org)
	 *
	 * (see http://xoroshiro.di.unimi.it/xorshift128plus.c and
	 *      http://xoroshiro.di.unimi.it/splitmix64.c)
	 */

	struct Xoroshiro
	{
		uint64_t seed;

		uint64_t splitmix64()
		{
			uint64_t z = (seed += __UINT64_C(0x9E3779B97F4A7C15));
			z = (z ^ (z >> 30)) * __UINT64_C(0xBF58476D1CE4E5B9);
			z = (z ^ (z >> 27)) * __UINT64_C(0x94D049BB133111EB);
			return z ^ (z >> 31);
		}

		Xoroshiro(uint64_t seed) : seed(seed)
		{
			s[0] = splitmix64();
			s[1] = splitmix64();
		}

		uint64_t s[2];

		static uint64_t rotl(uint64_t const x, int k) {
			return (x << k) | (x >> (64 - k));
		}

		uint64_t get()
		{
			uint64_t const s0 = s[0];
			uint64_t       s1 = s[1];
			uint64_t const result = s0 + s1;

			s1 ^= s0;

			s[0] = rotl(s0, 55) ^ s1 ^ (s1 << 14);
			s[1] = rotl(s1, 36);

			return result;
		}
	};
}


/*
 * Random test
 *
 * This test reads or writes the given number of bytes in a
 * deterministic order that depends and the seed value of a
 * PRNG in particular sized requests.
 */
struct Test::Random : Test_base
{
	bool _alternate_access { false };

	Util::Xoroshiro _random;

	size_t   const _size   = _node.attribute_value("size",   Number_of_bytes());
	uint64_t const _length = _node.attribute_value("length", Number_of_bytes());

	Block::Operation::Type _op_type = Block::Operation::Type::READ;

	block_number_t _next_block()
	{
		uint64_t r = 0;
		block_number_t max = _info.block_count;
		if (max >= _size_in_blocks + 1)
			max -= _size_in_blocks + 1;
		do {
			r = _random.get() % max;
		} while (r + _size_in_blocks > _info.block_count);

		return r;
	}

	template <typename... ARGS>
	Random(ARGS &&...args)
	:
		Test_base(args...),
		_random(_node.attribute_value("seed", 42UL))
	{ }

	void _init() override
	{
		if (_size > _scratch_buffer.size) {
			error("request size exceeds scratch buffer size");
			throw Constructing_test_failed();
		}

		if (!_size || !_length) {
			error("request size or length invalid");
			throw Constructing_test_failed();
		}

		if (_info.block_size > _size || (_size % _info.block_size) != 0) {
			error("request size invalid ", _info.block_size, " ", _size);
			throw Constructing_test_failed();
		}

		bool const r = _node.attribute_value("read", false);
		if (r) { _op_type = Block::Operation::Type::READ; }

		bool const w = _node.attribute_value("write", false);
		if (w) { _op_type = Block::Operation::Type::WRITE; }

		_alternate_access = w && r;

		_size_in_blocks   = _size   / _info.block_size;
		_length_in_blocks = _length / _info.block_size;
	}

	void _spawn_job() override
	{
		if (_bytes >= _length)
			return;

		_job_cnt++;

		block_number_t const lba = _next_block();

		Block::Operation::Type const op_type =
			_alternate_access ? (lba & 0x1) ? Block::Operation::Type::WRITE
			                                : Block::Operation::Type::READ
			                  : _op_type;

		Block::Operation const operation { .type         = op_type,
		                                   .block_number = lba,
		                                   .count        = _size_in_blocks };

		new (_alloc) Job(*_block, operation, _job_cnt);
	}

	Result result() override
	{
		return Result(_success, _end_time - _start_time,
		              _bytes, _rx, _tx, _size, _info.block_size, _triggered);
	}

	char const *name() const override { return "random"; }

	void print(Output &out) const override
	{
		Genode::print(out, name(),             " "
		                   "size:",   _size,   " "
		                   "length:", _length, " "
		                   "copy:",   _copy,   " "
		                   "batch:",  _batch);
	}
};

#endif /* _TEST_RANDOM_H_ */
