#pragma once
/**
	@file
	@brief definition of Op
	@author MITSUNARI Shigeo(@herumi)
	@license modified new BSD license
	http://opensource.org/licenses/BSD-3-Clause
*/
#ifdef MCL_DONT_USE_CSPRNG

// nothing

#elif defined(MCL_USE_WEB_CRYPTO_API)
#include <emscripten.h>

namespace mcl {
struct RandomGeneratorJS {
	void read(bool *pb, void *buf, uint32_t byteSize)
	{
		// cf. https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues
		if (byteSize > 65536) {
			*pb = false;
			return;
		}
		// use crypto.getRandomValues
		EM_ASM({Module.cryptoGetRandomValues($0, $1)}, buf, byteSize);
		*pb = true;
	}
};
} // mcl

#else
#include <cybozu/random_generator.hpp>
#if 0 // #if CYBOZU_CPP_VERSION >= CYBOZU_CPP_VERSION_CPP11
#include <random>
#endif
#endif
#ifdef _MSC_VER
	#pragma warning(push)
	#pragma warning(disable : 4521)
#endif
namespace mcl { namespace fp {

namespace local {

template<class RG>
uint32_t readWrapper(void *self, void *buf, uint32_t byteSize)
{
	bool b;
	reinterpret_cast<RG*>(self)->read(&b, (uint8_t*)buf, byteSize);
	if (b) return byteSize;
	return 0;
}

#if 0 // #if CYBOZU_CPP_VERSION >= CYBOZU_CPP_VERSION_CPP11
template<>
inline uint32_t readWrapper<std::random_device>(void *self, void *buf, uint32_t byteSize)
{
	const uint32_t keep = byteSize;
	std::random_device& rg = *reinterpret_cast<std::random_device*>(self);
	uint8_t *p = reinterpret_cast<uint8_t*>(buf);
	uint32_t v;
	while (byteSize >= 4) {
		v = rg();
		memcpy(p, &v, 4);
		p += 4;
		byteSize -= 4;
	}
	if (byteSize > 0) {
		v = rg();
		memcpy(p, &v, byteSize);
	}
	return keep;
}
#endif
} // local
/*
	wrapper of cryptographically secure pseudo random number generator
*/
class RandGen {
	typedef uint32_t (*readFuncType)(void *self, void *buf, uint32_t byteSize);
	void *self_;
	readFuncType readFunc_;
public:
	RandGen() : self_(0), readFunc_(0) {}
	RandGen(void *self, readFuncType readFunc) : self_(self) , readFunc_(readFunc) {}
	RandGen(const RandGen& rhs) : self_(rhs.self_), readFunc_(rhs.readFunc_) {}
	RandGen(RandGen& rhs) : self_(rhs.self_), readFunc_(rhs.readFunc_) {}
	RandGen& operator=(const RandGen& rhs)
	{
		self_ = rhs.self_;
		readFunc_ = rhs.readFunc_;
		return *this;
	}
	template<class RG>
	RandGen(RG& rg)
		: self_(reinterpret_cast<void*>(&rg))
		, readFunc_(local::readWrapper<RG>)
	{
	}
	void read(bool *pb, void *out, size_t byteSize)
	{
		uint32_t size = readFunc_(self_, out, static_cast<uint32_t>(byteSize));
		*pb = size == byteSize;
	}
#ifdef MCL_DONT_USE_CSPRNG
	bool isZero() const { return false; } /* return false to avoid copying default rg */
#else
	bool isZero() const { return self_ == 0 && readFunc_ == 0; }
#endif
	static RandGen& getDefaultRandGen()
	{
#ifdef MCL_DONT_USE_CSPRNG
		static RandGen wrg;
#elif defined(MCL_USE_WEB_CRYPTO_API)
		static mcl::RandomGeneratorJS rg;
		static RandGen wrg(rg);
#else
		static cybozu::RandomGenerator rg;
		static RandGen wrg(rg);
#endif
		return wrg;
	}
	static RandGen& get()
	{
		static RandGen wrg(getDefaultRandGen());
		return wrg;
	}
	/*
		rg must be thread safe
		rg.read(void *buf, size_t byteSize);
	*/
	static void setRandGen(const RandGen& rg)
	{
		get() = rg;
	}
	/*
		set rand function
		if self and readFunc are NULL then set default rand function
	*/
	static void setRandFunc(void *self, readFuncType readFunc)
	{
		if (self == 0 && readFunc == 0) {
			setRandGen(getDefaultRandGen());
		} else {
			RandGen rg(self, readFunc);
			setRandGen(rg);
		}
	}
};

} } // mcl::fp

#ifdef _MSC_VER
	#pragma warning(pop)
#endif