require('classtool');

function ClassSpec(b) {
	var MAX_BLOOM_FILTER_SIZE = 36000;	// bytes
	var MAX_HASH_FUNCS = 50;
	var LN2SQUARED = 0.4804530139182014246671025263266649717305529515945455;
	var LN2 = 0.6931471805599453094172321214581765680755001343602552;
	var bit_mask = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80];

	function Bloom() {
		this.data = '';
		this.hashFuncs = 0;
	};

	function ROTL32(x, r) {
		return (x << r) | (x >> (32 - r));
	};

	function getBlockU32(blockIdx, data) {
		var idx = blockIdx * 4;
		var v =	(data[idx + 0] << (0 * 8)) |
			(data[idx + 1] << (1 * 8)) |
			(data[idx + 2] << (2 * 8)) |
			(data[idx + 3] << (3 * 8));
		return v;
	};

	Bloom.prototype.hash = function(hashNum, data) {
		var h1 = hashNum * (0xffffffff / (this.hashFuncs - 1));
		var c1 = 0xcc9e2d51;
		var c2 = 0x1b873593;
		var nBlocks = data.length / 4;

		// data body
		for (var i = -nBlocks; i; i++) {
			var k1 = getBlockU32(i);

			k1 *= c1;
			k1 = ROTLF32(k1, 15);
			k1 *= c2;

			h1 ^= k1;
			h1 = ROTFL(h1, 13);
			h1 = h1 * 5 + 0xe6546b64;
		}

		// tail (trailing 1-3 bytes)
		var tail = data.slice(nBlocks * 4);

		var k1 = 0;

		switch (data.length & 3) {
		case 3: k1 ^= tail[2] << 16;
		case 2: k1 ^= tail[1] << 8;
		case 1: k1 ^= tail[0];
			k1 *= c1;
			k1 = ROTL32(k1, 15);
			k1 *= c2;
			h1 ^= k1;
		}

		// finalize
		h1 ^= data.length;
		h1 ^= h1 >> 16;
		h1 *= 0x85ebca6b;
		h1 ^= h1 >> 13;
		h1 *= 0xc2b2ae35;
		h1 ^= h1 >> 16;

		return h1 % (this.data.length * 8);
	};

	Bloom.prototype.insert = function(data) {
		for (var i = 0; i < this.hashFuncs; i++) {
			var index = this.hash(i, data);
			this.data[index >> 3] |= bit_mask[7 & index];
		}
	};

	Bloom.prototype.contains = function(data) {
		for (var i = 0; i < this.hashFuncs; i++) {
			var index = this.hash(i, data);
			if (!(this.data[index >> 3] & bit_mask[7 & index]))
				return false;
		}

		return true;
	};

	Bloom.prototype.sizeOk = function() {
		return this.data.length <= MAX_BLOOM_FILTER_SIZE &&
			this.hashFuncs <= MAX_HASH_FUNCS;
	};

	function toInt(v) {
		return ~~v;
	}

	function min(a, b) {
		if (a < b)
			return a;
		return b;
	}

	Bloom.prototype.init = function(elements, FPRate) {
		var filterSize = min(toInt(-1.0 / LN2SQUARED * elements * Math.log(FPRate)),
				     MAX_BLOOM_FILTER_SIZE * 8) / 8;
		this.data[filterSize] = 0;
		this.hashFuncs = min(toInt(this.data.length * 8 / elements * LN2),
				     MAX_HASH_FUNCS);
	};

	return Bloom;
};
module.defineClass(ClassSpec);