/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.flink.table.runtime.hashtable;

import org.apache.flink.core.memory.MemorySegment;
import org.apache.flink.runtime.operators.util.BloomFilter;
import org.apache.flink.util.MathUtils;

import static org.apache.flink.util.Preconditions.checkArgument;

/** Bloom filters for HashTable. */
class HashTableBloomFilter {

    private final MemorySegment[] buffers;
    private final int numBuffers;
    private final int numBuffersMask;
    private final BloomFilter filter;
    private final int maxSize;

    private int size = 0;

    HashTableBloomFilter(MemorySegment[] buffers, long numRecords) {
        checkArgument(buffers != null && buffers.length > 0);
        this.buffers = buffers;
        this.numBuffers = buffers.length;
        checkArgument(MathUtils.isPowerOf2(numBuffers));
        this.numBuffersMask = numBuffers - 1;
        int bufferSize = buffers[0].size();
        this.filter = new BloomFilter((int) (numRecords / numBuffers), buffers[0].size());
        filter.setBitsLocation(buffers[0], 0);

        // We assume that a BloomFilter can contain up to 2.44 elements per byte.
        // fpp roughly equal 0.2
        this.maxSize = (int) ((numBuffers * bufferSize) * 2.44);
    }

    private void setLocation(int hash) {
        if (numBuffers > 1) {
            filter.setBitsLocation(buffers[hash & numBuffersMask], 0);
        }
    }

    boolean testHash(int hash) {
        setLocation(hash);
        return filter.testHash(hash);
    }

    /**
     * @return false if the accuracy of the BloomFilter is not high.
     */
    boolean addHash(int hash) {
        setLocation(hash);
        filter.addHash(hash);
        size++;
        return size <= maxSize;
    }

    public MemorySegment[] getBuffers() {
        return buffers;
    }

    static int optimalSegmentNumber(long maxNum, int segSize, double fpp) {
        double numBytes = optimalNumOfBits(maxNum, fpp) / 8D;
        return (int) Math.ceil(numBytes / segSize);
    }

    private static int optimalNumOfBits(long maxNumEntries, double fpp) {
        return (int) (-maxNumEntries * Math.log(fpp) / (Math.log(2) * Math.log(2)));
    }
}
