/*
 * 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.solr.store.blockcache;

import java.util.concurrent.atomic.AtomicLongArray;

import org.apache.lucene.util.LongBitSet;

/**
 * @lucene.experimental
 */
public class BlockLocks {
  
  private AtomicLongArray bits;
  private int wlen;
  
  public BlockLocks(long numBits) {
    int length = LongBitSet.bits2words(numBits);
    bits = new AtomicLongArray(length);
    wlen = length;
  }
  
  /**
   * Find the next clear bit in the bit set.
   * 
   * @param index
   *          index
   * @return next next bit
   */
  public int nextClearBit(int index) {
    int i = index >> 6;
    if (i >= wlen) return -1;
    int subIndex = index & 0x3f; // index within the word
    long word = ~bits.get(i) >> subIndex; // skip all the bits to the right of
                                          // index
    if (word != 0) {
      return (i << 6) + subIndex + Long.numberOfTrailingZeros(word);
    }
    while (++i < wlen) {
      word = ~bits.get(i);
      if (word != 0) {
        return (i << 6) + Long.numberOfTrailingZeros(word);
      }
    }
    return -1;
  }
  
  /**
   * Thread safe set operation that will set the bit if and only if the bit was
   * not previously set.
   * 
   * @param index
   *          the index position to set.
   * @return returns true if the bit was set and false if it was already set.
   */
  public boolean set(int index) {
    int wordNum = index >> 6; // div 64
    int bit = index & 0x3f; // mod 64
    long bitmask = 1L << bit;
    long word, oword;
    do {
      word = bits.get(wordNum);
      // if set another thread stole the lock
      if ((word & bitmask) != 0) {
        return false;
      }
      oword = word;
      word |= bitmask;
    } while (!bits.compareAndSet(wordNum, oword, word));
    return true;
  }
  
  public void clear(int index) {
    int wordNum = index >> 6;
    int bit = index & 0x03f;
    long bitmask = 1L << bit;
    long word, oword;
    do {
      word = bits.get(wordNum);
      oword = word;
      word &= ~bitmask;
    } while (!bits.compareAndSet(wordNum, oword, word));
  }
}