private Bucket SetBucket(byte[] key, int offset, Bucket b)
		{
			bool found = false;
			int pos = FindPointerOrLower(b, new bytearr(key), out found);

			if (found)
			{
				KeyPointer p = b.Pointers[pos];
				int v = p.RecordNum;

				// duplicate found
				if (v != offset)
				{
					p.RecordNum = offset;
					DirtyBucket(b);
				}
			}
			else
			{
				if (b.Pointers.Count < _bucketItems)
				{
					KeyPointer k = new KeyPointer(new bytearr(key), offset);
					pos++;
					if (pos < b.Pointers.Count)
						b.Pointers.Insert(pos, k);
					else
						b.Pointers.Add(k);
					DirtyBucket(b);
				}
				else
				{
					int p = b.NextPageNumber;
					if (p != -1)
					{
						b = LoadBucket(p);
						SetBucket(key, offset, b);
					}
					else
					{
						Bucket newb = new Bucket(_indexfile.GetNewPageNumber());
						b.NextPageNumber = newb.DiskPageNumber;
						DirtyBucket(b);
						SetBucket(key, offset, newb);
					}
				}
			}
			return b;
		}
		public Bucket LoadBucketFromPage(int page)
		{
			SeekPage(page);
			byte[] b = new byte[_PageLength];
			_file.Read(b, 0, _PageLength);

			if (b[0] == _BlockHeader[0] && b[1] == _BlockHeader[1] && b[2] == _BlockHeader[2] && b[3] == _BlockHeader[3])
			{
				// create node here
				int bucketnum = Helper.ToInt32(b, 7);
				List<KeyPointer> list = new List<KeyPointer>();
				List<int> dups = new List<int>();
				short count = Helper.ToInt16(b, 5);
				if (count > _PageNodeCount)
					throw new Exception("Count > node size");
				int nextpage = Helper.ToInt32(b, 11);
				int index = _BlockHeader.Length;
				if ((b[4] & 4) == 4)
				{
					int dupc = Helper.ToInt32(b, index);
					index += 4;
					for (int i = 0; i < dupc; i++)
					{
						int l = Helper.ToInt32(b, index);
						dups.Add(l);
						index += 4;
					}
				}
				else
				{
					for (int i = 0; i < count; i++)
					{
						int idx = index + _rowSize*i;
						byte ks = b[idx];
						byte[] key = new byte[ks];
						Buffer.BlockCopy(b, idx + 1, key, 0, ks);
						int offset = Helper.ToInt32(b, idx + 1 + _maxKeySize);
						int duppage = Helper.ToInt32(b, idx + 1 + _maxKeySize + 4);

						KeyPointer kp = new KeyPointer(new bytearr(key), offset, duppage);
						list.Add(kp);
					}
				}
				Bucket n = new Bucket(b[4], bucketnum, list, dups, page, nextpage);
				return n;
			}
			throw new Exception("Page read error");
		}
		public void SaveBucket(Bucket bucket)
		{
			int pnum = bucket.DiskPageNumber;
			if (pnum > _LastPageNumber)
				throw new Exception("should not be here: page out of bounds");

			SeekPage(pnum);
			byte[] page = new byte[_PageLength];
			Buffer.BlockCopy(_BlockHeader, 0, page, 0, _BlockHeader.Length);
			// bucket type
			if (bucket.isBucket)
				page[4] = 8;
			if (bucket.isOverflow)
				page[4] += 16;

			// bucket count
			byte[] b = Helper.GetBytes(bucket.Count, false);
			Buffer.BlockCopy(b, 0, page, 5, b.Length);
			// bucket number
			b = Helper.GetBytes(bucket.BucketNumber, false);
			Buffer.BlockCopy(b, 0, page, 7, b.Length);
			// next page number
			b = Helper.GetBytes(bucket.NextPageNumber, false);
			Buffer.BlockCopy(b, 0, page, 11, b.Length);

			int index = _BlockHeader.Length;
			if (bucket.isOverflow == true)
			{
				b = Helper.GetBytes(bucket.Duplicates.Count, false);
				Buffer.BlockCopy(b, 0, page, index, b.Length);
				index += b.Length;
				foreach (long p in bucket.Duplicates)
				{
					b = Helper.GetBytes(p, false);
					Buffer.BlockCopy(b, 0, page, index, b.Length);
					index += b.Length;
				}
			}
			else
			{
				// bucket children
				for (int i = 0; i < bucket.Count; i++)
				{
					KeyPointer kp = bucket.Pointers[i];
					int idx = index + _rowSize*i;
					byte size = (byte) kp.Key.val.Length;
					if (size > _maxKeySize)
						size = _maxKeySize;
					// key size = 1 byte
					page[idx] = size;
					// key bytes
					Buffer.BlockCopy(bucket.Pointers[i].Key.val, 0, page, idx + 1, page[idx]);
					// offset = 4 bytes
					b = Helper.GetBytes(kp.RecordNum, false);
					Buffer.BlockCopy(b, 0, page, idx + 1 + _maxKeySize, b.Length);
					// duplicatepage = 4 bytes
					b = Helper.GetBytes(kp.DuplicatesPage, false);
					Buffer.BlockCopy(b, 0, page, idx + 1 + _maxKeySize + 4, b.Length);
				}
			}
			bucket.isOverflow = false;
			_file.Write(page, 0, page.Length);
			_file.Flush();
		}
		private Bucket CreateBucket(int bucketNumber)
		{
			var b = new Bucket(_indexfile.GetNewPageNumber()) {BucketNumber = bucketNumber};
			// get next free indexfile pointer offset
			return b;
		}
		private int FindPointerOrLower(Bucket b, bytearr key, out bool found)
		{
			found = false;
			if (b.Pointers.Count == 0)
				return 0;
			// binary search
			int lastlower = -1;
			int first = 0;
			int last = b.Pointers.Count - 1;
			int mid = 0;
			while (first <= last)
			{
				mid = (first + last) >> 1;
				KeyPointer k = b.Pointers[mid];
				int compare = Helper.Compare(k.Key, key);
				if (compare < 0)
				{
					lastlower = mid;
					first = mid + 1;
				}
				if (compare == 0)
				{
					found = true;
					return mid;
				}
				if (compare > 0)
				{
					last = mid - 1;
				}
			}

			return lastlower;
		}
		private void DirtyBucket(Bucket b)
		{
			if (b.isDirty)
				return;

			b.isDirty = true;
			if (_cachedBuckets.ContainsKey(b.DiskPageNumber) == false)
				_cachedBuckets.Add(b.DiskPageNumber, b);
		}
		private bool SearchBucket(Bucket b, byte[] key, ref int offset)
		{
			bool found = false;
			int pos = FindPointerOrLower(b, new bytearr(key), out found);
			if (found)
			{
				KeyPointer k = b.Pointers[pos];
				offset = k.RecordNum;
				return true;
			}
			else
			{
				if (b.NextPageNumber != -1)
				{
					b = LoadBucket(b.NextPageNumber);
					return SearchBucket(b, key, ref offset);
				}
			}
			return false;
		}